Getting started with Webpack5 to the principle

Webpack5 learning

Shang Silicon Valley Webpack5 new version video tutorial

B station direct: https://www.bilibili.com/video/BV14T4y1z7sw

Baidu network disk: https://pan.baidu.com/s/114lJRGua2uHBdLq_iVLOOQ Extraction code: yyds

Alibaba cloud disk: https://www.aliyundrive.com/s/UMkmCzdWsGh (please download the supporting materials of the tutorial from Baidu network disk)

Watch the front-end courses of Shang Silicon Valley: http://www.atguigu.com/web

For more Silicon Valley video tutorials, please visit: http://www.atguigu.com/download.shtml

Online course address: https://yk2012.github.io/sgg_webpack5/

Webpack5 Chinese Documentation: Loaders | webpack Chinese Documentation (docschina.org)
Code Warehouse: https://gitee.com/szxio/webpack5

1. Easy to use

1.1 Build the project

image-20230821224325473

1.2 Write JS code

count.js

export default function count(a,b){
    
    
    return a + b
}

sum.js

export default function sum(...args){
    
    
    return args.reduce((a,b)=>a+b,0)
}

main.js

import count from "./js/count";
import sum from "./js/sum";

console.log(count(1,2))
console.log(sum(1,2,3,4))

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 <h1>Hello World</h1>
</body>
<script src="./src/main.js"></script>
</html>

1.3 Running the code

image-20230821224516038

You can find that the browser reports an error. ES module syntax is not supported by default

1.4 Initialization dependencies

npm init -y

will be automatically generatedpackage.json

image-20230821224645037

Continue to install dependencies

npm i webpack webpack-cli -D

1.5 start webpack

Development environment construction

npx webpack ./src/main.js --mode=development

Production environment construction

npx webpack ./src/main.js --mode=production

Modify the reference address of the main.js file. The browser prints normally

2. Basic configuration

2.1 Five core concepts

  1. entry

Instruct Webpack which file to start packing

  1. output

Instruct Webpack where to output the packaged files, how to name them, etc.

  1. loader

webpack itself can only handle js, json and other resources, and other resources need to use loader for Webpack to parse

  1. plugins

Extending the functionality of Webpack

  1. mode

There are mainly two modes:

  • Development mode: development
  • Production mode: production

2.2 Basic configuration file

Create a new file in the project root directorywebpack.config.js

const path = require("path");
module.exports = {
    
    
    // 入口文件,相对路径
    entry: "./src/main.js",
    // 输出
    output: {
    
    
        // 要输出到的位置,绝对路径
        path: path.resolve(__dirname,"dist"),
        // 文件名
        filename: "main.js"
    },
    // 加载器
    module: {
    
    
        rules: [
            // loader 的配置
        ]
    },
    // 插件
    plugins: [
        // plugins的配置
    ],
    // 模式
    mode: "development"
}

With this configuration file, we only need to enter the following command when packaging again

npx webpack

This command will automatically find the configuration in the webpa.config.js file in the root directory for packaging

3. Processing style resources

3.1 Handling CSS resources

By default, webpack only supports JS files. After we write CSS, there will be errors in packaging. We can process CSS resources by adding a loader

More loaders can be viewed through the official webpack documentation

When we introduce the css file in main.js, the following error will occur when performing packaging

image-20230823223056375

install loader

npm i css-loader style-loader -D

Then configure the rules

module.exports = {
    
    
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
        ],
      },
    ],
  },
};

At this point, execute the packaging again

3.2 Handling Less resources

Install

npm install less less-loader --save-dev

configuration rules

module.exports = {
    
    
  module: {
    
    
    rules: [
      {
    
    
        test: /\.less$/i,
        use: [
          // compiles Less to CSS
          'style-loader',
          'css-loader',
          'less-loader',
        ],
      },
    ],
  },
};

3.3 Handling Scss resources

Install

npm install sass-loader sass --save-dev

configuration rules

module.exports = {
    
    
  module: {
    
    
    rules: [
      {
    
    
        test: /\.s[ac]ss$/i,
        use: [
          // 将 JS 字符串生成为 style 节点
          'style-loader',
          // 将 CSS 转化成 CommonJS 模块
          'css-loader',
          // 将 Sass 编译成 CSS
          'sass-loader',
        ],
      },
    ],
  },
};

4. Process image resources

4.1 Image to Base64

We can convert files smaller than a certain size to base64 through configuration, thereby reducing requests

Add the following configuration

module.exports = {
    
    
  module: {
    
    
    rules: [
      {
    
    
          test: /\.(png|jpe?g|gif|webp)$/,
          type: "asset",
          parser: {
    
    
              dataUrlCondition: {
    
    
                  maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
              }
          }
      },
    ],
  },
};

After converting to base64, the request will not take time

image-20230824214034674

5. Modify the file output address

At present, the files we package and generate are all under one file

image-20230824214711536

I want it to be a JS file under the JS file, and an image file under the image file

Modify the configuration as follows

const path = require("path");
module.exports = {
    // 入口文件,相对路径
    entry: "./src/main.js",
    // 输出
    output: {
        // 要输出到的位置,绝对路径
        path: path.resolve(__dirname,"dist"),
        // 文件名
+       filename: "js/main.js",
    },
    // 加载器
    module: {
        rules: [
            {
                test: /\.(png|jpe?g|gif|webp)$/,
                type: "asset",
                parser: {
                    dataUrlCondition: {
                        maxSize: 18 * 1024 // 小于10kb的图片会被base64处理
                    }
                },
+               generator: {
+                   // 修改图片文件的输出地址
+                   // hash:10 只取10位哈希值
+                   // ext图片后缀名
+                   // query 图片文件后面携带的参数
+                   filename: 'images/[hash:10][ext][query]'
+               }
            },
        ]
    },
    // 插件
    plugins: [
        // plugins的配置
    ],
    // 模式
    mode: "development"
}

Pack again to see the effect

image-20230824220006600

6. Automatically clear the last packaged file

Add the clean attribute under output

const path = require("path");

module.exports = {
    
    
    // 入口文件,相对路径
    entry: "./src/main.js",
    // 输出
    output: {
    
    
        // 要输出到的位置,绝对路径
        path: path.resolve(__dirname,"dist"),
        // 文件名
        filename: "js/main.js",
        // 每次打包前自动清空path对应的目录文件
        clean: true
    },
}

7. Handling font icon resources

We can find some icon resources in the Alibaba vector icon library , then add them to the project, click to download to the local

image-20230824222354716

Use the second way to import

image-20230824222420993

Import the downloaded css and icon files into the project

image-20230824222512888

Introduce iconfont.css in main.js

Then configure the rules to package and output the font icon into the font folder

module.exports = {
    
    
    module: {
    
    
        rules: [
            {
    
    
                test: /\.(ttf|woff2?)$/,
                type: "asset/resource",
                generator: {
    
    
                    filename: 'font/[hash:10][ext][query]'
                }
            },
        ]
    },
}

Then the page can be used in the following way

<span class="iconfont icon-aixin" style="font-size: 30px;color: orangered"></span>
<span class="iconfont icon-bianji"></span>
<span class="iconfont icon-dianzan"></span>
<span class="iconfont icon-dingwei"></span>

image-20230824222755309

8. Handling other resources

For other resources, we can directly add the corresponding file suffix in the following rules, and webpack will output the file to the specified directory intact

module.exports = {
    
    
    module: {
    
    
        rules: [
            {
    
    
                test: /\.(ttf|woff2?|mp3|mp4|word)$/,
                type: "asset/resource",
                generator: {
    
    
                    filename: 'font/[hash:10][ext][query]'
                }
            },
        ]
    },
}

9. Processing JS files

9.1 Eslint plugin

Composable JavaScript and JSX inspection tool.

The meaning of this sentence is: it is a tool used to detect js and jsx syntax, and can configure various functions

When we use Eslint, the key is to write the Eslint configuration file, which contains various rules rules. When running Eslint in the future, the code will be checked with the written rules.

How to write the configuration file

There are many ways to write configuration files:

  • .eslintrc.*

    : Create a new file, located in the root directory of the project

    • .eslintrc
    • .eslintrc.js
    • .eslintrc.json
    • The difference is that the configuration format is different
  • package.jsonMedium eslintConfig: No need to create a file, write on the basis of the original file

ESLint will find and read them automatically, so only one of the above configuration files needs to exist

specific configuration

Take for .eslintrc.jsexample

module.exports = {
    
    
  // 解析选项
  parserOptions: {
    
    },
  // 具体检查规则
  rules: {
    
    },
  // 继承其他规则
  extends: [],
  // ...
  // 其他规则详见:https://eslint.bootcss.com/docs/user-guide/configuring
};

parserOptions parser options

parserOptions: {
    
    
  ecmaVersion: 6, // ES 语法版本
  sourceType: "module", // ES 模块化
  ecmaFeatures: {
    
     // ES 其他特性
    jsx: true // 如果是 React 项目,就需要开启 jsx 语法
  }
}

rules Specific rules

  • "off"or 0- close the rule
  • "warn"or 1- enable rule, use warning level errors: warn(does not cause the program to exit)
  • "error"or 2- enable the rule, use the error level error: error(when triggered, the program will exit)
rules: {
    
    
  semi: "error", // 禁止使用分号
  'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告
  'default-case': [
    'warn', // 要求 switch 语句中有 default 分支,否则警告
    {
    
     commentPattern: '^no default$' } // 允许在最后注释 no default, 就不会有警告了
  ],
  eqeqeq: [
    'warn', // 强制使用 === 和 !==,否则警告
    'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少数情况下不会有警告
  ],
}

For more rules, see: Rules Documentation

extends inheritance

It is too strenuous to write rules little by little during development, so there is a better way to inherit existing rules.

The following well-known rules exist:

module.exports = {
    
    
  extends: ["eslint:recommended"],
  rules: {
    
    
    // 我们的规则会覆盖掉react-app的规则
    // 所以想要修改规则直接改就是了
    eqeqeq: ["warn", "smart"],
  },
};

The use of Esline in Webpack

Install

npm install eslint-webpack-plugin eslint --save-dev

Then add the plugin to your webpack config. For example:

const ESLintPlugin = require('eslint-webpack-plugin');

module.exports = {
    
    
  // ...
  plugins: [
  		new ESLintPlugin({
    
    
            // 指定文件的根目录
            context:path.resolve(__dirname, "src"),
        })
  ],
  // ...
};

New in the root directory.eslintrc.js

module.exports = {
    
    
    // 继承 Eslint 规则
    extends: ["eslint:recommended"],
    env: {
    
    
        node: true, // 启用node中全局变量
        browser: true, // 启用浏览器中全局变量
    },
    parserOptions: {
    
    
        ecmaVersion: 6,
        sourceType: "module",
    },
    rules: {
    
    
        "no-var": 2, // 不能使用 var 定义变量
    },
};

We write a wrong code

image-20230824231332115

When packaging, an error will be prompted and the packaging will be terminated

image-20230824231322801

You can enable eslint detection in webstorm

image-20230824231406983

so you can catch errors while writing your code

add eslint ignore file

new .eslintignorefile

# 忽略dist目录下所有文件
dist

9.2 Babel plugins

JavaScript compiler.

It is mainly used to convert code written in ES6 syntax to backward compatible JavaScript syntax so that it can run in current and old versions of browsers or other environments

configuration file

Configuration files can be written in many ways:

  • babel.config.*
    

    : Create a new file, located in the root directory of the project

    • babel.config.js
    • babel.config.json
  • .babelrc.*
    

    : Create a new file, located in the root directory of the project

    • .babelrc
    • .babelrc.js
    • .babelrc.json
  • package.jsonMedium babel: No need to create a file, write on the basis of the original file

Babel will find and read them automatically, so only one of the above configuration files needs to exist

Configuration example

Let's take babel.config.jsthe configuration file as an example:

module.exports = {
    
    
  // 预设
  presets: ["@babel/preset-env"],
};

presets presets

Simple understanding: it is a set of Babel plugins that extend Babel's functionality

  • @babel/preset-env: A smart preset that allows you to use the latest JavaScript.
  • @babel/preset-react: a preset for compiling React jsx syntax
  • @babel/preset-typescript: a preset for compiling TypeScript syntax

Use in webpack

Install

npm install -D babel-loader @babel/core @babel/preset-env

Usage one:

In the webpack configuration object, you need to add babel-loader to the module list, like this

