本文正在参与技术专题征文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
和module
commonjs
表示启用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的心智负担 - 码字不易,点个攒👍吧