导语
来到这家公司之后,一直在使用webpack,也写了不少笔记,但是都比较零散,现在决定整理一下webpack相关的知识点,由浅入深,方便自己后续查漏补缺,后续会一直更新。
前言
在上一篇文章中,简单介绍了提升构建速度的几种途径,而构建的产物,我们也想尽量让它体积小一点,在本文中,将从几个方面,介绍webpack如何对构建结果进行优化。
目录
- 打印体积分析
- 压缩css
- 压缩js
- 清除无用css
- tree shaking
- scope hosting
- 删除无用代码
- 文件指纹
打印体积分析
借助插件 webpack-bundle-analyzer 我们可以直观的看到打包结果中,文件的体积大小、各模块依赖关系、文件是否重复等问题,极大的方便我们在进行项目优化的时候,进行问题诊断。
安装
$ npm i -D webpack-bundle-analyzer
配置插件
// 引入插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const config = {
// ...
plugins:[
// ...
// 配置插件
new BundleAnalyzerPlugin({
// analyzerMode: 'disabled', // 不启动展示打包报告的http服务器
// generateStatsFile: true, // 是否生成stats.json文件
})
],
};
结果分析
每次打包结束后,会自行启动地址为 http://127.0.0.1:8888 的 web 服务,访问地址就可以看到
鼠标hover上去可以看到每个依赖的体积大小,从上往下,表示依赖的嵌套关系
如果,我们只想保留数据不想启动 web 服务,这个时候,我们可以加上两个配置
new BundleAnalyzerPlugin({
analyzerMode: 'disabled', // 不启动展示打包报告的http服务器
generateStatsFile: true, // 是否生成stats.json文件
})
这样再次执行打包的时候就只会产生 state.json 的文件了,state.json文件会静态的展示打包之后的体积信息
压缩css
安装 optimize-css-assets-webpack-plugin
$ npm install -D optimize-css-assets-webpack-plugin
修改 webapck.config.js 配置
// 压缩css
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const config = {
optimization: {
minimize: true,
minimizer: [
// 添加 css 压缩配置
new OptimizeCssAssetsPlugin({}),
]
},
}
压缩js
uglifyjs-webpack-plugin插件在webpack4之后已经不再维护了,现在已经弃用了,取而代之的是具有相同功能的terser-webpack-plugin插件
而webpack5 内置了terser-webpack-plugin 插件,所以我们不需重复安装,直接引用就可以了
const TerserPlugin = require('terser-webpack-plugin');
const config = {
// ...
optimization: {
minimize: true, // 开启最小化
minimizer: [
// ...
new TerserPlugin({})
]
},
// ...
}
在生成环境下打包默认会开启 js 压缩,但是当我们手动配置
optimization选项之后,就不再默认对 js 进行压缩,需要我们手动去配置。
当minimize设置为true,他会告知 webpack 使用 TerserPlugin 或其它在 optimization.minimizer定义的插件压缩 bundle。
删除无用的css
purgecss-webpack-plugin 会单独提取 CSS 并清除用不到的 CSS
安装插件
$ npm i -D purgecss-webpack-plugin
添加配置
// ...
const PurgecssWebpackPlugin = require('purgecss-webpack-plugin')
const glob = require('glob'); // 文件匹配模式
// ...
function resolve(dir){
return path.join(__dirname, dir);
}
const PATHS = {
src: resolve('src')
}
const config = {
plugins:[ // 配置插件
// ...
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, {nodir: true}) // nodir表示不匹配文件夹
}),
]
}
注意:PurgecssWebpackPlugin不能单独使用,必须先安装配置分离css
tree shaking
Tree-shaking意为摇树,作用是剔除没有使用的代码,以降低包的体积,它是基于ES Module 规范来实现的,所以Tree Shaking 只支持 ESM 的引入方式,不支持其他的引入方式。
原理
在 CommonJs、AMD、CMD 等旧版本的 JavaScript 模块化方案中,导入导出行为是高度动态,难以预测的,例如:
if(process.env.NODE_ENV === 'development'){
require('./bar');
exports.foo = 'foo';
}
而 ESM 方案则从规范层面规避这一行为,它要求所有的导入导出语句只能出现在模块顶层,且导入导出的模块名必须为字符串常量,这意味着下述代码在 ESM 方案下是非法的:
if(process.env.NODE_ENV === 'development'){
import bar from 'bar';
export const foo = 'foo';
}
所以,ESM 下模块之间的依赖关系是高度确定的,鉴于此,webpack可以在运行过程中静态分析模块之间的导入导出,确定 ESM 模块中哪些导出值未曾其它模块使用,并将其删除,以此实现打包产物的优化。
配置
生产环境(production)会默认开启tree-shaking,如果想在测试环境开启tree-shaking的话,需要设置
optimization: {
usedExports: true,
}
除此之外还需要在package.json中设置 sideEffects
{
"sideEffects": false,
}
sideEffects
sideEffects的值可以为boolean,也可以是一个数组
- sideEffects 默认为 true, 告诉 Webpack ,所有文件都有副作用,他们不能被 Tree Shaking。
- sideEffects 为 false 时,告诉 Webpack ,没有文件是有副作用的,他们都可以 Tree Shaking。
- sideEffects 为一个数组时,告诉 Webpack ,数组中那些文件不要进行 Tree Shaking,其他的可以 Tree Shaking。
sideEffects 对全局 CSS 的影响
当我们将sideEffects设置为false之后,被引入的全局css文件会被treeShaking掉
原因在于:上面我们将 sideEffects 设置为 false 后,所有的文件都会被 Tree Shaking,通过 import 这样的形式引入的 CSS 就会被当作无用代码处理掉。
例如这样的代码,在打包后,打开页面,你就会发现样式并没有应用上,
/* reset.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
background-color: #eaeaea;
}
// main.js
import "./styles/reset.css"
为了解决这个问题,可以在 loader 的规则配置中,添加 sideEffects: true ,告诉 Webpack 这些文件不要 Tree Shaking。
rules: [
// 支持加载css文件
{
test: /\.css/,
use: [{ loader: MiniCssExtractPlugin.loader }, 'css-loader'],
exclude: /node_modules/,
include: path.resolve(__dirname, 'src'),
sideEffects: true,
},
{
test: /\.less/,
use: [{ loader: MiniCssExtractPlugin.loader }, 'css-loader', 'less-loader'],
exclude: /node_modules/,
include: path.resolve(__dirname, 'src'),
sideEffects: true,
},
]
或者指定package.json中的数组
"sideEffects": ["./src/**/*.less"],
scope hosting
Scope Hoisting开启之前,webpack打包的代码像这样
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ var getter = module && module.__esModule ?
/******/ () => (module['default']) :
/******/ () => (module);
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
webpack打包出来是一根匿名闭包,modules是一个加载模块数组,webpack_require用来加载模块,当代码量比较多时会生成大量的函数闭包,体积增大,运行时作用域的定义变多,更消耗内存
原因
- 被webpack转换之后的模块会带上一层包裹
- import会被转化成 _webpack_require
- 主要是为了兼容不同的浏览器
开启Scope Hoisting
Scope Hoisting即作用域提升,将所有的模块按照引用顺序排列在一个函数作用域里面,再适当重命名一些函数,通过这种方式可以减少函数声明和内存开销,在生产环境下已经默认开启
删除无用代码
前面说到,使用TerserWebpackPlugin插件我们可以压缩js代码,除此之外,通过配置TerserWebpackPlugin插件,我们可以在打包时删除如console.log这种代码
minimizer: [
new TerserWebpackPlugin({
terserOptions: {
compress: {
drop_console: true, //传true就是干掉所有的console.*这些函数的调用.
drop_debugger: true, //干掉那些debugger;
// pure_funcs: ['console.log'], // 如果你要干掉特定的函数比如console.info ,又想删掉后保留其参数中的副作用,那用pure_funcs来处理 } }
},
},
}),
]
文件指纹(hash值)
文件指纹指的是 webpack 中的 hash、chunkhash、contenthash 几种hash值
用途:hash 一般是结合 CDN 缓存来使用,通过 webpack 构建之后,生成对应文件名自动带上对应的 hash 值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的 HTML 引用的 URL 地址也会改变,触发 CDN 服务器从源服务器上拉取对应数据,进而更新本地缓存。
但是在实际使用的时候,这几种 hash 计算还是有一定区别。
hash
默认由md5生成,默认32位,一般取前8位,hash 是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的 hash 值都会更改,并且全部文件都共用相同的 hash 值。
chunkhash
采用 hash 计算的话,每一次构建后生成的哈希值都不一样,即使文件内容压根没有改变。这样子是没办法实现缓存效果,我们需要换另一种哈希值计算方式,即 chunkhash。
chunkhash 和 hash 不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值,当我们有代码发生变动时,只会重新生成对应chunk的hash值。
contenthash
在使用 chunkhash 的例子中,如果 index.css 被 index.js 引用了,那么就会共用相同的 chunkhash 值。但是这样子有个问题,如果 index.js 更改了代码,css 文件就算内容没有任何改变,由于是该模块发生了改变,导致 css 文件会重复构建。
这个时候,我们可以使用 extra-text-webpack-plugin 里的 contenthash 值,保证即使 css 文件所处的模块里就算其他文件内容改变,只要 css 文件内容不变,那么不会重复构建。
使用方法
在配置项中,使用对应字符串占位,这里的数字代表编码长度
"[name][hash:8][ext]"
项目链接
最后
感谢你能看到这里,本文总结了减少webpack打包体积的几种方法,希望对你有所帮助,之后会陆续更新其他webpack相关的文章,如果能留下你的一个赞,笔者将感激不尽。