Using webpack to build react project development environment

Time:2020-9-16

preface

In daily development, we usually choose scaffoldingvue-clicreate-react-appTo help develop. But understand and masterwebpackTo complete the project engineering development is a basic skill

This paper is based on:
webpack: 4.41.2
node: 10.15.1
yarn: 1.16.0

Basic configuration of webpack

1.1 initialize react project

  • In the new directory

    yarn init 
  • Install react related packages

    yarn add react react-dom axios
  • Installing Babel
    In development, we often use ES6 + syntax, but not all browsers have the ability to convert ES6 + syntax to JavaScript, so that’s why we use Babel, which can convert ES6 + syntax to compatible JS syntax

    • installbabelCore package and command line tools for

        yarn add @babel/core
        yarn add @babel/cli 
    • installbabelPreset configuration and syntax plug-in for

      Yarn add @ Babel / preset env supports ES6 syntax  
       Yarn add @ Babel / preset react supports react syntax  
       Yarn add @ Babel / plugin proposal class properties supports class properties
    • edit.babelrcMake the installed plug-in and configuration take effect

      {
          "presets": [
             [
                "@babel/preset-env",
                {
                   "useBuiltIns": "entry",
                   "corejs": 3
                }
             ],
              "@babel/preset-react"
           ],
           "plugins": [
              "@babel/plugin-proposal-class-properties"
           ]
      }
    • installbabel-polyfill
      By default, Babel only converts syntax, but not objects. When it can be developed, since the original browser does not have the syntax of objects such as promise and symbol, we have to adjust the configuration of Babel when we need to use it
      We can use itbabel-polyfillTo solve this problem. Its principle is that it implements all the objects and instance methods of ES6 +, but its disadvantage is that it pollutes the global space and built-in objects, which may cause conflicts between objects with the same name

        yarn add @babel/polyfill

1.2 using webpack

  • install

    yarn add webpack webpack-cli
  • newly buildsrc/index.jsAs a global portal

    Import "@ Babel / Polyfill" // at the beginning
    import React from 'react'
    import ReactDOM from 'react-dom'
    import axios from 'axios'
  • establishwebpack.config.js

    const path = require('path');
    
    module.exports = {
        entry: './src/ index.js "// entry file
        Output: {// export file (after compilation)
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
        },
        //Babel loader will process the file according to. Babelrc
        module: {
            rules: [
                {test: /\.js$/, use: 'babel-loader'}
            ]
        }
    };
  • yarn add babel-loader

