开发手册 欢迎您!
软件开发者资料库

ECMAScript 模块 | Node.js

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。Node.js 的包管理器 npm,是全球最大的开源库生态系统。

Node.js v8.x 中文文档


ECMAScript模块#

稳定性: 1 - 试验的

Node.js包含了对基于 [Node.js EP for ES Modules] 的ES模块的支持。

不是所有EP的功能都是完整的并且将随着VM的支持和实现而就绪的。有问题的地方正在被修改完善。

启用#

--experimental-modules 标志可用于启用加载ES模块的功能。

一旦被设置启用,以 .mjs 为后缀的文件将能够作为ES模块加载。

node --experimental-modules my-app.mjs

特性#

Supported#

只有在程序主入口添加CLI参数可以成为ES模块的入口点。在未来 import() 可以在程序运行时创建ES模块入口点。

Unsupported#

特性原因
require('./foo.mjs')ES模块具有不同的加载方式,使用 import()语言标准
import()等待在Node.js中使用更加新的V8版本
import.meta等待V8实现

importrequire 之间的显著差异#

No NODE_PATH#

根据 NODE_PATH 查询模块不是解析 import 的环节之一。如果想要怎么做,那么请使用符号链接。

No require.extensions#

require.extensions 没有被 import 使用。但是值得期望的是,加载器钩子可能在未来提供这个工作流流程。

No require.cache#

require.cache 没有被 import 使用。它有一个独立的缓存。

URL based paths#

ESM基于URL语义来解析和缓存。这意味着那些包含特殊字符的文件名,如 #? 需要进行转义。

如果使用 import 来解析一个拥有不同查询或片段的模块,该模块将被加载多次。

import './foo?query=1'; // loads ./foo with query of "?query=1"import './foo?query=2'; // loads ./foo with query of "?query=2"

直到现在,模块只能使用 file: 协议来加载。

与现有模块的交互#

所有CommonJS,JSON和C++模块都可以通过 import 来加载。

以这种方式加载的模块只加载一次,即使 import 语句中同一个模块的查询或片段字符串不同。

当通过 import 加载时,这些模块将提供一个 default 导出相当于完成计算后的 module.exports

import fs from 'fs';fs.readFile('./foo.txt', (err, body) => {  if (err) {    console.error(err);  } else {    console.log(body);  }});

Loader hooks#

定制一个默认的模块解决方案,加载器钩子能通过提供给Node一个 --loader ./loader-name.mjs 参数来配置。

当此钩子被使用时,只支持ES模块加载,不支持任何的CommonJS模块加载。

Resolve hook#

The resolve hook returns the resolved file URL and module format for agiven module specifier and parent file URL:

import url from 'url';export async function resolve(specifier, parentModuleURL, defaultResolver) {  return {    url: new URL(specifier, parentModuleURL).href,    format: 'esm'  };}

The default NodeJS ES module resolution function is provided as a thirdargument to the resolver for easy compatibility workflows.

In addition to returning the resolved file URL value, the resolve hook alsoreturns a format property specifying the module format of the resolvedmodule. This can be one of the following:

formatDescription
"esm"Load a standard JavaScript module
"commonjs"Load a node-style CommonJS module
"builtin"Load a node builtin CommonJS module
"json"Load a JSON file
"addon"Load a [C++ Addon][addons]
"dynamic"Use a [dynamic instantiate hook][]

For example, a dummy loader to load JavaScript restricted to browser resolutionrules with only JS file extension and Node builtin modules support couldbe written:

import url from 'url';import path from 'path';import process from 'process';import Module from 'module';const builtins = Module.builtinModules;const JS_EXTENSIONS = new Set(['.js', '.mjs']);export function resolve(specifier, parentModuleURL/*, defaultResolve */) {  if (builtins.includes(specifier)) {    return {      url: specifier,      format: 'builtin'    };  }  if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) {    // For node_modules support:    // return defaultResolve(specifier, parentModuleURL);    throw new Error(      `imports must begin with '/', './', or '../'; '${specifier}' does not`);  }  const resolved = new url.URL(specifier, parentModuleURL);  const ext = path.extname(resolved.pathname);  if (!JS_EXTENSIONS.has(ext)) {    throw new Error(      `Cannot load file with non-JavaScript file extension ${ext}.`);  }  return {    url: resolved.href,    format: 'esm'  };}

With this loader, running:

NODE_OPTIONS='--experimental-modules --loader ./custom-loader.mjs' node x.js

would load the module x.js as an ES module with relative resolution support(with node_modules loading skipped in this example).

Dynamic instantiate hook#

To create a custom dynamic module that doesn't correspond to one of theexisting format interpretations, the dynamicInstantiate hook can be used.This hook is called only for modules that return format: "dynamic" fromthe resolve hook.

export async function dynamicInstantiate(url) {  return {    exports: ['customExportName'],    execute: (exports) => {      // get and set functions provided for pre-allocated export names      exports.customExportName.set('value');    }  };}

With the list of module exports provided upfront, the execute function willthen be called at the exact point of module evaluation order for that modulein the import tree.[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md[addons]: addons.html[dynamic instantiate hook]: #esm_dynamic_instantiate_hook