Webpack的性能优化
webpack作为前端目前使用最广泛的打包工具,在面试中也是经常会被问到的。
比较常见的面试题包括:
- 可以配置哪些属性来进行webpack性能优化?
- 前端有哪些常见的性能优化? (问到前端性能优化时, 除了其他常见的,也完全可以从webpack来回答)
webpack的性能优化较多,我们可以对其进行分类:
- 优化一: 打包后的结果,上线时的性能优化。(比如分包处理、 减小包体积、CDN服务器等)
- 优化二: 优化打包速度,开发或者构建时优化打包速度。(比如exclude、 cache-loader等)
大多数情况下,我们会更加侧重于优化一, 这对于线上的产品影响更大。
在大多数情况下webpack都帮我们做好了该有的性能优化:
- 比如配置mode为production或者development时,默认webpack的配置信息;
- 但是我们也可以针对性的进行自己的项目优化;
代码分离
代码分离(Code Splitting)是webpack一个非常重要的特性:
- 它主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件;
- 比如默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度;
- 代码分离可以分出更小的bundle,以及控制资源加载优先级,提供代码的加载性能;
Webpack中常用的代码分离有三种:
- 入口起点: 使用entry配置手动分离代码;
- 防止重复: 使用Entry Dependencies或者SplitChunksPlugin去重和分离代码;
- 动态导入: 通过模块的内联函数调用来分离代码;
分包处理的优势和必要性
webpack多入口依赖
入口起点的含义非常简单,就是配置多入口:
- 比如配置-个index.js和main.js的入口;
- 他们分别有自己的代码逻辑;
entry: {
index: "./src/index.js",
main: './src/main.js'
},
// 可以进行共享的
shared: ["axios"]
},
output: {
path: path.resolve(__dirname, "./build"),
// [name]为占位符
filename: "[name]-bundle.js",
clean: true
},
项目依赖
假如我们的index.js和main.js都依赖两个库: lodash、 dayjs
- 如果我们单纯的进行入口分离,那么打包后的两个bunlde都有会有一-份lodash和dayjs; .
- 事实上我们可以对他们进行共享;
entry: {
index: {
import: "./src/index.js",
dependOn: "shared"
},
main: {
import: './src/main.js',
dependOn: "shared"
},
// 可以进行共享的
shared: ["axios"]
},
webpack的动态导入(dynamic import)
另外一个代码拆分的方式是动态导入时,webpack提供了两种实现动态导入的方式:
- 第一-种,使用ECMAScript中的import()语法来完成,也是目前推荐的方式;
- 第二种,使用webpack遗留的require.ensure,目前已经不推荐使用;
比如我们有一个模块bar.js:
- 该模块我们希望在代码运行过程中来加载它(比如判断一-个条件成立时加载) ;
- 因为我们并不确定这个模块中的代码一定会用到,所以最好拆分成一个独立的js文件;
- 这样可以保证不用到该内容时,浏览器不需要加载和处理该文件的js代码;
- 这个时候我们就可以使用动态导入;
- 这里实现了一个懒加载
注意:使用动态导入barjs: .
- 在webpack中,通过动态导入获取到一个对象;
- 真正导出的内容,在该对象的default属性中,所以我们需要做一个简单的解构;
function about() {
console.log("about function exec~");
}
const name = "默认导出值"
export {
about
}
export default name
btn1.onclick = function () {
import("./router/about").then(res => {
res.about()
res.default()
})
}
分包重命名
动态导入的文件命名:
- 因为动态导入通常是一定会打包成独立的文件的,所以并不会在cacheGroups中进行配置;
- 那么它的命名我们通常会在output中,通过chunkFilename属性来命名;
output: {
clean: true,
path: path.resolve(__dirname, "./build"),
// [name]为占位符
filename: "[name]-bundle.js",
// 单独针对分包的文件进行命名
chunkFilename: "[name]_chunk.js"
},
但是,你会发现默认情况下我们获取到的[name]是和id的名称保持-致的
- 如果我们希望修改name的值,可以通过magic comments (魔法注释)的方式;
btn1.onclick = function () {
import(/* webpackChunkName:"about" */"./router/about").then(res => {
res.about()
res.default()
})
}
btn2.onclick = function () {
import(/* webpackChunkName:"category" */"./router/category")
}
SplitChunkPlugin
另外一种分包的模式是splitChunk, 它底层是使用SplitChunksPlugin来实现的:
- 因为该插件webpack已经默认安装和集成,所以我们并不需要单独安装和直接使用该插件;
- 只需要提供SplitChunksPlugin相关的配置信息即可;
// 优化配置
optimization: {
splitChunks: {
// 所有导入的第三包都会分开
chunks: "all"
}
},
自定义配置解析
Chunks:
- 默认值是async
- all表示对同步和异步代码都进行处理
minSize:
- 拆分包的大小,少为minSize;
- 如果一个包拆分出来达不到minSize,那么这个包就不会拆分;
maxSize:
- 将大于maxSize的包,拆分为不小于minSize的包;
cacheGroups:
- 用于对拆分的包就行分组,比如一个lodash在拆分之后,并不会立即打包,而是会等到有没有其他符合规则的包一起来打包;
- test属性:匹配符合规则的包;
- name属性:拆分包的name属性;
- filename属性:拆分包的名称,可以自己使用placeholder属性;
// 优化配置
optimization: {
splitChunks: {
chunks: "all",
// 当一个包大于指定值时,继续进行拆包
maxSize: 20000,
// 将包拆分成不小于minSize的包
minSize: 10000,
// 自己对需要拆包的内容进行分包
cacheGroups: {
vendors: {
// 在window上 /和\都有可能是斜杠 mac上只有/
test: /[\/]node_modules[\/]/, //完整写法
filename: "[id]_vendors.js"
},
utils: {
test: /utils/,
filename: "[id]_utils.js"
}
}
}
},
optimization.chunklds配置
optimization.chunklds配置用于告知webpack模块的id采用什么算法生成。 有三个比较常见的值:
-
natural:按照数字的顺序使用id;
-
named: development 下的默认值,一个可读的名称的id;
-
deterministic:确定性的,在不同的编译中不变的短数字id (有利于浏览器缓存)
- 在webpack4中是没有这个值的;
- 那个时候如果使用natural,那么在一些编译发生变化时, 就会有问题;
最佳实践:
- 开发过程中,我们推荐使用named;
- 打包过程中,我们推荐使用deterministic;
// 优化配置
optimization: {
// 设置生成的chunkId的算法
// development默认:named
// production:deterministic(确定性的数字)
// webpack4中使用:natural 自然数递增
chunkIds: "deterministic",
// 分包插件:splitChunksPlugin
splitChunks: {
chunks: "all",
// 当一个包大于指定值时,继续进行拆包
// maxSize: 20000,
// 将包拆分成不小于minSize的包
// minSize: 10000,
minSize: 10,
// 自己对需要拆包的内容进行分包
cacheGroups: {
vendors: {
// 在window上 /和\都有可能是斜杠 mac上只有/
test: /[\/]node_modules[\/]/, //完整写法
filename: "[id]_vendors.js"
},
utils: {
test: /utils/,
filename: "[id]_utils.js"
}
}
},
// 代码优化:TerserPlugin => 让代码更简单 => Terser
minimize:true,
minimizer: [
// JS代码简化 去掉注释
new TerserPlugin({
extractComments: false
})
]
},
prefetch和preload
webpack v4.6.0+增加了对预获取和预加载的支持。
在声明import时,使用下面这些内置指令,来告知浏览器:
- prefetch(预获取):将来某些导航下可能需要的资源
- preload(预加载):当前导航下可能需要资源
与prefetch指令相比,preload 指令有许多不同之处:
- preload chunk会在父chunk加载时,以并行方式开始加载。prefetch chunk会在父chunk加载结束后开始加载。
- preload chunk具有中等优先级,并立即下载。prefetch chunk在浏览器闲置时下载。
- preload chunk会在父chunk中立即请求,用于当下时刻。prefetch chunk会用于未来的某个时刻。
使用魔法注释 /* webpackPrefetch:true */ 就可以实现分包预加载
btn1.onclick = function () {
import(
/* webpackChunkName:"about" */
/* webpackPrefetch:true */
"./router/about").then(res => {
res.about()
res.default()
})
}
btn2.onclick = function () {
import(
/* webpackChunkName:"category" */
/* webpackPrefetch:true */
"./router/category")
}
CDN加速服务器配置
CDN称之为内容分发网络(Content Delivery Network或Content Distribution Network,缩写: CDN)
- 它是指通过相互连接的网络系统,利用最靠近每个用户的服务器;
- 更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户;
- 来提供高性能、可扩展性及低成本的网络内容传递给用户;
在开发中,我们使用CDN主要是两种方式:
-
方式一:打包的所有静态资源,放到CDN服务器
用户所有资源都是通过CDN服务器加载的;
-
方式二:一些第三方资源放到CDN服务器上;
所有的静态资源都想要放到CDN服务器上
如果所有的静态资源都想要放到CDN服务器上,我们需要购买自己的CDN服务器;
- 目前阿里、腾讯、亚马逊、Google等都可以购买CDN服务器;
- 我们可以直接修改publicPath,在打包时添加上自己的CDN地址;
output: {
clean: true,
path: path.resolve(__dirname, "./build"),
// [name]为占位符
filename: "[name]-bundle.js",
// 单独针对分包的文件进行命名
chunkFilename: "[name]_chunk.js",
// CND服务器地址
publicPath: "http://coderwhycnd.com/"
},
此时Html加载的包都是从CDN处加载
<script defer src="http://coderwhycnd.com/246_vendors.js"></script>
<script defer src="http://coderwhycnd.com/497_utils.js"></script>
<script defer src="http://coderwhycnd.com/main-bundle.js"></script>
第三方库的CND
通常一些比较出名的开源框架都会将打包后的源码放到一-些比较出名的、免费的CDN服务器上:
- 国际上使用比较多的是unpkg、JSDelivr、 cdnjs; .
- 国内也有一个比较好用的CDN是bootcdn;
在项目中,我们如何去引入这些CDN呢?
- 第一, 在打包的时候我们不再需要对类似于lodash或者dayjs这些库进行打包;
- 第二,在html模块中,我们需要自己加入对应的CDN服务器地址;
第一步,我们可以通过webpack配置,来排除一些库的打包:
// 排除某些包不需要进行打包
externals: {
react: "React",
// key属性名:排除的框架名称
// value值:从CDN地址请求下来的js中提供对应的名称
axios: "axios"
},
第二步,在html模块中,加入CDN服务器地址:
<body>
<div id="root"></div>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.2/axios.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
</body>
认识shimming
shimming是一个概念,是某一类功能的统称:
- shimming翻译过来我们称之为垫片,相当于给我们的代码填充一-些垫片来处理一-些问题;
- 比如我们现在依赖一个第三方的库,这个第三方的库本身依赖lodash,但是默认没有对lodash进行导入(认为全局存在lodash),那么我们就可以通过ProvidePlugin来实现shimming的效果;
注意: webpack并不推荐随意的使用shimming
- Webpack背后的整个理念是使前端开发更加模块化;
- 也就是说,需要编写具有封闭性的、不存在隐含依赖(比如全局变量)的彼此隔离的模块;
假如我们的lodash、dayjs都使用了CDN进行引入,所以相当于在全局是可以使用和dayjs的
- 假如一个文件中我们使用了axios,但是没有对它进行引入,那么下面的代码是会报错的;
// import axios from "axios";
// import dayjs from "dayjs"
axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
console.log(res);
})
console.log(dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss"));
/*
如果abc.js是一个第三方库,但是他没有引用axios,但是却使用了axios的方法,
此时如果本地没有引用axios这个第三方库就会报错,所以需要shimming
*/
我们可以通过使用ProvidePlugin来实现shimming的效果:
- ProvidePlugin能够帮助我们在每个模块中,通过一个变量来获取一-个package;
- 如果webpack看到这个模块,它将在最终的bundle中引入这个模块;
- 另外ProvidePlugin是webpack默认的一个插件,所以不需要专门导入;
这段代码的本质是告诉webpack:
如果你遇到了至少一处用到axios变量的模块实例,那请你将axios package引入进来,并将其提供给需要用到它的模块。
const { ProvidePlugin } = require("webpack")
plugins: [
new ProvidePlugin({
axios: ["axios", "default"],
dayjs: "dayjs"
})
]
CSS样式的单独提取
MiniCssExtractPlugin可以帮助我们将css提取到一个独立的css文件中,该插件需要在webpack4+才可以使用。
首先,我们需要安装mini-css-extract-plugin:
npm install mini-css-extract-plugin -D
配置rules和plugins:
module: {
rules: [
{
test: /.css$/,
use: [
// "style-loader", 开发阶段 直接插入到html文件
MiniCssExtractPlugin.loader, // 生产阶段 分离单独的css文件
'css-loader'
]
}
]
},
plugins: [
// 完成css的提取
new MiniCssExtractPlugin({
// 直接导入重命名
filename: 'css/[name].css',
// 动态导入重命名
chunkFilename: 'css/[name]_chunk.css'
})
]
打包重命名的Hash生成
在我们给打包的文件进行命名的时候,会使用placeholder, placeholder中有几个属性比较相似:
- hash、 chunkhash、 contenthash
- hash本身是通过MD4的散列函数处理后,生成一个128位的hash值(32个十六进制) ;
hash值的生成和整个项目有关系:
- 比如我们现在有两个入口index.js和mainjs;
- 它们分别会输出到不同的bundle文件中,粗在文件名称中我们有使用hash;
- 这个时候,如果修改了index.js文件中的内容,那么hash会发生变化;
- 那就意味着两个文件的名称都会发生变化;
chunkhash可以有效的解决上面的问题,它会根据不同的入口进行解析来生成hash值:
- 比如我们修改了index:js,那么main.js的chunkhash是不会发生改变的;
contenthash表示生成的文件hash名称,只和内容有关系:
- 比如我们的index.js,引入了一个style.css, style.css有 被抽取到一个独立的css文件中; .
- 这个css文件在命名时,如果我们使用的是chunkhash;
- 那么当index.js文件的内容发生变化时, css文件的命名也会发生变化;
- 这个时候我们可以使用contenthash;
const path = require("path")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
mode: "development",
entry: {
main: "./src/main.js",
index: "./src/index.js"
},
output: {
clean: true,
path: path.resolve(__dirname, './build'),
filename: "[name]_[contenthash]_bundle.js",
chunkFilename: "[contenthash]_chunk.js"
},
module: {
rules: [
{
test: /.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[contenthash]_[name].css"
})
]
}
TerserPlugin代码压缩
什么是Terser呢?
- Terser是一个JavaScript的解释(Parser) 、Mangler (绞肉机) /Compressor (压缩机)的工具集;
- 早期我们会使用uglify-js来压缩、丑化我们的JavaScript代码,但是目前已经不再维护,并且不支持ES6+的语法;
- Terser是从uglify-es fork过来的,并且保留它原来的大部分API以及适配uglify-es和uglify-js@3等;
也就是说,Terser可以帮助我们压缩、丑化我们的代码,让我们的bundle变得更小。
因为Terser是一个独立的工具,所以它可以单独安装:
#全局安装
npm install terser -g
#局部安装
npm install terser -D
命令行使用
我们可以在命令行中使用Terser:
terser [input files] [options]
#举例说明
terser js/file1.js -o foo.min.js -C -m
我们这里来讲解几个Compress option和Mangle(乱砍) option:
- 因为他们的配置非常多,我们不可能一个个解析,更多的查看文档即可;
- github.com/terser/ters…
- github.com/terser/ters…
Compress option:
- arrows: class或者object中的函数, 转换成箭头函数;
- arguments:将函数中使用arguments[index]转成对应的形参名称;
- dead_ code:移除不可达的代码(tree shaking) ;
- 他属性可以查看文档;
Mangle option
- toplevel:默认值是false,顶层作用域中的变量名称,进行丑化(转换) ;
- keep_classnames: 默认值是false,是否保持依赖的类名称;
- keep_fnames:默认值是false,是否保持原来的函数名称;
- 其他属性可以查看文档;
npx terser abx.js -o abx.min.js -c arrows=true arguments=true,dead_code=true -m toplevel=true,keep_fnames=true
Terser在webpack中配置
真实开发中,我们不需要手动的通过terser来处理我们的代码,我们可以直接通过webpack来处理:
- 在webpack中有一 个minimizer属性 ,在production模式下, 默认就是使用TerserPlugin来处理我们的代码的;
- 如果我们对默认的配置不满意,也可以自己来创建TerserPlugin的实例,組覆盖相关的配置;
首先,我们需要打开minimize,让其对我们的代码进行压缩(默认production模式 下已经打开了)
其次,我们可以在minimizer创建-个TerserPlugin:
-
extractComments: 默认值为true, 表示会将注释抽取到一个单独的文件中; .
- 在开发中,我们不希望保留这个注释时,可以设置为false
-
parallel: 使多进程并发运行提高构建的速度,默认值是true
- 并发运行的默认数量: os.cpus().length - 1;
- 我们也可以设置自己的个数,但是使用默认值即可;
-
terserOptions: 设置我们的terser相关的配置
- compress: 设置压缩相关的选项;
- mangle: 设置丑化相关的选项,可以直接设置为true;
- toplevel: 顶层变量是否进行转换;
- keep_ classnames: 保留类的名称;
- keep_ fnames:保留函数的名称;
// 优化配置
optimization: {
minimize: true,
// 代码优化:TerserPlugin => 让代码更简单 => Terser
minimizer: [
// JS压缩插件 TerserPlugin
new TerserPlugin({
extractComments: false,
terserOptions: {
compress: {
// 改变arguments参数名称
arguments: true,
// 启用 没有使用过的代码
unused: false
},
// 丑化代码
mangle: true,
// 顶层作用域中的变量名称
// toplevel: false
// 保持原来的函数名称
keep_fnames: true
}
})
// CSS压缩插件 CSSMinimizerPlugin
]
},
CSS压缩
另一个代码的压缩是CSS:
- CSS压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等;
- CSS的压缩我们可以使用另外一个插件: css-minimizer-webpack plugin;
- css-minimizer-webpack- plugin是使用cssnano工具来优化、压缩CSS (也可以单独使用) ;
第一步,安装css-minimizer-webpack-plugin:
npm install css-minimizer-webpack-plugin
第二步,在optimization.minimizer中配置
minimizer: [
// JS压缩插件 TerserPlugin
new TerserPlugin({
extractComments: false,
terserOptions: {
compress: {
// 改变arguments参数名称
arguments: true,
// 启用 没有使用过的代码
unused: false
},
// 丑化代码
mangle: true,
// 顶层作用域中的变量名称
// toplevel: false
// 保持原来的函数名称
keep_fnames: true
}
}),
// CSS压缩插件 CSSMinimizerPlugin
new CssMinimizerPlugin({
// parallel: true,
})
]
Tree Shaking的实现
将mode设置为development模式:
- 为了可以看到usedExports带来的效果,我们需要设置为development模式
- 因为在production模式下,webpack默认的一些优化会带来很大的影响。
optimization: {
// 导入模块时,分析模块中那些函数有被使用,那些函数没有被使用
usedExports: true,
}
设置usedExports为true和false对比打包后的代码:
- 在usedExports设置为true时,会有一段注释: unused harmony export mul;
- 这段注释的意义是什么呢?告知Terser在优化时,可以删除掉这段代码;
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "sum": function() { return /* binding */ sum; }
/* harmony export */ });
/* unused harmony export mul */
function sum(num1, num2) {
return num1 + num2;
}
function mul(num1, num2) {
return num1 + num2;
}
这个时候,我们讲minimize设置true:
- usedExports设置为false时,mul函数没有被移除掉; .
- usedExports设置为true时,mul函数有被移除掉;
Tree Shaking要注意的sideEffects(副作用)
sideEffects用于告知webpack compiler哪些模块时有副作用的:
- 副作用的意思是这里面的代码有执行一些特殊的任务 ,不能仅仅通过export来判断这段代码的意义;
- 副作用的问题,在讲React的纯函数时是有讲过的;
// 副作用代码
// 推荐:在平时写模块时,尽量编写纯模块
window.lyric = "哈哈哈哈哈哈哈"
在package.json中设置sideEffects的值:
- 如果我们将sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports;
- 如果有一些我们希望保留,可以设置为数组;
"sideEffects": [
"./src/demo/parse-lyric.js"
],
比如我们有一个format.js、 style.css文件:
- 该文件在导入时没有使用任何的变量来接受;
- 那么打包后的文件,不会保留format.js、 style.css相关的任何代码;
"sideEffects": [
"*.css"
],
所以,如何在项目中对JavaScript的代码进行TreeShaking呢(生成环境) ?
- 在optimization中配置usedExports为true,来帮助Terser进行优化;
- 在package.json中配置sideEffects,直接对模块进行优化;
CSS的TreeShaking
上面我们学习的都是关于JavaScript的Tree Shaking,那么CSS是否也可以进行Tree Shaking操作呢?
- CSS的Tree Shaking需要借助于一些其他的插件;
- 在早期的时候,我们会使用PurifyCss插件来完成CSS的tree shaking,但是目前该库已经不再维护了(最新更新也是在4年前了) ;
- 目前我们可以使用另外-一个库来完成CSS的Tree Shaking: PurgeCSS, 也是一个帮助我们删除未使用的CSS的工具;
配置这个插件(生成环境) :
- paths: 表示要检测哪些目录下的内容需要被分析,这里我们可以使用glob;
- 默认情况下,Purgecss会将我们的html标签的样式移除掉,如果我们希望保留,可以添加一个safelist的属性;
purgecss也可以对less文件进行处理(所以它是对打包后的css进行tree shaking操作) ;
Scope Hoisting作用
什么是Scope Hoisting呢?
- Scope Hoisting从webpack3开始增加的一个新功能;
- 功能是对作用域进行提升,并且让webpack打包后的代码更小、运行更快;
默认情况下webpack打包会有很多的函数作用域,包括一些(比如最外层的) IIFE:
- 无论是从最开始的代码运行,还是加载一个模块, 都需要执行一系列的函数;
- Scope Hoisting可以将函数合并到一一个模块中来运行;
使用Scope Hoisting非常的简单, webpack已经内置了对应的模块:
- 在production模式下,默认这个模块就会启用;
- 在development模式下,我们需要自己来打开该模块;
// webpack插件
plugins: [
// 作用域提升
new webpack.optimize.ModuleConcatenationPlugin()
]
HTTP文件压缩传输
HTTP压缩是一种内置在服务器和客户端之间的,以改进传输速度和带宽利用率的方式;
HTTP压缩的流程什么呢?
-
第一步: HTTP数据在服务器发送前就已经被压缩了; (可以在webpack中完成)
-
第二步:兼容的浏览器在向服务器发送请求时,会告知服务器自己支持哪些压缩格式;
-
第三步:服务器在浏览器支持的压缩格式下,直接返回对应的压缩后的文件,并且在响应头中告知浏览器;
目前的压缩格式非常的多:
- compress - UNIX的"compress" 程序的方法(历史性原因,不推荐大多数应用使用,应该使用gzip或deflate) ;
- deflate - 基于deflate算法(定义于RFC 1951)的压缩,使用zlib数据格式封装;
- gzip - GNU zip格式(定义于RFC 1952),是目前使用比较广泛的压缩算法;
- br - 一种新的开源压缩算法,专为HTTP内容的编码而设计;
webpack对文件的压缩
webpack中相当于是实现了HTTP压缩的第一步操作, 我们可以使用CompressionPlugin.
-
第一步,安装CompressionPlugin:
npm install compression-webpack-plugin -D -
第二步,使用CompressionPlugin即可:
plugins: [
// HTTP压缩
new CompressionPlugin({
test: /.(css|js)/,
minRatio: 0.7,
algorithm: "gzip"
})
]
HTML文件的压缩
我们之前使用了HtmlWebpackPlugin插件来生成HTML的模板,事实上它还有一些其他的配置:
inject:设置打包的资源插入的位置
- true、false 、body、head
cache:设置为true,只有当文件改变时,才会生成新的文件(默认值也是true)
minify: 默认会使用一个插件html-minifier-terser
plugins: [
new HtmlWebpackPlugin({
template: "./index.html",
// 缓存,如果有更新才生成新html
cache: true,
minify: isProduction ? {
// 移除注释
removeComments: true,
// 移除属性
removeEmptyAttributes: true,
// 移除默认属性
removeRedundantAttributes: true,
// 折叠空白字符
collapseWhitespace: true,
// 压缩内联css
minifyCSS: true,
// 压缩JavaScript
minifyJS: {
mangle: {
toplevel: true
}
}
} : false
})
]
webpack打包分析
对打包时间分析
如果我们希望看到每一个loader. 每一个Plugin消耗的打包时间, 可以借助于一个插件: speed-measure-webpack-plugin
第一步,安装speed-measure-webpack plugin插件
npm install speed-measure-webpack-plugin -D
第二步,使用speed-measure-webpack-plugin插件
- 创建插件导出的对象SpeedMeasurePlugin;
- 使用smp.wrap包裹我们导出的webpack配置;
const smp = new SpeedMeasurePlugin()
const finalConfig = merge(mergeConfig, getCommonConfig(isProduction))
return smp.wrap(finalConfig)
对打包文件分析
方案一:生成一个stats.json的文件
"build": "webpack --config ./config/comm.config.js --env production --profile --json=stats.json",
通过执行npm run build:status可以获取到一 个stats.json的文件:
-
这个文件我们自己分析不容易看到其中的信息;
-
可以放到webpack.github.io/analyse/进行分析
方案二:使用webpack bundle-analyzer工具
- 另一个非常直观查看包大小的工具是webpack- bundle-analyzer
第一步:安装
pnpm add webpack-bundle-analyzer -D
第二步:我们可以在webpack配置中使用该插件:
plugins: [
// 对打包结果分析
new BundleAnalyzerPlugin()
]
在打包webpack的时候,这个工具是帮助我们打开- -个8888端口上的服务,我们可以直接的看到每个包的大小。
- 比如有一个包时通过一个Vue组件打包的,但是非常的大,那么我们可以考虑是否可以拆分出多个组件,并且对其进行懒加载;
- 比如一-个图片或者字体文件特别大,是否可以对其进行压缩或者其他的优化处理;