2020年我们可以在Node中使用ES Modules了吗
1 前言
不同时期的的 JS 发展,诞生了不同的模块化机制;近些年,随着前端模块化的标准落地,不同端的 JS 对此也都做了各自的实现。今天我们就来聊聊这个话题。
本文我们将主要探讨以下四个方面:
JavaScript 模块化机制概览;
如何在 Node 中使用 ES Modules;
CommonJS 和 ES Modules 的模块机制的异同;
CommonJS 和 ES Modules 的模块文件相互引用;
2 JavaScript 模块化机制概览
JavaScript 常见的模块化机制主要有以下三种:
AMD (Asynchronous Module Definition): 在浏览器中使用,并用
define
函数定义模块;CJS (CommonJS): 在 NodeJS 中使用,用
require
和module.exports
引入和导出模块;ESM (ES Modules): JavaScript 从 ES6(ES2015) 开始支持的原生模块机制,使用
import
和export
引入和导出模块;
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.
come from speak )。
4 在 NodeJS 中使用 ES Modules
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 文件可以直接使用 import
和 export
;
文件目录结构:
.
├── 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.两个模块机制在原理上的区别
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.js 实现了以下几点:
它保持了顶层的变量(用 var、 const 或 let 定义)作用在模块范围内,而不是全局对象。
它有助于提供一些看似全局的但实际上是模块特定的变量,例如:
实现者可以用于从模块中导出值的 module 和 exports 对象。
包含模块绝对文件名和目录路径的快捷变量 __filename 和 __dirname 。
简言之,每个模块都有自己的函数包装器, Node 通过此种方式确保模块内的代码对它是私有的。
在包装器执行之前,模块内的导出内容是不确定的。除此之外,第一次加载的模块会被缓存到 Module._cache
中。
一个完整的加载周期大致如下:
Resolution (解析) –> Loading (加载) –> Wrapping (私有化) –> Evaluation (执行) –> Caching (缓存)
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
2020年我们可以在Node中使用ES Modules了吗相关推荐
- 二十五、Node中的Buffer缓冲器和EventEmitter事件触发器
@Author:Runsen @Date:2020/6/5 作者介绍:Runsen目前大三下学期,专业化学工程与工艺,大学沉迷日语,Python, Java和一系列数据分析软件.导致翘课严重,专业排名 ...
- 长连接及在Node中的应用——HTTP/1.1 keep-alive
HTTP请求都要经过TCP三次握手建立连接,四次分手断开连,如果每个HTTP请求都要建立TCP连接的话是极其费时的,因此HTTP/1.1中浏览器默认开启了Connection: keep-alive. ...
- 打开浏览器的包 node_如何发布可在浏览器和Node中使用的软件包
打开浏览器的包 node When you create a package for others to use, you have to consider where your user will ...
- node中模块、AMD与CMD、ES6模块,node中使用ES6
1.Nodejs 中的模块 在node环境中一个js文件就是一个模块(module) 我们采用的是CommonJS规范,使用require引入模块,使用module.exports导出接口 node的 ...
- node 进阶 | 通过node中如何捕获异常阐述express的特点
node如何捕获异常 node基于js的单线程,有了非阻塞异步回调的概念,但是在处理多个并发连接时,并发环境要求高,最重要的是单线程,单核CPU,一个进程crash则web服务都crash,但是为什么 ...
- Node中同步与异步的方式读取文件
场景 Node.js最大的特点就是异步式I/O(或者非阻塞I/O)与事件紧密结合的编程模式.这种模式与传统的同步式I/O线性的编程思路有很大的不同,因为控制流很大程度上要靠事件和回调函数来组织,一个逻 ...
- node中异步IO的理解
解释性语言和编译型语言的区别: 计算器不能直接的理解高级语言,只能理解机器语言,所以必须把高级语言翻译为机器语言,翻译的方式有两种,一个是编译,一个是解释. 解释性语言的程序不需要编译,它是在运行程序 ...
- node --- 在node中使用mongoosemongoDB的安装
*首先确保,你的电脑安装了mongodb,网址: mongodb官网 *使用npm安装 mongoose: mongoose官网 ps:mongoose是Node中操作mongoDB的第三方插件.用于 ...
- 什么流读取MultipartFile_深入理解并运用Node中的IO模型流
在 NodeJs 中,流随处可见,读/写文件流,HTTP请求/返回流,stdin/stdout流.理解并运用好流会让你的Node更具力量. Stream lib/_stream_readable.js ...
最新文章
- 交换友链的几个技巧!
- python项目ImportError: Plotly express requires pandas to be installed.解决方案
- 使用WTMPlus快速搭建发卡网
- ROADS POJ - 1724(限制条件的最短路)【邻接表+深搜】
- python生成list的时候 可以用lamda也可以不用_python 可迭代对象,迭代器和生成器,lambda表达式...
- linux yum的用法【ZT】
- C++ Coding潜意识(-ing)
- 国家开放大学英语和计算机考试答案,最新电大国家开放大学《人文英语》网络核心课形考网考作业试题及答案...
- 解决SVN汉化不成功问题,下载历史版本
- 数字电路基础知识(二) 复位设计-亚稳态的产生与影响
- Jdom的安装和使用
- Axure RP7.0学习记录
- 什么是“个人商业模式”?就是一个人出售自己时间的方式
- Word一行有空白格,却无法输入新的文字
- python中e怎么计算_蒙特卡洛法计算自然常数e——python编程及可视化
- 永洪bi日志清理配置
- c++left right 和 setw() 函数的用法Alignment of Code
- 计算机二级黑板板书书写,清华老师的板书惊艳朋友圈 8个技巧让黑板亮起来
- outlook express 邮件附件丢失
- 个人网站使用github登录