module: {
    
    
  rules: [
    {
    
    
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/, // 排除文件
      use: {
    
    
        loader: 'babel-loader',
        options: {
    
    
          presets: ['@babel/preset-env']
        }
      }
    }
  ]
}

Usage 2 (recommended usage):

New in the project root directorybabel.config.js

module.exports = {
    
    
    presets: ['@babel/preset-env']
}

Then add in the webpack configuration object

module: {
    
    
  rules: [
    {
    
    
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/, // 排除文件
      use: {
    
    
        loader: 'babel-loader',       
      }
    }
  ]
}

File code generated before packaging

image-20230826093200339

Packaged file code

image-20230826093532713

The result of modifying the output is not output as an arrow

Add environment configuration in output

// 输出
output: {
    
    
    // 要输出到的位置,绝对路径
    path: path.resolve(__dirname,"dist"),
    // 文件名
    filename: "js/main.js",
    // 每次打包前自动清空path对应的目录文件
    clean: true,
    
    environment: {
    
    
        // 关闭箭头函数输出
        arrowFunction: false
    }
},

10. Processing HTML files

We hope that JS can be automatically introduced for us after each package, which can be set in the following way

Install

npm install --save-dev html-webpack-plugin

Import and use

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
    
    
  plugins: [
      // 处理HTML文件
      new HtmlWebpackPlugin({
    
    
          template: path.resolve(__dirname, "index.html"), // 使用的模板地址
          title: "学习Webpack", // 网站title
          hash:true, // 添加哈希值
      })
  ],
};

After setting the title, you need to modify the setting method of the title in the template, which needs to be dynamically obtained

<title><%= htmlWebpackPlugin.options.title %></title>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
    <span class="iconfont icon-aixin" style="font-size: 30px;color: orangered"></span>
    <span class="iconfont icon-bianji"></span>
    <span class="iconfont icon-dianzan"></span>
    <span class="iconfont icon-dingwei"></span>
    <div class="box"></div>
    <div class="box1"></div>
    <div class="box2">
        <span class="desc">哈哈</span>
    </div>
     <h1>Hello World</h1>
</body>
</html>

View documentation for more options configuration: https://github.com/jantimon/html-webpack-plugin#options

Files generated after packaging

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学习Webpack</title>
<script defer src="js/main.js?15bb642196d30b484393"></script></head>
<body>
    <span class="iconfont icon-aixin" style="font-size: 30px;color: orangered"></span>
    <span class="iconfont icon-bianji"></span>
    <span class="iconfont icon-dianzan"></span>
    <span class="iconfont icon-dingwei"></span>
    <div class="box"></div>
    <div class="box1"></div>
    <div class="box2">
        <span class="desc">哈哈</span>
    </div>
    <h1>Hello World</h1>
</body>
</html>

11. Build a development server

We need to repackage every time we modify the code, and then run the html file to see the effect. We can view the changes in real time by building a development server

Install

npm i webpack-dev-server -D

add configuration

// 开发服务器
devServer: {
    
    
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
},

Then start it with the following command

npx webpack serve

After the startup is successful, the cursor will stay below, and the browser will automatically open at the same time. After we modify the code and refresh the browser, it will automatically take effect

image-20230826110943453

12. Production environment preparation

Create a new config file in the root directory, put the original webpck.config.js in the config file, and copy a configuration

Modify the two configuration file names to be

  • webpack.dev.js
  • webpack.prod.js

Then add before all the relative paths ../and jump one layer outside

Delete the output configuration in the dev configuration file, because the dev environment does not need to be packaged, delete the devServer configuration in the prod configuration

The configured configuration files are as follows

  • webpack.dev.js
const path = require("path");
const ESLintPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    
    
    // 入口文件,相对路径
    entry: "./src/main.js",
    // 加载器
    module: {
    
    
        rules: [
            {
    
    
                // 用来匹配 .css 结尾的文件
                test: /\.css$/,
                // use 数组里面 Loader 执行顺序是从右到左
                use: ["style-loader", "css-loader"],
            },
            {
    
    
                test: /\.less$/i,
                use: [
                    // compiles Less to CSS
                    'style-loader',
                    'css-loader',
                    'less-loader',
                ],
            },
            {
    
    
                test: /\.s[ac]ss$/i,
                use: [
                    // 将 JS 字符串生成为 style 节点
                    'style-loader',
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'sass-loader',
                ],
            },
            {
    
    
                test: /\.(png|jpe?g|gif|webp)$/,
                type: "asset",
                parser: {
    
    
                    dataUrlCondition: {
    
    
                        maxSize: 18 * 1024 // 小于10kb的图片会被base64处理
                    }
                },
                generator: {
    
    
                    // 修改图片文件的输出地址
                    // hash:10 只取10位哈希值
                    // ext图片后缀名
                    // query 图片文件后面携带的参数
                    filename: 'images/[hash:10][ext][query]'
                }
            },
            {
    
    
                test: /\.(ttf|woff2?|mp3|mp4|word)$/,
                type: "asset/resource",
                generator: {
    
    
                    filename: 'font/[hash:10][ext][query]'
                }
            },
            {
    
    
                test: /\.m?js$/,
                exclude: /(node_modules|bower_components)/, // 排除文件
                use: {
    
    
                    loader: 'babel-loader',
                }
            }
        ]
    },
    // 插件
    plugins: [
        // Eslint插件
        new ESLintPlugin({
    
    
            // 指定文件的根目录
            context:path.resolve(__dirname, "../src"),
        }),
        // 处理HTML文件
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname, "../index.html"), // 使用的模板地址
            title: "学习Webpack", // 网站title
            hash:true, // 添加哈希值
        })
    ],
    // 模式
    mode: "development",
    // 开发服务器
    devServer: {
    
    
        host: "localhost", // 启动服务器域名
        port: "3000", // 启动服务器端口号
        open: true, // 是否自动打开浏览器
        liveReload:true,
    },
}
  • webpack.build.js
const path = require("path");
const ESLintPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    
    
    // 入口文件,相对路径
    entry: "./src/main.js",
    // 输出
    output: {
    
    
        // 要输出到的位置,绝对路径
        path: path.resolve(__dirname,"../dist"),
        // 文件名
        filename: "js/main.js",
        // 每次打包前自动清空path对应的目录文件
        clean: true,
        // 关闭箭头函数输出
        environment: {
    
    
            arrowFunction: false
        }
    },

    // 加载器
    module: {
    
    
        rules: [
            {
    
    
                // 用来匹配 .css 结尾的文件
                test: /\.css$/,
                // use 数组里面 Loader 执行顺序是从右到左
                use: ["style-loader", "css-loader"],
            },
            {
    
    
                test: /\.less$/i,
                use: [
                    // compiles Less to CSS
                    'style-loader',
                    'css-loader',
                    'less-loader',
                ],
            },
            {
    
    
                test: /\.s[ac]ss$/i,
                use: [
                    // 将 JS 字符串生成为 style 节点
                    'style-loader',
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'sass-loader',
                ],
            },
            {
    
    
                test: /\.(png|jpe?g|gif|webp)$/,
                type: "asset",
                parser: {
    
    
                    dataUrlCondition: {
    
    
                        maxSize: 18 * 1024 // 小于10kb的图片会被base64处理
                    }
                },
                generator: {
    
    
                    // 修改图片文件的输出地址
                    // hash:10 只取10位哈希值
                    // ext图片后缀名
                    // query 图片文件后面携带的参数
                    filename: 'images/[hash:10][ext][query]'
                }
            },
            {
    
    
                test: /\.(ttf|woff2?|mp3|mp4|word)$/,
                type: "asset/resource",
                generator: {
    
    
                    filename: 'font/[hash:10][ext][query]'
                }
            },
            {
    
    
                test: /\.m?js$/,
                exclude: /(node_modules|bower_components)/, // 排除文件
                use: {
    
    
                    loader: 'babel-loader',
                }
            }
        ]
    },
    // 插件
    plugins: [
        // Eslint插件
        new ESLintPlugin({
    
    
            // 指定文件的根目录
            context:path.resolve(__dirname, "../src"),
        }),
        // 处理HTML文件
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname, "../index.html"), // 使用的模板地址
            title: "学习Webpack", // 网站title
            hash:true, // 添加哈希值
        })
    ],
    // 模式
    mode: "production",
}

Then modify the package.json file, set the startup command and packaging command

"scripts": {
    
    
  "dev": "webpack serve --config ./config/webpack.dev.js",
  "build": "webpack --config ./config/webpack.prod.js"
}

13. CSS Processing

13.1 Extract CSS into separate files

Install

npm install --save-dev mini-css-extract-plugin

Need to replace the original style-loader with MiniCssExtractPlugin.loader, and use the plugin in plugins

module.exports = {
    
    
  plugins: [new MiniCssExtractPlugin({
    
    
      filename:"state/css/main.css" // 指定css文件输出位置
  })],
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
};

Then cooperate with the above html-webpack-plugin plug-in, you can automatically complete the introduction of resources

The directory generated after packaging is as follows

image-20230826160424031

13.2 Dealing with CSS Compatibility Issues

Some CSS styles may have compatibility issues in some browsers, we can handle this part of compatibility issues through configuration

Install

npm i postcss-loader postcss postcss-preset-env -D

Add configuration. This configuration must be placed behind css-loader

rules:[
    {
    
    
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: [MiniCssExtractPlugin.loader, "css-loader", {
    
    
            loader: "postcss-loader",
            options: {
    
    
                postcssOptions: {
    
    
                    plugins: [
                        "postcss-preset-env", // 能解决大多数样式兼容性问题
                    ],
                },
            },
        }],
    },
]

Add configuration in package.json file

"browserslist": [
    "ie >= 6"
]

This means that it is compatible to ie6

We try to add the following style in the code

.box{
    
    
    width: 200px;
    height: 200px;
    background-color: pink;
    background-image: url("../images/1.png");
    transition: 0.5s;
}
.box:hover{
    
    
    transform: scale(1.2);
    transition: 0.5s;
}

Then execute the packaging command to view the packaged code

.box{
    
    
    width: 200px;
    height: 200px;
    background-color: pink;
    background-image: url(../../state/images/626c3888ec.png);
    transition: 0.5s;
}
.box:hover{
    
    
    -ms-transform: scale(1.2);
        transform: scale(1.2);
    transition: 0.5s;
}

You can see some compatibility code added

But under normal circumstances, we generally don't consider the old version of the browser, so we can set it like this:

"browserslist": [
  "last 2 version",
  "> 1%",
  "not dead"
]
  • last 2 versionConsider only the two most recent versions of the browser
  • > 1%Covers 99% of browsers
  • not deadDo not consider dead versions

Take the intersection of the above three configurations for compatibility processing

13.3 Extract duplicate configuration

const path = require("path");
const ESLintPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

function myCssLoader(preProcessor){
    
    
    return[
        MiniCssExtractPlugin.loader,
        "css-loader", {
    
    
            loader: "postcss-loader",
            options: {
    
    
                postcssOptions: {
    
    
                    plugins: [
                        "postcss-preset-env", // 能解决大多数样式兼容性问题
                    ],
                },
            },
        },
        preProcessor
    ].filter(Boolean)
}

module.exports = {
    
    
    // 入口文件,相对路径
    entry: "./src/main.js",
    // 输出
    output: {
    
    
        // 要输出到的位置,绝对路径
        path: path.resolve(__dirname,"../dist"),
        // 文件名
        filename: "state/js/main.js",
        // 每次打包前自动清空path对应的目录文件
        clean: true,
        // 关闭箭头函数输出
        environment: {
    
    
            arrowFunction: false
        }
    },

    // 加载器
    module: {
    
    
        rules: [
            {
    
    
                // 用来匹配 .css 结尾的文件
                test: /\.css$/,
                // use 数组里面 Loader 执行顺序是从右到左
                use: myCssLoader(),
            },
            {
    
    
                test: /\.less$/i,
                use: myCssLoader('less-loader')
            },
            {
    
    
                test: /\.s[ac]ss$/i,
                use: myCssLoader('sass-loader')
            },
            {
    
    
                test: /\.(png|jpe?g|gif|webp)$/,
                type: "asset",
                parser: {
    
    
                    dataUrlCondition: {
    
    
                        maxSize: 18 * 1024 // 小于10kb的图片会被base64处理
                    }
                },
                generator: {
    
    
                    // 修改图片文件的输出地址
                    // hash:10 只取10位哈希值
                    // ext图片后缀名
                    // query 图片文件后面携带的参数
                    filename: 'state/images/[hash:10][ext][query]'
                }
            },
            {
    
    
                test: /\.(ttf|woff2?|mp3|mp4|word)$/,
                type: "asset/resource",
                generator: {
    
    
                    filename: 'state/resource/[hash:10][ext][query]'
                }
            },
            {
    
    
                test: /\.m?js$/,
                exclude: /(node_modules|bower_components)/, // 排除文件
                use: {
    
    
                    loader: 'babel-loader',
                }
            }
        ]
    },
    // 插件
    plugins: [
        // Eslint插件
        new ESLintPlugin({
    
    
            // 指定文件的根目录
            context:path.resolve(__dirname, "../src"),
        }),
        // 处理HTML文件
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname, "../index.html"), // 使用的模板地址
            title: "学习Webpack", // 网站title
            hash:true, // 添加哈希值
        }),
        // 将CSS处理成单独文件
        new MiniCssExtractPlugin({
    
    
            filename:"state/css/main.css"
        })
    ],
    // 模式
    mode: "production",
}

