Node.js 8 说明

原PPT地址
翻译:@仙森

本文经译者授权,刊登在 Alinode 团队博客。如未经译者授权,谢绝转载。

主要内容

  • 5月30日发布 Node 8.0.0。
  • 8.x (LTS) 的 code name 是 Carbon。
  • ES2017 features 全部可以使用, 除了 shared memory 和 atomics。
  • N-API 已经添加,Native 模块开发更加方便。
  • WHATWG-URL的实施得到了增强。
  • 通过 util.promisify(),不需要再显式创建 Promise 了。

LTS - Long Term Support

github: nodejs/LTS

LTS介绍

偶数版本会在每年四月发布,然后在10月份开始长期支持。
LTS 会一直支持18个月,然后将进入长达一年的维护期。
这两种方式的区别主要是修复的优先级。

  • Bug修复
  • 安全更新
  • 文档更新
  • 无破坏性的功能更新

发布计划

62089fff1f0108e5.png

版本表格

Release LTS Status Codename Active LTS Start Maintenance Start Maintenance End
v0.10 End-of-Life - - 2015-10-01 2016-10-31
v0.12 End-of-Life - - 2016-04-01 2016-12-31
4.x Active Argon 2015-10-01 2017-04-01 2018-04-01
5.x No LTS
6.x Active Boron 2016-10-18 2018-04-18 2019-04-18
7.x No LTS
8.x Pending Carbon 2017-10-01 2019-04-01 2019-12-31
9.x No LTS
10.x Pending Pending 2018-10-01 2020-04-01 2021-04-01

V8

V5.7主要更新

  • promise 和 async 提速
  • spread operator, destructuring 和 generators 提速
  • 通过 TurboFan, RegExp 提速了 15%
  • padStart 和 padEnd 被添加到了 es2017 (ECMA 262)
  • Intl.DateTimeFormat.prototype.formatToParts 被添加到了 (ECMA402)
  • WebAssembly 默认打开
  • 添加 PromiseHook

v8-release-57
speeding-up-v8-regular-expressions

V5.8主要更新

  • 任意设置堆大小的限制值 (范围是带符号32位整数)
  • 启动性能提升约5%
  • 缩短 IC 系统的代码编译,分析,优化时间

v8-release-58
one-small-step-for-chrome-one-giant
how-v8-measures-real-world-performance

ES2017 在 (v5.7, v5.8) 中的状况

features v5.7 v5.8
Object static methods yes yes
String padding yes yes
Trailing commas in function syntax flag yes
Async Functions yes yes
Shared memory and atomics no no

Object static methods: values, entries, getOwnPropertyDescriptors

Table: node.green, compat-table
Slide: abouthiroppy/ecmascript

ES2017(MISC, ANNEX B), ES2018

  1. ES2017
  2. ES2018

TURBOFAN + IGNITION

V8: Behind the Scenes (November Edition feat. Ignition+TurboFan and ES2015)

TURBOFAN 编译器

V8 优化了 JIT 编译器,它是用 Sea of Nodes 概念进行设计的。之前采用的编译技术是 Crankshaft,它支持优化更多的代码。但是 ES 的标准发展很快,后来发现 Crankshaft 已经很难去优化 ES2015 代码了,而通过 Ignition 和 TurboFan 可以做到。

TurboFan
High-performance ES2015 and beyond

IGNITION 解释器

已知的是到目前为止 V8 都没有自己的解析器,都是直接把 JS 编译成机器码。
作为JIT的JIT的问题,即使代码被执行一次,它也消耗大量的存储空间,所以需要尽量避免内存开销。
使用 Ignition,可以精简 25% - 50% 的机器码。
Ignition 是没有依赖 Turbofan 的底层架构,它采用宏汇编指令。为每个操作码生成一个字节码处理程序。
通过 Ignition 可以较少系统内存使用情况。

如图:

27907aace3974172.png

eef89960fb716c20.png

Ignition Design Doc
Firing up the Ignition Interpreter
Node.js 与 v8 底层探索

