Node.js 12中新的ECMAScript模块支持(译)

本文于2019-04-23由Dr. Axel博士发表在我的网站2ality,全文对Node.js12中模块的新特性作了详细的分析解读。html

Node.js 12(于2019-04-23发布)为ECMAScript模块带来了改进的支持。它实施了去年年末发布的计划的第二阶段。目前,这种支持能够在experimental-modules得到。前端

继续阅读以了解这种对ECMAScript模块的新支持是如何工做的。node

简要说明:文件扩展名.mjs更方便,但.js也能够为ES模块启用。npm

1.本篇文章中使用的术语和缩写

  • CommonJS模块(CJS):指的是原始的Node.js模块标准。
  • ECMAScript模块(ES模块,ESM):指经过ECMAScript规范标准化的模块。
  • package.prop是指prop的package.json。

2.模块说明符

模块说明符是标识模块的字符串。它们在CommonJS模块和ES模块中的工做方式略有不一样。在咱们查看差别以前,咱们须要了解模块说明符的不一样类别。json

2.1 模块说明符的类别

在ES模块中,咱们区分如下类别的说明符。这些类别源自CommonJS模块。promise

  • 相对路径:以点开头。例:
./some/other/module.mjs
../../lib/counter.mjs
复制代码
  • 绝对路径:以斜杠开头。例:
/home/jane/file-tools.mjs
复制代码
  • URL:包括协议(从技术上讲,路径也是URL)。例:
'https://example.com/some-module.mjs'
'file:///home/john/tmp/main.mjs'
复制代码
  • 裸路径:不以点,斜杠或协议开头,而且由没有扩展名的单个文件名组成。例子:
'lodash'
'the-package'
复制代码
  • 深度导入路径:以裸路径开始,至少有一个斜杠。例:
'the-package/dist/the-module.mjs'
复制代码

2.2 CommonJS模块说明符

这是CommonJS处理模块说明符的方式:浏览器

  • CommonJS不支持URL做为说明符。
  • 相对路径和绝对路径按预期处理。
  • 您能够将目录foo做为模块加载:
    • 若是有文件 foo/index.js
    • 若是存在foo/package.json其属性"main"指向模块文件的文件。
  • 根据node_modules找到的目录解析裸路径和深度导入路径:
    • 与导入模块位于同一目录中
    • 在该目录的父级中
    • 其它。
  • 若是说明符X未引用文件,则系统会尝试使用说明符X.jsX.jsonX.node

此外,CommonJS模块能够访问两个特殊的模块全局变量:bash

  • __filename:包含当前模块的路径。
  • __dirname:包含当前模块的父目录的路径。

本节的来源:Node.js文档的“模块”页面。async

2.3 Node.js中的ES模块说明符

  • 除裸路径外,全部说明符都必须引用实际文件。与CommonJS相比,ESM不会添加缺乏的文件扩展名。
  • 仅file:支持URL说明符的协议。
  • 目前不支持绝对路径。做为解决方法,您可使用以file:///。开头的URL 。
  • 相对路径在Web浏览器中被解析 - 相对于当前模块的路径。
  • 裸路径相对于node_modules目录进行解析。裸路径引用的模块经过package.main(相似于CJS)指定。
  • 深度导入路径也相对于node_modules目录进行解析。
  • 不支持导入目录。换句话说,既不能使用package.main(只能用于packages)也不能经过index.*工做。

全部内置的Node.js模块均可以经过裸路径得到,并命名为ESM导出。例如:网站

import * as path from 'path';
import * as assert from 'assert';

assert.equal(
  path.join('a/b/c', '../d'), 'a/b/d');
复制代码

2.4 文件扩展名

Node.js支持如下默认文件扩展名:

  • mjs 用于ES模块

  • cjs 用于CommonJS模块 文件扩展名.js表明ESM或CommonJS。它是哪个,取决于package.type,它有两个设置:

  • commonjs(默认值):扩展名为.js或没有扩展名的文件被解析为CommonJS。

    "type": "commonjs"
    复制代码
  • module:具备扩展名.js或没有扩展名的文件被解析为ESM。

    "type": "module"
    复制代码

要查找package.json给定文件,Node.js将在与文件,父目录等相同的目录中进行搜索。

2.5将非文件源代码解释为CommonJS或ESM

并不是全部Node.js执行的源代码都来自文件。你也能够经过stdin --eval和它发送代码--print。命令行选项--input-type容许您指定如何解释此类代码:

  • 做为CommonJS(默认): --input-type=commonjs
  • 做为ESM: --input-type=module

