本文正在参与技术专题征文Node.js进阶之路,点击查看详情
前言
Node.js 是一个开源和跨平台的 JavaScript 运行时环境,诞生于 2009 年。
有人觉得 2022 年,Node.js似乎没有以往那么火了,但事实并非如此。从Node.js源码更新的内容来看,Node.js正在经历一个从性能好向着易用、好用的转变。Node.js社区重点工作组Node.js Diagnostics Working Group的核心产出 async_hooks、profiling、tracing、dump debug、report等都是在易用性和好用性上做提升,致力于让每个Node.js开发者以更低门槛来提升Node.js应用的开发体验。
今天我们就来看下 ES模块 在node中的应用吧
介绍
-
ECMAScript 模块是来打包 JavaScript 代码以供重用的官方标准格式。 模块使用各种
import和export语句定义。-
以下是 ES 模块导出函数的示例:
// addTwo.mjs function addTwo(num) { return num + 2; } export { addTwo }; -
以下是 ES 模块从
addTwo.mjs导入函数的示例:// app.mjs import { addTwo } from './addTwo.mjs'; // 打印: 6 console.log(addTwo(4));
-
-
Node.js 完全支持当前指定的 ECMAScript 模块,并且提供它们与其原始模块格式 CommonJS 之间的互操作性。
启用 es
-
Node.js 有两个模块系统:CommonJS 模块和 ECMAScript 模块。
-
可以通过
.mjs文件扩展名、package.json"type"字段、或--input-type标志告诉 Node.js 使用 ECMAScript 模块加载器。 在这些情况之外,Node.js 将使用 CommonJS 模块加载器。-
添加
type方式- node 16.13之后package.json新增了type属性,
commonjs和modulecommonjs表示启用commonjs模式,这也是node的默认模式module表示启用es模式
- 示例
-
注意
- 当我们使用
es模块之后,在引入es模块的时候需要手动加上后缀名,否则会报错(默认走的是common规范)
- 当我们使用
-
这样就好了
- node 16.13之后package.json新增了type属性,
-
运行时添加
--input-type="module"- 注意
- 在运行时添加
--input-type="module"执行es模块的代码,那么文件的后缀名需要改成mjs
- 在运行时添加
- 注意
-
协议支持
说明
- 支持
file:、node:和data:URL 协议。 除非使用自定义的 HTTPS 加载器,否则 Node.js 本身不支持像'https://example.com/app.js'这样的说明符
file: URL
- 如果用于解析模块的
import说明符具有不同的查询或片段,则会多次加载模块。
import './foo.mjs?query=1'; // 加载具有 "?query=1" 查询的 ./foo.mjs
import './foo.mjs?query=2'; // 加载具有 "?query=2" 查询的 ./foo.mjs
- 也可以通过
/、//或file:///引用。 鉴于网址和路径解析的差异(例如百分比编码细节),建议在导入路径时使用 url.pathToFileURL。
data: 导入
-
data:URL 支持使用以下 MIME 类型导入:text/javascript用于 ES 模块application/json用于 JSONapplication/wasm用于 Wasm
-
data:URL 只为内置模块解析裸说明符和绝对说明符。 解析相对说明符不起作用,因为data:不是特殊协议。 例如,尝试从data:text/javascript,import "./foo";加载./foo无法解析,因为data:URL 没有相对解析的概念。 正在使用的data:URL 示例是:import 'data:text/javascript,console.log("hello!");'; import _ from 'data:application/json,"world!"';
node: 导入
- 支持
node:URL 作为加载 Node.js 内置模块的替代方法。 此 URL 协议允许有效的绝对的 URL 字符串引用内置模块。
import fs from 'node:fs/promises';
导入断言
- 导入断言提案为模块导入语句添加了内联语法,以便在模块说明符旁边传入更多信息。
import fooData from './foo.json' assert { type: 'json' };
const { default: barData } =
await import('./bar.json', { assert: { type: 'json' } });
-
Node.js 支持以下
type值,其断言是强制性的:断言 type用于 'json'JSON 模块 -
示例
- 根据上图可以看到,node通过添加 断言可以很方便的导出json数据
import() 表达式
- 动态的
import()在 CommonJS 和 ES 模块中都受支持。 在 CommonJS 模块中它可以用来加载 ES 模块 - 示例
import.meta
import.meta元属性是包含url属性和resolve等方法的。
- 使用
es模块的时候__dirname和__filename是无法使用的,可以通过内置url方法+import.meta.url实现对应功能 - 示例
import path from 'node:path'
import url from 'node:url'
function filenameAndDirname(_url) {
const filename = url.fileURLToPath(_url)
const dirname = path.dirname(filename)
return {
filename,
dirname
}
}
const { filename, dirname } = filenameAndDirname(import.meta.url)
console.log(filename);
console.log(dirname);
与 CommonJS 的互操作性
import 声明
-
import语句可以引用 ES 模块或 CommonJS 模块。import语句只允许在 ES 模块中使用,但 CommonJS 支持动态import()表达式来加载 ES 模块。 -
当导入 CommonJS 模块 时,提供
module.exports对象作为默认导出。 命名导出可能可用,由静态分析提供,以方便更好的生态系统兼容性。
es模块 导入 es模块
- 示例
es模块 导入 commonJS模块
- 示例
require
- CommonJS 模块
require总是将它引用的文件视为 CommonJS。
require 导入 CommonJS模块
- 示例
- 注意
- 由于在
package.json中设置了"type": "module",node会按照es模块解析代码 - 所以
app.js需要显示的改为app.cjs,表示当前文件按照CommonJS规范走
- 由于在
require 导入 es模块
- 示例
-
注意
- require 导入 es模块 直接报错,写法问题
-
不支持使用
require加载 ES 模块,因为 ES 模块具有异步执行。 而是,使用import()从 CommonJS 模块加载 ES 模块。
CommonJS 命名空间
-
CommonJS 模块由可以是任何类型的
module.exports对象组成。 -
当导入 CommonJS 模块时,可以使用 ES 模块默认导入或其对应的语法糖可靠地导入
-
示例一
- 注意
import { default as cjs } from './addTwo.cjs' // 下面的导入语句是上面的导入语句中 // `{ default as cjsSugar }` 的 "语法糖"(等价但更方便): import { addTwo } from './addTwo.cjs' - 示例二
- 注意
- 由于在
package.json中设置了"type": "module",node支持顶层await - CommonJS 模块的 ECMAScript 模块命名空间表示始终是使用
default导出键指向 CommonJSmodule.exports值的命名空间
- 由于在
- 示例三
- 注意
- 当使用
import * as m from 'cjs'或动态导入时,可以直接观察到此模块命名空间外来对象
- 当使用
CommonJS 兼容 JS生态写法
- 为了更好地兼容 JS 生态系统中的现有用法,Node.js 还尝试确定每个导入的 CommonJS 模块的 CommonJS 命名导出,以使用静态分析过程将它们作为单独的 ES 模块导出提供。
- 示例
- 注意
-
从上一个记录模块命名空间外来对象的示例中可以看出,
name导出是从module.exports对象复制出来的,并在导入模块时直接设置在 ES 模块命名空间上。 -
未检测到这些命名导出的实时绑定更新或添加到
module.exports的新导出。 -
命名导出的检测基于通用语法模式,但并不总是正确地检测命名导出。 在这些情况下,使用上述默认导入形式可能是更好的选择。
-
命名导出检测涵盖了许多常见的导出模式、再导出模式、以及构建工具和转译器输出。
-
ES 模块和 CommonJS 之间的差异
- 没有 require、exports 或 module.exports
- 没有 __filename 或 __dirname
- 没有原生模块加载(es模块可以改用
module.createRequire()或process.dlopen。) - 没有 require.resolve
- 没有 NODE_PATH
- 没有 require.extension
- 没有 require.cache
写在最后
- node支持原生
es模块能力,燥起来吧,解锁更多es新姿势 - 虽然ES 模块和 CommonJS 之间仍然存在差异,绝大部分方法都有对应的
es代替方法 - 即使目前
es不支持的属性比如(require.extensions),加载器钩子在未来依旧可以提供这个工作流 - 共用
es模块开发,大大减少了前端der的心智负担 - 码字不易,点个攒👍吧