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

相關文章
相關標籤/搜索