经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
JS?加载性能Tree?Shaking优化详解
来源:jb51  时间:2022/11/17 8:48:28  对本文有异议

正文

随着 web 应用复杂性增加,JS 代码文件的大小也在不断的攀升,截住 2021年9月,在 httparchive 上有统计显示——在移动设备上 JS 传输大小大约为 447 KB,桌面端 JS 传输大小大约为 495 KB,注意这仅仅是在网络中传输的 JS 文件大小,JS 的实际大小要比传输大小大很多。

上图是下载和运行JavaScript的过程。

即使 JS 的传输大小被压缩为 300 KB,但仍然有 900 KB 的 JS 代码需要被浏览器解析、编译和执行。图像一旦被下载,浏览器只需要花费相对琐碎的解码时间,与图像不同的是,JS 必须被解析、编译,最终执行,这使得处理 JS 比处理其他类型的资源更耗时。

上图是浏览器解析/编译 170 KB的 JS 的处理成本与同等大小的 JPEG 的解码时间。JS 引擎的性能在不断被改进,改进网站 JS 性能也是开发者要做的事情。Code Splitting 是优化 JS 性能的技术之一,但是它不能减少应用程序的 JS 代码的总大小,在这里我们使用 Tree Shaking 来减小 js 代码的大小。

什么是 Tree Shaking

您可以将应用程序想象成一棵树。您实际使用的源代码和库表示树中绿色的活叶子,死代码表示秋天时树上棕色的枯叶,为了除掉枯叶,你必须摇动树,让它们掉下。Tree Shaking 是指消除死代码,下面通过一个应用程序演示了这个概念。使用 ES6 静态模块语法导入依赖项:

  1. // 在这里导入了所有的数组处理方法
  2. import arrayUtils from "array-utils";

在应用最初的时候,依赖项可能很少,随着功能逐渐增加,依赖项也会增加,更糟糕的是,旧的依赖项不再使用,但可能不会从代码库中删除,最终的结果是,应用程序带有大量未使用的 JS 代码,Tree Shaking 解决了这个问题,它通过分析我们在文件中使用的 ES6 静态模块语句来分析哪些模块被导入了:

  1. // 只导入部分方法
  2. import { unique, implode, explode } from "array-utils";

这个导入示例与前一个示例的区别在于,本示例只导入模块的特定部分,而不是从“array-utils”模块导入所有内容。

寻找 Tree Shaking 的机会

为了便于说明,这里有一个使用 webpack 的单页应用程序示例来演示 Tree Shaking 是如何工作的。界面如下:

这个程序打包之后的代码被分为两文件,如下:

在任何应用程序中,你需要从静态导入语句寻找 Tree Shaking 的机会,在示例程序中(FilterablePedalList.js)你将看到这样一行导入语句:

  1. import * as utils from "../../utils/utils";

在文件中这样的导入语句应该引起你的注意,它的意思是:从 utils 模块导入所有内容。问题是:你真的用到所有的内容了吗?现在我们来检查 FilterablePedalList.js 中究竟使用了 utils 模块中的那些方法,通过检索发现只使用了 utils.simpleSort:

  1. if (this.state.sortBy === "model") {
  2. // Simple sort gets used here...
  3. json = utils.simpleSort(json, "model", this.state.sortOrder);
  4. } else if (this.state.sortBy === "type") {
  5. // ..and here...
  6. json = utils.simpleSort(json, "type", this.state.sortOrder);
  7. } else {
  8. // ..and here.
  9. json = utils.simpleSort(json, "manufacturer", this.state.sortOrder);
  10. }

我们现在开始做 Tree Shaking 优化

防止 Babel 将 ES6 模块转换为 CommonJS 模块

在大型应用中 Babel 是不可或缺的工具,但是它会让 Tree Shaking 变得困难。如果你正在使用 babel-preset-env,它会自动为你将 ES6 模块转换为更广泛兼容的 CommonJS 模块,即:用 require 代替 import。对于 CommonJS 模块而言做 Tree Shaking 优化非常困难,这是因为 CommonJS 模块是动态的,在构建阶段 bundlers 不容易分析出 CommonJS 模块导出了什么和导入了什么。为了避免 babel-preset-env 将 ES6 模块转换成 CommonJS 模块,我们可以这么做:

  1. {
  2. "presets": [
  3. ["env", {
  4. "modules": false
  5. }]
  6. ]
  7. }

