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
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
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
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
- entry
Instruct Webpack which file to start packing
- output
Instruct Webpack where to output the packaged files, how to name them, etc.
- loader
webpack itself can only handle js, json and other resources, and other resources need to use loader for Webpack to parse
- plugins
Extending the functionality of Webpack
- 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
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
5. Modify the file output address
At present, the files we package and generate are all under one file
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
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
Use the second way to import
Import the downloaded css and icon files into the project
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>
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.json
MediumeslintConfig
: 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.js
example
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"
or0
- close the rule"warn"
or1
- enable rule, use warning level errors:warn
(does not cause the program to exit)"error"
or2
- 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:
- Eslint official rules open in new window :
eslint:recommended
- Vue Cli official rules open in new window :
plugin:vue/essential
- React Cli official rules open in new window :
react-app
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
When packaging, an error will be prompted and the packaging will be terminated
You can enable eslint detection in webstorm
so you can catch errors while writing your code
add eslint ignore file
new .eslintignore
file
# 忽略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.json
Mediumbabel
: 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.js
the 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
Packaged file code
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
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
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 version
Consider only the two most recent versions of the browser> 1%
Covers 99% of browsersnot dead
Do 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
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:
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
- 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.
- 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 test
is 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-plugin
Caching is enabled by default
View after packagingnode_modules/.catch
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-plugin
It 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-loader
It 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-runtime
and 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
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:
- Split files: Split the files generated by packaging to generate multiple js files.
- 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].js
to 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 splitChunks
to solve this problem
first create asum.js
export function sum(...args){
return args.reduce((a,b)=>a+b,0)
}
Then app.js
and main.js
use 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
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
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.js
Add code in
document.getElementById("btn").onclick = function (){
import("./count").then(res=>{
console.log(res.default(1,2))
})
}
Then index.html
add the button in
<body>
<button id="btn">按钮</button>
</body>
package file, openindex.html
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
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 Preload
the or Prefetch
technique.
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:
Preload
Loading priority is high,Prefetch
loading priority is low.Preload
Only the resources needed for the current page can be loaded,Prefetch
the 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
Preload
loaded with . - The resources to be used by the next page are
Prefetch
loaded with .
Their problem: poor compatibility.
- We can go to the Can I Useopen in new window website to check API compatibility issues.
Preload
CompatibilityPrefetch
is better.
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
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
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.
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-js
It is specially used for ES6 and above API polyfill
.
polyfill
The 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:
- Improve development experience
- Use to
Source Map
enable more accurate error prompts when code errors are reported during development or online.
- Improve webpack to increase the speed of packaging and building
- Use to
HotModuleReplacement
recompile, 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
OneOf
so 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/Exclude
Exclude or only detect certain files to process fewer files faster. - Use to
Cache
cache the results processed by eslint and babel to make the second packaging faster. - Use
Thead
multiprocessing 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)
- Reduce code size
- Use to
Tree Shaking
eliminate redundant code that is not used, making the code smaller. - Use
@babel/plugin-transform-runtime
plugins 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 Minimizer
to 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.)
- Optimize code execution performance
- Use
Code Split
to 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 / Prefetch
to 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 Cache
can better name the output resource file, which is easy to cache in the future, so that the user experience is better. - Use to
Core-js
perform compatibility processing on js, so that our code can run in low-version browsers. - Using
PWA
makes 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
running result
package test
npm run build
packaged files
run fine
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>
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
- Classification
- pre: front loader
- normal: normal loader
- inline: inline loader
- post: post loader
- 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.js
the 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
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
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
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
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
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
We changed it to true, packaged again, and the log was gone
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. Tapable
What 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
Tapable
Provides 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
, Tapable
as 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");
Tapable
Three 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.options
You 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.inputFileSystem
andcompiler.outputFileSystem
can perform file operations, which is equivalent to fs in Nodejs.compiler.hooks
Different types of Hooks of tapable can be registered, so that different logic can be embedded in the compiler life cycle.
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.modules
All modules can be accessed, and each file in the package is a module.compilation.chunks
A 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.assets
You can access the results of all files generated by this package.compilation.hooks
Different types of Hooks of tapable can be registered for logic addition and modification in the compilation module phase.
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
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
27.3 Add node debugging command
Add debugger to plugin
Then add package.json
add 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
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
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
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