最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 2021年我们可以在Node中使用ES Modules了吗

    正文概述 掘金(大转转FE)   2020-12-18   1007

    1 前言

    不同时期的的 JS 发展,诞生了不同的模块化机制;近些年,随着前端模块化的标准落地,不同端的 JS 对此也都做了各自的实现。今天我们就来聊聊这个话题。

    本文我们将主要探讨以下四个方面:

    • JavaScript 模块化机制概览;
    • 如何在 Node 中使用 ES Modules;
    • CommonJS 和 ES Modules 的模块机制的异同;
    • CommonJS 和 ES Modules 的模块文件相互引用;

    2 JavaScript 模块化机制概览

    JavaScript 常见的模块化机制主要有以下三种:

    • AMD (Asynchronous Module Definition): 在浏览器中使用,并用 define 函数定义模块;
    • CJS (CommonJS): 在 NodeJS 中使用,用requiremodule.exports引入和导出模块;
    • ESM (ES Modules): JavaScript 从 ES6(ES2015) 开始支持的原生模块机制,使用importexport引入和导出模块;

    3 Node 对 ES Modules 支持

    Node verison 13.2.0 起开始正式支持 ES Modules 特性。

    注:虽然移除了 --experimental-modules 启动参数,但是由于 ESM loader 还是实验性的,所以运行 ES Modules 代码依然会有警告:

    (node:47324) ExperimentalWarning: The ESM module loader is experimental.
    

    4 在 NodeJS 中使用 ES Modules

    在 Node 中使用 ESM 有两种方式:

    1)在 package.json 中,增加 type: "module" 配置;

    文件目录结构:

    .
    ├── index.js
    ├── package.json
    └── utils
        └── speak.js
    
    // utils/speak.js
    export function speak() {
        console.log('Come from speak.')
    }
    
    // index.js
    import { speak } from './utils/speak.js';
    speak(); //come from speak
    

    2)在 .mjs 文件可以直接使用 importexport

    文件目录结构:

    .
    ├── index.mjs
    ├── package.json
    └── utils
        └── sing.mjs
    
    // utils/sing.mjs
    export function sing() {
        console.log('Come from sing')
    }
    
    // index.mjs
    import { sing } from './utils/sing.mjs';
    sing(); //come from sing
    

    注意:

    • 若不添加上述两项中任一项,直接在 Node 中使用 ES Modules,则会抛出警告:
    Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
    
    • 根据 ESM 规范,使用 import 关键字并不会像 CommonJS 模块那样,在默认情况下以文件扩展名填充文件路径。因此,ES Modules 必须明确文件扩展名。

    模块作用域:

    一个模块的作用域,由父级中有type: "module"的 package.json 文件路径定义。而使用.mjs扩展文件加载模块,则不受限于包的作用域。

    同理,package.json中没有type标志的文件都会默认采用 CommonJS 模块机制,.cjs类型的扩展文件使用 CommonJS 方式加载模块同样不受限于包的作用域。

    包的入口

    定义包的入口有两种方式,在 package.json 中定义main字段或者exports字段:

    {
      "main": "./main.js",
      "exports": "./main.js"
    }
    

    需要注意的是,当exports字段被定义后,包的所有子路径都将被封装,子路径的文件不可再被导入。

    例如: require('pkg/subpath.js') 将会报错:

    ERR_PACKAGE_PATH_NOT_EXPORTED error.`
    

    5. 两个模块机制在执行时机上的区别

    • ES Modules 导入的模块会被预解析,以便在代码运行前导入;
    • 在 CommonJS 中,模块将在运行时解析;

    举一个简单的例子来直观的对比下二者的差别:

    // ES Modules
    
    // a.js
    console.log('Come from a.js.');
    import { hello } from './b.js';
    console.log(hello);
    
    // b.js
    console.log('Come from b.js.');
    export const hello = 'Hello from b.js';
    

    输出:

    Come from b.js.
    Come from a.js.
    Hello from b.js
    

    同样的代码使用 CommonJS 机制:

    // CommonJS
    
    // a.js
    console.log('Come from a.js.');
    const hello = require('./b.js');
    console.log(hello);
    
    // b.js
    console.log('Come from b.js.');
    module.exports = 'Hello from b.js';
    

    输出:

    Come from a.js.
    Come from b.js.
    Hello from b.js
    

    可以看到 ES Modules 预先解析了模块代码,而 CommonJS 是代码运行的时候解析的。

    需要注意的是,根据 EMS 规范 import / export 必须位于模块顶级,不能位于作用域内;其次模块内的 import/export 会提升到模块顶部,这是在编译阶段完成的。

    6.两个模块机制在原理上的区别

    1. CommonJS

    Node 将每个文件都视为独立的模块,它定义了一个 Module 构造函数,代表模块自身:

    function Module(id = '', parent) {
      this.id = id;
      this.path = path.dirname(id);
      this.exports = {};
      this.parent = parent;
      this.filename = null;
      this.loaded = false;
      this.children = [];
    };
    

    而 require 函数接收一个代表模块ID或者路径的值作为参数,它返回的是用module.exports导出的对象。在执行代码模块之前,NodeJs 将使用一个包装器对模块中的代码其进行封装:

    (function(exports, require, module, __filename, __dirname) { 
        // Module code actually lives in here 
    }); 
    

    简言之,每个模块都有自己的函数包装器, Node 通过此种方式确保模块内的代码对它是私有的。

    在包装器执行之前,模块内的导出内容是不确定的。除此之外,第一次加载的模块会被缓存到 Module._cache中。

    一个完整的加载周期大致如下:

      Resolution (解析) –> Loading (加载) –> Wrapping (私有化) –> Evaluation (执行) –> Caching (缓存)
    
    1. ES Modules

    在 ESM 中,import 语句用于在解析代码时导入模块依赖的静态链接。文件的依赖关系在编译阶段就确定了。对于 ESM,模块的加载大致分为三步:

      Construction (解析) -> Instantiation (实例化、建立链接) -> Evaluation (执行)
    

    这些步骤是异步执行的,每一步都可以看作是相互独立的。这一点跟 CommonJS 有很大不同,对于 CommonJS 来说,每一步都是同步进行的。

    7. 两种模块间的相互引用

    CommonJS 和 ES Modules 都支持Dynamic import(),它可以支持两种模块机制的导入。

    在 CommonJS 文件中导入 ES Modules 模块

    由于 ES Modules 的加载、解析和执行都是异步的,而 require() 的过程是同步的、所以不能通过 require() 来引用一个 ES6 模块。 ES6 提议的 import() 函数将会返回一个 Promise,它在 ES Modules 加载后标记完成。借助于此,我们可以在 CommonJS 中使用异步的方式导入 ES Modules:

    // 使用 then() 来进行模块导入后的操作
    import(“es6-modules.mjs”).then((module)=>{/*…*/}).catch((err)=>{/**…*/})
    // 或者使用 async 函数
    (async () => {
      await import('./es6-modules.mjs');
    })();
    
    在 ES Modules 文件中导入 CommonJS 模块

    在 ES6 模块里可以很方便地使用 import 来引用一个 CommonJS 模块,因为在 ES6 模块里异步加载并非是必须的:

    import { default as cjs } from 'cjs';
    
    // The following import statement is "syntax sugar" (equivalent but sweeter)
    // for `{ default as cjsSugar }` in the above import statement:
    import cjsSugar from 'cjs';
    
    console.log(cjs);
    console.log(cjs === cjsSugar);
    

    8. Node 中 ES Modules 的现状和未来

    在引入 ES6 标准之前,服务器端 JavaScript 代码都是依赖 CommonJS 模块机制进行包管理的。 如今,随着 ES Modules 的引入,开发人员可以享受到与发布规范相关的许多好处。但需要注意的是,截止至本文发布时间,在最新版 Node v15.1.0 中,该特性依然是实验性的(Stability: 1),不建议在生产环境中使用该功能。

    最后,由于两种模块格式之间存在不兼容问题,将当前项目从 CommonJS 到 ES Modules 转换将是一个挑战。可以借助 Babel 相关插件(plugin-transform-modules-commonjs, babel-plugin-transform-commonjs)实现 CommonJS 和 ES Modules 间的相互转换。

    参考链接

    • Node Documentation
    • Node version13+ release log
    • Node version14+ release log
    • Node modules wrapper
    • Node Source code: cjs
    • ECMA262 Modules
    • TC39 Proposal Dynamic import

    下载网 » 2021年我们可以在Node中使用ES Modules了吗

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元