3. 互操做能力

3.1 从ESM导入CommonJS

目前,有两种从ES模块导入CommonJS模块的选项。

考虑如下CommonJS模块。

// common.cjs
module.exports = {
  foo: 123,
};
复制代码

第一个选项是默认导入它(未来可能会添加对命名导入的支持):

// es1.mjs
import * as assert from 'assert';

import common from './common.cjs'; // default import
assert.equal(common.foo, 123);
复制代码

第二种选择是使用createRequire():

// es2.mjs
import * as assert from 'assert';

import {createRequire} from 'module';
const require = createRequire(import.meta.url);

const common = require('./common.cjs');
assert.equal(common.foo, 123);
复制代码

3.2 从CommonJS导入ESM

若是要从CommonJS模块导入ES模块,可使用该import()运算符。

例如,采用如下ES模块:

// es.mjs
export const bar = 'abc';
复制代码

这里咱们从CommonJS模块导入它:

// common.cjs
const assert = require('assert');

async function main() {
  const es = await import('./es.mjs');
  assert.equal(es.bar, 'abc');
}
main();
复制代码

4. 各类其余功能

4.1 import.meta.url

鉴于 __filename和 __dirname在ES模块不可用,咱们须要一个替代方案。import.meta.url是另外一种选择。它包含file:具备绝对路径的URL。例如:

'file:///Users/rauschma/my-module.mjs'
复制代码

注意:url.fileURLToPath()用于提取路径 - new URL().pathname并非总能够正常运行:

import * as assert from 'assert';
import {fileURLToPath} from 'url';

//::::: Unix :::::

const urlStr1 = 'file:///tmp/with%20space.txt';
assert.equal(
  new URL(urlStr1).pathname, '/tmp/with%20space.txt');
assert.equal(
  fileURLToPath(urlStr1), '/tmp/with space.txt');

const urlStr2 = 'file:///home/thor/Mj%C3%B6lnir.txt';
assert.equal(
  new URL(urlStr2).pathname, '/home/thor/Mj%C3%B6lnir.txt');
assert.equal(
  fileURLToPath(urlStr2), '/home/thor/Mjölnir.txt');

//::::: Windows :::::

const urlStr3 = 'file:///C:/dir/';
assert.equal(
  new URL(urlStr3).pathname, '/C:/dir/');
assert.equal(
  fileURLToPath(urlStr3), 'C:\\dir\\');
复制代码

下一节演示使用import.meta.url和url.fileURLToPath()。

反过来想url.fileURLToPath()是url.pathToFileURL():它将路径转换为文件URL。

4.2 fs.promises

fs.promises包含fsAPI 的promisified版本并按预期工做

import {fileURLToPath} from 'url';
import {promises as fs} from 'fs';

async function main() {
  // The path of the current module
  const pathname = fileURLToPath(import.meta.url);
  const str = await fs.readFile(pathname, {encoding: 'UTF-8'});
  console.log(str);
}
main();

复制代码

4.3 --experimental-json-modules

使用该标志--experimental-json-modules,Node.js将.json文件加载为JSON。

以JSON文件为例data.json:

{
  "first": "Jane",
  "last": "Doe"
}
复制代码

能够从ES模块导入它,以下所示(若是同时使用ESM和JSON模块的标志):

import * as assert from 'assert';
import data from './data.json';

assert.deepEqual(
  data,
  {first: "Jane", last: "Doe"});
复制代码

5.npm上的ES模块

目前,npm上的ES模块引用有如下两种方式能够选择:

  • require('mylib')
  • import from 'mylib

你不能二者兼顾(深度导入路径是合理的解决方法)。咱们正在努力改变这一情况。它可能会经过package.main更强大的功能来完成。

在该功能准备就绪以前,对处理该功能的人员有如下要求:

“在解决以前,请不要发布任何供Node.js使用的ES模块包。”
复制代码

6.在Node.js上使用ES模块

从Node.js 12开始,Node.js上使用ES模块有如下选项:

  • library:ESM由John-David Dalton维护。esm还支持旧版本的Node.js.

  • Flag--experimental-modules

当Node.js 12达到LTS状态时,可能会在2019年10月删除ESM支持的标志。

最后欢迎你们关注公众号前端小苑,我会按期在这里发表原创文章。

相关文章
相关标签/搜索