1.3 integration of webpck dev server

  • yarn add webapck-dev-serverQuickly build local operating environment
  • yarn add html-webpack-pluginGenerating HTML in memory
  • modifypackage.json

    "scripts": {
        "dev": "webpack-dev-server --open --port 8081 --contentBase src --hot",
        "build": "webpack --mode production",
        "test": "echo \"Error: no test specified\" && exit 1"
     },
  • modifywebpack.config.js

    const path = require('path');
    const htmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
        entry: path.join(__dirname, './src/index.js'),
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
        },
        plugins: [
            new htmlWebpackPlugin({
                template: path.join(__dirname, './index.html'),
                filename: 'index.html'
            })
        ],
        module: {
            rules: [
                {test: /\.js$/, use: 'babel-loader'}
            ]
        },
        mode: 'development' 
    };
  • After integrating webpack dev server and HTTP webpack plugin, access the localhost:8081 (HTML page in memory) for the introduction of CSS, images, less, fonts and other files into HTML, there will be an error, which needs to be handled separately

    • yarn add less less-loader css-loader style-loader file-loader
    • modifywebpack.config.js

      rules: [
         {test: /\.js$/, use: 'babel-loader'}, 
         {test: /\.css$/, use: ['style-loader', 'css-loader']},
         {test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader']},
         {test: /\.(eot|svg|ttf|woff|woff2|png)\w*/, use: 'file-loader'}
      ]
    • modify index.html

      <script></script>

1.4 configure react routing

  • yarn add react-router react-router-dom
  • In the configurationreact-routerAfter, the page cannot be accessed by entering the address or refreshing the subroute
    Reason: we only configure the front-end route through the react router, but webpack will not identify the front-end route. If the server fails to match the path, it will be automatically transferred to the 404 interface
    Solution: inwebpack.config.jsMedium configuration:

    //Webpack will return to the home page by default if it fails to match the corresponding path
    //This will be controlled by the front-end routing
    devServer: {
        historyApiFallback: true,
    }

1.5 distinguish development environment from production environment

  • staywebpack.config.jsThe development mode and the production mode are distinguished in China

    const path = require('path');
    
    module.exports = function(env, argv){
        //1. Judge whether it is development or production environment by argv
        const isEnvDevelopment = argv.mode === 'development' || !argv.mode;
        const isEnvProduction = argv.mode === 'production'; 
        
        return{
            //2. Mode and devtool are configured differently according to the environment
            mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
            devtool: isEnvProduction ? 'source-map' : isEnvDevelopment && 'cheap-module-source-map',
            entry: './src/index.js',
            output: {
                filename: 'bundle.js',
                path: path.resolve(__dirname, 'dist')
            },
            module: {
                rules: [{
                    test: /\.js$/,
                    exclude: /node_modules/,
                    use: 'babel-loader'
                }]
            }
        }
    }

1.6 development configuration

  • Source Map: map the target code to the original code
    After passing through the wrapper, the browser executes the object code. In the development process, we hope that the error reported can be directly located in the original code instead of displaying the target code. At this time, we need to configuresource map

    const path = require('path');
    const htmlWebpackPlugin = require('html-webpack-plugin');
    const webpack = require('webpack');
    
    module.exports = function(env, argv){
        const isEnvDevelopment = argv.mode === 'development' || !argv.mode;
        const isEnvProduction = argv.mode === 'production'; 
        
        return{
            mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', 
            Devtool: isenvproduction?'source map ': isenvdevelopment & amp; swap module source map' // modify
            entry: path.join(__dirname, './src/index.js'),
            output: {
                filename: 'bundle.js',
                path: path.resolve(__dirname, 'dist'),
            },
            plugins: [
                new htmlWebpackPlugin({
                    template: path.join(__dirname, './public/index.html'),
                    filename: 'index.html'
                })
            ],
            module: {
                rules: [
                   {test: /\.js$/, use: 'babel-loader'}, 
                   {test: /\.css$/, use: ['style-loader', 'css-loader']},
                   {test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader']},
                   {test: /\.(eot|svg|ttf|woff|woff2|png)\w*/, use: 'file-loader'}
                ]
            },
            devServer: {
                historyApiFallback: true,
                contentBase: './dist',
                hot: true
            }
        }
    }
  • Eslint and prettier

    • The role of eslint is to check whether the code conforms to the specification and prompt
    • Prettier is generally used with eslints. Eslints can detect whether the code is standard, but it can not completely unify the code style. However, prettier unifies the code style, but cannot detect the code specification
    • yarn add eslint babel-eslint
    • yarn add eslint-config-airbnb
    • yarn add eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
    • establish.eslintrc.js

      module.exports = {
          //Parser is used to specify the parser. Eslint itself cannot parse ES6 syntax and needs to be converted to Babel eslint
          "parser": "babel-eslint", 
          //Extends represents the specification based on airbnb 
          "extends": ["airbnb"], 
          //Env tells eslint not to report undefined if it encounters browser and ES6 objects
          "env": {  
              browser: true, 
              es6: true, 
           }, 
           //Rules are used to specify extended specification rules that override the configuration in airbnb rules 
          "rules": {
              'react/jsx-filename-extension': [1, {extensions: ['.js']}],
              'react/prop-types': 0,
              'react/prefer-stateless-function': 0,  
              'react/no-array-index-key': 0, 
              'no-console': 0, 
              'jsx-ally/anchor-is-valid': 0, 
              'react/destructuring-assignment': 0,
              'react/jsx-one-expression-per-line': 0  
          }
      }
    • Manual executioneslint
      yarn eslint src/Check the JS file under Src /
      npx eslint --fix src/Automatic repair is not standardized. Only those that cannot be automatically repaired are listed
  • Configure path alias
    In the development, we often configure the path alias, quickly locate the file, improve the efficiency and accuracy of file introduction@To expresssrccatalog

    resolve: {
       alias: {
         '@': path.resolve('src')
       }
    }
  • Modular CSS

    • Simple usestyle-loader、css-loaderAlthough CSS style files can be introduced into JS, the following problems appear:

      • Global pollution:CSSSelectors in files are global. Selectors with the same name in different files are selected according tobuildIn the post generated file, the following style will override the previous style
      • Selectors are complex and naming rules are confusing
    • After using the CSS module for modular processing, for each JS file, the CSS file introduced is only valid for the file, which is a bit similar to VuescopedThe role of attributes
    • modifywebpack.config.js

      {test: /\.css$/, use: ['style-loader', 'css-loader?modules']},
    • Examples of use:
      Using webpack to build react project development environment
    • The principle of CSS module: each class name is converted according to certain rules (plus hash value) to ensure its uniqueness, but only the classname and ID can be converted
  • CSS compatibility handling
    CSS can add different prefixes for different browsers to solve the problem of browser compatibility

    • yarn add postcss-loader autoprefixer
    • to configurepostcss.config.js

      module.exports = {
          plugins: {
              'autoprefixer': {}
          }
      };
    • modifywebpack.config.js

      //The post SS loader should be placed before the less loader. First, it should be converted from less to CSS, and then processed by postcss
      module: {
          rules: [
              {test: /\.js$/, exclude: /node_modules/, enforce: "pre", use: 'babel-loader'}, 
              {test: /\.css$/, include: [path.resolve(__dirname, 'src/styles'), /node_modules/], use: ['style-loader', 'css-loader', 'postcss-loader']},
              {test: /\.css$/, exclude: [path.resolve(__dirname, 'src/styles'), /node_modules/], use: ['style-loader', 'css-loader?modules', 'postcss-loader']},
              {test: /\.less$/, include: [path.resolve(__dirname, 'src/styles'), /node_modules/], use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']},
              {test: /\.less$/, exclude: [path.resolve(__dirname, 'src/styles'), /node_modules/], use: ['style-loader', 'css-loader?modules', 'postcss-loader', 'less-loader']},
              {test: /\.(eot|svg|ttf|woff|woff2|png)\w*/, use: 'file-loader'},     
              {
                  test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/],
                  loader: 'url-loader',
                  options: {
                      limit: 10000
                  }
              }
          ],
      },

1.7 production environment configuration

  • copywebpack.config.js, rename towebpack.config.prod.js
  • editpackage.json

    "scripts": {
        "dev": "webpack-dev-server --port 8081 --mode development",
        "build": "webpack --config webpack.prod.config.js --mode production",
        "watch": "webpack --watch",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
  • modifywebpack.prod.js
    deletedevServerto configure

ending

At this point, we can use the following command:
yarn run devStart the project in the development environment and access it on port 8081
yarn run buildPackage and compile the project file to generate dist directory
As for the optimized configuration of some webpack, such as code separation, file volume compression, configuration cache, etc., and the use of HMR module hot update in the development environment, it will not be introduced too much, or it will continue to be updated in the future