Skip to main content

Webpack 打包优化(CRA 项目)

· 6 min read
ulli

前端打包体积分析

在前端开发中,打包体积的大小一直是需要重点关注的问题。在 webpack 中,我们可以使用 webpack-bundle-analyzer进行打包体积分析。它可以根据 webpack 编译器生成的 Stats数据进行分析,在编译器的 done hook钩子函数中进行处理,见源码。默认情况下,webpack-bundle-analyzer 可以启动一个服务,提供一个可视化图表,展示每个 chunk 下各个 module 的占用体积,如下图所示:

从图表中我们可以找到在打包中占用体积最大的模块,并进行优化。

在查看页面中,有三个体积选项:

  1. stat: 每个模块的原始体积
  2. parsed: 每个模块经 webpack 打包处理之后的体积,比如 terser 等做了压缩,便会体现在上边
  3. 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 jscommon 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。以下代码会报错 Promise.any

好消息是,core-js 已集成到了 babel/swc 之中,你可以使用 @babel/preset-env 或者 @babel/polyfill 进行配置,详见文档 core-js通过配置,babel 编译代码后将会自动包含所需的 polyfill,如下所示。 Promise.any 点击查看以下代码在线演示

browserslist

Browserslist 是一个用于查询浏览器列表的工具,用户可以使用特定的语句来查询所需的浏览器版本,例如“last 2 Chrome versions”。

作为前端开发中垫片(polyfill)的必不可少组成部分,babel 和 postcss 都使用了 Browserslist 作为其背后的引擎。

垫片的作用在于为不存在某个新特性的浏览器提供兼容性支持,而不会影响到支持该特性的现代浏览器。因此,垫片的体积大小直接影响打包后的文件体积大小。以下为垫片与打包体积关系的几点共识:

  1. 垫片是必不可少的,因为低浏览器版本的持续存在。
  2. 垫片越少,则打包体积越小。
  3. 浏览器版本越新,则垫片越少,打包体积也会越小。因此,在前端工程化实践中,确定所需的浏览器版本号可以确认其垫片体积。

比如一个项目只需要支持最新的两个谷歌浏览器版本,那么可以使用如下查询语句: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 版本号的浏览器
Loading Comments...