ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
前面已经介绍了一些优化资源加载的方法,这一小节是这个主题的最后一部分,内容更为深入,主要介绍如何把我们的 JS 代码文件变得更小。 ## 按需加载模块 前面讲述了如何把大的代码文件进行拆分,抽离出多个页面共享的部分,但是当你的 Web 应用是单个页面,并且极其复杂的时候,你会发现有一些代码并不是每一个用户都需要用到的。你可能希望将这一部分代码抽离出去,仅当用户真正需要用到时才加载,这个时候就需要用到 webpack 提供的一个优化功能 —— 按需加载代码模块。 在 webpack 的构建环境中,要按需加载代码模块很简单,遵循 ES 标准的动态加载语法 [dynamic-import](https://github.com/tc39/proposal-dynamic-import) 来编写代码即可,webpack 会自动处理使用该语法编写的模块: ``` // import 作为一个方法使用,传入模块名即可,返回一个 promise 来获取模块暴露的对象 // 注释 webpackChunkName: "lodash" 可以用于指定 chunk 的名称,在输出文件时有用 import(/* webpackChunkName: "lodash" */ 'lodash').then((_) => { console.log(_.lash([1, 2, 3])) // 打印 3 }) ``` 注意一下,如果你使用了 [Babel](http://babeljs.io/) 的话,还需要 [Syntax Dynamic Import](https://babeljs.io/docs/plugins/syntax-dynamic-import/) 这个 Babel 插件来处理 `import()` 这种语法。 由于动态加载代码模块的语法依赖于 promise,对于低版本的浏览器,需要添加 promise 的 [polyfill](https://github.com/stefanpenner/es6-promise) 后才能使用。 如上的代码,webpack 构建时会自动把 lodash 模块分离出来,并且在代码内部实现动态加载 lodash 的功能。动态加载代码时依赖于网络,其模块内容会异步返回,所以 `import` 方法是返回一个 promise 来获取动态加载的模块内容。 `import` 后面的注释 `webpackChunkName: "lodash"` 用于告知 webpack 所要动态加载模块的名称。我们在 webpack 配置中添加一个 `output.chunkFilename` 的配置: ``` output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[hash:8].js', chunkFilename: '[name].[hash:8].js' // 指定分离出来的代码文件的名称 }, ``` 这样就可以把分离出来的文件名称用 lodash 标识了,如下图: ![](https://img.kancloud.cn/d4/b9/d4b9e8537c9ef30057e00fbbfc368b5d_1147x214.jpg) 如果没有添加注释 `webpackChunkName: "lodash"` 以及 `output.chunkFilename` 配置,那么分离出来的文件名称会以简单数字的方式标识,不便于识别。 ## Tree shaking Tree shaking 这个术语起源于 ES2015 模块打包工具 [rollup](https://github.com/rollup/rollup),依赖于 ES2015 模块系统中的[静态结构特性](http://exploringjs.com/es6/ch_modules.html#static-module-structure),可以移除 JavaScript 上下文中的未引用代码,删掉用不着的代码,能够有效减少 JS 代码文件的大小。拿官方文档的例子来说明一下。 ``` // src/math.js export function square(x) { return x * x; } export function cube(x) { return x * x * x; } // src/index.js import { cube } from './math.js' // 在这里只是引用了 cube 这个方法 console.log(cube(3)) ``` 如果整个项目代码只是上述两个文件,那么很明显,`square` 这个方法是未被引用的代码,是可以删掉的。在 webpack 中,只有启动了 JS 代码压缩功能(即使用 uglify)时,会做 Tree shaking 的优化。webpack 4.x 需要指定 mode 为 production,而 webpack 3.x 的话需要配置 UglifyJsPlugin。启动了之后,构建出来的结果就会移除 `square` 的那一部分代码了。 如果你在项目中使用了 [Babel](http://babeljs.io/) 的话,要把 Babel 解析模块语法的功能关掉,在 `.babelrc` 配置中增加 `"modules": false` 这个配置: ``` { "presets": [["env", { "modules": false }]] } ``` 这样可以把 `import/export` 的这一部分模块语法交由 webpack 处理,否则没法使用 Tree shaking 的优化。 有的时候你启用了 Tree shaking 功能,但是发现好像并没有什么用,例如这样一个例子: ``` // src/component.js export class Person { constructor ({ name }) { this.name = name } getName () { return this.name } } export class Apple { constructor ({ model }) { this.model = model } getModel () { return this.model } } // src/index.js import { Apple } from './components' const appleModel = new Apple({ model: 'X' }).getModel() console.log(appleModel) ``` 打包压缩后还是可以发现,`Person` 这一块看起来没用到的代码出现在文件中。关于这个问题,详细讲解的话篇幅太长了,建议自行阅读这一篇文章:[你的Tree-Shaking并没什么卵用](https://zhuanlan.zhihu.com/p/32831172)。 这篇文章最近没有更新,但是 uglify 的相关 issue [Class declaration in IIFE considered as side effect](https://github.com/mishoo/UglifyJS2/issues/1261) 是有进展的,现在如果你在 Babel 配置中增加 `"loose": true` 配置的话,`Person` 这一块代码就可以在构建时移除掉了。 ## sideEffects 这是 webpack 4.x 才具备的特性,暂时官方还没有比较全面的介绍文档,笔者从 webpack 的 examples 里找到一个东西:[side-effects/README.md](https://github.com/webpack/webpack/blob/master/examples/side-effects/README.md)。 我们拿 [lodash](https://github.com/lodash/lodash) 举个例子。有些同学可能对 [lodash](https://github.com/lodash/lodash) 已经蛮熟悉了,它是一个工具库,提供了大量的对字符串、数组、对象等常见数据类型的处理函数,但是有的时候我们只是使用了其中的几个函数,全部函数的实现都打包到我们的应用代码中,其实很浪费。 webpack 的 sideEffects 可以帮助解决这个问题。现在 lodash 的 [ES 版本](https://www.npmjs.com/package/lodash-es) 的 `package.json` 文件中已经有 `sideEffects: false` 这个声明了,当某个模块的 `package.json` 文件中有了这个声明之后,webpack 会认为这个模块没有任何副作用,只是单纯用来对外暴露模块使用,那么在打包的时候就会做一些额外的处理。 例如你这么使用 `lodash`: ``` import { forEach, includes } from 'lodash-es' forEach([1, 2], (item) => { console.log(item) }) console.log(includes([1, 2, 3], 1)) ``` 由于 lodash-es 这个模块的 `package.json` 文件有 `sideEffects: false` 的声明,所以 webpack 会将上述的代码转换为以下的代码去处理: ``` import { default as forEach } from 'lodash-es/forEach' import { default as includes } from 'lodash-es/includes' // ... 其他代码 ``` 最终 webpack 不会把 lodash-es 所有的代码内容打包进来,只是打包了你用到的那两个方法,这便是 sideEffects 的作用。 ## 小结 本小节主要是介绍如何使用 webpack 来进一步控制 JS 文件的大小: * 如何在 webpack 中实现按需加载模块 * 如何利用 webpack 的 Tree shaking 特性 * 如何利用 webpack 的 sideEffects 特性 前端资源加载优化的道路还很远,我们前面介绍的这些内容都是 webpack 可以提供给我们的关于这个方面的一些功能,而如何利用好这些功能取决于我们开发者。我们在日常的开发工作中可以多多思考,将更多的前端资源优化加载的思路和 webpack 整合在一起,应用到实践中去。 ## 例子 本小节提及的一些简单的 Demo 可以在 [webpack-examples](https://github.com/teabyii/webpack-examples) 找到。