13.4 Compressing CSS

download dependencies

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
plugins:[
    // 压缩CSS
    new CssMinimizerPlugin()
]

Package to see the effect

image-20230826163915973

14. Advanced optimization

14.1 Improve development experience SourceMap

The code we run during development is compiled by webpack, for example, it looks like this:

image-20230826165819723

If there is a problem in a certain line of code, it is very inconvenient for us to debug, but a mapping will be created through SourceMap, and we can directly locate the source code, so as to quickly and conveniently check which line of code has the problem

By looking at the Webpack DevTool document open in new window , we can see that there are many situations for the value of SourceMap.

But in actual development, we only need to pay attention to two situations:

  • Development mode:cheap-module-source-map
    • Advantages: fast package compilation, only contains line mapping
    • Cons: no column mapping
module.exports = {
    
    
  // 其他省略
  mode: "development",
  devtool: "cheap-module-source-map",
};
  • Production mode:

    source-map
    
    • Pros: Contains row/column mapping
    • Disadvantages: package compilation is slower
module.exports = {
    
    
  // 其他省略
  mode: "production",
  devtool: "source-map",
};

14.2 Hot hot replacement

Why

We modified one of the module codes during development, and Webpack will repackage and compile all modules by default, which is very slow.

So we need to modify the code of a certain module, only the code of this module needs to be repackaged and compiled, and the other modules remain unchanged, so that the packaging speed can be very fast.

what is

HotModuleReplacement (HMR/Hot Module Replacement): Replace, add, or delete modules while the program is running without reloading the entire page.

how to use

  1. basic configuration
module.exports = {
    
    
  // 其他省略
  devServer: {
    
    
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
    hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
  },
};

At this time, the css style has been processed by the style-loader and has the HMR function. But js doesn't work yet.

  1. JS configuration
// main.js
import count from "./js/count";
import sum from "./js/sum";
// 引入资源,Webpack才会对其打包
import "./css/iconfont.css";
import "./css/index.css";
import "./less/index.less";
import "./sass/index.sass";
import "./sass/index.scss";
import "./styl/index.styl";

const result1 = count(2, 1);
console.log(result1);
const result2 = sum(1, 2, 3, 4);
console.log(result2);

// 判断是否支持HMR功能
if (module.hot) {
    
    
  module.hot.accept("./js/count.js", function (count) {
    
    
    const result1 = count(2, 1);
    console.log(result1);
  });

  module.hot.accept("./js/sum.js", function (sum) {
    
    
    const result2 = sum(1, 2, 3, 4);
    console.log(result2);
  });
}

It will be troublesome to write the above, so we will use other loaders to solve the actual development.

比如:vue-loaderopen in new window, react-hot-loaderopen in new window

14.3 oneOf

Why

When packaging, each file will be processed by all loaders. Although it testis not actually processed because of the regularity, it must be processed once. slower.

what is

As the name implies, it can only match the previous loader, and the rest will not match

how to use

const path = require("path");
const ESLintPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

function myCssLoader(preProcessor){
    
    
    return[
        MiniCssExtractPlugin.loader,
        "css-loader", {
    
    
            loader: "postcss-loader",
            options: {
    
    
                postcssOptions: {
    
    
                    plugins: [
                        "postcss-preset-env", // 能解决大多数样式兼容性问题
                    ],
                },
            },
        },
        preProcessor
    ].filter(Boolean)
}

module.exports = {
    
    
    // 入口文件,相对路径
    entry: "./src/main.js",
    // 输出
    output: {
    
    
        // 要输出到的位置,绝对路径
        path: path.resolve(__dirname,"../dist"),
        // 文件名
        filename: "state/js/main.js",
        // 每次打包前自动清空path对应的目录文件
        clean: true,
        // 关闭箭头函数输出
        environment: {
    
    
            arrowFunction: false
        }
    },

    // 加载器
    module: {
    
    
        rules: [
            {
    
    
                oneOf: [
                    {
    
    
                        // 用来匹配 .css 结尾的文件
                        test: /\.css$/,
                        // use 数组里面 Loader 执行顺序是从右到左
                        use: myCssLoader(),
                    },
                    {
    
    
                        test: /\.less$/i,
                        use: myCssLoader('less-loader')
                    },
                    {
    
    
                        test: /\.s[ac]ss$/i,
                        use: myCssLoader('sass-loader')
                    },
                    {
    
    
                        test: /\.(png|jpe?g|gif|webp)$/,
                        type: "asset",
                        parser: {
    
    
                            dataUrlCondition: {
    
    
                                maxSize: 18 * 1024 // 小于10kb的图片会被base64处理
                            }
                        },
                        generator: {
    
    
                            // 修改图片文件的输出地址
                            // hash:10 只取10位哈希值
                            // ext图片后缀名
                            // query 图片文件后面携带的参数
                            filename: 'state/images/[hash:10][ext][query]'
                        }
                    },
                    {
    
    
                        test: /\.(ttf|woff2?|mp3|mp4|word)$/,
                        type: "asset/resource",
                        generator: {
    
    
                            filename: 'state/resource/[hash:10][ext][query]'
                        }
                    },
                    {
    
    
                        test: /\.m?js$/,
                        exclude: /(node_modules|bower_components)/, // 排除文件
                        use: {
    
    
                            loader: 'babel-loader',
                        }
                    }
                ]
            }
        ]
    },
    // 插件
    plugins: [
        // Eslint插件
        new ESLintPlugin({
    
    
            // 指定文件的根目录
            context:path.resolve(__dirname, "../src"),
        }),
        // 处理HTML文件
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname, "../index.html"), // 使用的模板地址
            title: "学习Webpack", // 网站title
            hash:true, // 添加哈希值
        }),
        // 将CSS处理成单独文件
        new MiniCssExtractPlugin({
    
    
            filename:"state/css/main.css"
        }),
        // 压缩CSS
        new CssMinimizerPlugin()
    ],
    // 模式
    mode: "production",
    devtool: "source-map"
}

14.4 Include/Exclude

Why

We need to use third-party libraries or plug-ins during development, and all files are downloaded to node_modules. And these files do not need to be compiled and can be used directly.

So when we process js files, we need to exclude the files under node_modules.

what is

  • include

Include, only process xxx files

  • exclude

Exclude, all files except xxx files are processed

how to use

const path = require("path");
const ESLintPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    
    
    // 入口文件,相对路径
    entry: "./src/main.js",
    // 加载器
    module: {
    
    
        rules: [
            // ....省略其他
            {
    
    
                test: /\.m?js$/,
                // exclude: /(node_modules|bower_components)/, // 排除文件
                include: path.resolve(__dirname,"../src"), // 也可以设置只包含某个文件夹下的JS
                use: {
    
    
                    loader: 'babel-loader',
                }
            }
        ]
    },
    // 插件
    plugins: [
        // Eslint插件
        new ESLintPlugin({
    
    
            // 指定文件的根目录
            context:path.resolve(__dirname, "../src"),
            exclude:["node_modules"] // 排除 node_modules 文件夹下的文件,默认是node_module
        }),
        // 处理HTML文件
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname, "../index.html"), // 使用的模板地址
            title: "学习Webpack", // 网站title
            hash:true, // 添加哈希值
        })
    ],
    // 模式
    mode: "development",
    devtool: "cheap-module-source-map",
    // 开发服务器
    devServer: {
    
    
        host: "localhost", // 启动服务器域名
        port: "3000", // 启动服务器端口号
        open: true, // 是否自动打开浏览器
        hot:true, // 热替换,改变JS或者CSS是可以不刷新页面就看到最新效果
    },
}

14.5 Catch

Why

The js file must be checked by Eslint and compiled by Babel every time it is packaged, which is relatively slow.

We can cache the results of previous Eslint checks and Babel compilation, so that the second packaging will be faster

how to use

rules:[
    {
    
    
        test: /\.m?js$/,
        exclude: /(node_modules|bower_components)/, // 排除文件
        use: {
    
    
            loader: 'babel-loader',
            options: {
    
    
                cacheDirectory: true, // 开启babel编译缓存
                cacheCompression: false, // 缓存文件不要压缩
            }
        }
    }
]

eslint-webpack-pluginCaching is enabled by default

View after packagingnode_modules/.catch

image-20230826212721210

14.6 Enable multi-threaded packaging

Why

When our project files are large enough and the number is large enough, it will take a long time to perform the packaging operation. At this time, we can increase the packaging speed by setting up multi-threaded packaging

Install

cnpm install terser-webpack-plugin thread-loader --save-dev

terser-webpack-pluginIt comes with webpack5 by default, but we need to modify the default configuration to enable multi-threaded packaging, so we need to reinstall it

TerserWebpackPlugin | webpack Chinese documentation (docschina.org)

thread-loaderIt is used to enable multi-threading and placed in front of the loader that needs to enable multi-threading

thread-loader | webpack Chinese documentation (docschina.org)

The default number of threads enabled is: number of cpu cores - 1

how to use

const path = require("path");

const ESLintPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");


function myCssLoader(preProcessor){
    
    
    return[
        MiniCssExtractPlugin.loader,
        "css-loader", {
    
    
            loader: "postcss-loader",
            options: {
    
    
                postcssOptions: {
    
    
                    plugins: [
                        "postcss-preset-env", // 能解决大多数样式兼容性问题
                    ],
                },
            },
        },
        preProcessor
    ].filter(Boolean)
}

