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

引入

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

为什么有 module

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

"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 等特性。git

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

"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 包已经同时支持两种模块,使用者能够根据状况自行选择,而且实现也比较简单,只是模块导出的方式。github

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

同时支持的效果相似这样:npm

<img width="693" alt="lib" src="https://user-images.githubuse...;>json

<img width="663" alt="es" src="https://user-images.githubuse...;>segmentfault

package.json 只须要对应相应的文件就能够了。babel

"name": "drag-list",
"version": "1.0.0",
"main": "lib/drag-list/index.js",
"module": "es/drag-list/index.js"

Tree-shaking

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

有一个图片很形象的解释了它的功能。
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(),
  ]
}

ugly-export1

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

babel-export1

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

modules-false

经过 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...

相关文章
相关标签/搜索