资讯专栏INFORMATION COLUMN

package.json 中的 Module 字段是干嘛的

gnehc / 3215人阅读

摘要:为何有查阅了的文档,并没有找到字段的定义,直到才知道它是中最早就提出的概念。况且目前大部分仍是采用,所以便使用了另一个字段。所以目前主流的打包工具都是支持的,鉴于其优点,字段很有可能加入的规范之中。

引入

最近团队的一个同学在搞 npm library 源码的调试插件,因为内部的一个组件库含有大量的逻辑,在某个项目中不经意就出现一个磨人的 bug,但是组件库发布都是打包编译后的代码,而且没有 publish src 代码,不方便调试,每次还要 down 一下包的源码,再改下 webpack 的配置(比如 rule 中 exclude 去掉组件库, 改下 resolve ,在 dll 中去掉组件库)。被他们耳语目染了好几天,我就想,记得 npm 包是可以直接引源码的,大概改下 webpack 配置就可以了。然后便找到了 package.json 中 module 字段,并查漏 js 中 tree shaking 的知识,所以我并没有去研究怎么搞那样的一个插件?,而是由 package 中的 module 字段延伸出的一些知识。

为何有 module

查阅了 package.json 的文档,并没有找到 module 字段的定义,直到 google 才知道它是 rollup 中最早就提出的概念 --- pkg.module。大概就是最早的 npm 包都是基于 CommonJS 规范的,package.json 形如:

"name": "package1",
"version": "1.0.0",
"main": "lib/index.js"

require("package1") 的时候,就会根据 main 字段去查找入口文件。
而 es2015 后,js 拥有了 ES Module,相较于之前的模块化方案更爽滑,更优雅,并且 ES 模块也是官方标准(JS 规范),而 CommonJS 模块是一种特殊的传统格式,在 ES 模块被提出之前做为暂时的解决方案。所以 rollup 去利用 ES Module 构建,就可以利用 ES Module 的很多特性,从而提高打包的性能,其中提升一个便是 tree shaking,这个我们后面去介绍。在这个构建思想的基础上,开发、产出的 npm 包同样使用 es6 的 module,即可同样受益于 tree shaking 等特性。

而 CommonJS 规范的包都是以 main 字段表示入口文件了,如果使用 ES Module 的也用 main 字段,就会对使用者造成困扰,假如他的项目不支持打包构建,比如大多数 node 项目(尽管 node9+ 支持 ES Module)。这就是库开发者的模块系统跟项目构建的模块系统的冲突,更像是一种规范上的问题。况且目前大部分仍是采用 CommonJS,所以 rollup 便使用了另一个字段:module
像这样:

"name": "package1",
"version": "1.0.0",
"main": "lib/index.js",
"module": "es/index.js"

webpack 从版本 2 开始也可以识别 pkg.module 字段。打包工具遇到 package1 的时候,如果存在 module 字段,会优先使用,如果没找到对应的文件,则会使用 main 字段,并按照 CommonJS 规范打包。所以目前主流的打包工具(webpack, rollup)都是支持 pkg.module 的,鉴于其优点,module 字段很有可能加入 package.json 的规范之中。另外,越来越多的 npm 包已经同时支持两种模块,使用者可以根据情况自行选择,并且实现也比较简单,只是模块导出的方式。

注意:虽然打包工具支持了 ES Module,但是并不意味着其他的 es6 代码可以正常使用,因为使用者可能并不会对你的 npm 包做编译处理,比如 webpack rules 中 exclude: /node_modules/,所以如果不是事先约定好后编译或者没有兼容性的需求,你仍需要用 babel 处理,从而产出兼容性更好的 npm 包。还好 rollup 在这方面做的不错,对于 library 开发者更友好一些。

同时支持的效果类似这样:

lib Tree-shaking

tree-shaking 是近两年才在 JS 中出现的,之前没有的,而模块化的概念是一直都有方案的,只不过直到 ES Module 才有统一的标准趋势。
前面提到 rollup 采用 ES Module,带来的一个优点便是 tree shaking,那什么是 tree-shaking 呢。

有一个图片很形象的解释了它的功能。

tree-shaking 的功能就是把我们 JS 中无用的代码,给去掉,如果把打包工具通过入口文件,产生的依赖树比作 tree,tree-shaking 就是把依赖树中用不到的代码 shaking 掉。

我们通过代码了解下,webpack3.x 下打包验证 tree-shaking。

// 入口文件 index.js
import { func1 } from "./export1";

func1();
// export1 文件
export function func1() {
  console.log("func1");
}

export function func2() {
  console.log("func2");
}

func2 方法虽然导出了,但是在 index.js 中是没有用到的,func2 就是无用代码,最终打包生成的 build 是应该去掉的。

使用最简单的 webpack 配置,不使用 babel,产出 build.js,export1 是这样的:

/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = func1;
/* unused harmony export func2 */
function func1() {
  console.log("func1");
}

function func2() {
  console.log("func2");
}

/***/ })

