blog icon indicating copy to clipboard operation
blog copied to clipboard

package.json 中 你还不清楚的 browser,module,main 字段优先级

Open SunshowerC opened this issue 5 years ago • 20 comments

  • browser VS module VS main
  • 文件优先级
  • browsermodulemain 字段
    • 字段定义
    • 使用场景与优先级
      • webpack + web + ESM
      • webpack + web + commonJS
      • webpack + node + ESM/commonJS
      • node + commonJS
      • node + ESM
  • 总结

browser VS module VS main

前端开发中使用到 npm 包那可算是家常便饭,而使用到 npm 包总免不了接触到 package.json 包配置文件。

那么这里就有一个问题,当我们在不同环境下 import 一个 npm 包时,到底加载的是 npm 包的哪个文件?

老司机们很快地给出答案:main 字段中指定的文件

然而我们清楚 npm 包其实又分为:只允许在客户端使用的,只允许造服务端使用的,浏览器/服务端都可以使用。
如果我们需要开发一个 npm 包同时兼容支持 web端 和 server 端,需要在不同环境下加载npm包不同的入口文件,显然一个 main 字段已经不能够满足我们的需求,这就衍生出来了 modulebrowser 字段。

本文就来说下 这几个字段的使用场景,以及同时存在这几个字段时,他们之间的优先级。

文件优先级

在说 package.json 之前,先说下文件优先级

由于我们使用的模块规范有 ESM 和 commonJS 两种,为了能在 node 环境下原生执行 ESM 规范的脚本文件,.mjs 文件就应运而生。

当存在 index.mjsindex.js 这种同名不同后缀的文件时,import './index' 或者 require('./index') 是会优先加载 index.mjs 文件的。

也就是说,优先级 mjs > js

browsermodulemain 字段

字段定义

  • main : 定义了 npm 包的入口文件,browser 环境和 node 环境均可使用
  • module : 定义 npm 包的 ESM 规范的入口文件,browser 环境和 node 环境均可使用
  • ·browser : 定义 npm 包在 browser 环境下的入口文件

使用场景与优先级

首先,我们假定 npmtest 有以下目录结构

----- 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 会根据这个顺序去寻找字段指定的文件,直到找到为止。

然而实际上的情况可能比这个更加复杂,具体可以参考流程图 image

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.jstarget 选项设置为 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 等多个规范的代码文件,请参考上述使用场景或流程图

SunshowerC avatar Jan 05 '19 15:01 SunshowerC

这个可有什么官方文档出处? 看 npm 的官方文档上只有以下两种。 https://docs.npmjs.com/files/package.json#main https://docs.npmjs.com/files/package.json#browser

terrilltang avatar Aug 10 '19 17:08 terrilltang

@terrilltang 这里面的 module 其实是一个 proposal, 但是由于广泛被使用,所以这个字段已经被官方采纳落实了。可以在 这里 找到对应的官方说明。

至于这几个字段的优先级,我好像没找到详细的官方说明,所以我才自己测试下进行归纳总结。

SunshowerC avatar Aug 11 '19 11:08 SunshowerC

https://github.com/webpack/webpack/issues/5673#issuecomment-329713876

另外这里有webpack的部分代码(搜 browser): https://github.com/webpack/webpack/blob/master/lib/WebpackOptionsDefaulter.js

按我的理解,正常来说main指向cjs,module指向mjs,当一个包兼容浏览器端与node端,但output不同时(比如同构应用),才使用 browser 指向兼容浏览器的版本,这种情况main和module两个都需要兼容版本,因此browser支持对象的格式提供一个映射关系。

tianyn1990 avatar Sep 17 '19 15:09 tianyn1990

测了下webpack的模块选择优先级可以根据resolve.mainFields配置的顺序进行调整,参见https://webpack.docschina.org/configuration/resolve/#resolve-mainfields

Yayure avatar Sep 23 '19 15:09 Yayure

试了browser,貌似只能使用string,不能使用对象

gitHber avatar Apr 29 '20 16:04 gitHber

@SunshowerC 请问博主有考虑webpack resolve.mainFields配置吗。 按照文档,浏览器环境下,解析顺序是 browser > module > main。但是看上面的图,会首先看module 是否有值,如果没有值的话,就算browser 有值,也会用main。这似乎有些出入? 其次,不知道图中的顺序是基于webpack resolve.mainFields什么顺序得出的结论?

colgin avatar Sep 11 '20 07:09 colgin

@SunshowerC 请问博主有考虑webpack resolve.mainFields配置吗。 按照文档,浏览器环境下,解析顺序是 browser > module > main。但是看上面的图,会首先看module 是否有值,如果没有值的话,就算browser 有值,也会用main。这似乎有些出入? 其次,不知道图中的顺序是基于webpack resolve.mainFields什么顺序得出的结论?

@colgin 流程图确实不够全,实际上不存在 module 时会再判断有没有 browser 的,解析顺序是 browser > module > main,所以有 browser 无 module 的场景下是用 browser 的。 如果自己的项目有用到 webpack 的,可以通过自行配置 webpack -> mainFields 改变其加载优先级 。 如果没用 webpack 的,可以参考上述默认的加载优先级。

SunshowerC avatar Sep 13 '20 15:09 SunshowerC

问一个看似不大相关的问题, 假设:test模块打包出的esm规范的文件 test.esm.js 有下面三种引用方式:

  1. 发布到npm,在目标工程中添加依赖,使用import test from 'test'直接引入
  2. 使用import test from './node_modules/test/dist/test.esm.js'引入
  3. 把打包后的文件test.esm.js 拷贝到目标工程直接引用 import test from './test.esm.js' 这三种引入方式 在目标工程打包时会不会有区别,我发现2 3 两种情况有可能会报错

Zippowxk avatar Mar 11 '21 02:03 Zippowxk

@Zippowxk 第三种方式可能会报错,你项目工程里面不一定有你test.esm.js的运行环境。

senfish avatar Apr 28 '21 14:04 senfish

@Zippowxk 第三种方式可能会报错,你项目工程里面不一定有你test.esm.js的运行环境。

有道理 但是第二种方式报错,就是因为打包环境不一致导致的吗?比如eslint 和 babel的preset不一致

Zippowxk avatar Apr 28 '21 14:04 Zippowxk

mark

BoBoooooo avatar Dec 29 '21 02:12 BoBoooooo

讲的很清楚,谢谢

NameWjp avatar Dec 29 '21 06:12 NameWjp

m

arleyGuoLei avatar Apr 07 '22 09:04 arleyGuoLei

已收到,谢谢~~

easy1090 avatar Apr 07 '22 09:04 easy1090

总结的时有错误:

错误:如果 npm 包在 web 端和 server 端都允许使用,使用 browser 和 main 改正:如果 npm 包在 web 端和 server 端都允许使用,使用 module 和 main

woow-wu7 avatar Jul 26 '22 03:07 woow-wu7

Mark

ying-bin avatar Nov 25 '22 06:11 ying-bin

已收到,谢谢~~

easy1090 avatar Nov 25 '22 06:11 easy1090

前排围观

oneyoung19 avatar May 07 '23 09:05 oneyoung19

”总结的时有错误“可以修改总结部分吗

iocscix avatar Jan 31 '24 06:01 iocscix

也就是说,优先级 mjs > js,这个结论是错误的

SirHe avatar Apr 08 '24 13:04 SirHe