8.0.0

变更了 8.0.0 的发布时间

原计划是四月25号发布的,现在被延期到了5月30号。
V8版本从 v5.7 更新到 v5.8。
这是为了兼容 ABI(Application Binary Interface)6.0。
让 V8 Ignition 和 TurboFan pipeline 进入 8.0.0,也方便 9.x backport 到 LTS。
Ignition 和 TurboFan 在 v5.9 默认打开,到时候 Node 会以 semver-minor 方式升级到 v5.9。

V8 plan for Node.js LTS Carbon (A potential path to TurboFan + Ignition)
Ignition + TurboFan: Node.js benchmarks

计划内容

  1. 04月21日: V8 v5.8 进入 stable
  2. 05月09日: semver-major 冻结
  3. 05月中旬: v6.0 API / ABI 进入 stable
  4. 05月30日: Node8 发布🎉
  5. 06月上旬: 如果 V8 v5.9 stable 则会被作为 semver-minor 进行更新
  6. 08月上旬: 更新到 V8 v6.0

版本说明

  1. semver-major: 8.0.0
  2. semver-minor: 7.x.0
  3. semver-patch: 7.x.y

8.0.0 Release Proposal

N-API

semver-minor since 7.8.0
nodejs/abi-stable-node

什么是 N-API(NODE-API)?

ABI stable abstraction layer of native module

不同的 Node 版本、VM间,提供 ABI (Application Binary Interface) 来保证兼容性,
支持 N-API 的本地模块将无需重新编译就可以工作。

VM Summit - 2017-03-03
node-eps/005-ABI-Stable-Module-API.md
module: add support for abi stable module API

问题点

目前 Node 的实现中,V8 的 API 是直接暴露出来的。由于 V8 经常变更 API,那就存在下面的这些问题:

  • Native 模块在每个版本间需要重新编译
  • Native 模块需要变更代码
  • Native 模块无法工作在其他JS engine 上 (比如: ChakraCore)