我们发现有两行注释,/* harmony export (immutable) 表明代码是有用的,unused harmony export func2表明 func2 是无用代码,说明 webpack 已经识别。不过 webpack 仅仅是做了“标记”,去掉这些代码的能力,是通过插件实现的,常用的便是 unglify。在 plugins 用启用 UglifyJsPlugin 后,查看下 build。

// webpack.config.js
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
module.exports = {
  ...
 
  plugins: [
    new UglifyJsPlugin(),
  ]
}

上图即编译后 export1 模块的截图,可以看到 func2 已经被去掉了。不过在我开启 babel-loader 以后,babel 配置就是一个简单的 "presets: ["env"]",却发现 func2 又回来了,如下:

这是为什么呢。因为 tree-shaking 是依赖 ES Module 的静态加载,而 babel-presets-env 中是包含 ES2015 modules to CommonJS transform 的 plugin,也就是转化成 CommonJS,所以无法识别哪些代码是未引用的,也就是无法 tree-shaking,所以 babel transform 的时候应该保留 ES Module。

通过 presets 的 option 选择,设置 modules 为 false 即可。

另外,tree-shaking 并不是完美无缺的,有些情况还无法识别。比如你导入了一个模块,但是这个变量代码中未使用,是不会去掉的,细节可以看这篇文章

为什么是 ES Module

ES Module 之前的 JS 实现模块化,都是基于第三方依赖管理实现的,比如 requirejs,seajs,这都是在代码执行的时候,才知道依赖了哪些模块,常用的 node 中的 commonjs,也是如此

(function (exports, require, module, __filename, __dirname) {
  // YOUR CODE INJECTED HERE!
});

所以,当 ES Module 在代码不执行的时候,就可以知道模块的依赖关系,就不难理解是为什么了。

思考

我的本意是,可否利用 module 字段的特性,让我的 npm 包支持引入源码,从而可以实现源码调试、并且后编译的效果,不过从目前的规范看来,内部还是可以试一下的,开源的包最好不要这样做,除非你有自己的一套规范以及后编译生态。虽然没有达到目的,不过也后知后觉的了解到 module 的用意,以及 rollup 在开发包时候的妙用,以及 tree-shaking 并不是自己了解的那么美好。

相关推荐

你的Tree-Shaking并没什么卵用

【译】如何在 Webpack 2 中使用 tree-shaking

手把手带你走进下一代的ES6模块打包工具—Rollup

原文:https://github.com/configu/bl...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107674.html

相关文章

  • package.json文件各字段的说明

    摘要:字段由脚本命令组成的字典,这些命令运行在包的各个生命周期中。在打包过程中,如果遇到字段会优先使用字段表示的路径下的文件,如果不存在,则用字段表示的作为入口,并按照的规范打包。其中还分析了文件中字段和字段的不同以及和两个字段的区别。 所有用npm下载的包或者要上传至npm的模块都会有一个package.json文件,这个文件总是存在于模块(或者包)的根目录下,这个文件到底是干嘛的,现在就...

    yzd 评论0 收藏0
  • 灵活使用vue单文件组件之--最少配置打包.vue组件

    摘要:但是,面对辣么多的配置文件,还是从开始自己来吧,毕竟我只想打包一个组件。这里想一下我们的需求,我们想要打包一个组件,使用,根据上面的说明,不难想到还应该需要一个可以用来识别并转换文件,一句话,就是把按下面格式的编写的组件转换为模块。 对于vue单文件组件的使用,我们知道使用vue-cli可以快速生成项目结构,进行.vue单文件组件的编写,使用 npm run build命令会从main...

    forrest23 评论0 收藏0
  • 灵活使用vue单文件组件之--最少配置打包.vue组件

    摘要:但是,面对辣么多的配置文件,还是从开始自己来吧,毕竟我只想打包一个组件。这里想一下我们的需求,我们想要打包一个组件,使用,根据上面的说明,不难想到还应该需要一个可以用来识别并转换文件,一句话,就是把按下面格式的编写的组件转换为模块。 对于vue单文件组件的使用,我们知道使用vue-cli可以快速生成项目结构,进行.vue单文件组件的编写,使用 npm run build命令会从main...

    izhuhaodev 评论0 收藏0
  • 灵活使用vue单文件组件之--最少配置打包.vue组件

    摘要:但是,面对辣么多的配置文件,还是从开始自己来吧,毕竟我只想打包一个组件。这里想一下我们的需求,我们想要打包一个组件,使用,根据上面的说明,不难想到还应该需要一个可以用来识别并转换文件,一句话,就是把按下面格式的编写的组件转换为模块。 对于vue单文件组件的使用,我们知道使用vue-cli可以快速生成项目结构,进行.vue单文件组件的编写,使用 npm run build命令会从main...

    Charles 评论0 收藏0
  • 虚拟主机开通是什么意思-虚拟主机管理系统,是干嘛的

    摘要:同时虚拟主机管理系统还包含了财务管理功能功能。随着互联网的发展和技术的不断更新,虚拟主机管理系统的开发也越来越完善,功能也越来越强大,尤其值得一说的是虚拟主机管理系统更加智能化。点开网页出现虚拟主机已开通是什么意思?虚拟主机是把一台服务器分成很多虚拟的服务器,每一个虚拟主机都具有独立的域名和完整的Internet服务器(支持WWW、FTP、E-mail等)功能。缺点只有一个id,一旦有别的网...

    yanwei 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<