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-syntax-dynamic-import is used to parse dynamic syntax that recognizes import(), not transform

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