foreplay

Hi~ o(* ̄▽ ̄*)ブ ブ this is the last piece of Webpack5 build React nice development environment. Because the last article is mainly used to test the water may be relatively shallow, the details are not considered in place, so through this article to improve it. Dry goods or can be harvested after reading, it is recommended to drink water while watching. I’ve been busy with burying things lately, and I didn’t want to coop, so I wrote it over the weekend. Ok, so let’s get down to business, and post an article portal so you can review it, and the full source code

This process planning

Graph TD development environment configuration improvement --> production environment configuration improvement --> optimization ideas

Perfect development and configuration

Babel translator and plug-ins

First, complete the previous Loader configuration

Here is the original loader configuration for js and JSX

{
    test: /\.(js|jsx)$/,
    exclude: /node_modules/,
    use: [
        {
            loader: "babel-loader".options: {
                presets: [
                    "@babel/preset-env"."@babel/preset-react",],},},],},Copy the code

Babel presets configuration

Babel is an official definition of a JavaScript compiler. It is used to translate the ES syntax and APIS into code that can be run by existing browsers.

The installation

babel-loader @babel/core @babel/preset-env
Copy the code

These three count as an essential presence in webpack

babel-loader

This package allows JavaScript file babel-loader to be compiled using Babel and Webpack

@babel/core

It is the core Babel library that provides many apis for translating source files, and it requires plug-ins so that the translation itself does not translate

import { transformSync } from "@babel/core";

function babelLoader(source, options) {
    // var options= {
    // presets: [
    // "@babel/preset-env",
    // "@babel/preset-react",
    / /,
    / /},
    var result = transformSync(source, options);
    return this.callback(null, result.code, result.map, result.ast);
}
module.exports = babelLoader;
Copy the code
  • sourceThe required translation source file or the result of the previous loader translation
  • optionsConfigure the options parameter passed by the loader
  • transformSyncSynchronous translation of incoming code, return after translationcode,sourceMapMapping andASTObject.
@babel/preset-env

Babel /preset-env is a grammar translator or preset, but it only converts new ES syntax. Instead of converting the new ES apis, such as Iterator, Generator, Set, Maps, Proxy, Reflect,Symbol,Promise, the new apis can be translated via babel-profill, Let the browser implement the functionality of the new API but babek-profill is no longer recommended and core-js is recommended

⚠️ As of Babel 7.4.0, this package has been deprecated in favor of directly including core-js/stable (to polyfill ECMAScript features) and regenerator-runtime/runtime (needed to use transpiled generator functions):

npm i core-js -S
Copy the code

Configuration is as follows

{
    test: /\.(js|jsx)$/,
    exclude: /node_modules/,
    use: [
        {
            loader: 'babel-loader'.options: {
                presets: [['@babel/preset-env', {
                    useBuiltIns: 'entry'.corejs: '3.9.1'.targets: {
                        chrome: '60',}}],'@babel/preset-react'],},},],},Copy the code
@ Babel/preset – env parameter
  • UseBuiltIns: “usage” | | “entry” false, the default is false, there something about the usage of other parameters specific see the official description of the portal

  • Usage will polyfill only the API you are using based on the browser compatibility configured, adding patches as needed

  • The targets:

// Compatible with market share >0.25%
{
  "targets": "> 0.25%, not dead." "
}
// Make compatible with objects in the lowest version of the environment to support
{
  "targets": {
    "chrome": "58"."ie": "11"}}Copy the code

When no target is specified, it behaves similarly: Preset -env converts all ES2015-ES2020 code to ES5 compatibility. It is not recommended to use the following preset-env directly because it does not take advantage of the environment/version specific features

{
  "presets": ["@babel/preset-env"]}Copy the code

Deprecated since @babel/ Polyfill7.4.0, it is recommended that you add and set versions of core-js directly with this corejs option

  • Corejs: ‘3.9.1’ This ‘3.9.1’ is the core-JS version number

@babel/preset-react

React Babel is called by JSX to React. CreateElement (), which is used to translate React code.

  • This is a piece of JSX code
<div className="wrap" style={{ color: "# 272822"}} ><span>Study together</span>React
</div>
Copy the code
  • Preset /preset/react
React.createElement(
    "div",
    {
        className: "wrap".style: {
            color: "# 272822",
        },
    },
    React.createElement("span".null."Learning together"),
    "React"
);
Copy the code

Babel plugin configuration

  • @babel/plugin-syntax-dynamic-import supports dynamic import loading, and @babel/preset-env does not support dynamic import syntax translation.

Currently, @babel/preset-env is unaware that using import() with Webpack relies on Promise internally. Environments which do not have builtin support for Promise, like Internet Explorer, will require both the promise and iterator polyfills be added manually.

  • @babel/plugin-proposal-decorators compile class and object decorators into ES5 code
  • @babel/plugin-proposal-class-properties Transforms static class properties and initializes properties declared by syntax using properties

