跳轉到

Node.js 從 14 升 18 的注意事項

Node.js v14 版將於 2023-04-30 起不再支援(EOL), 而 v16 版將於 2023-09-11 過期, 由於時間相差不大,勢必會有許多人從 v14 直接升到 v18(2025-04-30)。

這裡會列出一些需要注意的點。

還有其他地方也有摘要整理嗎

當然有,你除了直接吃生魚片:

也可以吃其他人煮過的:

ECMA Script

v14 到 v18 支援的協定從 ES2019 升到 ES2023, 但要注意 v14 並不是每個 ES2019 以上的功能都不支援, 例如 v14 支援 private class method,但這卻是 ES2022 才開始支援的 API:

class MyClass {
    #myPrivateField = 'this is private';
}

詳細的對照,你可以到 node.green 查看。

V8

Node.js 每次升版都會更新 V8 的版本, 所有版本的更新都可以到 Chrome road-map 查看。 v14 使用的版本是 8.6,而 v18 則是 10.1。

所謂 V8 10.1 版本, 就是對應 Chrome 101 版,詳見 V8 version numbering scheme

V8 的升版大致差異在於對 ES 的適應和效能的調校。 如果你想要知道 V8 新增或調整了哪些 API 你可以透過:

git log branch-heads/A.B..branch-heads/X.Y include/v8\*.h

來查看,詳見 api-changes

TypeScript

TypeScript 的設定也會因為 Node.js 升版而有改變,建議可以參考官方推薦的設定檔:

SSL

v14 使用的 openssl 版本是 v1.1.1,但是 v18 使用的 openssl 是 v3.0, 相關差異可以看 openssl migration_guide

對於網路服務來說,最需要注意的應該是 TLS 相關的差異。 在 v3.0 中,預設會拒絕 server 使用不安全的 renegotiation 機制, 詳見 RFC-5736 TLS Renegotiation Extension。 我們可以透過 openssl 的指令檢查你的服務是否符合這個協定:

$ openssl s_client -connect legacy-server.example.com:443
CONNECTED(00000005)
8056015BF87F0000:error:0A000152:SSL routines:final_renegotiate:unsafe legacy renegotiation disabled:ssl/statem/extensions.c:893:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 53 bytes and written 338 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : 0000
    Session-ID: 
    Session-ID-ctx: 
    Master-Key: 
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1681355997
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
---

可以注意到 Secure Renegotiation IS NOT supported 這個訊息, 代表這個服務使用不安全連線,所以請求方拒絕這次連線。 也因此如果你的環境還在使用舊版的 TLS 實作機制,就需要更新或設定。

封包上的差異

如果你透過 tcpdump 的手段來取得封包資訊時, 你可以在 server hello 的封包中,看到他缺少該 extension 的資訊。

Extension: renegotiation_info (len=1)
Type: renegotiation_info (65281)
Length: 1
Renegotiation Info extension
    Renegotiation info extension length: 0

如果環境很難改變,可以直接在 HTTP client 上做調整:

import { constants } from 'node:crypto'
axios.create({
  httpsAgent: new https.Agent({
    secureOptions: constants.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION,
  }),
});

也可以在啟動的時候餵給 OpenSSL 設定檔:

nodejs_conf = openssl_init

[openssl_init]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
Options = UnsafeLegacyRenegotiation

然後啟動 Node.js:

# 也可以透過環境變數 OPENSSL_CONF,但是下面優先權較高。
node --openssl-config=openssl.conf

過時功能

完整過時(deprecated)功能的列表可以參照官方文件。 但要注意這個文件包含歷來所有過時功能,至於 v14 到 v18 之間過時的功能, 可以透過比對 v14 的過時功能, 找出那些多出來的過時功能就是後面才新增的。

例如:DEP0153 dns.lookup and dnsPromises.lookup options type coercion。

這裡列出值得注意的點:

新功能

這裡整理一些有趣的新功能:

  • 異步的 setTimeout

    import { setTimeout } from 'timers/promises';
    async function run() {
        await setTimeout(5000);
    }
    
  • Event 和 EventTarget 的實作

  • 預設會設定服務的 Timeout:
  • headersTimeout:讀取 HTTP Header 超過 60 秒後會中斷連線
  • requestTimeout:處理 HTTP 請求超過 5 分鐘後會中斷連線
  • Blob,類似 Buffer, 但是允許多個線程對他進行讀取和修改。
  • BroadcastChannel,類似 EventTarget, 但是適合多線程的傳遞資訊。

下面是一些有趣但還在開發階段的功能:

  • 透過 Web Crypto API 來進行密碼學的應用, Node.js 一直都希望弭平瀏覽器和後端的差異。
  • fetch, 也是為了弭平和瀏覽器的差異,允許快速而簡單的做 HTTP 請求。

    const myInit = {
        method: "GET",
        headers: {
            Accept: "image/jpeg",
        },
        mode: "cors",
        cache: "default",
    };
    
    fetch(new Request("flowers.jpg"), myInit)
    
  • 原生單元測試框架:Test Runner

    import test from 'node:test';
    import assert from 'assert';
    
    test('synchronous passing test', (t) => {
        // This test passes because it does not throw an exception.
        assert.strictEqual(1, 1);
    });
    

結論

這次升版,幾乎是無痛升版。 也因為平常有在用靜態規則和單元測試來驗證,所以升版的時候也較有信心。 就放心給它升上去吧!