头图

Build Webpack5-react scaffolding from scratch (with source code)

Peter谭老师
中文

webpack5

Recently, I finally had time and energy to focus on the company’s technical infrastructure, so at the beginning, the company’s Saas system was transformed into a micro front-end model, which solved some of the problems left over from history.

Then, thinking that webpack5 has been released for so long, it should be used in the production environment, and I also want to promote micro front end, webpack5 , vite in the industry. Friends who have not read my previous article can find it at the end of the article. There are a lot of dry goods

Officially begin

What changes have been made after webpack5 is upgraded?

  • Improve performance through persistent caching
  • Adopt better persistent caching algorithm and default behavior
  • Reduce Bundle size by optimizing Tree Shaking and code generation (get rid of nodejs polyfill)
  • Improve the compatibility of the web platform
  • To clear the unreasonable state caused by no incompatibility changes in Webpack4 before
  • Try to introduce major changes now to prepare for future features so that we can use Webpack 5 for as long as possible
  • Added Module Federation (Federal Module)

    Build guide

    I recommend that you use the scaffolding I made in our company ( Shenzhen Mingyuan Cloud Space) to generate project templates with one click, so that everyone will get better upgrades when reading this article

Steps to generate template:

npm  i ykj-cli -g 
ykj init webpack5 (这里选择通用项目模板)
cd webpack5
yarn 
yarn dev

Start building

  • First create a new folder, use yarn to initialize the project

    mkdir webpack5-demo
    cd webpack5-demo
    yarn init webpack5-demo
    ...一路回车
  • Download the latest version of webpack webpack-cli

    yarn add webpack@next webpack-cli@next -D
  • Then install the React react-dom 17 version of the library

    yarn add react@17.0.0 react-dom@17.0.0 --save 
  • Then install the libraries recommended by the official hot update of react
yarn add react-refresh -D
  • Install less css style tag postcss and other style processing libraries ( mini-css-extract-plugin to install @next version)
yarn add less less-loader css-loader style-loader mini-css-extract-plugin@next -D
  • Installation related babel dependencies

    yarn add core-js@3.9.0 @babel/core@next  babel-loader@next @babel/preset-env@next -D
    babel What are the specific configurations, I suggest you refer to my template

Completed the preparatory work for dependencies and started to build the project

  • Create a 061036b411c1d3 folder in the project root directory to config webpack configuration file
  • Create four new files under the config

    paths.js//存放路径
    webpack.base.js //基础配置
    webpack.dev.js//开发配置
    webpack.prod.js//生产配置
  • In the paths file, use variables to record several key directories:

    const path = require('path');
    
    module.exports = {
      // 源码目录
      src: path.resolve(__dirname, '../src'),
    
      // 构建后的资源产物文件夹
      build: path.resolve(__dirname, '../dist'),
    
      // 静态资源
      public: path.resolve(__dirname, '../public'),
    };
    
  • Write the basic webpack.base.js configuration file and introduce dependencies

    //webpack.base.js
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const path = require('path');
    const paths = require('./paths');
  • Write the entry and output fields:

     entry: paths.src + 'index.tsx',
     output: {
          path: path.resolve(__dirname, '../dist'),
          filename: '[name].[contenthash].js',
          publicPath: '',
      },
    It should be noted here that webpack5 the contenthash algorithm, here you can choose one of chunkhash and contenthash contenthash
  • Write the basic loader configuration:

      module: {
          rules: [
              {
                  use: 'babel-loader',
                  test: /\.(ts|tsx)$/,
                  exclude: /node_modules/,
              },
              {
                  use: ['style-loader', 'css-loader', 'less-loader'],
                  test: /\.(css|less)$/,
              },
              {
                  type: 'asset',
                  test: /\.(png|svg|jpg|jpeg|gif)$/i,
              },
          ],
      },
    Note here: webpack5 For resources, similar: pictures, font files, etc., you can use the built-in asset to process, instead of url-loader and file-loader
  • Next, because the project needs to configure the alias and omit the suffix name, we first configure the resolve field (I am the TypeScript+React technology stack):

     resolve: {
          extensions: ['.ts', '.tsx', '.js', '.json', '.jsx'],
          alias: {
              '@': paths.src,
              '@c': paths.src + '/components',
              '@m': paths.src + '/model',
              '@s': paths.src + '/services',
              '@t': paths.src + '/types',
          },
      },
  • For plug-ins, since it is a basic configuration, only one plug-in of clean、html

    plugins: [
          new CleanWebpackPlugin(),
          new HtmlWebpackPlugin({
              template: './public/index.html',
          }),
      ],
  • babel.config.js in the project root directory

    const { argv } = require('yargs');
    const isDev = argv.mode === 'development';
    const plugins = [
      [
          'const-enum',
          {
              transform: 'constObject',
          },
      ],
      'lodash',
      '@babel/plugin-transform-runtime',
      //支持import 懒加载
      '@babel/plugin-syntax-dynamic-import',
      '@babel/plugin-transform-async-to-generator',
      'transform-class-properties',
      [
          'import',
          {
              libraryName: 'antd',
              libraryDirectory: 'es',
              style: true, // or 'css'
          },
          'antd',
      ],
      [
          'import',
          {
              libraryName: 'ykj-ui',
              libraryDirectory: 'lib/components',
              style: true, // or 'css'
          },
          'ykj-ui',
      ],
    ];
    module.exports = (api) => {
      api.cache(true);
      return {
          presets: [
              [
                  '@babel/preset-env',
                  {
                      corejs: 3.9,
                      useBuiltIns: 'usage',
                  },
              ],
              [
                  '@babel/preset-react',
                  {
                      runtime: 'automatic',
                  },
              ],
              '@babel/preset-typescript',
          ],
          plugins: isDev ? [...plugins, 'react-refresh/babel'] : [...plugins],
      };
    };
    
    In this way, our basic webpack configuration is just fine, take a look first:
  • Use babel to handle tsx ts and es high-level grammars
  • Use loader process less grammar
  • html with plug-in and was responsible for cleaning up
  • Configure the alias and omit the file suffix with the resolve
  • Processed static files, such as pictures, with the built-in asset

    Write webpack.dev.js development configuration

    Introduce dependencies

    const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
    const { HotModuleReplacementPlugin } = require('webpack');
    const { merge } = require('webpack-merge');
    const common = require('./webpack.base');

    First introduced hot update, merge configuration, basic configuration, official react hot update dependency