module.exports = {
    
    
    // 入口文件,相对路径
    entry: "./src/main.js",
    // 输出
    output: {
    
    
        // 要输出到的位置,绝对路径
        path: path.resolve(__dirname,"../dist"),
        // 文件名
        filename: "state/js/main.js",
        // 每次打包前自动清空path对应的目录文件
        clean: true,
        // 关闭箭头函数输出
        environment: {
    
    
            arrowFunction: false
        }
    },

    // 加载器
    module: {
    
    
        rules: [
            {
    
    
                oneOf: [
                    {
    
    
                        // 用来匹配 .css 结尾的文件
                        test: /\.css$/,
                        // use 数组里面 Loader 执行顺序是从右到左
                        use: myCssLoader(),
                    },
                    {
    
    
                        test: /\.less$/i,
                        use: myCssLoader('less-loader')
                    },
                    {
    
    
                        test: /\.s[ac]ss$/i,
                        use: myCssLoader('sass-loader')
                    },
                    {
    
    
                        test: /\.(png|jpe?g|gif|webp)$/,
                        type: "asset",
                        parser: {
    
    
                            dataUrlCondition: {
    
    
                                maxSize: 18 * 1024 // 小于10kb的图片会被base64处理
                            }
                        },
                        generator: {
    
    
                            // 修改图片文件的输出地址
                            // hash:10 只取10位哈希值
                            // ext图片后缀名
                            // query 图片文件后面携带的参数
                            filename: 'state/images/[hash:10][ext][query]'
                        }
                    },
                    {
    
    
                        test: /\.(ttf|woff2?|mp3|mp4|word)$/,
                        type: "asset/resource",
                        generator: {
    
    
                            filename: 'state/resource/[hash:10][ext][query]'
                        }
                    },
                    {
    
    
                        test: /\.m?js$/,
                        exclude: /(node_modules|bower_components)/, // 排除文件
                        use: [
                            // 在需要开启多线程运行的loader前加上 thread-loader
                            "thread-loader",
                            {
    
    
                                loader: 'babel-loader',
                                options: {
    
    
                                    cacheDirectory: true, // 开启babel编译缓存
                                    cacheCompression: false, // 缓存文件不要压缩
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    },
    // 插件
    plugins: [
        // Eslint插件
        new ESLintPlugin({
    
    
            // 指定文件的根目录
            context:path.resolve(__dirname, "../src"),
            threads:true // 开启多线程,默认线程数是cpu核心数-1
        }),
        // 处理HTML文件
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname, "../index.html"), // 使用的模板地址
            title: "学习Webpack", // 网站title
            hash:true, // 添加哈希值
        }),
        // 将CSS处理成单独文件
        new MiniCssExtractPlugin({
    
    
            filename:"state/css/main.css",
        }),
    ],
    optimization: {
    
    
        minimize: true,
        minimizer: [
            // 压缩CSS
            new CssMinimizerPlugin({
    
    
                parallel:true,// 开启多线程
            }),
            // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
            new TerserPlugin({
    
    
                parallel:true // 开启多线程
            })
        ]
    },
    // 模式
    mode: "production",
    devtool: "source-map"
}

14.7 Reduce code size

Why

Babel inserts helper code for every file it compiles, making it bloated!

Babel uses very small helper code for some public methods, eg _extend. Will be added by default to every file that needs it.

You can use these auxiliary codes as an independent module to avoid repeated imports.

what is

@babel/plugin-transform-runtime: disables Babel's automatic per-file runtime injection, and instead imports @babel/plugin-transform-runtimeand makes all helper code referenced from here.

how to use

npm i @babel/plugin-transform-runtime -D
rules:[
    {
    
    
        test: /\.m?js$/,
        // exclude: /(node_modules|bower_components)/, // 排除文件
        include: path.resolve(__dirname,"../src"), // 也可以设置只包含某个文件夹下的JS
        use: {
    
    
            loader: 'babel-loader',
            options: {
    
    
                cacheDirectory: true, // 开启babel编译缓存
                cacheCompression: false, // 缓存文件不要压缩
                plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
            }
        }
    }
]

14.8 Compressing Images

download

npm i image-minimizer-webpack-plugin imagemin -D

Continue to download dependencies according to the situation

lossless compression

npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D

lossy compression

npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D

We use lossless compression using

// 引入
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

module.exports = {
    
    
    optimization: {
    
    
        minimize: true,
        minimizer: [
            // 压缩图片
            new ImageMinimizerPlugin({
    
    
                minimizer: {
    
    
                    implementation: ImageMinimizerPlugin.imageminGenerate,
                    options: {
    
    
                        plugins: [
                            ["gifsicle", {
    
     interlaced: true }],
                            ["jpegtran", {
    
     progressive: true }],
                            ["optipng", {
    
     optimizationLevel: 5 }],
                            [
                                "svgo",
                                {
    
    
                                    plugins: [
                                        "preset-default",
                                        "prefixIds",
                                        {
    
    
                                            name: "sortAttrs",
                                            params: {
    
    
                                                xmlnsOrder: "alphabetical",
                                            },
                                        },
                                    ],
                                },
                            ],
                        ],
                    },
                },
            }),
        ]
    },
    // 模式
    mode: "production",
    devtool: "source-map"
}

Package to see the effect

image-20230826224900002

15. Improve code running efficiency

15.1 Code Split

When packaging the code, all js files will be packaged into one file, which is too large. If we only need to render the home page, we should only load the js file of the home page, and other files should not be loaded. Therefore, we need to split the code of the packaged and generated files to generate multiple js files, and only load a certain js file when rendering any page, so that the loaded resources are less and the speed is faster.

Code Split (Code Split) mainly does two things:

  1. Split files: Split the files generated by packaging to generate multiple js files.
  2. On-demand loading: load any file you need.

There are different ways to implement code splitting. In order to more easily reflect the differences between them, we will create new files to demonstrate

Multi-entry configuration

The entry needs to be set as an object, the key is the generated file name, and the value corresponds to the relative path of the file

Use in output [name].jsto set the output file

const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
    entry: {
    
    
        app:"./src/app.js",
        main:"./src/main.js"
    },
    output:{
    
    
        path:path.resolve(__dirname,"./dist"),
        filename: "js/[name].js",
        clean: true
    },
    plugins: [
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname,"index.html")
        })
    ],
    mode: "production"
}

Code directory structure

demo01             
├── src            
│   ├── app.js     
│   └── main.js
├── index.html
├── package.json
└── webpack.config.js

packaged directory

dist          
├── js        
│   ├── app.js
│   └── main.js
└── index.html

extract duplicate files

In the project, if we have two files that both refer to the content of another file, but when packaging, we will pack the common referenced files in two copies, which will cause the volume of the package to increase, thereby reducing the loading speed. We can pass Configure splitChunksto solve this problem

first create asum.js

export function sum(...args){
    
    
   return args.reduce((a,b)=>a+b,0)
}

Then app.jsand main.jsuse the sum function respectively

// app.js
import {
    
    sum} from "./sum";

console.log("app")
console.log(sum(1,2,3,4))
// main.js
import {
    
    sum} from "./sum";

console.log("main")
console.log(sum(5,6,7,8))

Let's perform a packaging operation now to see what the packaged output looks like

image-20230827110838347

image-20230827110851500

You can see that two files are still output, and there is a sum function in each file

Add the following configuration information

const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
    entry: {
    
    
        app:"./src/app.js",
        main:"./src/main.js"
    },
    output:{
    
    
        path:path.resolve(__dirname,"./dist"),
        filename: "js/[name].js",
        clean: true
    },
    plugins: [
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname,"index.html")
        })
    ],
    optimization: {
    
    
        // 代码分割配置
        splitChunks: {
    
    
            chunks: "all", // 对所有模块都进行分割
            // 以下是默认值
            // minSize: 20000, // 分割代码最小的大小
            // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
            // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
            // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
            // maxInitialRequests: 30, // 入口js文件最大并行请求数量
            // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
            // cacheGroups: { // 组,哪些模块要打包到一个组
            //   defaultVendors: { // 组名
            //     test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
            //     priority: -10, // 权重(越大越高)
            //     reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
            //   },
            //   default: { // 其他没有写的配置会使用上面的默认值
            //     minChunks: 2, // 这里的minChunks权重更大
            //     priority: -20,
            //     reuseExistingChunk: true,
            //   },
            // },
            // 修改配置
            cacheGroups: {
    
    
                // 组,哪些模块要打包到一个组
                // defaultVendors: { // 组名
                //   test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
                //   priority: -10, // 权重(越大越高)
                //   reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
                // },
                default: {
    
    
                    // 其他没有写的配置会使用上面的默认值
                    minSize: 0, // 我们定义的文件体积太小了,所以要改打包的最小文件体积
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true,
                },
            },
        },
    },
    mode: "production"
}

Then package again and you can see that the sum function is packaged separately into a file

image-20230827111109476

load on demand

For example, if a button is clicked on the page, a function will be called to perform the addition operation. If the button is not clicked, the addition operation will not be performed. At this time, we can use the on-demand loading method to load this JS. If you do not click, this file will not be requested, thereby increasing the page loading speed for the first time.

new buildcount.js

export default function (a,b){
    
    
    return a + b
}

main.jsAdd code in

document.getElementById("btn").onclick = function (){
    
    
    import("./count").then(res=>{
    
    
        console.log(res.default(1,2))
    })
}

Then index.htmladd the button in

<body>
    <button id="btn">按钮</button>
</body>

package file, openindex.html

image-20230827112722995

image-20230827112754300

Esllint does not support import dynamic import syntax by default, just add configuration in the .eslintrc.js file

plugins: ["import"]

Name the dynamically generated file

Careful students have discovered that when we click the button above, a file named 293.js is dynamically loaded, but we do not have this name in the code. In the future, when we debug, we may not be able to tell which file we are looking for. document.

So we can name dynamically loaded files by writing specific pairs of comment codes

document.getElementById("btn").onclick = function () {
    
    
    import(/* webpackChunkName: "count" */ "./js/count").then(res=>{
    
    
        console.log(res.default(1, 2))
    })
}

/* webpackChunkName: "count" */Fixed writing, add this comment before the dynamically loaded file . count is the name of the file we want to generate

Then add the following code to the configuration file

chunkFilename: "state/js/chunk/[name].js",

specific configuration

// 入口文件,相对路径
entry: "./src/main.js",
// 输出
output: {
    
    
    // 要输出到的位置,绝对路径
    path: path.resolve(__dirname,"../dist"),
    // 文件名
    filename: "state/js/main.js",
    // 给动态生成的文件命名
    chunkFilename: "state/js/chunk/[name].js",
    // 每次打包前自动清空path对应的目录文件
    clean: true,
    // 关闭箭头函数输出
    environment: {
    
    
        arrowFunction: false
    }
},

Then the packaged files will be automatically placed in the chunk folder

image-20230827125051405

16. Unified file naming

All places related to file output should [name].文件后缀be named uniformly, so that webpack will automatically set the source file name to the packaged file name

Modified configuration

const path = require("path");

const ESLintPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");


function myCssLoader(preProcessor){
    
    
    return[
        MiniCssExtractPlugin.loader,
        "css-loader", {
    
    
            loader: "postcss-loader",
            options: {
    
    
                postcssOptions: {
    
    
                    plugins: [
                        "postcss-preset-env", // 能解决大多数样式兼容性问题
                    ],
                },
            },
        },
        preProcessor
    ].filter(Boolean)
}

module.exports = {
    
    
    // 入口文件,相对路径
    entry: "./src/main.js",
    // 输出
    output: {
    
    
        // 要输出到的位置,绝对路径
        path: path.resolve(__dirname,"../dist"),
        // 文件名
        filename: "state/js/[name].js",
        // 给动态生成的文件命名
        chunkFilename: "state/js/chunk/[name].js",
        // 只要是使用 type:asset 的loader统一设置输出名
        // hash:10 只取10位哈希值
        // ext文件后缀名
        // query 图片文件后面携带的参数
        assetModuleFilename: "state/asset/[hash:10][ext][query]",
        // 每次打包前自动清空path对应的目录文件
        clean: true,
        // 关闭箭头函数输出
        environment: {
    
    
            arrowFunction: false
        }
    },

    // 加载器
    module: {
    
    
        rules: [
            {
    
    
                oneOf: [
                    {
    
    
                        // 用来匹配 .css 结尾的文件
                        test: /\.css$/,
                        // use 数组里面 Loader 执行顺序是从右到左
                        use: myCssLoader(),
                    },
                    {
    
    
                        test: /\.less$/i,
                        use: myCssLoader('less-loader')
                    },
                    {
    
    
                        test: /\.s[ac]ss$/i,
                        use: myCssLoader('sass-loader')
                    },
                    {
    
    
                        test: /\.(png|jpe?g|gif|webp)$/,
                        type: "asset",
                        parser: {
    
    
                            dataUrlCondition: {
    
    
                                maxSize: 18 * 1024 // 小于10kb的图片会被base64处理
                            }
                        },
                        // generator: {
    
    
                        //     // 修改图片文件的输出地址
                        //     // hash:10 只取10位哈希值
                        //     // ext图片后缀名
                        //     // query 图片文件后面携带的参数
                        //     // filename: 'state/images/[hash:10][ext][query]'
                        // }
                    },
                    {
    
    
                        test: /\.(ttf|woff2?|mp3|mp4|word)$/,
                        type: "asset/resource",
                        // generator: {
    
    
                        //     filename: 'state/resource/[hash:10][ext][query]'
                        // }
                    },
                    {
    
    
                        test: /\.m?js$/,
                        exclude: /(node_modules|bower_components)/, // 排除文件
                        use: [
                            // 在需要开启多线程运行的loader前加上 thread-loader
                            "thread-loader",
                            {
    
    
                                loader: 'babel-loader',
                                options: {
    
    
                                    cacheDirectory: true, // 开启babel编译缓存
                                    cacheCompression: false, // 缓存文件不要压缩
                                    plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
                                },
                            }
                        ]
                    }
                ]
            }
        ]
    },
    // 插件
    plugins: [
        // Eslint插件
        new ESLintPlugin({
    
    
            // 指定文件的根目录
            context:path.resolve(__dirname, "../src"),
            threads:true // 开启多线程,默认线程数是cpu核心数-1
        }),
        // 处理HTML文件
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname, "../index.html"), // 使用的模板地址
            title: "学习Webpack", // 网站title
            hash:true, // 添加哈希值
        }),
        // 将CSS处理成单独文件
        new MiniCssExtractPlugin({
    
    
            filename:"state/css/[name].css",
        }),
    ],
    optimization: {
    
    
        minimize: true,
        minimizer: [
            // 压缩CSS
            new CssMinimizerPlugin({
    
    
                parallel:true,// 开启多线程
            }),
            // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
            new TerserPlugin({
    
    
                parallel:true
            }),
            // 压缩图片
            new ImageMinimizerPlugin({
    
    
                minimizer: {
    
    
                    implementation: ImageMinimizerPlugin.imageminGenerate,
                    options: {
    
    
                        plugins: [
                            ["gifsicle", {
    
     interlaced: true }],
                            ["jpegtran", {
    
     progressive: true }],
                            ["optipng", {
    
     optimizationLevel: 5 }],
                            [
                                "svgo",
                                {
    
    
                                    plugins: [
                                        "preset-default",
                                        "prefixIds",
                                        {
    
    
                                            name: "sortAttrs",
                                            params: {
    
    
                                                xmlnsOrder: "alphabetical",
                                            },
                                        },
                                    ],
                                },
                            ],
                        ],
                    },
                },
            }),
        ]
    },
    // 模式
    mode: "production",
    devtool: "source-map"
}

