Node.js 8 说明

2,263 阅读6分钟
原文链接: zhuanlan.zhihu.com

原PPT地址


主要内容

  • 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修复
  • 安全更新
  • 文档更新
  • 无破坏性的功能更新

发布计划

版本表格


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) 中的状况

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 可以较少系统内存使用情况。

如图:



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)(nodejs/nan) 搞不定。

N-API

目前已经在 ChakraCore 中验证了

abi-stable-node/tree/doc

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


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.request 和 https.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

添加 NODE_PRESERVE_SYMLINKS

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 开启优化 Promise 和 UnhandledPromiseRejectionWarning 堆栈信息。

$ 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()