Then write the configuration

const devConfig = {
    mode: 'development',
    devServer: {
        port: 3000,
        contentBase: '../dist',
        open: true,
        hot: true,
    },
    target: 'web',
    plugins: [new HotModuleReplacementPlugin(), new ReactRefreshWebpackPlugin()],
    devtool: 'eval-cheap-module-source-map',
};

module.exports = merge(common, devConfig);
Note: Here you have to set target: 'web' to have the hot update effect
  • The best practice of devtool in development mode is: eval-cheap-module-source-map

In this way, our development mode configuration is set up, as long as you write a index.html public folder, you can start writing the react project as before.


Start writing webpack.prod.js production configuration

  • Introduce dependencies:

    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    const { merge } = require('webpack-merge');
    const common = require('./webpack.base');
  • The production environment needs to be separated from the css tag, so there is special treatment for less and css. One is postcss deal with style compatibility issues, and the other is MiniCssExtractPlugin.loader :

    const prodConfig = {
      mode: 'production',
      devtool: 'hidden-source-map',
      module: {
          rules: [
              {
                  test: /\.(css|less)$/,
                  use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'],
              },
          ],
      },
      optimization: {
          splitChunks: {
              chunks: 'all',
              name: false,
          },
      },
      plugins: [new MiniCssExtractPlugin()],
    };
    module.exports = merge(common, prodConfig);
  • So the production configuration is also prepared

    The best practice of hidden-source-map

    Write scripts commands

    "build": "webpack --config config/webpack.prod.js  --mode production",
    "dev": "webpack serve --config config/webpack.dev.js  --mode development",
    Note: The hot update used to be webpack-dev-server , now it is webpack serve !!!

