前端打包体积分析
在前端开发中,打包体积的大小一直是需要重点关注的问题。在 webpack 中,我们可以使用 webpack-bundle-analyzer进行打包体积分析。它可以根据 webpack 编译器生成的 Stats数据进行分析,在编译器的 done hook钩子函数中进行处理,见源码。默认情况下,webpack-bundle-analyzer 可以启动一个服务,提供一个可视化图表,展示每个 chunk 下各个 module 的占用体积,如下图所示:
从图表中我们可以找到在打包中占用体积最大的模块,并进行优化。
在查看页面中,有三个体积选项:
- stat: 每个模块的原始体积
- parsed: 每个模块经 webpack 打包处理之后的体积,比如 terser 等做了压缩,便会体现在上边
- gzip: 经 gzip 压缩后的体积
ANALYZE 环境变量
在实际项目中,我们往往通过环境变量 ANALYZE 配置该插件。在打包时,通过指定环境变量即可进行打包体积分析。
以下是相关代码示例:
const webpack = require("webpack");
const BundleAnalyzerPlugin =
require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
// 1. BundleAnalyzerPlugin 是如何工作的?
// 2. Stat、Parsed、Gziped 分别是何意义?
// 3. terserplugin 对此结果有影响吗?
function f1() {
return webpack({
entry: "./index.js",
mode: "none",
plugins: [process.env.ANALYZE && new BundleAnalyzerPlugin()],
});
}
f1().run((err, stat) => {});
在打包时,通过制定环境变量即可分析打包体积
ANALYZE=true npm run build
Tree Shaking
Tree Shaking 指基于 ES Module 进行静态分析,通过 AST 将用不到的函数进行移除,从而减小打包体积。
尽量使用es module
代替 common js
,common js
不支持tree shaking
polyfill
当浏览器不支持某一最新 API 时,可以通过 Polyfill 来实现兼容。core-js 是目前最出名的 Polyfill 库,它包含了所有 ES6+ 的 Polyfill,并集成在 Babel 等编译工具之中。因为 Polyfill 的存在,打包后体积会增加。支持的浏览器版本越高,所需的 Polyfill 也就越少,因此体积也会越小。
以下是 Array.from(ES6) 的 Polyfill 代码示例:
// Production steps of ECMA-262, Edition 6, 22.1.2.1
if (!Array.from) {
Array.from = () => { // 省略若干代码 }
}
而 core-js
的伟大之处是它包含了所有 ES6+
的 polyfill,并集成在 babel
等编译工具之中 在实际开发中,如果需要使用某个新的 API,在使用 Babel 进行编译时,需要配置相应的 Polyfill。例如,如果需要在代码中使用 Promise.any,那么可以在 webpack 配置文件中设置 @babel/preset-env 或者 @babel/polyfill 进行配置。通过配置,Babel 编译后将会自动包含所需的 Polyfill,如下图所示:
为了使代码能够在大部分浏览器里能够实现,你将会使用 babel
或者 swc
将代码编译为 ES5。
但是此时你会发现问题,如果不做任何配置,babel
/swc
只能处理操作符,而无法处理新的 API。以下代码会报错
好消息是,core-js
已集成到了 babel
/swc
之中,你可以使用 @babel/preset-env
或者 @babel/polyfill
进行配置,详见文档 core-js 。通过配置,babel
编译代码后将会自动包含所需的 polyfill,如下所示。 点击查看以下代码在线演示
browserslist
Browserslist 是一个用于查询浏览器列表的工具,用户可以使用特定的语句来查询所需的浏览器版本,例如“last 2 Chrome versions”。
作为前端开发中垫片(polyfill)的必不可少组成部分,babel 和 postcss 都使用了 Browserslist 作为其背后的引擎。
垫片的作用在于为不存在某个新特性的浏览器提供兼容性支持,而不会影响到支持该特性的现代浏览器。因此,垫片的体积大小直接影响打包后的文件体积大小。以下为垫片与打包体积关系的几点共识:
- 垫片是必不可少的,因为低浏览器版本的持续存在。
- 垫片越少,则打包体积越小。
- 浏览器版本越新,则垫片越少,打包体积也会越小。因此,在前端工程化实践中,确定所需的浏览器版本号可以确认其垫片体积。
比如一个项目只需要支持最新的两个谷歌浏览器版本,那么可以使用如下查询语句:last 2 Chrome versions。随着时间推移,该查询语句将会返回更新的浏览器,因此其垫片体积也会减小。
例如,一年前使用该查询语句可能仍需要为 Promise.any 添加垫片,而现在已经不再需要了。
babel
,在@babel/preset-env
中使用core-js
作为垫片postcss
使用autoprefixer
作为垫片
那在前端工程化实践中,当我们确认了浏览器版本号,那么它的垫片体积就会确认。
假设项目只需要支持最新的两个谷歌浏览器。那么关于 browserslist
的查询,可以写作 last 2 Chrome versions
。
原理
Browserslist 的实现原理是基于正则表达式解析查询语句,并根据浏览器版本数据库 caniuse-lite 进行数据查询,返回符合查询条件的浏览器版本列表。
caniuse-lite 数据库是由 Browserslist 团队进行维护的,它是基于 caniuse 数据库进行数据整合的。
需要注意的是,Browserslist 并不维护数据库,因此需要经常更新 caniuse-lite 数据库以保证数据最新。使用以下命令可以手动更新 caniuse-lite 数据库:
npx browserslist@latest --update-db
该命令将会对 caniuse-lite 进行升级,可体现在 lock 文件中。
"caniuse-lite": {
- "version": "1.0.30001265",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz",
- "integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==",
+ "version": "1.0.30001332",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001332.tgz",
+ "integrity": "sha512-10T30NYOEQtN6C11YGg411yebhvpnC6Z102+B95eAsN0oB6KUs01ivE8u+G6FMIRtIrVlYXhL+LUwQ3/hXwDWw==",
"dev": true
},
常用的查询语法
根据用户份额:
> 5%
: 在全球用户份额大于 5% 的浏览器> 5% in CN
: 在中国用户份额大于 5% 的浏览器
根据最新浏览器版本:
last 2 versions
: 所有浏览器的最新两个版本last 2 Chrome versions
: Chrome 浏览器的最新两个版本
不再维护的浏览器:
dead
: 官方不在维护已过两年,比如 IE10
浏览器版本号:
Chrome > 90
: Chrome 大于 90 版本号的浏览器