Configure the plug-ins required for translation. The order in which plug-ins are used is the order in which they are called in the array

Now the babel-loader parameter is bloated and can be mentioned in the.babelrc.js file

module.exports = {
    presets: [["@babel/preset-env",
            {
                useBuiltIns: "entry".corejs: "3.9.1".targets: {
                    chrome: "58".ie: "11",},},], ["@babel/preset-react",
            {
                development: process.env.NODE_ENV === "development",}]].plugins: [["@babel/plugin-proposal-decorators", { legacy: true }],
        ["@babel/plugin-proposal-class-properties", { loose: true}]."@babel/plugin-syntax-dynamic-import",]};Copy the code

Eslint configuration

The eslint-webpack-plugin is currently recommended for esList, as eslint-Loader will soon be obsolete

⚠️ The loader eslint-loader will be deprecated soon

The installation

npm i
eslint
eslint-webpack-plugin
eslint-config-airbnb-base
eslint-plugin-import -D
Copy the code
  • Eslint >= 7 (version)

  • Eslint-config-airbnb-base supports all ES6 + syntax specifications and requires esLint to be used in conjunction with eslint-plugin-import

  • Eslint-plugin-import is used to support eslint-config-airbnb-base for import/export syntax checking

webpack.dev.js

 new ESLintPlugin({
    fix: true.// Enable ESLint automatic repair
    extensions: ['js'.'jsx'].context: paths.appSrc, // File root directory
    exclude: '/node_modules/'.// Specify files/directories to exclude
    cache: true./ / cache
}),
Copy the code

React JSX also needs to react JSX.