17. Preload / Prefetch

Why

We have already done code splitting before, and at the same time, we will use the import dynamic import syntax to load code on demand (we are also called lazy loading, such as routing lazy loading is implemented in this way).

But the loading speed is not good enough. For example, the resource is loaded when the user clicks the button. If the resource is large in size, the user will feel the obvious lagging effect.

We want to load the resources that need to be used later when the browser is idle. We need to use Preloadthe or Prefetchtechnique.

what is

  • Preload: Tells the browser to load the resource immediately.
  • Prefetch: Tells the browser to start loading resources only when it is idle.

What they have in common:

  • Both will only load resources and not execute them.
  • Both have caches.

They differ:

  • PreloadLoading priority is high, Prefetchloading priority is low.
  • PreloadOnly the resources needed for the current page can be loaded, Prefetchthe current page resources can be loaded, and the resources needed for the next page can also be loaded.

Summarize:

  • Resources with high priority on the current page are Preloadloaded with .
  • The resources to be used by the next page are Prefetchloaded with .

Their problem: poor compatibility.

how to use

Install

npm i @vue/preload-webpack-plugin -D

configuration

const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

// 插件
plugins: [
    // 资源懒加载
    new PreloadWebpackPlugin({
    
    
        //rel: "preload", // preload 兼容性更好
        as: "script",
        rel: 'prefetch' // prefetch 兼容性更差
    })
],

After the configuration is complete, click the button to load JS and it will be obtained from the cache, which is faster

image-20230827161019364

18. contenthash

Why

We found through packaging that each packaged file is called main.js, so the browser may have a cache problem when loading the file. We can add a hash value after each package by setting, so that when loading resources there will be no caching issues when

what is

There are several ways webpack sets the hash value, and they all generate a unique hash value.

  • fullhash (webpack4 is hash)

Every time any file is modified, the hash of all file names will change. So once any file is modified, the file cache of the entire project will be invalid.

  • chunkhash

According to different entry files (Entry), the dependency file is parsed, the corresponding chunk is constructed, and the corresponding hash value is generated. Our js and css are the same import and share a hash value.

  • contenthash

The hash value is generated according to the content of the file. Only when the content of the file changes, the hash value will change. All file hash values ​​are unique and unique.

By comparison, contenthash is the most suitable for us to use

how to use

module.exports = {
    
    
    // 入口文件,相对路径
    entry: "./src/main.js",
    // 输出
    output: {
    
    
        // 要输出到的位置,绝对路径
        path: path.resolve(__dirname,"../dist"),
        // 文件名
        filename: "state/js/[name].[contenthash:8].js",
        // 给动态生成的文件命名
        chunkFilename: "state/js/chunk/[name].[contenthash:8].js",
        // 只要是使用 type:asset 的loader统一设置输出名
        // hash:10 只取10位哈希值
        // ext文件后缀名
        // query 图片文件后面携带的参数
        assetModuleFilename: "state/asset/[hash:10][ext][query]",
        // 每次打包前自动清空path对应的目录文件
        clean: true,
        // 关闭箭头函数输出
        environment: {
    
    
            arrowFunction: false
        }
    },
    // 插件
    plugins: [
        // 将CSS处理成单独文件
        new MiniCssExtractPlugin({
    
    
            filename:"state/css/[name].[contenthash:8].css",
        }),
    ],
}

Static resources still use hash:10

19. runtimeChunk

Why

We set the packaging hash value through the above configuration, but in some scenarios, for example: file a depends on file b, when file b changes, only change the hash value of file b, do not change the hash value of file a . The purpose of this is to improve the loading efficiency under the premise of controlling the cache, instead of only changing one file, other files must change.

what is

Save the hash value separately in a runtime file.

We end up outputting three files: main, math, runtime. When the math file changes, the changes are the math and runtime files, and the main remains unchanged.

The runtime file only saves the hash value of the file and their relationship with the file. The entire file size is relatively small, so the cost of changing and re-requesting is also small.

how to use

Add the following configuration

module.exports = {
    
    
    optimization: {
    
    
        // 提取runtime文件
        runtimeChunk: {
    
    
            name: (entrypoint) => `runtime~${
      
      entrypoint.name}`, // runtime文件命名规则
        },
    },
}

Modify count.js

image-20230827163828575

Then package it again. Through comparison, it is found that only the hash values ​​​​of the count.js and runtime files have changed, and the others have not changed.

image-20230827163638656

20. CoreJS

Why

In the past we used babel for compatibility processing of js code, which used @babel/preset-env smart presets to handle compatibility issues.

It can compile and transform some syntax of ES6, such as arrow function, dot operator, etc. But if it is an async function, a promise object, some methods (includes) of an array, etc., it cannot handle it.

So at this time, our js code still has compatibility problems. Once it encounters a low-version browser, it will directly report an error. So we want to completely solve the js compatibility problem

what is

core-jsIt is specially used for ES6 and above API polyfill.

polyfillThe translation is called shim/patch. It is to use a piece of code provided by the community to allow us to use this new feature on browsers that are not compatible with certain new features

how to use

Install

npm i core-js

use, modifybabel.config.js

module.exports = {
    
    
    // 智能预设:能够编译ES6语法
    presets: [
        [
            "@babel/preset-env",
            // 按需加载core-js的polyfill
            {
    
     useBuiltIns: "usage", corejs: {
    
     version: "3", proposals: true } },
        ],
    ],
};

21. Optimization Summary

We optimized webpack and code from 4 angles:

  1. Improve development experience
  • Use to Source Mapenable more accurate error prompts when code errors are reported during development or online.
  1. Improve webpack to increase the speed of packaging and building
  • Use to HotModuleReplacementrecompile, package and update only the changed code during development, and use the cache for the unchanged code, so that the update speed is faster.
  • Use OneOfso that once the resource file is processed by a loader, it will not continue to traverse, and the packaging speed will be faster.
  • Use Include/ExcludeExclude or only detect certain files to process fewer files faster.
  • Use to Cachecache the results processed by eslint and babel to make the second packaging faster.
  • Use Theadmultiprocessing to handle eslint and babel tasks, which is faster. (It should be noted that there is overhead in process startup communication, and it will be effective when using more code processing)
  1. Reduce code size
  • Use to Tree Shakingeliminate redundant code that is not used, making the code smaller.
  • Use @babel/plugin-transform-runtimeplugins to process babel, allowing auxiliary code to be imported from it, instead of generating auxiliary code for each file, so that the size is smaller.
  • Use Image Minimizerto compress the pictures in the project, the size is smaller, and the request speed is faster. (It should be noted that if the pictures in the project are all online links, then it is not necessary. Only the static pictures of the local project need to be compressed.)
  1. Optimize code execution performance
  • Use Code Splitto split the code into multiple js files, so that the size of a single file is smaller, and the js is loaded in parallel faster. And use the import dynamic import syntax to load on-demand, so that the resource is loaded when it is needed, and the resource is not loaded when it is not in use.
  • Use Preload / Prefetchto load the code in advance, so that it can be used directly when it is needed in the future, so that the user experience is better.
  • Use Network Cachecan better name the output resource file, which is easy to cache in the future, so that the user experience is better.
  • Use to Core-jsperform compatibility processing on js, so that our code can run in low-version browsers.
  • Using PWAmakes the code accessible offline, improving the user experience.

22. Manually build Vue scaffolding

22.1 Project Structure

MyVueCli              
├── config            
│   └── webpack.dev.js
│   └── webpack.prod.js
├── public
│   └── index.html
├── src
│   ├── router
│   │   └── index.js
│   ├── view
│   │   ├── About
│   │   │   └── index.vue
│   │   └── Home
│   │       └── index.vue
│   ├── App.vue
│   └── main.js
├── .eslintrc.js
├── babel.config.js
└── package.json

22.2 Development mode configuration

webpack.dev.js

// webpack.dev.js
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const {
    
     VueLoaderPlugin } = require("vue-loader");
const {
    
     DefinePlugin } = require("webpack");

const getStyleLoaders = (preProcessor) => {
    
    
    return [
        "vue-style-loader",
        "css-loader",
        {
    
    
            loader: "postcss-loader",
            options: {
    
    
                postcssOptions: {
    
    
                    plugins: [
                        "postcss-preset-env", // 能解决大多数样式兼容性问题
                    ],
                },
            },
        },
        preProcessor,
    ].filter(Boolean);
};

module.exports = {
    
    
    entry: "./src/main.js",
    output: {
    
    
        path: undefined,
        filename: "static/js/[name].js",
        chunkFilename: "static/js/[name].chunk.js",
        assetModuleFilename: "static/js/[hash:10][ext][query]",
    },
    module: {
    
    
        rules: [
            {
    
    
                // 用来匹配 .css 结尾的文件
                test: /\.css$/,
                // use 数组里面 Loader 执行顺序是从右到左
                use: getStyleLoaders(),
            },
            {
    
    
                test: /\.less$/,
                use: getStyleLoaders("less-loader"),
            },
            {
    
    
                test: /\.s[ac]ss$/,
                use: getStyleLoaders("sass-loader"),
            },
            {
    
    
                test: /\.(png|jpe?g|gif|svg)$/,
                type: "asset",
                parser: {
    
    
                    dataUrlCondition: {
    
    
                        maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
                    },
                },
            },
            {
    
    
                test: /\.(ttf|woff2?)$/,
                type: "asset/resource",
            },
            // vue-loader不支持oneOf
            {
    
    
                test: /\.vue$/,
                loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
                options: {
    
    
                    // 开启缓存
                    cacheDirectory: path.resolve(
                        __dirname,
                        "node_modules/.cache/vue-loader"
                    ),
                },
            },
        ],
    },
    plugins: [
        new ESLintWebpackPlugin({
    
    
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules",
            cache: true,
            cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/.eslintcache"
            ),
        }),
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname, "../public/index.html"),
        }),
        new VueLoaderPlugin(),
        // 解决页面警告
        new DefinePlugin({
    
    
            __VUE_OPTIONS_API__: "true",
            __VUE_PROD_DEVTOOLS__: "false",
        }),
    ],
    optimization: {
    
    
        splitChunks: {
    
    
            chunks: "all",
        },
        runtimeChunk: {
    
    
            name: (entrypoint) => `runtime~${
      
      entrypoint.name}`,
        },
    },
    resolve: {
    
    
        extensions: [".vue", ".js", ".json"], // 自动补全文件扩展名,让vue可以使用
    },
    devServer: {
    
    
        open: true,
        host: "localhost",
        port: 3000,
        hot: true,
        compress: true,
        historyApiFallback: true, // 解决vue-router刷新404问题
    },
    mode: "development",
    devtool: "cheap-module-source-map",
};

22.3 Production mode configuration

webpack.prod.js

const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin")
const {
    
     VueLoaderPlugin } = require("vue-loader");
const {
    
     DefinePlugin } = require("webpack");

const getStyleLoaders = (preProcessor) => {
    
    
    return [
        MiniCssExtractPlugin.loader,
        "css-loader",
        {
    
    
            loader: "postcss-loader",
            options: {
    
    
                postcssOptions: {
    
    
                    plugins: [
                        "postcss-preset-env", // 能解决大多数样式兼容性问题
                    ],
                },
            },
        },
        preProcessor,
    ].filter(Boolean);
};

