• home > webfront > ECMAS > npm-node >

    package.json中的browser/module/main字段与mjs杂谈

    Author:zhoulujun Date:

    需要在不同环境下加载npm包不同的入口文件,分别为browser、module 、 main,NodeJS原始的模块方式CJS,现在主流的webpack2及以上采用esm。当存在同名 mjs 和 js ,优先加载mjs文件。模块优先级 :browser = browser+mjs > module > browser+cjs > main

    npm 包其实又分为:

    1. 只允许在客户端使用的,

    2. 只允许造服务端使用的,

    3. 浏览器/服务端都可以使用。

    如果我们需要开发一个 npm 包同时兼容支持 web端 和 server 端,需要在不同环境下加载npm包不同的入口文件,显然一个 main 字段已经不能够满足我们的需求,这就衍生出来了 module 与 browser 字段。

    browser、module 、 main字段定义

    • main : 定义了 npm 包的入口文件,browser 环境和 node 环境均可使用

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

    • browser : 定义 npm 包在 browser 环境下的入口文件

    我们将 Node 原始的模块方式 CommonJS简称为CJS,而 EcmaScript Module称为ESM。现在主流的webpack2及以上采用esm。

    • CJS 采用的是动态同步加载,也就是说运行的时候确定加载的文件,很明显这样做有一个好处就是灵活,但是缺点就是无法很好的处理循环引用的问题。而且是同步加载,这会导致加载速度过慢。

    • ESM 采用的是静态异步加载,最大的区别便是采用了静态分析。大家都知道 import 必须要写在文件的顶层,这也就是为了能够静态分析你需要加载的模块。首先他能很好的解决循环依赖的问题。

    其次是异步加载。在 CJS 中,JS 的加载是同步进行的,也就是说我必须要等待上一个 JS 加载完成,才能够加载下一个 JS,大家也懂得,这样很明显浪费了 Node 异步的有点。这也就会导致如果 JS 文件过多,系统的启动时间会被大大加长。

    我们npm的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
    }

    当我们在不同环境下 import 一个 npm 包时,到底加载的是 npm 包的哪个文件?

    文件优先级

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

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

    也就是说,优先级 mjs > js

    现在的包,一般有很多个版本,比如

    • vue.js : vue.js则是直接用在<script>标签中的,完整版本,直接就可以通过script引用。

    • vue.common.js :预编译调试时,CommonJS规范的格式,可以使用require("")引用的NODEJS格式。

    • vue.esm.js:预编译调试时, EcmaScript Module(ES MODULE),支持import from 最新标准的。

      预编译+运行时,也就是模板字符串和现在最常用的单文件组件.vue文件,需要经过它预编译转化成纯javascrit,然后再运行,适用于开发环境。

    运行是版本

    • vue.runtime.js :生产的运行时,需要预编译,比完整版小30%左右,前端性能最优。适用于生产环境,需要经过预编译。

      官方说法是用来创建 Vue 实例,渲染并处理 virtual DOM 等行为的代码。基本上就是除去编译器的其他一切。

    • vue.runtime.esm.js:生产运行时,esm标准。

    • vue.runtime.common.js:生产运行时,commonJS标准。

    webpack + web + ESM

    通过 webpack 打包构建我们的 web 应用,模块语法使用 ESM

    import test from 'test'

    实际上的加载优先级是 browser = browser+mjs > module > browser+cjs > main 

    也就是说 webpack 会根据这个顺序去寻找字段指定的文件,直到找到为止。

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

    webpack加载ESM模块顺序

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



    参考文章:

    ES6 模块化的时代真的来临了么?Using MJS https://www.jianshu.com/p/fa54a2e6e168

    commonjs 与 esm 的区别 https://juejin.im/post/5cf8e159f265da1b94213a63

    browser VS module VS main https://github.com/SunshowerC/blog/issues/8




    转载本站文章《package.json中的browser/module/main字段与mjs杂谈》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/nodejs/8413.html

    延伸阅读: