原文連結:10 modern Node.js runtime features to start using in 2024, 2024.5.29, by Liran Tal。翻譯時有刪改。
伺服器端 JavaScript 執行時進來充滿了創新,例如 Bun 憑藉相容的 Node.js API 取得了長足進步,而 Node.js 執行時則進一步提供了豐富的標準庫和執行時功能。
時間進入 2024 年,是時候瞭解 Node.js 執行時所提供的最新特性和功能了。這樣做不僅是爲了“與時俱進”,更是爲了利用現代 API 的力量來編寫更高效、效能更安全的程式碼。
下面我們就將詳細探討每個開發人員在 2024 年都應該開始使用的 10 項現代 Node.js 執行時功能。
先決條件:Node.js LTS 版本
在開始探索這些現代功能之前,請確保您使用的是 Node.js LTS(長期支援)版本。在撰寫本文時,最新的 Node.js LTS 版本是 v20.14.0。
使用以下指令,檢查 Node.js 版本:
$ node --version v20.14.0
如果您當前沒有使用 LTS 版本,可以考慮使用 fnm 或 nvm 等版本管理器在不同 Node.js 版本之間輕鬆切換。
Node.js 20 有哪些新功能?
以下我們將介紹 Node.js 最新版本中引入的一些新功能。有些是穩定的,有些仍處於實驗階段,還有一些在之前的版本中就已經得到了支援,只不過你可能還還沒有聽說過。
討論的主題包括:
Node.js 測試執行器(test runner)
Node.js 原生 mocking
Node.js 原生測試覆蓋率
Node.js 監聽模式(watch mode)
Node.js corepack
Node.js import.meta.file:訪問
__dirname
和__file
Node.js 原生計時器 Promise
Node.js 許可權模組(permissions module)
Node.js 策略模組(policy module)
Node.js 測試執行器
在 Node.js 引入原生測試執行支援之前,你會用什麼工具進行測試呢?當然,這裏有一些流行的選項:vitest、jest、mocha 或者 node-tap。
下面,我們將瞭解如何在開發工作流程中使用 Node.js 原生測試執行器進行測試。
首先,你需要將 Node.js 中的 test
模組匯入到測試檔案中,如下所示:
import { test } from 'node:test';
使用 node:test 執行單個測試
要建立單個測試,你可以使用 test 函式,傳入測試的名稱和回撥函式。回撥函式是你定義測試邏輯的地方。
import { test } from "node:test"; import assert from "node:assert"; import { add } from "../src/math.js"; test("should add two numbers", () => { const result = add(1, 2); assert.strictEqual(result, 3); }); test("should fail to add strings", () => { assert.throws(() => { add("1", "2"); }); });
要執行此測試,請使用 node --test [測試檔案]
命令:
node --test tests/math.test.js
Node.js 測試執行程式可以自動檢測並執行專案中的測試檔案。按照約定,這些檔案應以 .test.js
結尾,但這個約定並不需要嚴格遵守。
如果省略測試檔案位置引數(positional argument),那麼 Node.js 測試執行程式將應用一些啓發式和 glob 模式匹配來查詢測試檔案——例如 test/
/tests/
中的所有檔案,或是帶有 test-
字首或 .test
字尾的資料夾或檔案。
你還可以透過 glob 語法匹配測試檔案:
node --test '**/*.test.js'
透過 node:assert 使用斷言
Node.js 測試執行程式透過內建的 assert 模組支援斷言。你可以使用 assert.strictEqual 等不同方法來驗證測試結果。
import assert from 'node:assert'; test('Test 1', () => { assert.strictEqual(1 + 1, 2); });
執行測試套件 & 使用測試 hooks 函式
describe 函式用於將相關測試分組到測試套件中。這使您的測試更有組織性並且更易於管理。
import { test, describe } from "node:test"; describe('My Test Suite', () => { test('Test 1', () => { // Test 1 logic }); test('Test 2', () => { // Test 2 logic }); });
測試 hooks 函式是在測試之前或之後執行的特殊函式,它們對於設定或清理測試環境很有用。
test.beforeEach(() => { // Runs before each test }); test.afterEach(() => { // Runs after each test });
你還可以選擇使用 test.skip 函式跳過測試。這在某些你想暫時忽略特定測試時很有幫助。
test.skip('My skipped test', () => { // Test logic });
此外,Node.js 測試執行器提供了不同的報告器(reporter),以各種方式格式化和顯示測試結果,使用 --reporter 選項指定。
node --test --test-reporter=tap
Jest 的缺陷
雖然 Jest 是 Node.js 社羣中流行的測試框架,但它具有某些缺點,讓原生 Node.js 測試執行器成為更具吸引力的選擇。
Jest 即使作為一個開發依賴項安裝後,你將擁有一個包含不同許可證的 277 個其他依賴項,這些許可證包括 MIT、Apache-2.0、CC-BY-4.0 和 1 個未知許可證。你知道嗎?
Jest 會修改全域性變數,這可能會導致測試中出現意外行為。
instanceof 運算子在 Jest 中並不總是按預期工作
Jest 為你的專案引入了大量的依賴項,你需要關心使用過程中的安全問題和開發時依賴項可能會引起的其他問題
Jest 可能比原生 Node.js 測試執行器要慢
原生 Node.js 測試執行器的其他強大功能包括執行子測試和併發測試。
子測試允許每個 test() 回撥接收一個 context 引數,這個引數允許你使用 context.test 方式建立巢狀測試。
如果你知道如何很好地使用併發測試並能避免競爭條件(racing condition),那麼併發測試是一個很棒的功能。只需將 concurrency: true 作為第二個引數傳遞給 describe() 測試套件即可。
什麼是測試執行器?
測試執行器是一種軟體工具,允許開發人員對其程式碼進行管理並執行自動化測試。 Node.js 測試執行程式幫助你與 Node.js 可以無縫協作,為在 Node.js 應用程式上編寫和執行測試提供了一條龍服務。
Node.js 原生 mocking
Mocking(模擬)是開發人員用來隔離程式碼進行測試的一種策略,Node.js 執行時引入了原生模擬功能,這對開發人員更加有效地理解和操作程式碼幫助很多。
在此之前,你可能使用過其他測試框架的模擬功能,例如 Jest 的 jest.spyOn
或 mockResolvedValueOncel
。當你想要避免在測試中執行實際程式碼(例如 HTTP 請求或檔案系統 API)並期望稍後可以呼叫過程時,模擬就非常有用了。
與其他 Node.js 執行時功能(例如監聽和測試覆蓋率功能)不同,模擬並未宣告為實驗性的。但是,它後續可能會迎來更多更改,因為它實在 Node.js 18 中才引入的新功能。
使用 node:test 中的 mock 進行原生模擬測試
讓我們看看如何在實際示例中使用 Node.js 原生模擬功能。測試執行器和(模組)模擬功能現已作為穩定功能在 Node.js 20 LTS 中提供了。
我們會用到一個工具模組 dotenv.js
,它的作用是從 .env
檔案載入環境變數。下面,我們將使用一個測試檔案 dotenv.test.js
來測試 dotenv.js
模組。
dotenv.js 內容如下:
import fs from "node:fs/promises"; export async function loadEnv(path = ".env") { const rawDataEnv = await fs.readFile(path, "utf8"); const env = {}; rawDataEnv.split("\n").forEach((line) => { const [key, value] = line.split("="); env[key] = value; }); return env; }
在 dotenv.js 檔案中,我們有一個非同步函式 loadEnv ,它使用 fs.readFile 方法讀取檔案並將檔案內容拆分為鍵值對儲存在 env 物件中並返回。
現在,讓我們看看如何使用 Node.js 中的原生模擬功能來測試這個函式。
// dotenv.test.js import { describe, test, mock } from "node:test"; import assert from "node:assert"; import fs from "node:fs/promises"; import { loadEnv } from "../src/dotenv.js"; describe("dotenv test suite", () => { test("should load env file", async () => { const mockImplementation = async (path) => { return "PORT=3000\n"; }; const mockedReadFile = mock.method(fs, "readFile", mockImplementation); const env = await loadEnv(".env"); assert.strictEqual(env.PORT, "3000"); assert.strictEqual(mockedReadFile.mock.calls.length, 1); }); });
在測試檔案中,我們從 node:test 匯入 mock 方法,用它來建立 fs.readFile 的模擬實現。在模擬實現中,無論傳遞的檔案路徑如何,我們都會返回一個字串 "PORT=3000\n"。
然後我們呼叫 loadEnv 函式,並使用 assert 模組,我們檢查 2 個地方:
返回的物件有一個值為 "3000" 的 PORT 屬性
fs.readFile 方法只被呼叫了一次
透過使用 Node.js 中的原生模擬功能,我們能夠有效地將 loadEnv 函式與檔案系統隔離並單獨測試。Node.js 20 的模擬功能還支援模擬計時器。
什麼是 mocking?
在軟體測試中,模擬是用自定義實現替換特定模組的實際功能的過程。主要目標是將正在測試的程式碼與外部依賴項隔離,確保測試僅驗證單元的功能而不驗證依賴項。模擬還能幫助你你模擬不同的測試場景,例如來自依賴項的錯誤,這可能很難在真實環境驗證。
Node.js 原生測試覆蓋率
什麼是測試覆蓋率?
測試覆蓋率是軟體測試中使用的一個指標。它可以幫助開發人員瞭解應用程式原始碼的測試程度。這很重要,因為它展示了程式碼庫中未經測試的區域,使開發人員能夠識別其軟體中的潛在弱點。
為什麼測試覆蓋率很重要?因為它是透過減少 BUG 數量和避免迴歸來確保軟體的質量。此外,它還可以深入瞭解測試的有效性,並幫助指導我們構建更強大、可靠和安全的應用程式。
使用原生 Node.js 測試覆蓋率
從 v20 開始,Node.js 執行時包含測試覆蓋率的功能。不過,原生 Node.js 測試覆蓋率目前被標記為實驗性功能,表示雖然現在可用,但在未來版本中可能會發生一些變化。
要使用原生 Node.js 測試覆蓋率,你需要使用 --experimental-coverage 命令列標誌。以下示例說明了如何在執行專案測試的 package.json 指令碼欄位中新增 test:coverage 指令碼:
{ "scripts": { "test": "node --test ./tests", "test:coverage": "node --experimental-coverage --test ./tests" } }
test:coverage 指令碼中透過新增 --experimental-coverage 標誌就能在執行測試期間生成覆蓋率資料了。
執行 npm run test:coverage 後,你會看到類似於以下內容的輸出:
ℹ tests 7 ℹ suites 4 ℹ pass 5 ℹ fail 0 ℹ cancelled 0 ℹ skipped 1 ℹ todo 1 ℹ duration_ms 84.018917 ℹ start of coverage report ℹ --------------------------------------------------------------------- ℹ file | line % | branch % | funcs % | uncovered lines ℹ --------------------------------------------------------------------- ℹ src/dotenv.js | 100.00 | 100.00 | 100.00 | ℹ src/math.js | 100.00 | 100.00 | 100.00 | ℹ tests/dotenv.test.js | 100.00 | 100.00 | 100.00 | ℹ tests/math.test.js | 94.64 | 100.00 | 91.67 | 24-26 ℹ --------------------------------------------------------------------- ℹ all files | 96.74 | 100.00 | 94.44 | ℹ --------------------------------------------------------------------- ℹ end of coverage report
這個報告展示了目前的測試所覆蓋的語句、分支、函式和行的百分佔比。
Node.js 原生測試覆蓋率是一個強大的工具,可以幫助你提高 Node.js 應用程式的質量。儘管它目前被標記為實驗性功能,但它可以為你的測試覆蓋範圍提供有價值的見解並指導你的測試工作。透過瞭解和利用這個功能,可以確保你的程式碼健壯、可靠且安全。
Node.js 監聽模式
Node.js 監聽模式是一項強大的開發人員功能,允許實時跟蹤 Node.js 檔案的更改並自動重新執行指令碼。
在深入瞭解 Node.js 的原生監聽功能之前,有必要了解一下 nodemon,它是一個流行的工具程式,有助於滿足 Node.js 早期版本中的這一需求。Nodemon 是一個命令列介面 (CLI) 工具程式,用於在檔案目錄中檢測到任何更改時重新啟動 Node.js 應用程式。
npm install -g nodemon nodemon
這個功能在開發過程中特別有用,避免每次修改檔案時手動重新啟動,可以節省時間並提高工作效率。
隨著 Node.js 本身的進步,平臺本身提供了內建功能來實現相同的結果,也不需要在專案中安裝額外的第三方依賴項,例如 nodemon。
值得注意的是,Node.js 中的原生監聽模式功能仍處於實驗階段,未來可能會發生變化。請始終確保你使用的 Node.js 版本支援這個功能。
使用 Node.js 20 原生監聽功能
Node.js 20 使用 --watch 命令列標誌引入了原生檔案監聽功能。這個功能使用起來很簡單,甚至支援匹配模式來滿足更復雜的檔案監聽需要。
node --watch app.js
同事,支援使用 glob 語法做匹配,當你想要監聽一組與特定模式匹配的檔案時,這特別有用:
node --watch 'lib/**/*.js' app.js
--watch 標誌還可以與 --test 結合使用,這樣修改測試檔案時也能重新執行測試:
node --watch --test '**/*.test.js'
需要注意的是,從 Node.js v20 開始,監聽模式功能仍被標記為實驗性的,表示現在功能功能雖然齊全,但後續可能會有修改,不像其他非實驗功能那麼穩定或經過最佳化。在實際s使用時,可能會遇到一些 quirks 或 BUG。
Node.js Corepack
Node.js Corepack 是一個值得探索的有趣功能,它是在 Node.js 16 中引入的,至今仍被標記為實驗性的。
什麼是 Corepack?
Corepack 是一個零執行時依賴專案,充當 Node.js 專案和所要使用的包管理器之間的橋樑。安裝後,它提供了一個名為 corepack
的程式,開發人員可以在他們的專案中使用它,確保使用了正確的包管理器,而不必擔心其全域性安裝。
為什麼要使用 Corepack?
作為 JavaScript 開發人員,我們經常處理多個專案,每個專案可能都有自己首選的包管理器。比如,一個專案使用 pnpm 管理其依賴項,而另一個專案使用 yarn 管理其依賴項,導致你需要在不同的包管理器之間切換。
這個可能就會導致衝突和不一致。Corepack 允許每個專案以無縫的方式指定和使用其首選的包管理器,從而解決了這個問題。
此外,Corepack 在你的專案和全域性系統之間提供一層隔離,確保即使全域性包升級或刪除,你的專案也能正常執行,這提高了專案的一致性和可靠性。
安裝和使用 Corepack
安裝 Corepack 非常簡單。由於它從版本 16 開始與 Node.js 捆綁在一起,因此你只需安裝或升級 Node.js 到這個或更高版本即可。
安裝後,你可以在 package.json 檔案中為專案指定包管理器,如下所示:
{ "packageManager": "yarn@2.4.1" }
然後,你可以在專案中啟用 Corepack,如下所示:
corepack enable
如果你在專案目錄下的終端中輸入 yarn
,即便當前沒有安裝 Yarn,Corepack 會自動檢測並安裝正確版本的。
這種做法會確保始終使用 Yarn v2.4.1 版本來安裝專案的依賴項,無論系統上安裝的全域性 Yarn 版本如何。
如果你想全域性安裝 Yarn 或使用特定版本,您可以執行:
corepack install --global yarn@stable
Corepack 仍然是一個實驗性功能
儘管在 Node.js 16 中引入了 Corepack,但它仍然被標記為實驗性的。這表示雖然它現在設計並執行的良好,但仍在積極開發中,並且其行為的某些方面將來可能會發生變化。
總之,Corepack 易於安裝、使用簡單,能為你的專案提供額外的可靠性,這絕對是一個值得探索並融入到你的開發工作流程中的功能。
Node.js .env
載入器
應用程式的配置至關重要,作為 Node.js 開發人員,通常會面臨管理 API 憑據、伺服器埠號或資料庫等配置的需求。
而我們需要一種方法,能在不改變原始碼的情況下,為不同的環境提供不同的設定。在 Node.js 應用程式中實現這個目的的一種流行方法是使用儲存在 .env
檔案中的環境變數。
npm 包 dotenv
在 Node.js 引入對載入 .env 檔案的原生支援之前,開發人員主要使用 dotenv npm 包。dotenv 包將環境變數從 .env 檔案載入到 process.env
中,然後在整個應用程式中都可訪問了。
以下是 dotenv 包的典型用法:
require('dotenv').config(); console.log(process.env.MY_VARIABLE);
很有用,但需要向你的專案新增額外的依賴項。而在透過引入原生 .env 載入器後,你現在可以直接載入環境變數,而無需任何外部包。
使用原生 .env
載入器
從 Node.js 20 開始,內建了從 .env 檔案載入環境變數的功能。這個功能正在積極開發中,但對開發人員來說已經遊戲改變者了。
要載入 .env 檔案,我們可以在啟動 Node.js 應用程式時使用 --env-file
CLI 標誌。該標誌指定要載入的 .env 檔案的路徑。
node --env-file=./.env index.js
這會將環境變數從指定的 .env 檔案載入到 process.env 中。然後,這些變數就可以像以前一樣在你的應用程式中那樣使用了。
載入多個 .env
檔案
Node.js .env 載入器還支援載入多個 .env 檔案。當你針對不同環境(例如開發、測試、生產)有不同的環境變數集時,這非常有用。
你可以指定多個 --env-file 標誌來載入多個檔案。檔案按照指定的順序載入,後面檔案中的變數會覆蓋前面檔案中的變數。
node --env-file=./.env.default --env-file=./.env.development index.js
在此示例中, ./.env.default 包含預設變數, ./.env.development 包含特定於開發環境的變數。 ./.env.development、./.env.default 中同時存在的變數,前者都將覆蓋後者中的變數。
Node.js 中對載入 .env 檔案的原生支援對於 Node.js 開發人員來說是一項重大改進,它簡化了配置管理並消除了對額外軟體包的需要。
開始在 Node.js 應用程式中使用 --env-file
CLI 標誌並親身體驗便利吧!
Node.js import.meta
支援 __dirname
和 __file
__dirname
和 __file
其實是 CommonJS 模組中提供的變數,通常用來作為獲取當前檔案的目錄名稱和檔案路徑的方式。然而,直到最近,這些在 ESM 上還不容易獲得,N你必須使用以下程式碼來提取 __dirname
:
import url from 'url' import path from 'path' const dirname = path.dirname(url.fileURLToPath(import.meta.url))
或者,如果你是 Matteo Collina 的粉絲,您可能已經選擇使用 Matteo 的 desm npm 包。
Node.js 不斷髮展,為開發人員提供更有效的方法來處理檔案和路徑操作。Node.js v20.11.0 和 Node.js v21.2.0 中引入了一項對 Node.js 開發人員的重大更改,即內建了對 import.meta.dirname 和 import.meta.filename 的支援。
使用 Node.js import.meta.filename
和 import.meta.dirname
值得慶幸的是,隨著 import.meta.filename 和 import.meta.dirname 的引入,這個過程變得更加容易,讓我們看一下使用新功能載入配置檔案的示例。
假設你需要載入的 JavaScript 檔案所在目錄中有一個 YAML 配置檔案,你可以這樣做:
import fs from 'fs'; const { dirname: __dirname, filename: __filename } = import.meta; const projectSetup = fs.readFileSync(`${__dirname}/setup.yml`, "utf8"); console.log(projectSetup);
在這個例子中,我們使用 import.meta.dirname 來獲取當前檔案的目錄名,並將其分配給 __dirname
變數,以便符合 CommonJS 的命名約定。
Node.js 原生計時器 Promise
我們都知道,Node.js 是一種基於 Chrome V8 JavaScript 引擎構建的流行 JavaScript 執行時,始終致力於透過不斷的更新和新功能讓開發人員的生活變得更加輕鬆。
儘管 Node.js 在 Node.js v15 中就引入了原生計時器 Promise 的支援,但我承認我並沒有經常使用它們。
JavaScript 計時器的簡要回顧
在深入探討本機計時器 Promise 之前,我們來簡要回顧一下 JavaScript setTimeout() 和 setInterval() 計時器。
setTimeout() API 是一個 JavaScript 函式,一旦計時器到期,它就會執行函式或指定的程式碼段。
setTimeout(function(){ console.log("Hello World!"); }, 3000);
在上面的程式碼中,“Hello World!”將在 3 秒(3000 毫秒)後列印到控制檯。
另一方面,setInterval() 則用於重複執行指定的函式,每次呼叫之間間隔指定延遲。
setInterval(function(){ console.log("Hello again!"); }, 2000);
在上面的程式碼中,“Hello again!”每 2 秒(2000 毫秒)就會列印到控制檯。
舊方法:用 Promise 包裝 setTimeout()
在過去,開發人員通常必須人為地包裝 setTimeout() 函式,並透過 Promise 使用它,這樣做是爲了將 setTimeout() 和 async/await 一起使用。
下面是一個封裝的示例:
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function demo() { console.log('Taking a break...'); await sleep(2000); console.log('Two seconds later...'); } demo();
這將列印"Taking a break...",等待兩秒鐘,然後列印 "Two seconds later..."。
這麼做沒問題,卻給程式碼增加了不必要的複雜性。
Node.js Native 計時器 Promise
使用 Node.js Native 計時器 Promise,我們不再需要將 setTimeout() 包裝在 Promise 中。相反,我們可以直接將 setTimeout() 與 async/await 一起使用。
以下是如何使用 Node.js 原生計時器 Promise 的示例:
const { setTimeout, } = require('node:timers/promises'); setTimeout(2000, 'Two seconds later...').then((res) => { console.log(res); }); console.log('Taking a break...');
在上面的程式碼中,setTimeout() 是從 node:timers/promises 匯入的。然後我們直接將其與 async/await 一起使用。它將列印"Taking a break...",等待兩秒鐘,然後列印 "Two seconds later..."。
這極大地簡化了非同步程式設計,並使程式碼更易於閱讀、編寫和維護。
Node.js 許可權模型
目前加入 Node.js TSC 的 Rafael Gonzaga 恢復了 Node.js 許可權模組的開發工作,這個模組與 Deno 類似,提供了一組程序級的可配置資源約束。
出於安全和合規性原因,管理和控制 Node.js 應用程式可以訪問的資源變得越來越重要。因為有時候你也不知道專案使用的 npm 包突然被黑了,或是誤下載了一個惡意 npm 包等情況的發生。
在這方面,Node.js 引入了一項稱為許可權模組的實驗性功能,用於管理 Node.js 應用程式中的資源許可權。使用 --experimental-permission 命令列標誌可以啟用這個功能。
Node.js 資源許可權模型
Node.js 中的許可權模型提供了一套抽象來管理對各種資源(如檔案系統、網路、環境變數和工作執行緒等)的訪問。當你想要限制應用程式的某個部分可以訪問的資源時,這個功能就很有用。
你可以使用許可權模型的常見資源約束,包括:
使用 --allow-fs-read=* 和 --allow-fs-write=* 進行檔案系統讀寫,可以指定目錄和特定檔案路徑,還可以透過重複標誌來限定多種資源
使用 --allow-child-process 進行子程序呼叫
使用 --allow-worker 進行工作執行緒呼叫
Node.js 許可權模型還透過 process.permission.has(resource, value) 提供執行時 API,以允許查詢特定訪問許可權。
如果你嘗試訪問不能允許的資源,例如讀取 .env 檔案,就會看到 ERR_ACCESS_DENIED 錯誤:
> start:protected > node --env-file=.env --experimental-permission server.js node:internal/modules/cjs/loader:197 const result = internalModuleStat(filename); ^ Error: Access to this API has been restricted at stat (node:internal/modules/cjs/loader:197:18) at Module._findPath (node:internal/modules/cjs/loader:682:16) at resolveMainPath (node:internal/modules/run_main:28:23) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:24) at node:internal/main/run_main_module:28:49 { code: 'ERR_ACCESS_DENIED', permission: 'FileSystemRead', resource: '/Users/lirantal/repos/modern-nodejs-runtime-features-2024/server.js' } Node.js v20.14.0
Node.js 許可權模型示例
考慮這樣一個場,有一個處理檔案上傳的 Node.js 應用程式。你希望限制應用程式的這一部分,以便它只能訪問儲存上傳檔案的特定目錄。
使用 --experimental-permission 標誌啟動 Node.js 應用程式時啟用實驗性許可權功能。
node --experimental-permission ./app.js
我們還想專門允許應用程式讀取 2 個受信任的檔案 .env 和 setup.yml ,因此我們需要將上面的內容更新為:
node --experimental-permission --allow-fs-write=/tmp/uploads --allow-fs-read=.env --allow-fs-read=setup.yml ./app.js
這樣一來,如果應用程式對提供的上傳路徑之外的檔案進行寫入,就會因報錯而終止。
請參閱以下程式碼示例,瞭解如何透過 try/catch 包裝資源訪問的地方,或透過許可權檢 API 來進行防禦性程式設計,確保不會因為許可權模型的引入導致出現的異常意外終止程式。
const { dirname: __dirname, filename: __filename } = import.meta; // @TODO to avoid the Node.js resource permission issue you should update // the path to be `setup.yml` in the current directory and not `../setup.yml`. // the outside path for setup.yml was only changed in the source code to // show you how Node.js resource permission module will halt if trying to access // something outside the current directory. const filePath = `${__dirname}/../setup.yml`; try { const projectSetup = fs.readFileSync(filePath, "utf8"); // @TODO do something with projectSetup if you want to } catch (error) { console.error(error.code); } // @TODO or consider using the permissions runtime API check: if (!process.permission.has("read", filePath)) { console.error("no permissions to read file at", filePath); }
值得注意的是,Node.js 中的許可權功能仍處於實驗階段,未來可能會發生變化。
Node.js 策略模組
Node.js 策略模組是一項安全功能,旨在防止惡意代碼在 Node.js 應用程式中載入和執行。雖然它不會追蹤載入程式碼的來源,但它提供了針對潛在威脅的可靠防禦機制。
策略模組利用 --experimental-policy CLI 標誌來啟用基於策略的程式碼載入。此標誌採用策略清單檔案(JSON 格式)作為引數。例如, --experimental-policy=policy.json。
策略清單檔案包含 Node.js 在載入模組時遵循的策略,這提供了一種強大的方法來控制載入到應用程式中的程式碼的性質。
使用 Node.js 策略模組
讓我們透過一個簡單的示例來演示如何使用 Node.js 策略模組:
建立策略檔案。這個檔案應該是一個 JSON 檔案,指定應用程式載入模組的策略。我們稱之為 policy.json。
類似:
{ "resources": { "./moduleA.js": { "integrity": "sha384-xxxxx" }, "./moduleB.js": { "integrity": "sha384-yyyyy" } } }
這個策略檔案指定 moduleA.js 和 moduleB.js 應載入特定的 integrity 值。
然而,為所有直接和間接依賴項生成策略檔案並不簡單。幾年前,Bradley Meck 建立了 node-policy npm 包,它提供了一個 CLI 來自動生成策略檔案。
使用 --experimental-policy 標誌執行 Node.js 應用程式
node --experimental-policy=policy.json app.js
這個命令告訴 Node.js 在載入 app.js 中的模組時遵守 policy.json 中指定的策略。
爲了防止篡改策略檔案,你可以使用 --policy-integrity 標誌為策略檔案本身提供 integrity 值:
node --experimental-policy=policy.json --policy-integrity="sha384-zzzzz" app.js
這個命令可確保保持策略檔案的完整性,即使磁碟上的檔案發生更改也是如此。
integrity 策略的注意事項
Node.js 執行時沒有內建功能來生成或管理策略檔案,並且可能會帶來困難,例如根據生產環境與開發環境以及動態模組匯入來管理不同的策略。
另一個需要注意的是,如果你已經擁有處於當前狀態的惡意 npm 包,那麼生成模組完整性策略檔案就為時已晚。
我個人建議你可以持續關注這方面的更新,並慢慢嘗試逐步採用此功能。
有關 Node.js 策略模組的更多資訊,你可以檢視有關向 Node.js 引入實驗性完整性策略這篇文章,其中提供了有關使用 Node.js 策略完整性的更詳細的詳細教程。
總結
當我們瀏覽了你應該在 2024 年開始使用的現代 Node.js 執行時功能時,很明顯,這些功能幫助你簡化開發流程、增強應用程式效能並加強安全性。這些功能不僅僅是時尚,也重新定義了 Node.js 開發方式的巨大潛力。