参考:https://github.com/SunshowerC/blog/issues/8

browser VS module VS main

我们经常见到 package.json 里有这样一段内容:

{
...
  main: dist/index
  module: dist/index.esm.js
  browser: dist/zhihu-hybrid.umd.js
}

当我们在不同环境下 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 种情况。下面说下具体使用场景。

    Web + ESM 模式

    当我们代码中使用

    import test from 'test'

    加载 test 模块时, 实际上的加载优先级是: browser = browser+mjs > module > browser+cjs > main
    也就是说打包工具会根据这个顺序去寻找 test package.json 文件中该字段指定的文件,直到找到为止。
    然而实际上的情况可能比这个更加复杂,具体可以参考流程图:
    image.png

Web + commonJS 模式

当我们代码中使用

const test = require('test')

加载 test 模块时,优先级依然是 browser = browser+mjs > module > browser+cjs > main,打包工具会根据这个顺序去寻找 test package.json 文件中该字段指定的文件,直到找到为止。

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

test.mjs:

import test from 'test'

只有 main 字段有效。

总结

以下是当你创建一个 NPM 包事,package.json 中引用入口的声明总结:

  • 如果导出是的 ESM 规范的包,使用 module 来声明入口
  • 如果 npm 包只在 Web 端使用,并且严禁在 server 端使用,使用 browser 来声明入口
  • 如果 npm 包只在 Server 端使用,使用 main 来声明入口
  • 如果 npm 包在 Web 端和 Server 端都允许使用,使用 browser(可选,用到了宿主环境能力时需要声明) 和 main
  • 如果你的 NPM 包满足上面多种情况,可以把符合的字段都声明出来,如 Server 端 ESM包,声明 module 和 main

specialcoder
2.2k 声望171 粉丝

前端 设计 摄影 文学