在你的 Babel -preset-env 配置中简单指定"modules": false 就可以让 Babel 按照我们想要的方式运行,这允许 webpack 分析你的依赖树并摆脱那些未使用的依赖。

留意 side effects

当函数修改了它作用域之外的内容,我们就认为这个函数有 side effects。side effects 也适用于ES6模块,在你做 Tree Shaking 的时候你需要留意你的模块是否有 side effects(副作用),如果模块接受可预测的输入,并输出同样可预测的输出,而不修改其自身范围之外的任何内容,我们就认为这个模块没有 side effects,对于没有 side effects 的模块你可以放心的做 Tree Shaking。在这里我举两个 side effects 例子:

  1. import './index.module.scss';
  2. import './assets/shoot.svg';

如果在某个模块中出现了上面这样的 import 语句,则认为这个模块有 side effects,这时在做 Tree Shaking 的时候你需要小心一些。默认情况下,webpack 在做 Tree Shaking 的时候,会认为 index.module.scss 与 assets/shoot.svg 没有被用到,所以它们会被移除,如果不想被移除,可以告诉 webpack 它们是 side effects:

  1. {
  2. "name": "webpack-tree-shaking-example",
  3. "version": "1.0.0",
  4. "sideEffects": [
  5. "./index.module.scss",
  6. "./assets/shoot.svg"
  7. ]
  8. }

在项目的 package.json 中配置 sideEffects 字段。sideEffects 字段也能为 false,这表示项目中不存在有 side effects 的模块。

只导入你需要的

现在我们已经告诉 babel 不要将 ES6 模块转成 CommonJS 模块,现在我们需要对导入语法做一点微调,只从 utils 模块中引入我们需要的函数吗,在本指南的例子中,我们只需要 simpleSort:

  1. import { simpleSort } from "../../utils/utils";
  2. if (this.state.sortBy === "model") {
  3. json = simpleSort(json, "model", this.state.sortOrder);
  4. } else if (this.state.sortBy === "type") {
  5. json = simpleSort(json, "type", this.state.sortOrder);
  6. } else {
  7. json = simpleSort(json, "manufacturer", this.state.sortOrder);
  8. }

现在我们已经完成了 Tree Shaking 的工作,下面是 Tree Shaking 之前 webpack 打包生成的 js 包大小:

  1. Asset Size Chunks Chunk Names
  2. js/vendors.16262743.js 37.1 KiB 0 [emitted] vendors
  3. js/main.797ebb8b.js 20.8 KiB 1 [emitted] main

下面是 Tree Shaking 之后 webpack 打包生成的 js 包大小:

  1. Asset Size Chunks Chunk Names
  2. js/vendors.45ce9b64.js 36.9 KiB 0 [emitted] vendors
  3. js/main.559652be.js 8.46 KiB 1 [emitted] main

main 的文件大小下降比较明显,这是因为 webpack 移除了不需要的 utils 方法。

更复杂的情况

在有些情况你按照上面的步骤进行 Tree Shaking,但是 webpack 还是将模块的所有内容都打包到最终的 Chunk 中了,例如:lodash。

  1. // 仍然会导入所有的内容
  2. import { sortBy } from "lodash";
  3. // 这只会导入 sortBy
  4. import sortBy from "lodash/sortBy";

如果你想要使用第一种写法,那么你还需要安装 babel-plugin-lodash。如果你使用了第三方库,你可以看一下这个库的导出是否使用了 ES6 语法,如果它的导出用的是 CommonJS 语法,例如:module.exports,那么 webpack 不能对它进行 Tree Shaking 优化。有些插件,例如:webpack-common-shake,提供了对 CommonJS 模块进行 Tree Shaking 的能力,但是它有一些限制

总结

为了确保构建工具可以成功地优化你的应用程序,应该避免依赖 CommonJS 模块,并在整个应用程序中使用 ES6 模块语法。

以上就是JS 加载性能Tree Shaking优化详解的详细内容,更多关于JS 加载Tree Shaking的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号