按需打包的10个技巧,会的都是高手,全部都会我愿称你为最强

186 阅读6分钟

各位网友大家好,构建性能优化是前端性能优化的常见手段。由于构建可以和业务脱离关系,可以在不改业务代码情况下进行优化,因此是性能优化第二考虑要素。按需打包是构建优化的手段之一,是实打实的减少体积,值得我们研究。今天我就为大家盘点 10 个按需打包的技巧,内容只包含按需打包,其他性能优化手段并不在本文讨论范围内,欢迎大家收藏。

删除未使用的 export 

一个库里面往往有着许多 export,但是未必都被项目所使用,通过删除未使用的导出可以减少包大小。rollup 的 tree-sharking 功能就是一个能实现删除未使用的导出的方案。默认是开启的,不需要配置。

转化对库 import 语句为库的内部引用。

例如 ant-design 组件库,它提供了完整引入的方式,也提供了按需引入的方式。我们可以在业务代码中使用完整引入,打包时转为按需引入。rollup-plugin-import 就是一个能提供转化的工具。以 material-ui 为例,配置如下
通过上面配制,能把这样的代码:

import { Button } from "@material-ui/core";

转化成这样:

import Button from '@material-ui/core/Button';

相比之前说的 tree-sharking 方案,这种方式能够使得有副作用的模块也能够按需构建。要实现这样的转化可以使用 rollup-plugin-import 这个插件转化,参考以下配置。

import importPlugin from 'rollup-plugin-import';

importPlugin({
	libraryName: "@material-ui/core",
	exportName:"default"
})

dead code

dead code 是代码中永远不会执行的片段,例如,

if(false) {
    console.log([].includes(1));
}

上面代码中,if 内部的代码就是 dead code,可以被优化掉。
常用的优化工具有,rollup 的 tree-sharking 功能,代码压缩工具 Terser,babel 插件 babel-plugin-minify-dead-code-elimination

polyfill 按需引入

我发现有些人竟然在入口全局引入 polyfill,这样做打包代码就过大了,而且还是在入口。Polyfill 的本质上就是一类特殊的依赖,也可以根据是否被实际使用来决定是否引入该 Polyfill。但是,根据需要手动引入 polyfill 比较繁琐,而且容易遗漏。我们可以在构建时通过工程化自动引入 Polyfill。常见的方式有 babel 的 useBuiltIns 配置设置为 usage;@rollup/plugin-inject 这个插件可用于无污染引入;rollup-plugin-polyfill-inject 这个插件可以用于有污染引入。

先减后加的构建策略

在构建过程中有对源代码进行许多操作,大体上分为两种,增加内容(如自动引入 polyfill),和减少内容(如移除 console.log)。
构建过程应当先减后加。例如以下代码

if(false) {
    console.log([].includes(1));
}

如果先加后减会变成这样

import "core-js/modules/es.array.includes";

if(false) {
    console.log([].includes(1));
}

再变成这样

import "core-js/modules/es.array.includes";

我们可以看到移除 console 的操作并不能移除其中的 polyfill 引入。

而先减后加会变成这样

而后加入 polyfill 的操作就不会加入这个没用到的 polyfill。这样可以打出更小的包。

插件按需引入

一般许多库为了适应更多特殊需求,会使用一些扩展机制。例如,jQuery.fn、Vue.prototype、Vue.directive 等。
为了省事,很多人都是一言不和就在入口引入全部插件。这样不管有没有使用,都会在一开始加载插件代码。这就导致不能按需引入且首屏文件过大。

typescript 级的 polyfill 引入

在没有 typescript 时,自动引入 polyfill 是这样的过程:解析语法,跟据属性名字引入。这样做达不到按需引入的目确。比如下面代码。
会自动引入 String 的 includes 和 Array 的 includes。

import "core-js/modules/es.array.includes";
import "core-js/modules/es.string.includes";

console.log([].includes(1));

如果有了 typescript 我们就可以推断出前面的类型,从而实现按需引入。

import "core-js/modules/es.array.includes";

console.log([].includes(1));

库插件的引入也可以通过 typescript,达到更精准的按需引入

动态生成 polyfill 间的依赖关系

有时候你会发现,我只是引入一个 polyfill 怎么就自动引入了这么多的 polyfill?这是因为 polyfill 之间也是有依赖关系的。

比如我引入一个 Array.prototype.includes,由于这个 polyfill 内部依赖了 Object.defineProperty,于是就见接引入了 Object.defineProperty 的 polyfill。如果你看过构建后未压缩的代码,可能会看到 corejs 对 IE8 的吐槽注释,你可能会疑惑:我的项目根本不支持 IE8,不应该构建出这样的代码。这就是因为你依赖的 polyfill,最终依赖了 Object.defineProperty。可以通过优化 polyfill,让 polyfill 间的依赖关系也通过前文说的『polyfill 按需引入』来生成。这样就能更精确的按需引入 polyfill。

删除未使用的全局 css

PurgeCSS 是一个高效的 CSS 优化工具,可以帮助开发者减小 CSS 文件的大小,提升页面加载速度。可以通过扫描源代码来获取一个安全列表,进而删除未使用的 CSS。也可以在构建时扫描构建的代码,达到更精确的按需使用。

按需生成字体图标

字体开发者可以配置 rollup-plugin-font 插件来按需打包字体图标。可以通过 import 引用来获取一个安全列表,进而删除未使用的字体图标。也可以通过扫描源代码,达到更方标的配置。参考配置

import font from "rollup-plugin-font";

export default {
	input: './src/index.tsx',
	output: {
		dir:'./dist',
		format: 'iife',
		assetFileNames:"assets/[name].[hash][extname]"
	},
	plugins: [
		font({
			"svg":"./node_modules/font-awesome/fonts/fontawesome-webfont.svg",
			"css":{
				"name":"font-awesome",
				"include":["node_modules/font-awesome/css/font-awesome.css"],
				"prefix":"fa-",
				"common":"fa"
			}
		})
	]
};

通过按自动化测试,收集动态依赖

许多库的使用包含动态的使用,无法在构建时分析出使用了什么,只能在运行时才能确定使用了什么,因而无法按需构建。但是如果我们的自动化测试足够覆盖所有情况,我们就可以在自动化测试时,实时收集依赖,而后传输给构建步骤,从而只保留用到的。

总结

大家已经发现了吧,按需打包可能并不是你项目的性能瓶颈,收益不一定大。但是按需打包是脱离业务的手段,应该作为基建标配使用。项目代码本身问题可以具体问题具体分析。