browser VS module VS main
前端開發中使用到 npm 包那可算是家常便飯,而使用到 npm 包總免不了接觸到 package.json 包配置文件。那么這里就有一個問題,當我們在不同環境下 import 一個 npm 包時,到底加載的是 npm 包的哪個文件?
老司機們很快地給出答案:main 字段中指定的文件。
然而我們清楚 npm 包其實又分為:
- 只允許在客戶端使用的,
- 只允許造服務端使用的,
- 瀏覽器/服務端都可以使用。
如果我們需要開發一個 npm 包同時兼容支持 web端 和 server 端,需要在不同環境下加載npm包不同的入口文件,顯然一個 main 字段已經不能夠滿足我們的需求,這就衍生出來了 module 與 browser 字段。
本文就來說下 這幾個字段的使用場景,以及同時存在這幾個字段時,他們之間的優先級。
文件優先級
在說 package.json 之前,先說下文件優先級
由於我們使用的模塊規范有 ESM 和 commonJS 兩種,為了能在 node 環境下原生執行 ESM 規范的腳本文件,.mjs 文件就應運而生。
當存在 index.mjs 和 index.js 這種同名不同后綴的文件時,import './index' 或者 require('./index') 是會優先加載 index.mjs 文件的。
也就是說,優先級 mjs > js
browser,module 和 main 字段
字段定義
- main : 定義了 npm 包的入口文件,browser 環境和 node 環境均可使用
- module : 定義 npm 包的 ESM 規范的入口文件,browser 環境和 node 環境均可使用
- browser : 定義 npm 包在 browser 環境下的入口文件
使用場景與優先級
首先,我們假定 npm 包 test 有以下目錄結構
----- lib
|-- index.browser.js
|-- index.browser.mjs
|-- index.js
|-- index.mjs
其中 *.js 文件是使用 commonJS 規范的語法(require('xxx')),*.mjs 是用 ESM 規范的語法(import 'xxx')
其 package.json 文件:
"main": "lib/index.js", // main "module": "lib/index.mjs", // module // browser 可定義成和 main/module 字段一一對應的映射對象,也可以直接定義為字符串 "browser": { "./lib/index.js": "./lib/index.browser.js", // browser+cjs "./lib/index.mjs": "./lib/index.browser.mjs" // browser+mjs }, // "browser": "./lib/index.browser.js" // browser
根據上述配置,那么其實我們的 package.json 指定的入口可以有
- main
- module
- browser
- browser+cjs
- browser+mjs
這 5 種情況。下面說下具體使用場景。
webpack + web + ESM
這是我們最常見的使用場景,通過 webpack 打包構建我們的 web 應用,模塊語法使用 ESM
當我們加載
import test from 'test'
實際上的加載優先級是 browser = browser+mjs > module > browser+cjs > main
也就是說 webpack 會根據這個順序去尋找字段指定的文件,直到找到為止。
然而實際上的情況可能比這個更加復雜,具體可以參考流程圖
webpack + web + commonJS
const test = require('test')
事實上,構建 web 應用時,使用 ESM 或者 commonJS 模塊規范對於加載優先級並沒有任何影響
優先級依然是 browser = browser+mjs > module > browser+cjs > main
webpack + node + ESM/commonJS
我們清楚,使用 webpack 構建項目的時候,有一個 target 選項,默認為 web,即進行 web 應用構建。
當我們需要進行一些 同構項目,或者其他 node 項目的構建的時候,我們需要將 webpack.config.js 的 target 選項設置為 node 進行構建。
import test from 'test' // 或者 const test = require('test')
優先級是: module > main
node + commonJS
通過 node test.js 直接執行腳本
const test = require('test')
只有 main 字段有效。
node + ESM
通過 --experimental-modules 可以讓 node 執行 ESM 規范的腳本(必須是 mjs 文件后綴)
`node --experimental-modules test.mjs
import test from 'test'
只有 main 字段有效。
總結
- 如果 npm 包導出的是 ESM 規范的包,使用 module
- 如果 npm 包只在 web 端使用,並且嚴禁在 server 端使用,使用 browser。
- 如果 npm 包只在 server 端使用,使用 main
- 如果 npm 包在 web 端和 server 端都允許使用,使用 browser 和 main
- 其他更加復雜的情況,如npm 包需要提供 commonJS 與 ESM 等多個規范的多個代碼文件,請參考上述使用場景或流程圖