Webpack profile
In essence,
webpack
Is a modern JavaScript application
Static Module Bundler
. As WebPack processes the application, it builds one recursively
Dependency Graph
Contains each module your application needs, and then packages all of these modules into one or more
bundle
.
Initialize the project
Create a folder called my-react-webpack
npm init
mkdir my-react-webpack
cd my-react-webpack
npm init # Enter all the wayCopy the code
Once the initialization is complete, you can see that you have created a package.json file and start creating folders and files.
|--config
|--|--webpack.config.js
|--src
|--|--index.js
|--index.htmlCopy the code
Webpack module
Patterns, entrances, exits
Download the WebPack dependencies first
cnpm install webpack webpack-cli webpack-dev-server -D
cnpm install path -DCopy the code
Add the following two lines to package.json
"script": {"build":"webpack --config ./config/webpack.config.js",}Copy the code
Now write the webpack.config.js file
const path = require('path');
const config = {
mode:'production'Development Production Entry: {index: [path.resolve(__dirname,'.. /src/index.js'}, output:{filename:'[name].[hash:8].js', // Config entry file name after packaging, to distinguish the name usedhashEncryption chunkFilename:'[name].[chunkhash:8].js'Path :path.resolve(__dirname,'.. /dist'}}; module.exports = config;Copy the code
Mode: The two modes will produce different files after packaging. The packaged files in the Development environment are uncompressed JS files, while the packaged files in the production environment are the opposite.
The value of entry can be a single character path entry:path.resolve(__dirname,’.. / SRC /index.js’), which can also be a collection like mine.
The output: Filename indicates the name of the entry file. In the filename value, name is the index of the entry file. Hash :8 indicates that 8-bit random characters are generated after hash encryption.
ChunkFilename is used to indicate the name of a chunk file that is not packaged in the entry, such as an external JS file imported in index.js.
Path is used to indicate the location of the folder after all the files are packed. In the above example, it indicates that all the files are stored in a folder named dist.
module
Module module is used to add loader module to parse and convert JS files, CSS files, images, etc. Next, introduce some common module modules according to different functions.
Handles JS and JSX files
Balel-loader is used to convert ES6 to ES5 code
@babel/core converts the incoming JS code
@babel/preset-env is used to be compatible with different browsers because different browsers have different compatibility for ES syntax
@babel/preset-react is used to convert react syntax
Babel /plugin-transform-runtime is used to convert new apis after ES6, such as generator functions
cnpm install babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/plugin-syntax-dynamic-import @babel/plugin-transform-runtime -DCopy the code
const config = {
module:{
rules:[
{
test:/\.js[x]? // Exclude :/node_modules/, // Exclude :/node_modules/, // Include: path.join(__dirname,'.. /src'// use:[{loader:'babel-loader? cacheDirectory=true',
options:{
presets:['@babel/preset-env'.'@babel/preset-react'],
plugins:['@babel/plugin-syntax-dynamic-import'['@babel/plugin-transform-runtime'[]}}]}}Copy the code
Working with CSS files
Css-loader is used to load CSS files and convert them into JS files
Style-loader uses the <style> tag to inject csS-loader internal styles into HTML
Postcss-loader Uses autoprefixer to automatically add CSS prefixes compatible with various browsers
Autoprefixer automatically adds various browser CSS prefixes
cnpm install css-loader style-loader postcss-loader autoprefixer -DCopy the code
const config = {
module:{
rules:[
{
test:/\.css$/,
use:[
{
loader:'style-loader',
},{
loader:'css-loader', options:{modules:{// Create CSS modules to prevent CSS global contaminationlocalIndentName:'[local][name]-[hash:base64:4]'
}
}
},{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer']}}]}}Copy the code
Since the Loader module parses from right to left, it is necessary to add the CSS prefix of various browsers first, then load the CSS style, and finally add it to the HTML through the style tag.
Handling less files
Less Installs the LESS service
Less-loader parses and packages less files
cnpm install less less-loader -DCopy the code
const config = {
module:{
rules:[
{
test:/\.less$/,
use:[
{
loader:'style-loader',
},{
loader:'css-loader'
},{
loader:'less-loader',
},
{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer']}}]}}Copy the code
Process SCSS files
Node-sass installs Node to parse sass services (there is a difference between less and sass, less is JavaScript based and handled on the client side, sass is ruby based and so handled on the server side)
Sass -loader parses and packages SASS, SCSS files
cnpm intsall node-sass scss -DCopy the code
const config = {
module:{
rules:[
{
test:/\.(sa|sc)ss$/,
use:[
{
loader:'style-loader',
},{
loader:'css-loader'
},{
loader:'sass-loader',
},
{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer']}}]}}Copy the code
Handle pictures, audio and other files
Url-loader uses the base64 code to load files. It depends on file-loader. You can set the limit attribute to use file-loader when files are smaller than 1 MB
File-loader directly loads files
cnpm intsall url-loader file-loader --DCopy the code
const config = {
module:{
rules:[
{
test:/\.(png|jpg|jpeg|gif)$/,
use:{
loader:'url-loader',
options:{
limit:1024, // Use url-loader fallback:{loader:'file-loader',
options:{
name:'img/[name].[hash:8].[ext]'// Create an img folder and store the images in}}}}}, {test:/\.(mp4|mp3|webm|ogg|wav)$/,
use:{
loader:'url-loader',
options:{
limit:1024,
fallback:{
loader:'file-loader',
options:{
name:'media/[name].[hash:8].[ext]'}}}}}]}}Copy the code
plugins
The plugins module adds various plug-ins to WebPack to extend the functionality of WebPack
Deploy different environments
CNPM install cross-env -d specifies the mode enabled by webpack
Modify the package.json file
"scripts": {"build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"."dev":"cross-env NODE_ENV=development webpack --config ./config/webpack.config.js"
}Copy the code
Modify the webpack. Config. Js
const isDev = process.env.NODE_ENV === 'development' ? true : false;
const Webpack = require('webpack');
const config = {
mode:isDev ? 'development':'production'Plugins :[new webpack.defineplugin ({// Create a global variable that can be configured at compile time'process.env':{
NODE_ENV:isDev ? 'development':'production'}}), new Webpack HotModuleReplacementPlugin () / / Webpack hot update module]}Copy the code
Adding HTML Templates
Html-webpack-plugin creates a template for an HTML file for Webpack
The clean-webpack-plugin will automatically clean up the files that have been packed last time
cnpm install html-webpack-plugin clean-webpack-plugin -DCopy the code
Create a template for the index.html file in the project root directory and add the code to webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const config = {
plugins:[
new HtmlWebpackPlugin({
title:'home'// Generate the title of the HTML page filename:'index.html'Template: path.join(__dirname,'.. /index.html'// chunks:all, // select all when you need to pack all of the entry files as script tags), New CleanWebpackPlugin() // Clears the specified packaged folder by default]}Copy the code
Packing CSS files
Mini-css-extract-plugin packs CSS, LESS, SASS, SCSS files
cnpm install mini-css-extract-plugin -DCopy the code
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); Const config={module:{rules:[//test:'/\.(sa|sc)ss$/'Use :[// the miniCssExtractPlugin can not be used in the development environment, document is not defined by the error isDev?'style-loader' : MiniCssExtractPlugin.loader,
{
loader:'css-loader',
options:{
modules:[
localIndentName:'[local][name]-[hash:base64:4]'
]
}
},{
loader:'sass-loader',
},{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer')]
}
}
]
}
]},
plugins:[
new MiniCssExtractPlugin({
filename:'[name].[contenthash:8].css',
chunkFilename:'[id].[contenthash:8].css'}})]Copy the code
devServer
To improve development efficiency, can be used to set up hot update, reverse proxy and other functions
const config = {
devServer:{
hot:true// contentBase: path.join(__dirname,'.. /dist'), // Set the root directory to enable the HTTP servicehistoryApiFallback:true// When a route matches a path, an HTML page is returned by default.true, // Start gzip compression open:true// Overlay :{error:true, // Display error} in the browser in full screen, port:3000, // start port number host:'localhost'// Start IP/API /server:{target:'http://localhost:3010'// Reverse proxy the request from/API /server to changeOrigin on port 3010:true// Allow target to be the domain name pathRewrite:{'^/api/server':' '}, //secure:false, // Support HTTPS proxy}}}Copy the code
Next modify package.json
"script": {"dev": "cross-env NODE_ENV=development webpack-dev-server --config ./config/webpack.config.js"
}Copy the code
Then enter the command NPM run dev to successfully launch the devServer setup page
resolve
Resolve configuates how webPack will find files for all dependent modules in the entry module when it starts
const config = {
resolve:{
modules:['node_modules'], // Where to look for extensions under node_modules :['js'.'jsx'.'json'], // When an imported file does not have a suffix, WebPack automatically looks for the file with that suffixalias: {'@src':path.join(__dirname,'.. /src'), // Set the original import path to the new path, so that there is no need to import with a long slash}}}Copy the code
devtool
Devtool makes it easy to develop and debug code
- Source-map source code debugging will generate a source map file, error will report the current error row and column
- Inline-source-map does not produce a source map file, but if an error occurs, the current error row and column is reported
const config = {
devtool:'source-map'
}Copy the code
Webpack general preview
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const Webpack = require('webpack');
const isDev = process.env.NODE_ENV === 'development';
const config = {
mode: isDev ? 'development' : 'production',
entry:{
index:[path.resolve(__dirname,'.. /src/index.js')]
},
output:{
filename:'[name].[hash:8].js',
chunkFilename:'[name].[chunkhash:8].js',
path:path.resolve('.. /dist')
},
module:{
rules:[
{
test:/\.js[x]? $/, exclude: /node_modules/, include: path.join(__dirname,'.. /src'),
use:[
{loader:'babel-loader? cacheDirectory=true',
options:{
presets:['@babel/preset-env'.'@babel/preset-react'],
plugins:['@babel/plugin-syntax-dynamic-import',[@babel/plugin-transform-runtime]]
}
}
]
},
{
test:/\.(sa|sc|c)ss$/,
use:[
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader:'css-loader',
options:{
modules: {
localIndentName:'[local][name]-[hash:base64:4]'
}
}
},
{
loader:'sass-loader',
},
{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer'}}]}, {test:/\.less$/,
use:[
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader:'css-loader',
options:{
modules:{
localIndentName:'[local][name]-[hash:base64:4]'
}
}
},
{
loader:'less-loader',
},
{
loader:'postcss-loader',
options:{
plugins:[require('autopredixer'}}]}, {test:/\.(jpg|png|jpeg|gif)/,
use:[
{
loader:'url-loader',
options:{
limit:1024,
fallback:{
loader:'file-loader',
options:{
name:'img/[name].[hash:8].[ext]'}}}}]}, {test:/\.(mp3|mp4|webm|ogg|wav)/,
use:[{
loader:'url-loader',
options:{
limit:1024,
fallback:{
loader:'file-loader',
options:{
name:'media/[name].[hash:8].[ext]'
}
}
}
}]
}
]
},
plugin:{
new Webpack.DefinePlugin({
process.env:{
NODE_ENV: isDev ? 'development' : 'production'
}
}),
new Webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
title:'home',
filename:'index.html',
template: path.join(__diranme,'.. /index.html'),
chunk:'all'
}),
new MiniCssExtractPlugin({
filename:'[name].[contenthash:8].css',
chunkFilename:'[id].[contenthash:8].css'
}),
new CleanWebpackPlugin()
},
resolve:{
modules:['node_modules'],
extensions:['jsx'.'js'.'json'].alias: {'@src':path.join(__dirname,'.. /src')
}
},
devServer:{
contentBase:path.join(__dirname,'.. /dist'),
hot:true,
compress:true,
open:true.historyApiFallback:true,
overlay:{
error:true
},
host:'localhost',
port:3000,
proxy:{
'/api/server':{
target:'http:localhost:3010',
pathRewrite:{
'^/api/server':' '
},
changeOrigin:true,
//secure:false
}
}
},
devtool:'inline-source-map',
}
module.exports = config;Copy the code
Start writing React
Create the page
CNPM install react react-dom –save
Add code to index.js
import React from 'react';
import ReactDom from 'react-dom';
import App from '@src/app';
ReactDom.render(
<div>
<App />
</div>,
document.getElementById('app'))Copy the code
Since the app component is referenced, create a new folder named app in the SRC folder and create an index.jsx file under the app folder. Now write the app component
import React,{ Component } from 'react';
import styles from './style.scss';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
data: 'hello'}}render() {
return (
<div className={styles.a}>
<p className={styles.b}>
{this.state.data}
</p>
</div>
)
}}Copy the code
Add styles. SCSS to verify that the CSS Module and SCSS compiled successfully
.a{
.b{
text-align:'center';
color:'red'; }}Copy the code
Start NPM run dev, and you’ll see a red, centered Hello on the page, indicating success
Join the routing
CNPM install react-router-dom history –save
Create another component for Other under the SRC folder
import React, { Component } from 'react';
export default class Other extends Component {
render() {
return (
<div>
other
</div>
)
}}Copy the code
Now modify the main function index.js
import React from 'react';
import ReactDom from 'react-dom';
import App from '@src/app';
import Other from '@src/other';
import { Router, Route, Switch } from 'react-router-dom';
import { createBrowserHistory } from 'history';
ReactDom.render(
<div>
<Router history={createBrowserHistory()}>
<Switch>
<Route path='/other' exact component={Other} />
<Route path='/' component={App} />
</Switch>
</Router>
</div>,document.getElementById('app'))Copy the code
Start the Node background service
Create a folder named server in the root directory of the project and put an Express application in it. Add express and nodemon module CNPM install Express nodemon Body-parser –save
appServer.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3010;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.get('/get', (req, res) => {
res.send(port + 'Get request successful')}
)
app.post('/post', (req, res) => {
console.log(req.body)
res.send(port + 'Post request successful, front-end transfer data as'+req.body)}
);
app.listen(port, () => {
console.log(port + 'Port start')})Copy the code
Add express startup listening code to package.json
"script": {"express:dev" : "nodemon ./server/appServer.js"
}Copy the code
Join Redux Communications
Add redux dependency redux redux-redux-thunk
Create actiontype. js, action.js and Reducer.js in the app folder
//actionType.js
export GET_DATA = 'app/getData';
export POST_DATA = 'app/postData';
//action.js
import * as actionTypes from './actionType.js'
export const getDataAction = () => {
return(dispatch) => {// Because webpack devServer is set to reverse proxy, so here/API /server stands for node port fetch(localhost:3010)'/api/server/get',{
method:'GET',
header:{
'Content-Type':'application/json'.'Accept':'application/json,text/plain',
}
})
.then(res => res.text())
.then(obj => dispatch(getDataReducer(obj)) )
}
}
const getDataReducer = (data) => ({
type: actionTypes.GET_DATA,
data
})
export const postDataAction = (meg) => {
return (dispatch) => {
fetch('/api/server/post',{
method:'POST',
header:{
'Content-Type':'application/json'.'Accept':'application/json,text/plain'
},
body:JSON.Sringify({
data:meg
})
})
.then(res => res.text())
.then(obj => dispatch(postDataReducer(obj)) )
}
}
const postDataReducer = (data) => ({
type: actionTypes.POST_DATA,
data
})
//reducer.js
import * as actionTypes from './actionType.js'
export default (state={},action) => {
switch(action.type){
case actionTypes.GET_DATA:{
return { ...state, getData:data}
},
case actionTypes.POST_DATA:{
return { ...state, postData:data}
},
default:
returnstate; }}Copy the code
Create store.js in the SRC folder to manage all the data
import { createStore, combinReducers, applyMiddleware, compose } from 'redux';
import { thunkMiddleware } from 'redux-thunk';
import app_reducer from '@src/app/reducer';
const win = window;
const reducers = combinReducers({
app:app_reducer,
})
const middlewares = [thunkMiddleware];
conststoreEnhancers = compose( applyMiddleware(... middlewares), (win && win.__REDUX_DEVTOOLS_EXTENSION__) ? win.__REDUX_DEVTOOLS_EXTENSION__() :(f) = > f,
);
const initState = {
app: {getData:' '.postData:' '}};export default createStore(reducers,initState,storeEnhancers);Copy the code
Now modify index.js
import React from 'react';
import ReactDom from 'react-dom';
import { Provider } from 'react-redux';
import store from '@src/store';
import App from '@src/app';
import Other from '@src/other';
import { Router, Route, Switch } from 'react-router-dom';
import { createBrowserHistory } from 'history';
ReactDom.render(
<div>
<Provider store={store}>
<Router history={createBrowserHistory()}>
<Switch>
<Route path='/other' exact component={Other} />
<Route path='/' component={App} />
</Switch>
</Router>
</Provider>
</div>,document.getElementById('app'))Copy the code
Let’s go back to the app component
import React,{ Component } from 'react';
import styles from './style.scss';
import { connect } from 'react-redux';
import * actions from './action';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: 'hello'}}componentDidMount(){
this.props.getDataFunc();
this.props.postDataFunc(this.state.data);
}
render() {
const {getData,postData} = this.props;
return (
<div className={styles.a}>
<p className={styles.b}>
{this.state.data}
</p>
<p>
{getData}
</p>
<p>
{postData}
</p>
</div>
)
}}
const MapStateToProps = (state) =>({
getData:state.app.getData,
postData:state.app.postData
})
const MapDispatchToProps = (dispatch) => ({
getDataFunc(){
dipatch(actions.getDataRequest())
},
postDataFunc(meg){
dispatch(actions.postDataRequest(meg))
}
})
export connect(MapStateToProps,MapDispatchToProps)(App);Copy the code
conclusion
It was a bit difficult to write the webpack+ React configuration from 0, but after writing it, I felt like I had made a big step forward, and gained a lot. It helped me understand some of the underlying packaging mechanism that I didn’t know before, and I will prepare for the chapter of webpack+ React optimization in the future.
Please point out any mistakes or omissions.
Reference documentation
webpack.wuhaolin.cn/Webpack is easy to understand
www.webpackjs.com/Webpack official Chinese document