Configure code quality control process

  • Add dependency

    yarn add lint-staged @commitlint/cli @commitlint/config-conventional -D
  • Write code and submit testing process

     "husky": {
          "hooks": {
              "pre-commit": "lint-staged",
              "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
          }
      },
      "lint-staged": {
          "src/**/*.{js,jsx,ts,tsx,json,css,less,md}": [
              "prettier --write",
              "eslint --fix",
              "git add"
          ]
      },
      "browserslist": [
          "ie >= 10",
          "ff >= 30",
          "chrome >= 34",
          "safari >= 8",
          "opera >= 23"
      ]
    }
  • Add eslint configuration:

    //.eslintrc.js
    module.exports = {
      root: true,
      parserOptions: {
          ecmaVersion: 7,
          sourceType: 'module',
      },
      parser: '@typescript-eslint/parser',
      plugins: ['typescript', 'react'],
      env: {
          browser: true,
          node: true,
          es6: true,
      },
      rules: {
          semi: ['error', 'always'], // 该规则强制使用一致的分号
          'no-unused-vars': 'off', // 禁止未使用过的变量
          'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', //生产环境禁用 debugger
          'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', //生产环境禁用 console
          'default-case': ['warn', { commentPattern: '^no default$' }], //要求 Switch 语句中有 Default
          'dot-location': ['warn', 'property'], // 强制在点号之前或之后换行
          eqeqeq: ['error', 'allow-null'], //要求使用 === 和 !==
          'new-parens': 'warn', //要求调用无参构造函数时带括号
          'no-caller': 'error', // 禁用 caller 或 callee
          'no-const-assign': 'error', //不允许改变用 const 声明的变量
          'no-dupe-args': 'error', //禁止在 function 定义中出现重复的参数
          'no-dupe-class-members': 'error', //不允许类成员中有重复的名称
          'no-dupe-keys': 'warn', //禁止在对象字面量中出现重复的键
          'no-extend-native': 'warn', //禁止扩展原生对象
          'no-extra-bind': 'warn', //禁止不必要的函数绑定
          'no-fallthrough': 'error', //禁止 case 语句落空
          'no-func-assign': 'warn', //禁止对 function 声明重新赋值
          'no-implied-eval': 'error', //禁用隐式的 eval()
          'no-label-var': 'error', //禁用与变量同名的标签
          'no-loop-func': 'error', //禁止循环中存在函数
          'no-mixed-operators': [
              'warn',
              {
                  groups: [
                      ['&', '|', '^', '~', '<<', '>>', '>>>'],
                      ['==', '!=', '===', '!==', '>', '>=', '<', '<='],
                      ['&&', '||'],
                      ['in', 'instanceof'],
                  ],
                  allowSamePrecedence: false,
              },
          ], //禁止混合使用不同的操作符
          'no-multi-str': 'warn', //禁止多行字符串 (需要多行时用\n)
          'no-native-reassign': 'warn', //禁止重新分配本地对象
          'no-obj-calls': 'warn', //禁止将全局对象当作函数进行调用
          'no-redeclare': 'error', //禁止重新声明变量
          'no-script-url': 'warn', //禁用 Script URL
          'no-shadow-restricted-names': 'warn', //关键字不能被遮蔽
          'no-sparse-arrays': 'warn', //禁用稀疏数组
          'no-this-before-super': 'warn', //在构造函数中禁止在调用 super()之前使用 this 或 super
          'no-undef': 'error', //禁用未声明的变量
          'no-unexpected-multiline': 'warn', //禁止使用令人困惑的多行表达式
          'no-use-before-define': [
              'warn',
              {
                  functions: false,
                  classes: false,
                  variables: false,
              },
          ], //禁止定义前使用
          'no-with': 'error', //禁用 with 语句
          radix: 'error', //禁用函数内没有 yield 的 generator 函数
          'rest-spread-spacing': ['warn', 'never'], //强制限制扩展运算符及其表达式之间的空格
          'react/jsx-no-undef': 'error', //在 JSX 中禁止未声明的变量
          'react/no-direct-mutation-state': 'error', //禁止 this.state 的直接变化
          'react/jsx-uses-react': 'warn', //防止 React 被错误地标记为未使用
          'no-alert': 0, //禁止使用alert confirm prompt
          'no-duplicate-case': 2, //switch中的case标签不能重复
          'no-eq-null': 2, //禁止对null使用==或!=运算符
          'no-inner-declarations': [2, 'functions'], //禁止在块语句中使用声明(变量或函数)
          'no-iterator': 2, //禁止使用__iterator__ 属性
          'no-negated-in-lhs': 2, //in 操作符的左边不能有!
          'no-octal-escape': 2, //禁止使用八进制转义序列
          'no-plusplus': 0, //禁止使用++,--
          'no-self-compare': 2, //不能比较自身
          'no-undef-init': 2, //变量初始化时不能直接给它赋值为undefined
          'no-unused-expressions': 2, //禁止无用的表达式
          'no-useless-call': 2, //禁止不必要的call和apply
          'init-declarations': 0, //声明时必须赋初值
          'prefer-const': 0, //首选const
          'use-isnan': 2, //禁止比较时使用NaN,只能用isNaN()
          'vars-on-top': 2, //var必须放在作用域顶部
      },
    };
    

    unit test

    New commands:

    "test": "jest", //进行测试
    "test-c": "jest --coverage" //生成测试报告

    Install jest and other dependencies:

    yarn add jest-environment-enzyme ts-jest@next enzyme enzyme-adapter-react-17 enzyme-to-json  @types/enzyme @types/enzyme-adapter-react-17 @types/enzyme-to-json -D 

    Create a new folder test

Write the first unit test and introduce dependencies:

import App from '../src/App';
import { mount, shallow } from 'enzyme';
import React from 'react';
import toJson from 'enzyme-to-json'; //做快照

Then you can start writing unit tests happily

  • In this way, a webpack5 scaffolding build better, webpack built something that allows us to save a lot of configuration, simpler look

After watching two things

If you think this content is very inspiring for you, I would like to invite you to help me with a few small things

1. Click "Watching, Like, Follow" so that more people can see this content (click "Watching", bug -1 😊)
2. Follow the WeChat public account "Front End Peak", let me continue to push selected good articles for you
I am Peter Tan, a front-end development engineer in a small factory. I like to do architecture, and I have a certain research on performance optimization and cross-platform development. I also like to do self-media and blockchain. Welcome to follow me

阅读 1.1k

前端巅峰
注重前端性能优化和前沿技术,重型跨平台开发,即时通讯技术等。 欢迎关注微信公众号:前端巅峰

前端架构师

14.2k 声望
28.7k 粉丝
0 条评论
你知道吗?

前端架构师

14.2k 声望
28.7k 粉丝
宣传栏