module.exports = {
    
    
    // 入口文件
    entry: "./src/main.js",
    output: {
    
    
        // 打包结果输出的位置
        path: path.resolve(__dirname,"dist"),
        // JS文件打包的命名:文件名.哈希值.js
        filename: "static/js/[name].[contenthash:10].js",
        // 映射文件名
        chunkFilename: "static/js/[name].[contenthash:10].chunk.js",
        // type:asset loader的文件生成名
        assetModuleFilename: "static/asset/[hash:10][ext][query]",
        // 自动清理上次打包文件
        clean: true,
    },
    module: {
    
    
        rules: [
            {
    
    
                // 用来匹配 .css 结尾的文件
                test: /\.css$/,
                // use 数组里面 Loader 执行顺序是从右到左
                use: getStyleLoaders(),
            },
            {
    
    
                test: /\.less$/,
                use: getStyleLoaders("less-loader"),
            },
            {
    
    
                test: /\.s[ac]ss$/,
                use: getStyleLoaders("sass-loader"),
            },
            {
    
    
                test: /\.(png|jpe?g|gif|svg)$/,
                type: "asset",
                parser: {
    
    
                    dataUrlCondition: {
    
    
                        maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
                    },
                },
            },
            {
    
    
                test: /\.(ttf|woff2?)$/,
                type: "asset/resource",
            },
            {
    
    
                test: /\.(jsx|js)$/,
                include: path.resolve(__dirname, "../src"),
                loader: "babel-loader",
                options: {
    
    
                    cacheDirectory: true,
                    cacheCompression: false,
                    plugins: [
                        // "@babel/plugin-transform-runtime" // presets中包含了
                    ],
                },
            },
            // vue-loader不支持oneOf
            {
    
    
                test: /\.vue$/,
                loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
                options: {
    
    
                    // 开启缓存
                    cacheDirectory: path.resolve(
                        __dirname,
                        "node_modules/.cache/vue-loader"
                    ),
                },
            },
        ],
    },
    plugins: [
        // Eslint
        new ESLintWebpackPlugin({
    
    
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules",
            cache: true,
            cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/.eslintcache"
            ),
        }),
        // 自动引入js
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname, "../public/index.html"),
        }),
        // 复制public文件到打包目录中
        new CopyPlugin({
    
    
            patterns: [
                {
    
    
                    from: path.resolve(__dirname, "../public"),
                    to: path.resolve(__dirname, "../dist"),
                    toType: "dir",
                    noErrorOnMissing: true,
                    globOptions: {
    
    
                        ignore: ["**/index.html"],
                    },
                    info: {
    
    
                        minimized: true,
                    },
                },
            ],
        }),
        // 将CSS单独放在一个文件中
        new MiniCssExtractPlugin({
    
    
            filename: "static/css/[name].[contenthash:10].css",
            chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
        }),
        // 处理vue
        new VueLoaderPlugin(),
        // 处理Vue的警告
        new DefinePlugin({
    
    
            __VUE_OPTIONS_API__: "true",
            __VUE_PROD_DEVTOOLS__: "false",
        }),
    ],
    optimization: {
    
    
        // 压缩的操作
        minimizer: [
            // 压缩CSS
            new CssMinimizerPlugin(),
            // 提取重复文件
            new TerserWebpackPlugin(),
            // 无损压缩图片
            new ImageMinimizerPlugin({
    
    
                minimizer: {
    
    
                    implementation: ImageMinimizerPlugin.imageminGenerate,
                    options: {
    
    
                        plugins: [
                            ["gifsicle", {
    
     interlaced: true }],
                            ["jpegtran", {
    
     progressive: true }],
                            ["optipng", {
    
     optimizationLevel: 5 }],
                            [
                                "svgo",
                                {
    
    
                                    plugins: [
                                        "preset-default",
                                        "prefixIds",
                                        {
    
    
                                            name: "sortAttrs",
                                            params: {
    
    
                                                xmlnsOrder: "alphabetical",
                                            },
                                        },
                                    ],
                                },
                            ],
                        ],
                    },
                },
            }),
        ],
        // 分割代码,重复引用的文件会抽离成单独的文件
        splitChunks: {
    
    
            chunks: "all",
        },
        // 缓存
        runtimeChunk: {
    
    
            name: (entrypoint) => `runtime~${
      
      entrypoint.name}`,
        },
    },
    // 导入文件自动加上后缀
    resolve: {
    
    
        extensions: [".vue", ".js", ".json"],
    },
    // 模式为开发模式
    mode: "production",
    // 报错提示的信息具体到行和列
    devtool: "source-map",
};

22.4 Other file codes

.eslintrc.js

module.exports = {
    
    
    root: true,
    env: {
    
    
        node: true,
    },
    extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
    parserOptions: {
    
    
        parser: "@babel/eslint-parser",
    },
};

babel.config.js

module.exports = {
    
    
    presets: ["@vue/cli-plugin-babel/preset"],
};

package.json

{
    
    
  "name": "myvuecli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    
    
    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    
    
    "@babel/eslint-parser": "^7.22.11",
    "vue": "^3.3.4"
  },
  "devDependencies": {
    
    
    "@vue/cli-plugin-babel": "^5.0.8",
    "babel-loader": "^9.1.3",
    "cross-env": "^7.0.3",
    "css-loader": "^6.8.1",
    "eslint": "^8.48.0",
    "eslint-plugin-vue": "^9.17.0",
    "eslint-webpack-plugin": "^4.0.1",
    "html-webpack-plugin": "^5.5.3",
    "less": "^3.13.1",
    "less-loader": "^10.2.0",
    "postcss-loader": "^7.3.3",
    "postcss-preset-env": "^9.1.1",
    "sass-loader": "^13.3.2",
    "vue-loader": "^17.2.2",
    "vue-router": "^4.2.4",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.7.14",
    "webpack": "^5.88.2",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1"
  }
}

main.js

import {
    
    createApp} from "vue";
import App from "./App";
import router from "./router"


const app = createApp(App);
app.use(router)
app.mount(document.getElementById("app"))

router/index.js

import {
    
    createRouter,createWebHashHistory} from "vue-router";

export default createRouter({
    
    
    history:createWebHashHistory(),
    routes:[
        {
    
    
            path:"/",
            redirect:"/home"
        },
        {
    
    
            path:"/home",
            component:()=> import("../view/Home")
        },{
    
    
            path:"/about",
            component:()=> import("../view/About")
        }
    ]
})

App.vue

<template>
  <ul>
    <li>
      <router-link to="/home">Home</router-link>
    </li>
    <li>
      <router-link to="/about">About</router-link>
    </li>
  </ul>

  <router-view/>
</template>

<script setup>

</script>

<style scoped lang="less">
ul{
  display: flex;
  flex-direction: column;
  gap: 15px;
  margin: 0;
  padding: 0;
  li{
    margin-left: 0;
    list-style: none;
    line-height: 35px;
    height: 35px;
    background-color: pink;
  }
}
</style>

22.5 Test run

run test

npm run dev

image-20230827211045139

running result

image-20230827211057675

package test

npm run build

image-20230827222309795

packaged files

image-20230827222407506

run fine

image-20230827222437558

22.6 Merging Configurations

const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin")
const {
    
     VueLoaderPlugin } = require("vue-loader");
const {
    
     DefinePlugin } = require("webpack");

const isProduction = process.env.NODE_ENV === "production";

const getStyleLoaders = (preProcessor) => {
    
    
    return [
        isProduction ? MiniCssExtractPlugin.loader : "vue-style-loader",
        "css-loader",
        {
    
    
            loader: "postcss-loader",
            options: {
    
    
                postcssOptions: {
    
    
                    plugins: [
                        "postcss-preset-env", // 能解决大多数样式兼容性问题
                    ],
                },
            },
        },
        preProcessor,
    ].filter(Boolean);
};

module.exports = {
    
    
    // 入口文件
    entry: "./src/main.js",
    output: {
    
    
        // 打包结果输出的位置
        path: isProduction ? path.resolve(__dirname,"../dist") : undefined,
        // JS文件打包的命名:文件名.哈希值.js
        filename: isProduction ? "static/js/[name].[contenthash:10].js" : "static/js/[name].js",
        // 映射文件名
        chunkFilename:isProduction ? "static/js/[name].[contenthash:10].chunk.js" : "static/js/[name].chunk.js",
        // type:asset loader的文件生成名
        assetModuleFilename: "static/asset/[hash:10][ext][query]",
        // 自动清理上次打包文件
        clean: true,
    },
    module: {
    
    
        rules: [
            {
    
    
                // 用来匹配 .css 结尾的文件
                test: /\.css$/,
                // use 数组里面 Loader 执行顺序是从右到左
                use: getStyleLoaders(),
            },
            {
    
    
                test: /\.less$/,
                use: getStyleLoaders("less-loader"),
            },
            {
    
    
                test: /\.s[ac]ss$/,
                use: getStyleLoaders("sass-loader"),
            },
            {
    
    
                test: /\.(png|jpe?g|gif|svg)$/,
                type: "asset",
                parser: {
    
    
                    dataUrlCondition: {
    
    
                        maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
                    },
                },
            },
            {
    
    
                test: /\.(ttf|woff2?)$/,
                type: "asset/resource",
            },
            {
    
    
                test: /\.(jsx|js)$/,
                include: path.resolve(__dirname, "../src"),
                loader: "babel-loader",
                options: {
    
    
                    cacheDirectory: true,
                    cacheCompression: false,
                    plugins: [
                        // "@babel/plugin-transform-runtime" // presets中包含了
                    ],
                },
            },
            // vue-loader不支持oneOf
            {
    
    
                test: /\.vue$/,
                loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
                options: {
    
    
                    // 开启缓存
                    cacheDirectory: path.resolve(
                        __dirname,
                        "node_modules/.cache/vue-loader"
                    ),
                },
            },
        ],
    },
    plugins: [
        // Eslint
        new ESLintWebpackPlugin({
    
    
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules",
            cache: true,
            cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/.eslintcache"
            ),
        }),
        // 自动引入js
        new HtmlWebpackPlugin({
    
    
            template: path.resolve(__dirname, "../public/index.html"),
        }),
        // 复制public文件到打包目录中
        isProduction && new CopyPlugin({
    
    
            patterns: [
                {
    
    
                    from: path.resolve(__dirname, "../public"),
                    to: path.resolve(__dirname, "../dist"),
                    toType: "dir",
                    noErrorOnMissing: true,
                    globOptions: {
    
    
                        ignore: ["**/index.html"],
                    },
                    info: {
    
    
                        minimized: true,
                    },
                },
            ],
        }),
        // 将CSS单独放在一个文件中
        isProduction && new MiniCssExtractPlugin({
    
    
            filename: "static/css/[name].[contenthash:10].css",
            chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
        }),
        // 处理vue
        new VueLoaderPlugin(),
        // 处理Vue的警告
        new DefinePlugin({
    
    
            __VUE_OPTIONS_API__: "true",
            __VUE_PROD_DEVTOOLS__: "false",
        }),
    ].filter(Boolean),
    optimization: {
    
    
        minimize: isProduction,
        // 压缩的操作
        minimizer: [
            // 压缩CSS
            new CssMinimizerPlugin(),
            // 提取重复文件
            new TerserWebpackPlugin(),
            // 无损压缩图片
            new ImageMinimizerPlugin({
    
    
                minimizer: {
    
    
                    implementation: ImageMinimizerPlugin.imageminGenerate,
                    options: {
    
    
                        plugins: [
                            ["gifsicle", {
    
     interlaced: true }],
                            ["jpegtran", {
    
     progressive: true }],
                            ["optipng", {
    
     optimizationLevel: 5 }],
                            [
                                "svgo",
                                {
    
    
                                    plugins: [
                                        "preset-default",
                                        "prefixIds",
                                        {
    
    
                                            name: "sortAttrs",
                                            params: {
    
    
                                                xmlnsOrder: "alphabetical",
                                            },
                                        },
                                    ],
                                },
                            ],
                        ],
                    },
                },
            }),
        ],
        // 分割代码,重复引用的文件会抽离成单独的文件
        splitChunks: {
    
    
            chunks: "all",
        },
        // 缓存
        runtimeChunk: {
    
    
            name: (entrypoint) => `runtime~${
      
      entrypoint.name}`,
        },
    },
    // 导入文件自动加上后缀
    resolve: {
    
    
        extensions: [".vue", ".js", ".json"],
    },
    // 模式为开发模式
    mode: isProduction ? "production" : "development",
    // 报错提示的信息具体到行和列
    devtool: isProduction ? "source-map" : "cheap-module-source-map",
    // 运行
    devServer: {
    
    
        open: true,
        host: "localhost",
        port: 3000,
        hot: true,
        compress: true,
        historyApiFallback: true, // 解决vue-router刷新404问题
    },
};