这些问题之前的 NAN(Native Abstractions for Node.js)(https://github.com/nodejs/nan) 搞不定。

N-API

目前已经在 ChakraCore 中验证了

abi-stable-node/tree/doc

已经进行 N-API 适配的模块

Module Conversion Status Performance Assesment
leveldown Completed #55
nanomsg Completed #57
canvas Completed #77
node-sass Completed #82
iotivity Completed N/A
node-sqlite3 Started -

ASSERT

默认支持 map, set

semver-major

> assert.deepEqual(new Set([1, 2]), new Set([1]))
AssertionError: Set { 1, 2 } deepEqual Set { 1 }
    at repl:1:8
    at ContextifyScript.Script.runInThisContext (vm.js:44:33)
    at REPLServer.defaultEval (repl.js:239:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:433:10)
    at emitOne (events.js:120:20)
    at REPLServer.emit (events.js:210:7)
    at REPLServer.Interface._onLine (readline.js:262:10)
    at REPLServer.Interface._line (readline.js:611:8)
> assert.deepEqual(new Set([1, 2]), new Set([1, 2]))
undefined

nodejs/node#12142

BUFFER

添加 icu.transcode()

semver-minor since 7.1.0

通过使用 icu transcode,把 Node 支持的编码从缓冲区转移到另一个缓冲区。

> const icu = process.binding('icu');
undefined
> const newBuf = icu.transcode(Buffer.from('€'), 'utf8', 'ascii'); // source, from, to
undefined
> console.log(newBuf)
<Buffer 3f>
> Buffer.isBuffer(newBuf)
true

buffer: add buffer.transcode

CHILD_PROCESS

添加 channel 到公共接口

semver-minor since 7.1.0

之前是名为 _channel 的私有接口,这个私有接口将来会被废弃掉。
IPC(Inter Process Communication) 会返回一个到 channel 的引用。

> const fork = require('child_process').fork
> const n = fork('./test.js')
> n.channel
Pipe {
  bytesRead: 0,
  _externalStream: [External],
  fd: 12,
  writeQueueSize: 0,
  buffering: false,
  onread: [Function],
  sockets: { got: {}, send: {} } }
> n.channel === n._channel
true

child_process: add public API for IPC channel

CLUSTER

worker.disconnect() 返回引用

semver-minor since 7.3.0

if (cluster.isMaster) {
  const worker = cluster.fork();
  cluster.fork().on('listening', (address) => {
    setTimeout(() => {
      const w = worker.disconnect();
      console.log(w); // undefined
                      // Worker // 7.3.0 ~
    }, 1000);
  });
  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  http.createServer((req, res) => {}).listen(8000);
}

cluster: return worker reference from disconnect()

CRYPTO

setAuthTag 和 setAAD 返回 this 对象

semver-minor since 7.2.0

> const key = '0123456789'
> const tagbuf = Buffer.from('tagbuf')
> const aadbuf = Buffer.from('aadbuf')
> const decipher = crypto.createDecipher('aes-256-gcm', key)
> decipher.setAuthTag(tagbuf)
Decipher {
  _handle: {},
  _decoder: null,
  _options: undefined,
  writable: true,
  readable: true }
> assert.strictEqual(decipher.setAuthTag(tagbuf), decipher)
undefined
> assert.strictEqual(decipher.setAAD(aadbuf), decipher)
undefined

crypto: return this in setAuthTag/setAAD

支持系统 CA

semver-minor since 7.5.0

支持能够使用系统提供 CA。
CLI: --use-openssl-ca, --use-bundled-ca
环境变量: SSL_CERT_DIR=dir, SSL_CERT_FILE=file

crypto: use system CAs instead of bundled ones

DNS

TTL 变成可用

semver-minor since 7.2.0

可以通过 dns.resolve4()dns.resolve6() 获取每个记录的TTL(生存时间)。

> dns.resolve4("google.com", {ttl:true}, console.log)
null [ { address: '216.58.221.174', ttl: 273 } ]
> dns.resolve6("google.com", {ttl:true}, console.log)
null [ { address: '2404:6800:400a:807::200e', ttl: 242 } ]

dns: implement {ttl: true} for dns.resolve4() and dns.resolve6()

FS

支持 File 协议

semver-minor since 7.6.0

在 fs 模块中支持使用 whatwg-url 的文件协议。
根据 whatwg-url 的 spec,必须使用文件的绝对路径。

> const URL = require('url').URL;
undefined
> const myURL = new URL('file:///C:/path/to/file');
undefined
> fs.readFile(myURL, (err, data) => {});
TypeError: path must be a string or Buffer
// 7.6.0 ~
> fs.readFile(myURL, (err, data) => {});
undefined

fs: allow WHATWG URL and file: URLs as paths

fs.SyncWriteStream 已经废弃

semver-major

> fs.SyncWriteStream
...
> (node:57352) [DEP0061] DeprecationWarning: fs.SyncWriteStream is deprecated.

nodejs/node#10467

HTTP/HTTPS

添加 OutgoingMessage

semver-minor since 7.7.0

添加了三个方法:
getHeaderNames(), getHeaders(), hasHeader()

无需再调用 _headers 私有对象

const http = require('http');

http.createServer((req, res) => {
  res.setHeader('x-test-header', 'testing');
  res.setHeader('X-TEST-HEADER2', 'testing');
  console.log(res._headers); // { 'x-test-header': 'testing', 'x-test-header2': 'testing' }
  console.log(res.getHeaders()); // { 'x-test-header': 'testing', 'x-test-header2': 'testing' }
  console.log(res.getHeaderNames()); // [ 'x-test-header', 'x-test-header2' ]
  console.log(res.hasHeader('X-TEST-HEADER2')); // true
}).listen(3000, function() {
  http.get({ port: this.address().port }, (res) => {});
});

http: add new functions to OutgoingMessage

在 request 中使用 URL

semver-minor since 7.5.0

http.requesthttps.request 可以使用 URL 对象。

const http = require('http');
const url = require('url');
const URL = url.URL;

const server = http.createServer((req, res) => {
  console.log(req.url); // /foo?bar
  console.log(req.method); // GET
  res.end();
  server.close();
}).listen(3000, function() {
  const u = `http://localhost:${this.address().port}/foo?bar`;
  http.get(u);
  http.get(url.parse(u));
  http.get(new URL(u)); // 7.5.0 ~
});

url: allow use of URL with http.request and https.request

INSPECTOR

添加 --inspect-brk

semver-minor since 7.6.0

--debug-brk 通过这个参数,在开始调试的时候能够定位到代码的第一行。

$ node --inspect-brk test.js
Debugger listening on 127.0.0.1:9229.
To start debugging, open the following URL in Chrome:
    chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229 ...
Debugger attached.

inspector: add --inspect-brk flag

V8 inspector 的切换

semver-major

Node 8 之后 --debug 就不再支持了。

$ node --debug
node: bad option: --debug
$ node --inspect
Debugger listening on 127.0.0.1:9229.
To start debugging, open the following URL in Chrome:
    chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229 ...
>
$ node --inspect-brk
Debugger listening on 127.0.0.1:9229.
To start debugging, open the following URL in Chrome:
    chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229 ...

Switch the CLI debugger to V8 inspector
deps: Add node-inspect
src: Remove support for --debug

添加 --

semver-minor since 7.5.0

---e 之后说明是 end-of-options

$ node -e "console.log(process.argv)" -- -arg1 -arg2
[ '/Users/xxx/.yyy/node/v7.4.0/bin/node' ]

# 7.5.0 ~
$ node -e "console.log(process.argv)" -- -arg1 -arg2
[ '/Users/xxx/.yyy/node/v7.5.0/bin/node', '-arg1', '-arg2' ]

-- after -e <script> means end-of-options

PROCESS

添加 NODE_NO_WARNINGS

semver-minor since 7.5.0

通过设置 NODE_PRESERVE_SYMLINKS 环境变量为 1 关闭进程的 warning 信息。
参数 --no-warnings 效果同上。

process: add NODE_NO_WARNINGS environment variable

semver-minor since 7.1.0

通过设置 NODE_PRESERVE_SYMLINKS 环境变量为 1 开启符号链接。
参数 --preserve-symlinks 效果同上。

Add NODE_PRESERVE_SYMLINKS environment variable

添加 externalMemory

semver-minor since 7.2.0

返回 C++ 对象的内存使用量。

> console.log(util.inspect(process.memoryUsage()));
{ rss: 23371776,
  heapTotal: 10465280,
  heapUsed: 5756560 }

// 7.2.0 ~
> console.log(util.inspect(process.memoryUsage()));
{ rss: 23244800,
  heapTotal: 7692288,
  heapUsed: 4918392,
  external: 22846 }

process: add externalMemory to process.memoryUsage

PROMISE

堆栈的优化

通过参数 --trace-warnings 开启优化 PromiseUnhandledPromiseRejectionWarning 堆栈信息。

$ node --trace-warnings
> const p = Promise.reject(new Error('This was rejected'))
> setImmediate(() => p.catch(() => {}))
(node:40981) Error: This was rejected
...
(node:40981) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated.
In the future, promise rejections that are not handled will terminate
the Node.js process with a non-zero exit code.
...
(node:40981) PromiseRejectionHandledWarning:
Promise rejection was handled asynchronously (rejection id: 1)
    at getAsynchronousRejectionWarningObject (internal/process/promises.js:12:10)
    at rejectionHandled (internal/process/promises.js:42:21)
    ...

promise: better stack traces for --trace-warnings

URL

WHATWG-URL

NODE-WHATWG-URL

UTIL

添加 [Array] 的符号

semver-major

对于嵌套数组,改为写为[Array]。

> const obj = util.inspect({'a': {'b': ['c']}}, false, 1)
> obj
'{ a: { b: [Object] } }'
> assert.strictEqual(obj, '{ a: { b: [Array] } }')
AssertionError: '{ a: { b: [Object] } }' === '{ a: { b: [Array] } }'

// 8.0.0
> obj
'{ a: { b: [Array] } }'
> assert.strictEqual(obj, '{ a: { b: [Array] } }')
undefined

nodejs/node#12046

添加新的 format

semver-minor since 7.9.0

  • %i: 格式化为整型
  • %f: 格式化为浮点型
> util.format('%d', 42.2)
'42.2'
> util.format('%i', 42.2)
'%i 42.2'
> util.format('%f', 42.2)
'%f 42.2'

// 8.0.0
> util.format('%d', 42.2)
'42.2'
> util.format('%i', 42.2)
'42'
> util.format('%f', 42.2)
'42.2'

util: add %i and %f formatting specifiers

从 util.format 中删除 SIMD

semver-major

这是因为 V8 已经不支持 SIMD 了。

$ node --harmony_simd

> assert.strictEqual(util.inspect(SIMD.Int32x4()), 'Int32x4 [ 0, 0, 0, 0 ]');
undefined

// 8.0.0
> assert.strictEqual(util.inspect(SIMD.Int32x4()), 'Int32x4 [ 0, 0, 0, 0 ]');
AssertionError: 'Int32x4 {}' === 'Int32x4 [ 0, 0, 0, 0 ]'

lib: remove simd support from util.format()

url.format 支持 WHATWG-URL

semver-minor since 7.6.0

> const URL = require('url').URL
> const myURL = new URL('http://example.org/?a=b#c')
> const str = url.format(myURL, {fragment: false, search: false})
> console.log(str)
http://example.org/?a=b#c

// 7.6.0 ~
> console.log(str)
http://example.org/

url: extend url.format to support WHATWG URL

v8对象

添加 does_zap_garbage

semver-minor since 7.2.0

v8 HeapStatistics 已经添加 does_zap_garbage
这是一种覆盖堆垃圾的模式。
通过参数--zap_code_space 开启。
malloced_memory, peak_malloced_memory 同时被添加到字段中。

> v8.getHeapStatistics()
{ total_heap_size: 7168000,
  total_heap_size_executable: 3670016,
  total_physical_size: 6132432,
  total_available_size: 1492201768,
  used_heap_size: 5416688,
  heap_size_limit: 1501560832,
  malloced_memory: 8192,
  peak_malloced_memory: 1412016,
  does_zap_garbage: 0 }

src: Add does_zap_garbage to v8 HeapStatistics

正在进行中的任务

迁移 errors 到 internal/errors.js

semver-major

在当前 Node 的核心模块中,每个文件中都会定义错误信息。现在的任务是统一到internal/errors.js

/lib/internal/errors.js
Tracking Issue: Migrate errors to internal/errors.js

支持 Uint8Array

  1. open: stream: support Uint8Array input to methods
  2. open: string_decoder: support Uint8Array input to methods
  3. closed: zlib: support Uint8Array in convenience methods
  4. closed: dgram: support Uint8Array input to send()
  5. closed: tls: support Uint8Arrays for protocol list buffers
  6. closed: crypto: support Uint8Array prime in createDH
  7. closed: child_process: support Uint8Array input to methods
  8. closed: fs: support Uint8Array input to methods
  9. closed: buffer: allow Uint8Array input to methods

添加 promisify()

semver-minor since ???

这可以说是最令人兴奋的 PR 了。通过 util.promisify,原生模块完美支持 async/await

const util = require('util');

const setTimeoutPromise = util.promisify(setTimeout);
setTimeoutPromise(2000, 'foobar').then((value) => console.log(value));

const stat = util.promisify(require('fs').stat);
async function callStat() {
  const stats = await stat('.');
  console.log(`This directory is owned by ${stats.uid}`);
}

callStat();

util: add util.promisify()