导语
来到这家公司之后,一直在使用webpack,也写了不少笔记,但是都比较零散,现在决定整理一下webpack相关的知识点,由浅入深,方便自己后续查漏补缺,后续会一直更新。
前言
在上一篇文章中,简单介绍了提升构建速度的几种途径,而构建的产物,我们也想尽量让它体积小一点,在本文中,将从几个方面,介绍webpack如何对构建结果进行优化。
目录
- 打印体积分析
- 压缩css
- 压缩js
- 清除无用css
- tree shaking
- scope hosting
- 删除无用代码
打印体积分析
借助插件 webpack-bundle-analyzer 我们可以直观的看到打包结果中,文件的体积大小、各模块依赖关系、文件是否重复等问题,极大的方便我们在进行项目优化的时候,进行问题诊断。
安装
代码语言:txt复制$ npm i -D webpack-bundle-analyze
配置插件
代码语言:txt复制// 引入插件
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 服务,这个时候,我们可以加上两个配置
代码语言:txt复制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 插件,所以我们不需重复安装,直接引用就可以了
代码语言:txt复制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
安装插件
代码语言:txt复制$ npm i -D purgecss-webpack-plugin
添加配置
代码语言:txt复制// ...
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 模块化方案中,导入导出行为是高度动态,难以预测的,例如:
代码语言:txt复制if(process.env.NODE_ENV === 'development'){
require('./bar');
exports.foo = 'foo';
}
而 ESM 方案则从规范层面规避这一行为,它要求所有的导入导出语句只能出现在模块顶层,且导入导出的模块名必须为字符串常量,这意味着下述代码在 ESM 方案下是非法的:
代码语言:txt复制if(process.env.NODE_ENV === 'development'){
import bar from 'bar';
export const foo = 'foo';
所以,ESM 下模块之间的依赖关系是高度确定的,鉴于此,webpack可以在运行过程中静态分析模块之间的导入导出,确定 ESM 模块中哪些导出值未曾其它模块使用,并将其删除,以此实现打包产物的优化。
配置
生产环境(production)会默认开启tree-shaking,如果想在测试环境开启tree-shaking的话,需要设置
代码语言:txt复制 optimization: {
usedExports: true,
}
除此之外还需要在package.json中设置 sideEffects
代码语言:txt复制{
"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 就会被当作无用代码处理掉。
例如这样的代码,在打包后,打开页面,你就会发现样式并没有应用上,
代码语言:txt复制reset.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
background-color: #eaeaea;
}
代码语言:txt复制// main.js
import "./styles/reset.css"
为了解决这个问题,可以在 loader 的规则配置中,添加 sideEffects: true ,告诉 Webpack 这些文件不要 Tree Shaking。
代码语言:javascript复制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中的数组
代码语言:txt复制"sideEffects": ["./src/**/*.less"],
scope hosting
Scope Hoisting开启之前,webpack打包的代码像这样
代码语言:javascript复制/******/ /* 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这种代码
代码语言:txt复制minimizer: [
new TerserWebpackPlugin({
terserOptions: {
compress: {
drop_console: true, //传true就是干掉所有的console.*这些函数的调用.
drop_debugger: true, //干掉那些debugger;
// pure_funcs: ['console.log'], // 如果你要干掉特定的函数比如console.info ,又想删掉后保留其参数中的副作用,那用pure_funcs来处理 } }
},
},
}),
]
项目链接
https://github.com/AdolescentJou/webpack-base-demo
最后
感谢你能看到这里,本文总结了减少webpack打包体积的几种方法,希望对你有所帮助,之后会陆续更新其他webpack相关的文章,如果能留下你的一个赞,笔者将感激不尽。
参考链接
https://juejin.cn/post/7004297344300777502
https://juejin.cn/post/7023242274876162084#heading-48
https://webpack.docschina.org/configuration/optimization/#optimizationminimize