23. ElementPlus import on demand

Install element-plus

npm install element-plus --save

Install dependencies imported on demand

npm install -D unplugin-vue-components unplugin-auto-import

configuration

// webpack.config.js
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const {
    
     ElementPlusResolver } = require('unplugin-vue-components/resolvers')

module.exports = {
    
    
  // ...
  plugins: [
    AutoImport({
    
    
      resolvers: [ElementPlusResolver()],
    }),
    Components({
    
    
      resolvers: [ElementPlusResolver()],
    }),
  ],
}

Then use it directly on the page

<template>
  <div>Home</div>
  <el-button type="primary">按钮</el-button>
</template>

image-20230828220543058

24. loader principle

24.1 loader concept

Helps webpack convert different types of files into modules that webpack recognizes.

24.2 Execution order of loader

  1. Classification
  • pre: front loader
  • normal: normal loader
  • inline: inline loader
  • post: post loader
  1. order of execution
  • The execution priorities of the four types of loaders are: pre > normal > inline > post.
  • The execution order of loaders with the same priority is: 从右到左,从下到上.

For example:

// 此时loader执行顺序:loader3 - loader2 - loader1
module: {
    
    
  rules: [
    {
    
    
      test: /\.js$/,
      loader: "loader1",
    },
    {
    
    
      test: /\.js$/,
      loader: "loader2",
    },
    {
    
    
      test: /\.js$/,
      loader: "loader3",
    },
  ],
},
// 此时loader执行顺序:loader1 - loader2 - loader3
module: {
    
    
  rules: [
    {
    
    
      enforce: "pre",
      test: /\.js$/,
      loader: "loader1",
    },
    {
    
    
      // 没有enforce就是normal
      test: /\.js$/,
      loader: "loader2",
    },
    {
    
    
      enforce: "post",
      test: /\.js$/,
      loader: "loader3",
    },
  ],
},

24.3 Custom loader

Initialize project structure

MyLoader
├── js
│   └── main.js
├── loader
│   └── test-loader.js
├── src
│   └── index.html
├── package.json
└── webpack.config.js

main.js

console.log("hello word")

Write a simple loader, test-loader.jsthe code is as follows

module.exports = function (content){
    
    
    console.log("loader打印内容",content)
    return content
}

use

