共计 20440 个字符,预计需要花费 52 分钟才能阅读完成。
写在最后面
最近工作中 webpack
用的比拟多,想零碎的学习一下,找到了极客工夫的教程,跟着视频上讲的学了一遍,播种挺大的。尽管视频教程是 2019 年出的,有点老,有些常识曾经过期了,但并不障碍学习。那些过期的常识全当理解,能够搜寻寻找代替计划。学习资源:玩转 webpack,上面是我的学习笔记。
构建工具
前端技术一直倒退,比方:React
的 jsx
,Vue
和Angular
指令,CSS
预处理器,最新的 ES
语法等,浏览器都不能辨认。
构建工具的作用就是将这些浏览器不能辨认的语法(高级语法)转换成了浏览器能辨认的语法(低级语法)。
还有一个作用是将代码压缩混同,在压缩代码体积的同时也让代码不易浏览。
webpack
是当初前端风行的构建工具。
初识webpack
配置 script
脚本
装置好 webpack
后,在命令行中应用 webpack
时,有两种形式:
- 指定门路
./node_modules/.bin/webpack
- 应用
npx
工具,npx webpack
这两种办法应用挺麻烦的。有种简略的形式应用 package.json
中的 scripts
,它可能读到node_modules/.bin
目录下的命令。
在命令行中执行 npm run build
即可。
"scripts": {"build": "webpack"}
配置文件
webpack
的默认配置文件 webpack.config.js
,能够通过webpack --config
来指定配置文件。
比方,生产环境的配置文件webpack.config.prod.js
,开发环境的配置文件是webpack.config.dev.js
。
"scripts": {
"build": "webpack --config webpack.config.prod.js",
"build:dev": "webpack --config webpack.config.dev.js"
}
外围概念
webpack
有 5 个外围概念:entry
、output
、mode
、loaders
、plugins
。
entry
打包时的入口文件,默认是./src/index.js
entry
是有两种形式:单入口、多入口。
单入口用于单页面利用,entry
是个字符串
module.exports = {entry: "./src/index.js",};
多入口用于多页面利用,entry
是对象模式
module.exports = {
entry: {
index: "./src/index.js",
app: "./src/app.js",
},
};
output
打包后的输入文件,默认是./dist/main.js
。
entry
是单入口,output
可通过批改参数 path
和filename
。
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {path: path.resolve(__dirname, "dist"),
filename: "index.js",
},
};
entry
是多入口,output
的 filename
须要用 [name]
占位符,用来指定打包后的名称,对应的是 entry
中的key
const path = require("path");
module.exports = {
entry: {
index: "./src/index.js",
app: "./src/app.js",
},
output: {path: path.resolve(__dirname, "dist"),
filename: "[name].js",
},
};
mode
可设置为development
、production
、none
,默认是production
。
development
:开发环境,设置process.env.NODE_ENV
的值为development
。开启NamedChunksPlugin
和NamedModulesPlugin
production
:生产环境,设置process.env.NODE_ENV
的值为production
。开启FlagDependencyUsagePlugin
,FlagIncludedChunksPlugin
,ModuleConcatenationPlugin
,NoEmitonErrorsPlugin
,OccurrenceOrderPlugin
,SideEffectsFlagPlugin
和TerserPlugin
none
:不开启任何优化选项
ps:
development
模式下开启的两个插件在代码热更新阶段,会在控制台打印出哪个模块产生了热更新,这个模块的门路是啥。production
模式下开启的插件会在压缩代码。
loaders
webpack
只反对 js
和json
两种文件类型,loader
的作用就是用来解决其余的文件,并将它们增加到依赖图中。
loader
是个函数,接管源文件作为参数,返回转换后的后果。
个别 loader
命名的形式都是以 -loader
为后缀,比方css-loader
、babel-loader
。
test
是指定匹配的规定,use
是指定应用 loader
的名称
module.exports = {
module: {
rules: [
{
test: /.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
],
},
};
一个 loader
个别只做一件事,在解析 less
时,须要用 less-loader
将less
转成 css
,因为webpack
不能辨认 css
,又须要用css-loader
将css
转换成 commonjs
对象放到 js
中,最初须要用 style-loader
将css
插入到页面的 style
中。
ps:
loader
的组合通常由两种形式,一种是从左到右(相似unix
的pipe
),另一种是从右到左(compose
)。webpack
抉择的是compose
,从右到左一次执行loader
plugins
任何 loader
没法做的事件,都能够用 plugin
解决,它次要用于文件优化、资源管理、环境变量注入,作用于整个构建过程。
个别 plugin
命名的形式是以 -webpack-plugin
为后缀结尾,也有是以 -plugin
为后缀结尾的。
const CleanWebpackPlugin = require("clean-webpack-plugin");
module.exports = {plugins: [new CleanWebpackPlugin()],
};
webpack
资源解析
解析es6
须要装置@babel/core
,@babel/preset-env
,babel-loader
在根目录上面新建 .babelrc
文件,将 @babel/preset-env
增加到 presets
中
{"presets": ["@babel/preset-env"]
}
webpack
中配置babel-loader
module.exports = {
module: {
rules: [
{
test: /.js$/,
use: "babel-loader",
},
],
},
};
还有一种配置的形式,不应用 .babelrc
文件,将它配置在 use
的参数 options
中
module.exports = {
module: {
rules: [
{
test: /.js$/,
use: {
loader: "babel-loader",
options: {presets: ["@babel/preset-env"],
},
},
},
],
},
};
解析css
须要装置 css-loader
和style-loader
。
module.exports = {
moudle: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
],
},
};
解析less
须要装置less-loader
,css-loader
,style-loader
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
],
},
};
解析图片和字体
须要装置file-loader
module.exports = {
module: {
rules: [
{test: /.(png|jpeg|jpg|gif)$/,
use: ["file-loader"],
},
{test: /.(woff|woff2|eot|ttf|otf)$/,
use: ["file-loader"],
},
],
},
};
url-loader
url-loader
能够将较小的图片转换成base64
。
module.exports = {
module: {
rules: [
{test: /.(png|jpeg|jpg|gif)$/,
use: {
loader: "url-loader",
options: {limit: 10240, // 小于 10k 图片,webpack 在打包时主动转成 base64},
},
},
],
},
};
配置vue
配置 vue
开发环境,须要装置vue
,vue-loader
,vue-template-compiler
const {VueLoaderPlugin} = require("vue-loader");
module.export = {plugins: [new VueLoaderPlugin()],
module: {
rules: [
{
test: /\.vue$/,
use: "vue-loader",
},
],
},
};
ps: 这里应用的
vue-loader
是15.x
版本,我装置最新的版本16.x
有问题,始终没有解决。
配置react
配置 react
开发环境须要装置react
,react-dom
,@babel/preset-react
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
},
],
},
};
webpack
文件监听及热更新
文件监听
每次批改代码后,都须要手动构建,影响开发效率。webpack
提供了文件监听的作用。开启监听时,webpack
会调用 node
中fs
模块来判断文件是否发生变化,如果产生了变动,主动从新构建输入新的文件。
webpack
开启监听模式,有两种形式:(须要手动刷新浏览器)
- 启动
webpack
命令时,带上--watch
参数
"scripts": {"watch": "webpack --watch"}
webpack.config.js
中设置watch: true
module.exports = {watch: true,};
文件监听剖析
webpack
文件监听判断根据是看文件的最初编辑工夫是否发生变化。
它会将批改的工夫保存起来,当文件批改时,它会和上一次的批改工夫进行比对。
如果发现不统一,它并不会马上通知监听者,而是先把文件的批改缓存起来,期待一段时间,如果这段时间内还有其余文件发生变化,它就会把这段时间内的文件列表一起构建。这个期待的工夫就是aggregateTimeout
。
module.exports = {
watch: true,
watchOptions: {
ignored: /node_modules/,
aggregateTimeout:300,
poll: 1000,
}
}
watch
:默认为false
-
watchOptions
:只有watch
为true
时,才失效ignored
:须要疏忽监听的文件或者文件夹,默认为空,疏忽node——modules
性能会有所晋升。aggregateTimeout
:监听到文件变动后,期待的工夫,默认300ms
poll
:轮询工夫,1s
一次
热更新
热更新须要 webpack-dev-server
和HotModuleReplacementPlugin
两个插件背地应用。
与 watch
相比,它不输入文件,间接形式内存中,所以它的构建熟读更快。
热更新在开发模式中才会应用。
const webpack = require("webpack");
const path = require("path");
module.exports = {
mode: "development",
plugins: [new webpack.HotModuleReplacementPlugin()],
devServer: {contentBase: path.join(__dirname, "dist"),
hot: ture,
},
};
在 package.json
中配置命令
webpack4.x
"scripts": {"dev": "webpack-dev-server --open"}
webpack5.x
"scripts": {"dev": "webpack server"}
ps:
webpack5.x
和webpack-dev-server
有抵触,不能应用--open
关上浏览器。
HotModuleReplacementPlugin
热更新的外围是 HMR Server
和HMR Runtime
。
HMR Server
:是服务端,用来将变动的js
模块通过websocket
的音讯告诉给浏览器端HMR Runtime
:是浏览器端,用于接管HMR Server
传递过去的模块数据,浏览器端能够看到.hot-update.json
文件
hot-module-replacement-plugin
的作用:webpack
自身构建进去的 bundle.js
自身是不具备热更新的,HotModuleReplacementPlugin
的作用就是 HMR Runtime
注入到 bundle.js
,使得bundle.js
能够和 HMR Server
建设 websocket
的通信能力。一旦磁盘里的文件产生批改,那么 HMR Server
将有批改的 js
模块通过 websocket
发送给 HMR Runtime
,而后HMR Runtime
去部分更新页面的代码,这种办法不会刷新浏览器。
webpack-dev-server
的作用:提供 bundle server
的能力,就是生成的 bundle.js
能够通过 localhost://xxx
的形式去拜访,另外也提供livereload
(浏览器主动刷新)。
文件指纹
什么是文件指纹
文件指纹是指打包后输入的文件名的后缀。比方:index_56d51795.js
。
通常用于用于版本治理
文件指纹类型
hash
: 和我的项目的构建相干,只有我的项目文件扭转,构建我的项目的hash
值就会扭转。采纳hash
计算的话,每一次构建后的哈希值都不一样,如果说文件内容没有发生变化,那这样子是没法实现缓存的。chunkhash
:和webpack
打包的chunk
无关,不同的entry
会生成不同的chunkhash
。生产环境会将一些公共库和源码离开来,独自用chunkhash
构建,只有不扭转公共库的代码,它的哈希值就不会变,就能够实现缓存。contenthash
:依据文件内容来定义hash
,文件内容不变,则contenthash
不变。个别用于css
资源,如果css
资源应用chunkhash
,那么批改了js
,css
资源就会变动,缓存就会生效,所以css
应用contenthash
。
ps:
- 应用
hash
的场景要联合mode
来思考,如果mode
是development
,在应用HMR
状况下,应用chunkhash
是不适宜的,应该应用hash
。而mode
是production
时,应该用chunkhash
。js
应用chunkhash
是便于寻找资源,因为js
的资源关联度更高。css
应用contenthash
是因为css
个别是依据不同的页面书写的,css
资源之间的关联度不高,也就不必在其余资源批改时,从新更新css
。
js
文件指纹
设置 output
的filename
,应用[chunkhash]
,
const path = require("path");
module.exports = {
output: {path: path.join(__dirname, "dist"),
filename: "[name]_[chunkhash:8].js", // 取 hash 的前 8 位
},
};
css
文件指纹
须要装置mini-css-extract-plugin
style-loader
是将 css
插入到页面的 head
中,mini-css-extract-plugin
是提取为独自的文件,它们之间是互斥的。
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
plugins: [
new MiniCssExtractPlugin({filename: "[name]_[contenthash:8].css",
}),
],
};
图片文件指纹
须要装置file-loader
占位符名称 | 含意 |
---|---|
[ext] |
资源后缀名 |
[name] |
文件名称 |
[path] |
文件的相对路径 |
[folder] |
文件所在的文件夹 |
[contenthash] |
文件内容的hash ,默认是md5 |
[hash] |
文件内容的hash ,默认是md5 |
[emoji] |
一个随机的指代文件内容的emoji |
图片的 hash
和css/js
的 hash
概念不一样,图片的 hash
是有图片的内容决定的。
module.exports = {
module: {
rules: [
{test: /.(png|jpeg|jpg|gif)$/,
use: {
loader: "file-loader",
options: {filename: "[folder]/[name]_[hash:8].[ext]",
},
},
},
{test: /.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: "file-loader",
options: {filename: "[name]_[hash:8].[ext]",
},
},
],
},
],
},
};
代码压缩
代码压缩次要分为 js
压缩,css
压缩,html
压缩。
js
压缩
webpack
内置了 uglifyjs-webpack-plugin
插件,默认打包后的文件都是压缩过的,这里无需额定配置。
能够手动装置这个插件,能够额定设置一下配置,比方开启并行压缩,须要将 parallel
设置为true
const UglifyjsWebpackPlugin = require("uglifyjs-webpack-plugin");
module.exports = {
optimization: {
minimizer: [
new UglifyjsWebpackPlugin({parallel: true,}),
],
},
};
css
压缩
装置 optimize-css-webpack-plugin
,同时装置css
预处理器cssnano
。
const OptimizeCssWebpackPlugin = require("optimize-css-webpack-plugin")
module.exports = {
plugins: [
new OptimizeCssWebpackPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require("cssnano)
})
]
}
html
压缩
须要装置html-webpack-plugin
,通过设置压缩参数。
ps: 多页面利用须要写多个
new HtmlWebpackPlugin({...})
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
plugins: [
new HtmlWebpackPlugin({template: path.join(__dirname, "src/search.html"), //html 模版所在的地位,模版外面能够应用 ejs 的语法
filename: "search.html", // 打包进去后的 html 名称
chunks: ["search"], // 打包进去后的 html 要应用那些 chunk
inject: true, // 设置为 true,打包后的 js css 会主动注入到 HTML 中
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}),
new HtmlWebpackPlugin({...})
],
};
HtmlWebpackPlugin
参数 minify
外面的 minifyJS
和minifyCSS
的作用是用于压缩一开始就内联在 html
里的 js
(不能应用ES6
语法)和css
。
chunks
对应的是 entry
中的 key
。你心愿哪个chunk
主动注入,就将哪个 chunk
写到chunks
。
chunk
、bundle
、module
区别:
chunk
:每个chunk
是又多个module
组成,能够通过代码宰割成多个chunk
bundle
:打包生成的最终文件module
:webpack
中的模块(js
、css
、图片)
主动清理构建目录
每次在构建的时候,会产生新的文件,造成输入目录 output
文件越来越多。
最常见的清理办法用命令 rm
去删除,在打包之前先执行 rm -rf
命令将 dist
目录删除,在进行打包。
"scripts": {"build": "rm -rf ./dist && webpack"}
另一种是应用 rimraf
进行删除。
装置 rimraf
,应用时也是先在打包前先将dist
目录进行删除,而后在打包。
"scripts": {"build": "rimraf ./dist && webpack"}
这两种计划尽管都能将 dist
目录清空,但不太优雅。
webpack
提供 clean-webpack-plugin
,它会主动清理output.path
下的文件。
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
module.exports = {plugins: [new CleanWebpackPlugin()],
};
主动补齐款式前缀
不同的浏览器厂商在实现 css
个性的规范没有齐全对立,比方 display: flex
,在webkit
内核中要写成display: -webkit-box
。
在开发的时候一个个加上内核,将是一个微小的工程。
在 webpack
中能够用 loader
去解决主动补齐 css
前缀的问题。
装置postcss-loader
,以及它的插件autoprefixer
。
autoprefixer
是依据 can i use
这个网站提供的 css
兼容性进行不全前缀的。
autoprefixer
是后置处理器,它和预处理器不同,预处理器是在打包的时候解决,而 autoprefixer
是在代码解决好之后,款式曾经生成了再进行解决。
在 webpack4.x
中装置postcss-loader@3.0.0
,autoprefixer@9.5.1
。
形式一: 间接在 webpack.config.js
中配置
webpack.config.js
:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"less-loader",
{
loader: "postcss-loader",
options: {plugins: () => [require("autoprefixer")({overrideBrowserslist: ["last 2 version", ">1%", "ios 7"], // 最新的两个版本,用户大于 1%,且兼容到 ios7
}),
],
},
},
],
},
],
},
};
形式二: 利用 postcss
配置文件 postcss.config.js
,webpack.config.js
间接写 postcss-loader
即可。
webpack.config.js
:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"less-loader",
"postcss-loader",
],
},
],
},
};
postcss.config.js
:
module.exports = {
plugins: [require("autoprefixer")({overrideBrowserslist: ["last 2 version", ">1%", "ios 7"],
}),
],
};
办法三: 浏览器的兼容性能够写 package.json
中,postcss.config.js
中只需加载 autofixer
即可。
webpack.config.js
:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"less-loader",
"postcss-loader",
],
},
],
},
};
postcss.config.js
:
module.exports = {plugins: [require("autofixer")],
};
package.json
:
"browserslist": {
"last 2 version",
"> 1%",
"ios 7"
}
资源内联
在一些场景中须要应用到资源内联,常见的有:
- 页面框架的初始化脚本
- 缩小
http
网络申请,将一些小的图片变成base64
内容到代码中,较少申请 css
内联减少页面体验- 尽早执行的
js
,比方REM
计划
html
内联
在多页面我的项目中,head
中有很多专用的标签,比方meta
,想要晋升维护性,会将它提取成一个模版,而后援用进来。
装置raw-loader@0.5.1
<head>
${require("raw-loader!./meta.html")}
</head>
js
内联
在 REM
计划中,须要尽早的 html
标签的 font-size
,那么这段js
就要尽早的加载、执行。
<head>
<script>
${require('raw-loader!babel-loader!./calc-root-fontsize.js')}
</script>
</head>
css
内联
为了更好的体验,防止页面闪动,须要将打包好的 css
内联到 head
中。
装置 html-inline-css-webpack-plugin
,这个插件须要放在html-webpack-plugin
前面。
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const HtmlInlineCssWebpackPlugin = require("html-inline-css-webpack-plugin");
module.exports = {
module: {rules: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
},
plugins: [
new MiniCssExtractPlugin({filename: "[name]_[contenthash:8].css",
}),
new HtmlWebpackPlugin(),
new HtmlInlineCssWebpackPlugin(),],
};
须要先将 css
提取成独自的文件才行。
ps:
style-laoder
和html-inline-css-webpack-plugin
的区别是:
style-loader
:插入款式是一个动静的过程,打包后的源码不会有style
标签,在执行的时候通过js
动静插入style
标签html-inline-css-webpack-plugin
:是在构建的时候将css
插入到页面的style
标签中
多页面利用
每个页面对应一个 entry
,同时对应plugins
中一个html-webpack-plugin
。
这种形式有个毛病,每个减少一个 entry
就要减少一个html-webpack-plguin
。
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
index: "./src/index.js",
search: "./src/search.js",
},
plugins: [new HtmlWebpackPlugin({ template: "./src/index.html"}),
new HtmlWebpackPlugin({template: "./src/search.html"}),
],
};
借助 glob
能够实现通用化的配置形式。
装置 glob
,同时所有的页面都要放在src
上面,并且入口文件都要叫做index.js
。
const path = require("path");
const glob = require("glob");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const setMPA = () => {const entry = {};
const htmlWebpackPlugins = [];
const entryFiles = glob.sync(path.join(__dirname, "./src/*/index.js"));
entryFiles.forEach((entryFile) => {const match = entryFile.match(/src\/(.*)\/index\.js/);
const pageName = match && match[1];
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({template: path.join(__dirname, `src/${pageName}/index.html`),
filename: `${pageName}.html`,
chunks: [pageName],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false,
},
})
);
});
return {
entry,
htmlWebpackPlugins,
};
};
const {entry, htmlWebpackPlugins} = setMPA();
module.exports = {
entry,
plugins: [].concat(htmlWebpackPlugins),
};
sourceMap
应用办法
公布到生成环境中的代码,要将代码进行压缩混同,然而在压缩混同之后,看代码就像在看天书一样。
sourceMap
提供了压缩后的代码和源代码之间的映射关系。
sourceMap
在开发环境开启,线上环境中敞开,在线上排查问题时将 source map
上传到谬误监控零碎。
source map
关键字
eval
:应用eval
包裹模块代码source map
:产生map
文件cheap
:不蕴含列信息inline
:将.map
作为DataURI
嵌入,不独自生成.map
文件module
:蕴含loader
的sourcemap
source map
类型
devtool | 首次构建 | 二次构建 | 是否适宜生产环境 | 能够定位的代码 |
---|---|---|---|---|
(none) |
+++ | +++ | yes | 最终输入的代码 |
eval |
+++ | +++ | no | webpack 生成的代码(一个个的模块) |
cheap-eval-source-map |
+ | ++ | no | 通过 loader 转换后的代码(只能看到行) |
cheap-module-eval-source-map |
o | ++ | no | 源代码(只能看到行) |
eval-source-map |
— | + | no | 源代码 |
cheap-source-map |
+ | o | yes | 通过 loader 转换后的代码只能看到行) |
cheap-module-source-map |
o | – | yes | 源代码(只能看到行) |
inline-cheap-source-map |
+ | – | no | 通过 loader 转换后的代码(只能看到行) |
inline-cheap-module-source-map |
o | – | no | 源代码(只能看到行) |
source-map |
— | — | yes | 源代码 |
Inline-source-map |
— | — | no | 源代码 |
hidden-source- map |
— | — | yes | 源代码 |
提取页面公共资源
在我的项目中,多个页面中都会应用到一些根底库,比方react
、react-dom
,还有一写公共的代码,当在打包时这些都会被打包进最终的代码中,这是比拟节约的,而且打包后的体积也比拟大。
webpack4
内置了 SplitChunksPlugin
插件。
chunks
参数阐明:
async
异步引入的库进行拆散(默认)initial
同步引入的库进行拆散all
所有引入的库进行拆散(举荐)
抽离出根底库的名称 cacheGroups
里中的 filename
放到 html-webpack-plugin
中chunks
中,会主动导入:
module.exports = {
plugins: [
new HtmlWebpackPlugin({chunks: ["vendors", "commons", "index"], // 打包进去后的 html 要应用那些 chunk
})
);
],
optimization: {
splitChunks: {
chunks: "async",
minSize: 30000, // 抽离的公共包最小的大小,单位字节
maxSize: 0, // 最大的大小
minChunks: 1, // 资源应用的次数(在多个页面应用到),大于 1,最小应用次数
maxAsyncRequests: 5, // 并发申请的数量
maxInitialRequests: 3, // 入口文件做代码宰割最多能分成 3 个 js 文件
automaticNameDelimiter: "~", // 文件生成时的连接符
automaticNameMaxLength: 30, // 主动主动命名最大长度
name: true, // 让 cacheGroups 里设置的名字无效
cacheGroups: {
// 当打包同步代码时, 下面的参数失效
vendors: {test: /[\\/]node_modules[\\/]/, // 检测引入的库是否在 node_modlues 目录下的
priority: -10, // 值越大, 优先级越高. 模块先打包到优先级高的组里
filename: "vendors.js", // 把所有的库都打包到一个叫 vendors.js 的文件里
},
default: {
minChunks: 2, // 下面有
priority: -20, // 下面有
reuseExistingChunk: true, // 如果一个模块曾经被打包过了, 那么再打包时就疏忽这个上模块
},
},
},
},
};
tree shaking
(摇树优化)
在一个模块中有多个办法,用到的办法会被打包进 bundle
中,没有用到的办法不会打包进去。
tree shaking
就是只把用到的办法打包到 bundle
,其余没有用到的会在uglify
阶段擦除。
webpack
默认反对,在 .babelrc
里设置 modules: false
即可。production
阶段默认开启。
必须是 ES6
语法,CJS
的形式不反对。
ps: 如果把
ES6
转成ES5
,同时又要开启tree shaking
,须要在.babelrc
里设置module: false
,不然babel
默认会把ES6
转成CJS
标准的写法,这样就不能进行tree shaking
。
DCE
(deal code elimination)
DEC
全称 deal code elimination,中文意思是死代码删除,次要是上面三种:
- 代码不会被执行
if (false) {console.log("代码不会被执行");
}
- 代码执行的后果不会被用到
function a() {return "this is a";}
let A = a();
- 代码只写不读
let a = 1;
a = 2;
tree shaking
原理
对代码进行动态剖析,在编译阶段,代码有没有用到是要确定下来的,不能通过在代码运行时在剖析哪些代码有没有用到,tree shaking
会把没用到的代码用正文标记进去,在 uglify
阶段将这些无用代码擦除。
利用 ES6
模块的特点:
- 只能作为模块顶层的语句呈现
import
的模块名只能是字符串常量import binding
是immutable
删除无用的css
-
purgecss-webpack-plugin
:遍历代码,辨认曾经用到的css class
- 和
mini-css-extract-plugin
配合应用
- 和
uncss
:html
须要通过jsdom
加载,所有的款式通过postCSS
解析,通过document.querySelector
来辨认在html
文件外面不存在的选择器
const PurgecssPlugin = require("purgecss-webpack-plugin");
const PATHS = {src: path.join(__dirname, "src"),
};
module.exports = {
plugins: [
new PurgecssPlugin({paths: glob.sync(`${PATHS.src}/**/*`, {nodir: true}),
}),
],
};
scope hoisting
应用和原理剖析
构建够的代码会产生大量的闭包,如下图所示:
当引入内部模块时,会用一个函数包裹起来。当引入的模块越多时,就会产生大量的函数包裹代码,导致体积变大,在运行的时候,因为创立的函数作用域越多,内存开销也会变大。
- 被
webpack
转换后的模块会被包裹一层 import
会被转换成__webpack_require
剖析
- 打包进去的是一个
IIFE
(匿名闭包) modules
是一个数组,每一项是一个模块初始化函数__webpack_require
用来加载模块,返回module.exports
- 通过
WEBPACK_REQUIRE_METHOD(0)
启动程序
scope hoisting
原理
将所有模块的代码依照援用程序放在一个函数作用域里,而后适当的重名一些变量以避免变量名抵触。
模块调用有先后顺序,a
模块调用 b
模块,因为有函数包裹,a
模块和 b
模块的程序无所谓,如果打消包裹代码的话,须要依据模块的援用程序来排放模块,就要将 b
模块放到 a
模块之前,b
模块能力被 a
模块调用。
通过 scope hoisting
能够缩小函数申明和内存开销。production
阶段默认开启。
必须是 ES6
语法,CJS
不反对。
ps:
scope hoisting
把多个作用域变成一个作用域。当模块被援用的次数大于1
次时,是不产生成果的。如果一个模块援用次数大于1
次,那么这个模块的代码会被内联屡次,从而减少打包后文件的体积。
应用ESLint
是 ESLint
可能对立团队的代码格调,可能帮忙发现带谬误。
两种应用办法:
- 与
CI/CD
集成 - 与
webpack
集成
webpack
与 CI/CD
集成
须要装置husky
。
减少 scripts
,通过lint-staged
查看批改文件。
"scripts": {"precommit": "lint-staged"},
"lint-staged": {
"linters": {"*.{js,less}": ["eslint --fix", "git add"]
}
}
webpack
与 ESLint
集成
应用 eslint-loader
,构建是查看js
标准
装置插件babel-eslint
、eslint
、eslint-config-airbnb
、eslint-config-airbnb-base
、eslint-loader
、eslint-plugin-import
、eslint-plugin-jsx-ally
、eslint-plugin-react
。
新建 .eslintrc.js
文件
module.exports = {
parser: "babel-eslint",
extends: "airbnb",
env: {
browser: true,
node: true,
},
rules: {indent: ["error", 2],
},
};
webpack.config.js
文件配置eslint-loader
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: ["babel-loader", "eslint-loader"],
},
],
},
};
代码宰割和动静import
对于大的 web
利用来说,将所有的代码都放在一个文件中显然是不够无效的,特地是当你的某些代码块是在某些非凡状况下才会被用到。
webpack
有一个性能是将你的代码宰割成chunks
(语块),当代码运行到须要它们的时候在进行加载。
应用场景:
- 抽离雷同代码到一个共享块
- 脚本懒加载,使得初始下载的代码更小
懒加载 js
脚本的形式
CJS
:require.ensure
ES6
:import
(目前还没有原生反对,须要bable
转换)
装置 @bable/plugin-syntax-dynamic-import
插件
.babelrc
文件:
{plugins: ["@bable/plugin-syntax-dynamic-import"];
}
例子:
class Index extends React.Component {constructor() {super(...arguments);
this.state = {Text: null,};
}
loadComponent = () => {import("./text.js").then((Text) => {this.setState({ Text: Text.default});
});
};
render() {const { Text} = this.state;
return (
<div className="search">
react1
{Text && <Text />}
<div onClick={this.loadComponent}> 点我 </div>
<img src={img} alt="" />
</div>
);
}
}
这里要留神一点,当配置了 cacheGroups
时,minChunks
设置了 1
,下面设置的懒加载脚本就不失效了,因为import
在加载时是动态剖析的。
cacheGroups: {
commons: {
name: "commons",
chunks: "all",
priority: -20, // 值越大, 优先级越高. 模块先打包到优先级高的组里
minChunks: 1,
}
}
多过程 / 多实例:并行压缩
办法一:应用 webpack-parallel-uglify-plugin
插件
const WebpackParalleUglifyPlugin = require("webpack-parallel-uglify-plugin");
module.exports = {
plugins: [
new WebpackParalleUglifyPlugin({
uglifyJs: {
output: {
beautify: false,
comments: false,
},
compress: {
warnings: false,
drop_console: true,
collapse_vars: true,
reduce_vars: true,
},
},
}),
],
};
办法二:应用 uglifyjs-webapck-plugin
开启 parallel
参数
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")
modules.exports = {
plugins: [
UglifyJsPlugin: {
warnings: false,
parse: {},
compress: {},
mangle: true,
output: null,
toplevel: false,
nameCache: null,
ie8: false,
keep_fnames: false,
},
parallel: true
]
}
办法三:terser-webpack-plugin
开启 parallel
参数(webpack4
举荐)
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({parallel: 4,}),
],
},
};
晋升构建速度
思路:将 react
、react-dom
、redux
、react-redux
根底包和业务根底包打包成一个文件
办法:应用 DLLPlugin
进行分包,DLLReferencePlugin
对 manifest.json
援用
应用 DLLPlugin
进行分包
const path = require("path");
const webpack = require("webpack");
module.exports = {context: process.cwd(),
resolve: {extensions: [".js", ".jsx", ".json", ".less", ".css"],
modules: [__dirname, "nodu_modules"],
},
entry: {library: ["react", "react-dom", "redux", "react-redux"],
},
output: {filename: "[name].dll.js",
path: path.resolve(__dirname, "./build/library"),
library: "[name]",
},
plugins: [
new webpack.DllPlugin({name: "[name]",
path: "./build/library/[name].json",
}),
],
};
在 webpack.config.js
引入
module.exports = {
plugins: [
new webapck.DllReferencePlugin({manifest: require("./build/library/manifest.json"),
}),
],
};
在我的项目应用了 webpack4
,对dll
的依赖没那么大了,应用 dll
相对来说晋升也不是特地显著,而 hard-source-webpack-plugin
能够极大的晋升二次构建。
不过从理论的前端工厂中来说,dll
还是很有必要的。对于一个团队而言,根本是应用雷同的技术栈,要么 react
,要么vue
。这时候,通常的做法都是把公共框架打成一个common bundle
文件供所有我的项目应用。dll
就能够很好的满足这种场景:将多个 npm
包打成一个公共包。因而团队外面的分包计划应用 dll
还是很有价值。
splitChunks
也能够做 DllPlugin
的事件,然而举荐应用 splitChunks
去提取页面间的公共 js
文件,因为应用 splitChunks
每次去提取根底包还是须要消耗构建工夫的,如果是 DllPlugin
只须要预编译一次,前面的根底包工夫都是能够省略掉的。
晋升二次构建速度
办法一:应用 terser-webpack-plugin
开启缓存
module.exports = {
optimization: {
minimizer: [
new TerserWebpackPlugin({
parallel: true,
cache: true,
}),
],
},
};
办法二:应用 cache-loader
或者hard-source-webpack-plugin
module.exports = {plugins: [new HardSourceWebpackPlugin()],
};
放大构建指标
比方 babel-loader
不解析node_modules
module.exports = {
rules: {
test: /\.js$/,
loader: "happypack/loader",
// exclude: "node_modules"
/-- 或者 --/
// include: path.resolve("src"),
}
}
缩小文件搜寻范畴
- 优化
resolve.modules
配置(缩小模块搜寻层级) -
优化
resolve.mainFields
配置- 先查找
package.json
中main
字段指定的文件 -> 查找根目录的index.js
-> 查找lib/index.js
- 先查找
-
优化
resolve.extensions
配置- 模块门路的查找,
import xx from "index"
会先找.js
后缀的
- 模块门路的查找,
- 正当应用
alias
module.exports = {
resolve: {
alias: {react: path.resolve(__dirname, "./node_modules/react/dist/react.min.js"),
},
modules: [path.resolve(__dirname, "node_modules")], // 查找依赖
extensions: [".js"], // 查找模块门路
mainFields: ["main"], // 查找入口文件
},
};