Tree Shaking与package.json 中的 module 字段

本文来和大家聊聊字段的功能以及使用场景。

在谈pkg.module之前,让我们来了解一个和它有着紧密关系的概念 ——TreeShaking。

什么是 Tree Shaking?

让我们通过两个小例子来了解。假设我们有以下两个文件:

app.js文件通过 import 引入了math.js中的add1方法。我们通过 webpack 命令打包:

在生成的app.bundle.js文件中我们可以看到以下内容:

这里我们可以看到虽然我们只用到了math.js文件中的add1方法,但是在最终生成的 bundle 文件中却包含了add1和add2两个方法。这是为什么呢?

这是因为在CommonJS 规范(math.js文件的模块格式即为 CommonJS)中,模块只能通过exports对象向外暴露属性。所有要暴露的方法、变量等都只能作为exports对象的一个属性出现。

由于在 JavaScript 中访问对象的属性是在是太灵活了,例如:

所以打包工具并不知道我们代码中最终会用到模块中的哪些方法。为了安全起见,整个模块的代码都被包含在了最终生成的 bundle 中。

随着 ES6 规范的出现,这个问题得到了解决。ES6 定义了一套基于 import、 export 操作符的模块规范。它与 CommonJS 规范最大的区别在 ES6 中的import 和 export 都是静态的。静态意味着一个模块要暴露或引入的所有方法在编译阶段就全部确定了,之后不能再改变。这样做的好处就是打包工具在打包阶段就可以分析出代码中用到了某个模块中的哪几个方法。其它没有用到的方法就可以从最终的 bundle 文件中剔除掉。这样既可以减少 bundle 文件的大小,又可以提高脚本的执行速度。这个机制就叫做 Tree Shaking。是不是很形象。

让我们把math.js改写成 ES6 的模块格式来看一下实际效果:

再次使用 webpack 打包。查看生成的 bundle 文件:

我们可以注意到原来直接定义在exports对象上的两个方法现在都成了两个函数声明,并且只有add1方法被添加到了模块向外暴露的对象上。同时 webpack 还在注释中告诉我们add2方法没有被其它模块用到。配合uglifyjs-webpack-plugin,就可以很轻松的把它从最终的 bundle 文件中移除。

关于 Tree Shaking 我们已经说得差不多了。你可能会想这和我们今天要聊的pkg.module字段有什么关系呢?

其实只需要进一步思考一个问题。假如我们是一个 npm 包的开发者,我们该如何发布我们的包以便于使用者在使用我们包的时候也可以利用 Tree Shaking 机制呢?

如何让一个npm包支持Tree Shaking?

你可能很容易想到直接把pkg.main指向我们 ES6 格式的源码文件不就可以了吗?但仔细想想这样做会带来两个问题:

通常人们在使用打包工具的 babel 插件编译代码时都会屏蔽掉node_modules目录下的文件。因为按照约定大家发布到 npm 的模块代码都是基于 ES5 规范的,因此配置 babel 插件屏蔽node_modules目录可以极大的提高编译速度。但用户如果使用了我们发布的基于 ES6 规范的包就必须配置复杂的屏蔽规则以便把我们的包加入编译的白名单。

如果用户是在 NodeJS 环境使用我们的包,那么极有可能连打包这一步骤都没有。如果用户的 NodeJS 环境又恰巧不支持 ES6 模块规范,那么就会导致代码报错。

基于以上两点我们可以确定pkg.main字段指向的应该是编译后生成的 ES5 版本的代码。既然利用现有字段这条路走不通,那很自然的就会想到引入一个新字段来解决问题。这就是本文要说的pkg.module字段。

综合前文讨论的结果,pkg.module字段要指向的应该是一个基于 ES6 模块规范使用ES5语法书写的模块。

听起来是不是比较拗口?基于 ES6 模块规范是为了用户在使用我们的包时可以享受 Tree Shaking 带来的好处;使用 ES5 语法书写是为了用户在配置 babel 插件时可以放心的屏蔽node_modules目录。

我们的package.json文件中看起来会是这个样子:

相当于在一个包内同时发布了两种模块规范的版本。

当打包工具遇到我们的模块时:

如果它已经支持pkg.module字段则会优先使用 ES6 模块规范的版本,这样可以启用 Tree Shaking 机制。

如果它还不识别pkg.module字段则会使用我们已经编译成 CommonJS 规范的版本,也不会阻碍打包流程。

是不是很完美?

写在最后

要构建一个满足pkg.module字段要求的包其实很简单。如果你是使用Rollup打包代码, 那么只需要把 output 的格式设置为es就可以啦。

目前pkg.module还只是一个提案,并不是 package.json 文件标准格式的一部分。但它极有可能会成为标准的一部分,因为它目前已经是事实上的标准了(由 Rollup 提出,Webpack已支持)。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180302G0Y1D300?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券