Node.js v8.9.3 文档


ECMAScript模块#

查看英文版 / 查看英文md文件 / 编辑中文md文件

稳定性: 1 - 试验的

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

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

启用#

查看英文版 / 查看英文md文件 / 编辑中文md文件

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

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

node --experimental-modules my-app.mjs

特性#

Supported#

查看英文版 / 查看英文md文件 / 编辑中文md文件

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

Unsupported#

查看英文版 / 查看英文md文件 / 编辑中文md文件

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

importrequire 之间的显著差异#

No NODE_PATH#

查看英文版 / 查看英文md文件 / 编辑中文md文件

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

No require.extensions#

查看英文版 / 查看英文md文件 / 编辑中文md文件

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

No require.cache#

查看英文版 / 查看英文md文件 / 编辑中文md文件

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

URL based paths#

查看英文版 / 查看英文md文件 / 编辑中文md文件

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: 协议来加载。

与现有模块的交互#

查看英文版 / 查看英文md文件 / 编辑中文md文件

所有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#

查看英文版 / 查看英文md文件 / 编辑中文md文件

To customize the default module resolution, loader hooks can optionally be provided via a --loader ./loader-name.mjs argument to Node.

When hooks are used they only apply to ES module loading and not to any CommonJS modules loaded.

Resolve hook#

查看英文版 / 查看英文md文件 / 编辑中文md文件

The resolve hook returns the resolved file URL and module format for a given 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 third argument to the resolver for easy compatibility workflows.

In addition to returning the resolved file URL value, the resolve hook also returns a format property specifying the module format of the resolved module. This can be one of "esm", "cjs", "json", "builtin" or "addon".

For example a dummy loader to load JavaScript restricted to browser resolution rules with only JS file extension and Node builtin modules support could be written:

import url from 'url';
import path from 'path';
import process from 'process';

const builtins = new Set(
  Object.keys(process.binding('natives')).filter((str) =>
    /^(?!(?:internal|node|v8)\/)/.test(str))
);
const JS_EXTENSIONS = new Set(['.js', '.mjs']);

export function resolve(specifier, parentModuleURL/*, defaultResolve */) {
  if (builtins.has(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#

查看英文版 / 查看英文md文件 / 编辑中文md文件

To create a custom dynamic module that doesn't correspond to one of the existing format interpretations, the dynamicInstantiate hook can be used. This hook is called only for modules that return format: "dynamic" from the 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 will then be called at the exact point of module evalutation order for that module in the import tree.