// webpack.config.js

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
module.exports = {
    
    
    entry: "./js/main.js",
    output: {
    
    
        path: path.resolve(__dirname,"dist"),
        filename: "js/[name].[hash:10].js",
        clean: true
    },
    module: {
    
    
        rules: [
            {
    
    
                test: /\.js$/,
                loader: "./loader/test-loader.js"
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugins({
    
    
            template:path.resolve(__dirname,"./src/index.html")
        })
    ],
    mode: "development"
}

Then install dependencies

npm install webpack webpack-cli html-webpack-plugin -D

Then execute the packaging command

npx webpack

observe the output

image-20230831224039986

Summarize:

The loader receives the source code of the file to be processed, and returns it after the internal processing of the loader

24.4 Synchronous loader and asynchronous loader

Create a new synchronous loader

// loader/async/loader1.js

module.exports = function (content,map,meta){
    
    
    console.log("同步loader打印")
    console.log(meta)
    /**
     * 第一个参数:错误信息,没有就是null,有的话传错误信息
     * content:要处理的源文件信息
     * map:继续传递source-map
     * meta:给下一个loader传递参数
     */
    this.callback(null,content,map,meta)
}

Create a new asynchronous loader

// loader/async/loader2.js

module.exports = function (content,map,meta){
    
    
    console.log("异步loader打印")
    let callback = this.async()
    /**
     * 第一个参数:错误信息,没有就是null,有的话传错误信息
     * content:要处理的源文件信息
     * map:继续传递source-map
     * meta:给下一个loader传递参数
     */
    setTimeout(()=>{
    
    
        callback(null,content,map, {
    
    
            name:"我是来自异步loader的参数"
        })
    },2000)
}

use

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
module.exports = {
    
    
    entry: "./js/main.js",
    output: {
    
    
        path: path.resolve(__dirname,"dist"),
        filename: "js/[name].[hash:10].js",
        clean: true
    },
    module: {
    
    
        rules: [
            // {
    
    
            //     test: /\.js$/,
            //     loader: "./loader/test-loader.js"
            // }
            {
    
    
                test:/\.js$/,
                use: [
                    "./loader/async/loader1.js",
                    "./loader/async/loader2.js",
                ]
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugins({
    
    
            template:path.resolve(__dirname,"./src/index.html")
        })
    ],
    mode: "development"
}

Package output results

image-20230831230039876

24.5 raw loader

The file received by the raw loader is Buffer data, which is generally used to process pictures, fonts and other files

define raw-loader

// loader/raw/raw-loader.js

module.exports = function (content){
    
    
    console.log(content)
    return content
}

// 暴露raw为true,表示这个是raw loader
module.exports.raw = true

use

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
module.exports = {
    
    
    entry: "./js/main.js",
    output: {
    
    
        path: path.resolve(__dirname,"dist"),
        filename: "js/[name].[hash:10].js",
        clean: true
    },
    module: {
    
    
        rules: [
            // {
    
    
            //     test: /\.js$/,
            //     loader: "./loader/test-loader.js"
            // }
            // {
    
    
            //     test:/\.js$/,
            //     use: [
            //         "./loader/async/loader1.js",
            //         "./loader/async/loader2.js",
            //     ]
            // }
            {
    
    
                test: /\.js$/,
                loader: "./loader/raw/raw-loader.js"
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugins({
    
    
            template:path.resolve(__dirname,"./src/index.html")
        })
    ],
    mode: "development"
}

view output

image-20230831230842737

24.6 pitch loader

The pitch loader will be executed before normal, and when there are multiple pitch loaders, the order of execution is opposite to that of the normal loader, from left to right, from top to bottom

define pitch loader1

// loader/pitch/pitch1.js

module.exports = function (content){
    
    
    console.log("pitch-normal-1",content)
    return content
}

module.exports.pitch = function (){
    
    
    console.log("pitch1")
}

define pitch loader2

// loader/pitch/pitch1.js

module.exports = function (content){
    
    
    console.log("pitch-normal-2",content)
    return content
}

module.exports.pitch = function (){
    
    
    console.log("pitch2")
}

use

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
module.exports = {
    
    
    entry: "./js/main.js",
    output: {
    
    
        path: path.resolve(__dirname,"dist"),
        filename: "js/[name].[hash:10].js",
        clean: true
    },
    module: {
    
    
        rules: [
            // {
    
    
            //     test: /\.js$/,
            //     loader: "./loader/test-loader.js"
            // }
            // {
    
    
            //     test:/\.js$/,
            //     use: [
            //         "./loader/async/loader1.js",
            //         "./loader/async/loader2.js",
            //     ]
            // }
            // {
    
    
            //     test: /\.js$/,
            //     loader: "./loader/raw/raw-loader.js"
            // }
            {
    
    
                test:/\.js$/,
                use:[
                    "./loader/pitch/pitch1.js",
                    "./loader/pitch/pitch2.js",
                ]
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugins({
    
    
            template:path.resolve(__dirname,"./src/index.html")
        })
    ],
    mode: "development"
}

Execute the package output result

image-20230902095630023

From the results, we can see that the pitch1 and pitch2 methods are executed first, and then the normal loader method is executed

When return is added in a pitch loader, the following pitch method and normal loader method will be terminated, and then the previous normal loader method will be executed

For example, create a new pitch3

module.exports = function (content){
    
    
    console.log("pitch-normal-3",content)
    return content
}

module.exports.pitch = function (){
    
    
    console.log("pitch3")
}

Then modify pitch2

module.exports = function (content){
    
    
    console.log("pitch-normal-2",content)
    return content
}

module.exports.pitch = function (){
    
    
    console.log("pitch2")
    return "pitch2"
}

Change setting


module.exports = {
    
    
    module: {
    
    
        rules: [
            {
    
    
                test:/\.js$/,
                use:[
                    "./loader/pitch/pitch1.js",
                    "./loader/pitch/pitch2.js",
                    "./loader/pitch/pitch3.js",
                ]
            }
        ]
    },
}

Execute package effects

image-20230902100354552

image-20230902100748081

The execution flow chart is as above

24.7. loader Api

method name meaning usage
this.async Asynchronous callback loader. return this. callback const callback = this.async()
this.callback A function that can be called synchronously or asynchronously and returns multiple results this.callback(err, content, sourceMap?, meta?)
this.getOptions(schema) Get loader options this.getOptions(schema)
this.emitFile generate a file this.emitFile(name, content, sourceMap)
this.utils.contextify return a relative path this.utils.contextify(context, request)
this.utils.absolutify returns an absolute path this.utils.absolutify(context, request)

For more documentation, please refer to webpack official loader api documentation

25. Self-implementing loader

25.1 clear-log-loader

During the project development stage, we will use console.log to print information, but we do not need this log to print after the production environment. So you can remove the console.log when packaging through the loader

new buildclear-log-loader.js

module.exports = function (content){
    
    
    return content.replace(/console\.log\(.*\);?/g,"")
}

use


module.exports = {
    
    
    module: {
    
    
        rules: [
            {
    
    
                test:/\.js$/,
                loader: "./loader/clear-log-loader.js"
            }
        ]
    },
}

25.2 Add options parameter to loader

In the loader above, we add an enable attribute through option to control whether to clear the log

First add the enable configuration in the loader


module.exports = {
    
    
    module: {
    
    
        rules: [
            {
    
    
                test:/\.js$/,
                loader: "./loader/clear-log-loader.js",
                options: {
    
    
                    enable:false
                }
            }
        ]
    },
}

Modify clear-log-loader.js

// 定义loader options属性的规则约束
const schema = {
    
    
    // options的类型是一个object
    type:"object",
    // 定义options中的属性有哪些
    properties:{
    
    
        // 有enable属性
        enable:{
    
    
            // enable属性是一个布尔值
            type:"boolean"
        }
    },
    // 不允许使用未定义的属性
    additionalProperties:false
}

module.exports = function (content){
    
    
    // this.getOptions 获取配置项
    let {
    
    enable} = this.getOptions(schema)
    
    return enable ? content.replace(/console\.log\(.*\);?/g,"") : content
}

At this time, the enable value we configured is false, so the packaged file has a log

image-20230902111210143

We changed it to true, packaged again, and the log was gone

image-20230902111245817

25.3 Self-implementing babel-loader

Babel official website: Babel Chinese Documentation | Babel Chinese Website Babel Chinese Documentation | Babel Chinese Website (babeljs.cn)

Convert code to ES5 syntax

Install

npm i @babel/core @babel/preset-env -D

New babel-loader.js

const schema = {
    
    
    "type": "object",
    "properties": {
    
    
        "presets": {
    
    
            "type": "array"
        }
    },
    "additionalProperties": true
}
const babel = require("@babel/core");

module.exports = function (content) {
    
    
    const options = this.getOptions(schema);
    // 使用异步loader
    const callback = this.async();
    // 使用babel对js代码进行编译
    babel.transform(content, options, function (err, result) {
    
    
        callback(err, result.code);
    });
};

wabpack configuration

rules:[
    {
    
    
        test:/\.js$/,
        loader: "./loader/babel-loader.js",
        options: {
    
    
            presets:["@babel/preset-env"],
        }
    }
]

25.4 file-loader

Output the file intact

Install

npm i loader-utils -D

file-loader

const loaderUtils = require("loader-utils")

module.exports = function (content){
    
    
    // 根据文件内容自动生成文件名
    const interpolatedName = loaderUtils.interpolateName(
        this,
        "asset/[name].[hash].[ext]",
        {
    
    
            // 文件内容
            content:content
        }
    )

    // 将文件输出出去
    this.emitFile(interpolatedName,content)

    // 返回 module.export = "文件路径/文件名"
    return `module.exports = "${
      
      interpolatedName}"`
}

module.exports.raw = true

use

rules:[
    {
    
    
        test:/\.(png|jpg)$/,
        loader: "./loader/file-loader.js",
        type:"javascript/auto" // 阻止默认的asset打包配置
    }
]

26. Plugin principle

The role of Plugins

Through plug-ins, we can extend webpack and add custom build behaviors, so that webpack can perform a wider range of tasks and have stronger build capabilities.

How Plugins work

Webpack is like a production line, and it takes a series of processes to convert source files into output results. The responsibility of each processing process on this production line is single, and there are dependencies among multiple processes. Only after the current processing is completed can it be handed over to the next process for processing. A plug-in is like a function inserted into the production line to process resources on the production line at a specific time. webpack uses Tapable to organize this complex production line. Webpack will broadcast events during operation, and the plug-in only needs to listen to the events it cares about, and can join this production line to change the operation of the production line. The event flow mechanism of webpack ensures the order of the plug-ins, making the whole system very scalable. —— "In-depth explanation of Webpack"

From the perspective of code logic: webpack will trigger a series of hook events in the process of compiling code. TapableWhat the plug-in does is to find the corresponding hook and hang its own tasks on it, which is to register events. In this way, when webpack When building, the events registered by the plug-in will be executed as the hook is triggered.

Hooks inside Webpack

what is a hook

The essence of hooks is: events. In order to facilitate us to directly intervene and control the compilation process, webpack encapsulates various key events triggered during the compilation process into event interfaces and exposes them. These interfaces are aptly called: hooks(hooks). The development of plug-ins is inseparable from these hooks.

Tapable

TapableProvides a unified plug-in interface (hook) type definition for webpack, which is the core function library of webpack. There are currently ten types in webpack hooks, Tapableas you can see in the source code, they are:

// https://github.com/webpack/tapable/blob/master/lib/index.js
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");

TapableThree methods are also uniformly exposed to plugins for injecting different types of custom build behaviors:

  • tap: You can register synchronous hooks and asynchronous hooks.
  • tapAsync: Callback method to register an asynchronous hook.
  • tapPromise: Promise way to register asynchronous hooks.

Plugin build object

Compiler

The complete Webpack environment configuration is saved in the compiler object, which is a unique object that will only be created once each time the webpack build is started.

This object will be created when Webpack is started for the first time, and we can access the main environment configuration of Webpack through the compiler object, such as loader, plugin and other configuration information.

It has the following main properties:

  • compiler.optionsYou can access all configuration files when starting webpack this time, including but not limited to loaders, entry, output, plugin and other complete configuration information.
  • compiler.inputFileSystemand compiler.outputFileSystemcan perform file operations, which is equivalent to fs in Nodejs.
  • compiler.hooksDifferent types of Hooks of tapable can be registered, so that different logic can be embedded in the compiler life cycle.

compiler hooks documentationopen in new window

Compilation

The compilation object represents the construction of a resource, and the compilation instance can access all modules and their dependencies.

A compilation object compiles all modules in the build dependency graph. During the compilation phase, modules are loaded, sealed, optimized, chunked, hashed, and restored.

It has the following main properties:

  • compilation.modulesAll modules can be accessed, and each file in the package is a module.
  • compilation.chunksA chunk is a code block composed of multiple modules. The resources introduced by the entry file form a chunk, and the modules separated by code are another chunk.
  • compilation.assetsYou can access the results of all files generated by this package.
  • compilation.hooksDifferent types of Hooks of tapable can be registered for logic addition and modification in the compilation module phase.

compilation hooks documentationopen in new window

life cycle diagram

27. Self-implementing Plugin

27.1 Test Plugin

Plugin is a constructor, so we use es6 class syntax to customize Plugin

webpack triggers the apply method in the plugin during the packaging process

class TestPlugin{
    
    
    constructor() {
    
    
        console.log("TestPlugin constructor")
    }
    apply(){
    
    
        console.log("TestPlugin apply")
    }
}

module.exports = TestPlugin

use

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
const TestPlugin = require("./plugins/test-plugin")

module.exports = {
    
    
    plugins: [
        new HtmlWebpackPlugins({
    
    
            template:path.resolve(__dirname,"./src/index.html")
        }),
        new TestPlugin()
    ],
    mode: "development"
}

When packaging, the content in the plugin will be output first

image-20230902173132814

27.2 Registration hooks

class TestPlugin{
    
    
    constructor() {
    
    
        console.log("TestPlugin constructor")
    }
    apply(compiler){
    
    
        console.log("TestPlugin apply")

        // 从文档可知, compile hook 是 SyncHook, 也就是同步钩子, 只能用tap注册
        compiler.hooks.compile.tap("TestPlugin", (compilationParams) => {
    
    
            console.log("compiler.compile()");
        });

        // 从文档可知, make 是 AsyncParallelHook, 也就是异步并行钩子, 特点就是异步任务同时执行
        // 可以使用 tap、tapAsync、tapPromise 注册。
        // 如果使用tap注册的话,进行异步操作是不会等待异步操作执行完成的。
        compiler.hooks.make.tap("TestPlugin", (compilation) => {
    
    
            setTimeout(() => {
    
    
                console.log("compiler.make() 111");
            }, 2000);
        });

        // 使用tapAsync、tapPromise注册,进行异步操作会等异步操作做完再继续往下执行
        compiler.hooks.make.tapAsync("TestPlugin", (compilation, callback) => {
    
    
            setTimeout(() => {
    
    
                console.log("compiler.make() 222");
                // 必须调用
                callback();
            }, 1000);
        });

        compiler.hooks.make.tapPromise("TestPlugin", (compilation) => {
    
    
            console.log("compiler.make() 333");
            // 必须返回promise
            return new Promise((resolve) => {
    
    
                resolve();
            });
        });

        // 从文档可知, emit 是 AsyncSeriesHook, 也就是异步串行钩子,特点就是异步任务顺序执行
        compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
    
    
            setTimeout(() => {
    
    
                console.log("compiler.emit() 111");
                callback();
            }, 3000);
        });

        compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
    
    
            setTimeout(() => {
    
    
                console.log("compiler.emit() 222");
                callback();
            }, 2000);
        });

        compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
    
    
            setTimeout(() => {
    
    
                console.log("compiler.emit() 333");
                callback();
            }, 1000);
        });
    }
}

module.exports = TestPlugin

image-20230902210225146

27.3 Add node debugging command

Add debugger to plugin

Then add package.jsonadd debug startup command

"scripts": {
    
    
  "debug": "node --inspect-brk ./node_modules/webpack-cli/bin/cli.js"
},

run

npm run debug

Then open the console of the browser on any page and click the node icon

image-20230902212955581

After clicking, it will jump to a new page. The default is to break the first line of the program. If we click Next to jump to the next breakpoint, we can jump to the position of our breakpoint

image-20230902213024409

27.4 BannerWebpackPlugin

Function, add author information to the package generated file

class BannerWebpackPlugin{
    
    
    constructor(options) {
    
    
        this.options = options;
    }
    apply(compile){
    
    
        compile.hooks.emit.tapAsync("BannerWebpackPlugin",(compilation,callback)=>{
    
    

            // 得到将要输出的文件,只获取JS和CSS
            const assetPaths = Object.keys(compilation.assets).filter(assetsFile=>{
    
    
                let extensions  = ["js","css"]
                let splits = assetsFile.split(".")
                let splitEd = splits[splits.length - 1]
                return extensions.includes(splitEd)
            })

            debugger

            assetPaths.forEach(path=>{
    
    
                // 得到文件本身
                const asset = compilation.assets[path]
// 要添加的注释信息
const newSource = `/**
 * author:${
      
      this.options.auther}
 */
 ${
      
      asset.source()}
`
                // 覆盖资源
                compilation.assets[path] = {
    
    
                    // 返回新的资源
                    source(){
    
    
                        return newSource
                    },
                    // 返回新的大小
                    size(){
    
    
                        return newSource.length
                    }
                }
            })
            callback()
        })
    }
}

module.exports = BannerWebpackPlugin

use

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
const BannerWebpackPlugin = require("./plugins/banner-webpack-plugin")

module.exports = {
    
    
    entry: "./js/main.js",
    plugins: [
        new HtmlWebpackPlugins({
    
    
            template:path.resolve(__dirname,"./src/index.html")
        }),
        new BannerWebpackPlugin({
    
    
            auther:"SongZX"
        })
    ],
    mode: "production"
}

The effect after packaging

image-20230902230032368

27.5 CleanWebpackPlugin

Delete old pack files before packing

Add toclean-webpack-plugin.js

class CleanWebpackPlugin{
    
    
    apply(compiler) {
    
    
        // 获取打包输出的目的地址
        let outputPath = compiler.options.output.path;
        // 获取操作文件的对象
        let fs = compiler.outputFileSystem;

        // 注册钩子,在文件输出前执行删除旧文件操作
        compiler.hooks.emit.tap("CleanWebpackPlugin",(compilation)=>{
    
    
            // 通过fs删除打包目录下的所有文件
            this.removeFiles(fs,outputPath)
        })
    }

    removeFiles(fs,filePath){
    
    
        // 读取指定文件夹下的文件内容,得到的是一个数组
        let files = fs.readdirSync(filePath);

        // 遍历文件一个个删除
        files.forEach(fileName=>{
    
    
            // 得到文件全路径
            let path = `${
      
      filePath}/${
      
      fileName}`
            // 判断是否是文件夹
            let stats = fs.statSync(path);
            // isDirectory方法用来判断是否是文件夹
            if(stats.isDirectory()){
    
    
                this.removeFiles(fs,path)
            }else{
    
    
                fs.unlinkSync(path)
            }
        })
    }
}

module.exports = CleanWebpackPlugin

use

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
const TestPlugin = require("./plugins/test-plugin")
const BannerWebpackPlugin = require("./plugins/banner-webpack-plugin")
const CleanWebpackPlugin = require("./plugins/clean-webpack-plugin")

module.exports = {
    
    
    entry: "./js/main.js",
    output: {
    
    
        path: path.resolve(__dirname,"dist"),
        filename: "js/[name].[hash:10].js",
        clean: false, // 关闭默认的删除文件
        assetModuleFilename: "asset/[name].[hash:10][ext]"
    },
    plugins: [
        new HtmlWebpackPlugins({
    
    
            template:path.resolve(__dirname,"./src/index.html")
        }),
        // new TestPlugin()
        new BannerWebpackPlugin({
    
    
            auther:"SongZX"
        }),
        // 使用自定义的删除文件插件
        new CleanWebpackPlugin()
    ],
    mode: "production"
}

27.6 AnalyzeWebpackPlugin

Automatically generate an md file that analyzes the file size when packaging

new buildanalyze-webpack-plugin.js

class AnalyzeWebpackPlugin{
    
    
    apply(compiler){
    
    
        compiler.hooks.emit.tap("AnalyzeWebpackPlugin",(compilation)=>{
    
    
            // 获取即将输出的文件
            let assets = Object.entries(compilation.assets)

            let content = `| 文件名称 | 文件大小 |
| ------ | ------ |`
            assets.forEach(([fileName,file])=>{
    
    
                content += `\n|${
      
      fileName}|${
      
      Math.ceil(file.size()/1024)}kb|`
            })

            // 覆盖资源
            compilation.assets['analyze.md'] = {
    
    
                // 返回新的资源
                source(){
    
    
                    return content
                },
                // 返回新的大小
                size(){
    
    
                    return content.length
                }
            }
        })
    }
}
module.exports = AnalyzeWebpackPlugin

use

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
const TestPlugin = require("./plugins/test-plugin")
const BannerWebpackPlugin = require("./plugins/banner-webpack-plugin")
const CleanWebpackPlugin = require("./plugins/clean-webpack-plugin")
const AnalyzeWebpackPlugin = require("./plugins/analyze-webpack-plugin")

module.exports = {
    
    
    entry: "./js/main.js",
    output: {
    
    
        path: path.resolve(__dirname,"dist"),
        filename: "js/[name].[hash:10].js",
        clean: false,
        assetModuleFilename: "asset/[name].[hash:10][ext]"
    },
    plugins: [
        new HtmlWebpackPlugins({
    
    
            template:path.resolve(__dirname,"./src/index.html")
        }),
        // new TestPlugin()
        new BannerWebpackPlugin({
    
    
            auther:"SongZX"
        }),
        new CleanWebpackPlugin(),
        new AnalyzeWebpackPlugin()
    ],
    mode: "production"
}

Effect

image-20230903144121290

Guess you like

Origin blog.csdn.net/SongZhengxing_/article/details/132683971