npm i eslint-plugin-react -D
// Configure react in the esLint config extension default
extends: [
    "plugin:react/recommended".// JSX specification support
    "airbnb-base".// contains the desired ES6+ specification].// Or set it in the plugin

"plugins": [
    "react"
  ]
Copy the code

Also configure the.eslintrc.js file in the root directory

.eslintrc.js

module.exports = {
    env: {
        browser: true.es2021: true,},extends: [
        "airbnb-base".// contains the desired ES6+ specification
        "plugin:react/recommended".// React JSX specification support].parserOptions: {
        ecmaFeatures: {
            jsx: true,},ecmaVersion: 12.sourceType: "module",},plugins: [].rules: {
        "consistent-return": 0.// Arrow functions do not force return
        semi: 0."no-debugger": process.env.NODE_ENV === "production" ? "error" : "off"."react/jsx-uses-react": "error".// Prevent react from being wrongly marked as unused
        "react/jsx-uses-vars": "error"."react/jsx-filename-extension": [1, { extensions: [".js".".jsx"]}],"react/jsx-key": 2.// Verify that JSX has the key attribute in an array or iterator
        "import/no-dynamic-require": 0."import/no-extraneous-dependencies": 0."import/no-named-as-default": 0.// 'import/no-unresolved': 2,
        "import/no-webpack-loader-syntax": 0."import/prefer-default-export": 0."arrow-body-style": [2."as-needed"].// Arrow function
        "class-methods-use-this": 0.// Force class methods to use this
        // Indent with 4 Spaces
        indent: ["error".4, { SwitchCase: 1}].// SwitchCase conflict flicker problem
        // Indent JSX with 4 spaces
        "react/jsx-indent": ["error".4].// Indent props with 4 spaces
        "react/jsx-indent-props": ["error".4]."no-console": 0.// Stop using console
        "react/jsx-props-no-spreading": 0."import/no-unresolved": [
            2,
            {
                ignore: ["^ @ /"].// @ is the path alias},],},// Eslint-import-resolver-webpack is required if an alias is configured in webpack.config.js and an alias is used when importing
    settings: {
        "import/resolve": {
            webpack: {
                config: "config/webpack.dev.js",},},},};/* "off" or 0 // Close the rule close "WARN" or 1 // open the rule as a warning (does not affect the exit code) "error" or 2 // treat the rule as an error (1 when the exit code is triggered) */
Copy the code

You could also put the ESLint configuration in package.json, as follows but it’s a bit too much to put in the root directory to reduce coupling

"eslintConfig": {
    "extends": ["plugin:react/recommended"."airbnb-base"],... Omit}Copy the code

Intellisense import Alias import file

By default, alias is configured in Vscode via webpack.resolve.alias. There is no path for import

jsconfig.json


{
    "compilerOptions": {
        "baseUrl": "./src".// Base directory for resolving non-relative module names
        "paths": {
            "@ / *": [". / *"] // Specify the path map to calculate the alias relative to the baseUrl option
        },
      "experimentalDecorators": true Provide experimental support for the ES decorator proposal
    },
    "exclude": ["node_module"]}Copy the code

This alias should match the alias in WebPack Resolve

webpack.common.js

resolve: {
    modules: [paths.appNodeModules],
    extensions: ['.js'.'.jsx'.The '*'].mainFields: ['browser'.'jsnext:main'.'main'].alias: {
        moment$: 'moment/moment.js'.'@/src': paths.appSrc,

    },
},
Copy the code

Effect:

Complete environment configuration

Environment variable configuration can be divided into node environment configuration and module environment configuration, both of which are separate Settings and cannot be shared

Node global variable

Cross-env allows you to set global variables in the Node environment to distinguish between development and production mode

⚠️ is invalid under the ESM

npm i cross-env -D
Copy the code

package.json

 "scripts": {
    "build": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js"."start": "cross-env NODE_ENV=development node server". Omit},Copy the code

.eslintrc.js

Once you have configured the node environment global variables, you can obtain the values from process.env.node_env

 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'.// The debugger is not allowed in a production environment
Copy the code
DefinePlugin is used to set global variables within a module

This is a plugin that comes with Webpack and can be obtained from process.env.node_env in any module

 new webpack.DefinePlugin({
    NODE_ENV: isEnvProduction && JSON.stringify('production'), // Set global
}),
Copy the code
const App = () = > {
    console.log(process.env.NODE_ENV); // development

    return (
        <div>
            <Index />
            1133366
        </div>
    );
};
Copy the code
IgnorePlugin
  • IgnorePluginUsed to ignore specific modules so that WebPack does not pack them
new webpack.IgnorePlugin(/^\.\/locale/./moment$/);
Copy the code

The production environment is well configured

Out of the CSS

The default packaging is to inject styles into JS files and add runtimes to the Head style tag. This is convenient in development mode, but production environments recommend separating CSS into separate files, so that if the application code changes, the browser can only get the changed JS files. Extracted CSS files can be cached separately.

The installation

npm i -D mini-css-extract-plugin
Copy the code
  • The plug-in extracts CSS into a separate file. It creates a CSS file for each JS file that contains CSS, which cannot be used with style-loader, so let it work in production mode.

  • Add loader and Plugin to the WebPack configuration file

webpack.commom.js

const isEnvProduction = options.mode === 'production';

 {
    test: sassRegex,
    exclude: sassModuleRegex,
    use: [
        isEnvProduction ? MiniCssExtractPlugin.loader : 'style-loader',
        {
            loader: 'css-loader'.options: {
                importLoaders: 1,}},'postcss-loader'.'sass-loader',]},Copy the code

webpack.prod.js


plugins[... ellipsisnew MiniCssExtractPlugin({
        filename: 'css/[name].[contenthash:8].css'.// The name of the output CSS file
        chunkFilename: 'css/[name].[contenthash:8].chunk.css'.// Non-entry CSS Chunk file name
        ignoreOrder: true.// Ignore warnings about order conflicts}),].Copy the code

Compress CSS

Optimize CSS Assets Webpack Plugin with Webpack 5 Optimize CSS Assets Webpack Plugin with CSS -minimizer-webpack-plugin

⚠️ For webpack v5 or above please use css-minimizer-webpack-plugin instead.

This plugin uses CSSNano to optimize and compress CSS. It’s like the optimize-CSS-assets-webpack-plugin, but using query strings in Source maps and Assets is more accurate, enabling caching and concurrent mode.

The installation


npm install image-webpack-loader --save-dev
Copy the code

This will only enable CSS compression optimization in production mode, with the optimization.minimize option set to true if needed in development mode

webpack.common.js

 optimization: {
    minimize: isEnvProduction, // Whether it is a production environment
    minimizer: [
        new CssMinimizerPlugin({
            parallel: true.// Enable concurrent execution of multiple processes, default os.cpus().length-1
        }),
        new TerserPlugin()
    ],
},
Copy the code

After the compression

Compression js

Terser-webpack-plugin uses Terser’s JavaScript parser for ES6+ to compress JS files. Webpack4 is built into WebPack 5 and needs to be installed separately

webpack.common.js

const TerserPlugin = require('terser-webpack-plugin')...optimization: {
    minimize: isEnvProduction,
    minimizer: [...new TerserPlugin({
            parallel: true.// Enable concurrent execution of multiple processes})],},Copy the code

The compressed image

Compressed pictures are also an important part of packaging optimization at ordinary times

Image-webpack-loader can help compress and optimize images, but Cannot find module ‘gifsicle ‘Cannot find module ‘gifsicle’ Cannot find module ‘gifsicle Image-minimizer-webpack-plugin also does not install the GIF plugin, but it can be installed on scientific Web

image-webpack-loader

The installation

npm i -D image-webpack-loader
Copy the code

Image-webpack-loader Specifies the Issues corresponding to the installation

CNPM is downloadable but CNPM is not a standard download tool relative to Webpack 5 rules or try using image-Webpack-loader lower version, I tried yarn and NPM and both encountered Cannot find module ‘gifsicle’

If you have successfully used image-webpack-loader, you can use the following configuration


 {
    test: /\.(gif|png|jpe? g|svg|webp)$/i,
    type: 'asset'.parser: {
        dataUrlCondition: {
            maxSize: imageInlineSizeLimit, // 4kb}},use: [{loader: 'image-webpack-loader'.options: {
                mozjpeg: {
                    progressive: true.quality: 65,},optipng: {
                    enabled: false,},pngquant: {
                    quality: '65-90'.speed: 4,},gifsicle: {
                    interlaced: false,},webp: {
                    quality: 75,},},},],},Copy the code
  • Mozjpeg – Compress JPEG images
  • Optipng – Compress PNG images
  • Pngquant – Compressed PNG images
  • Svgo – Compress SVG images
  • Gifsicle – Compressed GIF images

Before compression

After the compression

image-minimizer-webpack-plugin

Choose one of these two and you can install it

npm install image-minimizer-webpack-plugin --save-dev
Copy the code

Images can be optimized in two modes:

  • Non-destructive (no mass loss)
  • Lossy (loss of mass)

The specific selection of official documents has been given

The Imagemin plug-in is optimized nondestructively

npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev
Copy the code

The Imagemin plug-in is used for lossy optimization

npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev
Copy the code

Imagemin-gifsicle doesn’t install either,

The following configuration can be used if the installation is successful

webpack.prod.js

new ImageMinimizerPlugin({
    minimizerOptions: {
        plugins: [['gifsicle', { interlaced: true }],
            ['jpegtran', { progressive: true }],
            ['optipng', { optimizationLevel: 5 }],
            [
                'svgo',
                {
                    plugins: [{removeViewBox: false,},],},],],},})Copy the code

The idea of optimization

The optimization direction is divided into two: one is time (speed) and space (volume) Webpack. No matter the module specification is packaged and unified, it is webpack_require. If the development mode can support ESM scheme, it will be good.

Now this kind of optimization means is also more, or you can try other tools Vite, Snowpack, but the stage is still mature, really used in the project may still be relatively few, pit more, I also used Vite for a few pages in a small project, but the tool itself is really nice, looking forward to its development. Webpack optimization I will mention the idea, and then a lot of blogs have mentioned, you can go to see their.

No matter what you do, you need to have a clear goal, and optimization is the same. You need to know where it takes the most time, and you need to focus on what you can do. First, the speed-measure-webpack-plugin can analyze the time for each step of packaging

I failed to use this package, and some people have encountered this problem in the issue

npm i speed-measure-webpack-plugin -D
Copy the code

Webpack-bundle-analyzer analyzes what files are packaged, what is the size ratio, module inclusion relationships, dependencies, whether files are duplicated, and how big are the compressed files

npm i webpack-bundle-analyzer -D
Copy the code

Compile time optimization

Narrow down the file search
  • resolve
resolve: {
    modules: [paths.appNodeModules],
    extensions: ['.js'.'.jsx'.The '*'].mainFields: ['browser'.'jsnext:main'.'main'].alias: {
        moment$: 'moment/moment.js'.'@/src': paths.appSrc,
    },
},
Copy the code
  • rule.oneOf

If oneOf can be used to solve this problem, you can exit as long as there is a match. Similar to array. find, if it finds the right one, it will return and will not continue to search

module.exports = {
  module: {
    rules: [{oneOf: [...,]}]}}Copy the code
  • external

  • Multiprocess processing

The cache
  • Persistent cache
  • babel-loaderOpen the cache

Volume optimization

  • CSS HTML Image JS compression

  • Tree-sharking removes useless code

    In the Webpack4 version, it is easier to remove unnecessary code when packaging. The main thing is to find out if an imported variable appears in the module, and Webpack5 can optimize it based on the relationship between scopes

    Tree-shaing relies on ESM, and if it is not ESM, it will not be supported, such as CommonJS, mainly because EMS is static dependency analysis and compilation can determine its dependencies, while require is running or loading, do not know how it depends on the painful.

  • SplitChunks subcontract

The problem

There was a problem with webpack-dev-server in the last post, which is that the page failed to refresh after the error was reported. I tried it and it did exist. I can’t find the exact reason for this either, but esLint is configured to restore hot updates when it is recompiled, so try it out.

conclusion

So here the whole construction is over ^_^ there are some knowledge points I may not write, the specific details can be source code. And this should be the last webpack post. Some interesting Webpack plug-ins might be written later. If you encounter problems debugging Webpack5, please discuss them in the comments section. Finally, I hope you all have a harvest, see you next…