探索 Node.js v20 功能的實際應用
原文地址:https://blog.logrocket.com/exploring-node-js-v20-features/
翻譯:一川
1寫在前面
Node.js的每個版本都帶有令人興奮的新功能,v20也不例外。Node.js v20 于 2023 年 4 月 18 日發(fā)布。此版本附帶了更新的功能,旨在通過使用穩(wěn)定的內置測試運行程序減少依賴性,使 Node.js 比以往任何時候都更安全。Node.js v20還提供了創(chuàng)建單個可執(zhí)行應用程序的功能,這些應用程序可以在Windows,macOS和Linux上執(zhí)行,而無需在其系統上安裝Node.js。
在本教程中,我們將探討 Node.js v20 中提供的一些功能。您需要在計算機上安裝 Node.js v20 或更高版本,并熟悉創(chuàng)建和運行 Node.js 程序才能遵循。
2實驗性權限模型
Node.js v20 中引入的主要功能之一是實驗性的權限模型,旨在使 Node.js 更安全。長期以來,Node.js沒有權限系統。任何應用程序都可以與文件系統交互,甚至可以在用戶計算機上生成進程。
這為攻擊打開了大門,第三方軟件包在未經用戶同意的情況下訪問了用戶的計算機資源。為了降低風險,權限模型限制Node.js應用程序訪問文件系統、創(chuàng)建工作線程和生成子進程。
啟用權限模型后,用戶可以運行應用程序,而不必擔心惡意第三方包可以訪問機密文件、刪除或加密文件,甚至運行有害程序。權限模型還允許用戶在運行應用程序或運行時向 Node.js 應用授予特定權限。
實現權限模型
讓我們看看如何使用權限模型。使用您選擇的名稱創(chuàng)建一個目錄:
mkdir example_app
創(chuàng)建一個 package.json 文件:
npm init -y
添加到 type:module 支持 ESM 模塊:
{
...
"type": "module"
}
然后,創(chuàng)建包含以下內容的: data.txt
Text content that will be read in a Node.js program.
接下來,創(chuàng)建一個 index.js 文件并添加以下代碼來讀取 data.txt 該文件:
import { readFile } from "fs/promises";
async function readFileContents(filename) {
const content = await readFile(filename, "utf8"); // <!-
console.log(content);
}
readFileContents("./data.txt");
在這里,定義一個readFileContents函數,該函數接受 并從 filename 文件系統讀取文件。在函數中,調用readFile() fs模塊的方法讀取data.txt文件內容,然后將它們記錄在控制臺中。
現在,使用 node 以下命令運行文件:
node index.js
我們將在控制臺中看到如下所示的輸出:
Text content that will be read in a Node.js program.
若要啟用實驗性權限模型,請使用以下--experimental-permission標志運行文件:
node --experimental-permission index.js
這次我們收到如下所示的錯誤:
// output
node:internal/modules/cjs/loader:179
const result = internalModuleStat(filename);
^
Error: Access to this API has been restricted
at stat (node:internal/modules/cjs/loader:179:18)
at Module._findPath (node:internal/modules/cjs/loader:651:16)
at resolveMainPath (node:internal/modules/run_main:15:25)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:24)
at node:internal/main/run_main_module:23:47 {
code: 'ERR_ACCESS_DENIED',
permission: 'FileSystemRead',
resource: '/home/<your_username>/example_app/index.js'
}
錯誤消息讓我們知道我們沒有讀取文件的權限。權限模型限制了文件系統訪問。如果嘗試生成工作線程和子進程,也會收到一條錯誤消息。
要授予對文件或目錄的讀/寫訪問權限,可以使用該 --allow-fs-read 標志。以下是可以使用的一些選項:
- --allow-fs-read=* :通配符 * 提供對文件系統上所有目錄/文件的讀取訪問權限
- --allow-fs-read=/home/<username>/ :指定 /home/<username> 目錄應具有讀取訪問權限
- --allow-fs-read=/tmp/filename.txt :這只允許對給定文件名的讀取訪問,即 filename.txt
還可以使用標志 --allow-fs-write 授予寫入訪問權限。它還接受通配符、目錄路徑或文件名,如上所述。如前所述,權限模型還阻止 Node.js 程序創(chuàng)建子進程。要授予權限,我們需要傳遞: --allow-child-process
node --experimental-permission --allow-child-process index.js
為了允許創(chuàng)建工作線程來并行執(zhí)行任務,可以改用該 --allow-worker 標志:
node --experimental-permission --allow-worker index.js
因此,回到前面的示例,授予Node.js讀取data.txt.執(zhí)行此操作的更靈活的方法是提供 駐data.txt留的完整目錄路徑,如下所示:
node --experimental-permission --allow-fs-read=/home/<your_username>/example_app index.js
現在,程序可以毫無問題地讀取文件,盡管它提供了警告:
(node:8506) ExperimentalWarning: Permission is an experimental feature
(Use `node --trace-warnings ...` to show where the warning was created)
Text content that will be read in a Node.js program.
啟用權限模型后,可能不知道應用程序是否具有寫入或讀取文件系統的權限。為了防止運行時 ERR_ACCESS_DENIED 錯誤,權限模型允許在運行時檢查權限。
要檢查是否具有讀取權限,可以執(zhí)行以下操作:
if(process.permission.has('fs.read')) {
// proceed to read the file
}
或者,可以檢查目錄的權限。在下面的代碼中,檢查是否對給定目錄具有寫入權限:
if (process.permission.has('fs.write', '/home/username/') ) {
//do your thing
}
有了這個,我們現在就可以創(chuàng)建安全的應用程序并保護我們機器的資源在未經我們同意的情況下不被訪問。若要探索權限模型中的更多功能,請訪問文檔。
3穩(wěn)定的測試運行器
在Node.js v18發(fā)布之前,Node.js 中的所有測試運行器都是第三方軟件包,例如 Jest 和 Mocha。雖然它們?yōu)?nbsp;Node.js 社區(qū)提供了良好的服務,但第三方庫可能是不可預測的。
首先,Jest有一個錯誤,它會破壞 instanceof 控制器,產生誤報。解決方案是安裝另一個第三方軟件包。內置工具傾向于按預期工作并且更加標準化,如 Python 或 Go,它們都附帶內置測試運行器。
甚至像Deno和 Bun 這樣的更新的 JavaScript 運行時也帶有測試運行器。Node.js 一直被拋在后面,直到Node.js v18發(fā)布,它附帶了一個實驗性測試運行器。現在,隨著 Node.js v20 版本的發(fā)布,測試運行程序是穩(wěn)定的,可以在生產中使用。以下是測試運行程序中提供的一些功能:
- mocking
- skipping tests 跳過測試
- filtering tests 過濾測試
- test coverage collection 測試覆蓋率收集
- 監(jiān)視模式(實驗性),在檢測到更改時自動運行測試
使用測試運行程序
讓我們詳細探討測試運行程序。首先, calculator.js 使用以下代碼創(chuàng)建一個:
// calculator.js
export function add(x, y) {
return x + y;
}
export function divide(x, y) {
return x / y;
}
之后,創(chuàng)建一個 test 目錄中的文件目錄 calculator_test.js 。在文件中,添加以下代碼以使用內置測試運行程序測試函數:
// calculator_test.js
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { add, divide } from "../calculator.js";
describe("Calculator", () => {
it("can add two numbers", () => {
const result = add(2, 5);
assert.strictEqual(result, 7);
});
it("can divide two numbers", () => {
const result = divide(15, 5);
assert.strictEqual(result, 3);
});
});
在上面的代碼中,我們從 導入 describe node:test / it 關鍵字,如果您使用過 Jest,應該很熟悉。我們也 assert 從 . node:assert/strict 然后,我們測試 divide() 和 add() 函數是否按預期工作。按如下方式運行測試:
node --test
運行測試將生成與以下內容匹配的輸出:
? Calculator
? can add two numbers (0.984478ms)
? can divide two numbers (0.291951ms)
? Calculator (5.135785ms)
? tests 2
? suites 1
? pass 2
? fail 0
? cancelled 0
? skipped 0
? todo 0
? duration_ms 158.853226
當我們運行測試時,內置運行器會搜索所有后綴為 .js 、 .cjs 的JavaScript測試文件,前提是 .mjs :
- 它們駐留在名為 test
- 文件名以 test-
- 文件名以 、 -test 或_test結尾 _test
我們還可以在運行時 node --test 提供包含測試的目錄。如果我們想跳過一些測試,我們需要提供該 skip: true 選項作為 it 塊的第二個參數:
...
describe("Calculator", () => {
it("can add two numbers", () => {
const result = add(2, 5);
assert.strictEqual(result, 7);
});
// skip test
it("can divide two numbers", { skip: true }, () => {
const result = divide(15, 5);
assert.strictEqual(result, 3);
});
}
)
當重新運行測試時,將看到只有一個測試運行:
? Calculator
? can add two numbers (0.954955ms)
﹣ can divide two numbers (0.214886ms) # SKIP
? Calculator (5.111238ms)
...
Node.js v20 還附帶了一個實驗性監(jiān)視模式,一旦檢測到測試文件中的更改,它就可以自動運行測試。我們需要傳遞 --watch 標志并提供目錄以在監(jiān)視模式下運行測試:
node --test --watch test/*.js
如果更改文件,Node.js 將自動選取更改并重新運行測試。
只是觸及了測試運行程序可以做什么的表面。查看文檔以繼續(xù)探索它。
4V8 JavaScript 引擎更新到 v11.3
Node.js建立在高性能的V8 JavaScript引擎之上,該引擎也為Google Chrome提供支持。它實現了更新的 ECMAScript 特性。當新版本的 Node.js 發(fā)布時,它附帶了最新版本的 V8 JavaScript 引擎。最新版本是V8 v11.3,它具有一些顯著的功能,包括:
- 可 ArrayBuffer 調整大小:根據給定的大小(以字節(jié)為單位)調整大小 ArrayBuffer
- 可 SharedArrayBuffer 增長: ShareArrayBuffer 根據給定的大小(以字節(jié)為單位)增長
- String.prototype.isWellFormed() :如果字符串格式正確且不包含單獨的代理項,則返回 true
- String.prototype.toWellFormed() :修復并返回沒有單獨代理項問題的字符串
- 正則表達式 -v 標志:改進了不區(qū)分大小寫的匹配
讓我們探索用于調整 ArrayBuffer、SharedArrayBuffer 最令人興奮的新功能之一是調整 ArrayBuffer 。在 Node.js v20 之前,在創(chuàng)建緩沖區(qū)后調整緩沖區(qū)大小以容納更多數據是不可能的。使用 Node.js 20,我們可以使用該resize()方法調整它的大小,如以下示例所示:
//resize_buffer.js
const buffer = new ArrayBuffer(4, { maxByteLength: 10 });
if (buffer.resizable) {
console.log("The Buffer can be resized!");
buffer.resize(8); // resize the buffer
}
console.log(`New Buffer Size: ${buffer.byteLength}`);
首先,創(chuàng)建一個大小為字節(jié)的緩沖區(qū),其緩沖區(qū)限制為 10 使用該 maxByteLength 屬性指定的 4 字節(jié)數。有了緩沖區(qū)限制,如果我們要將其大小調整為超過 10 字節(jié),它將失敗。如果需要更多字節(jié),可以將 修改 maxByteLength 為所需的值。
接下來,我們檢查緩沖區(qū)是否可調整大小,然后調用該方法 resize() 將緩沖區(qū)的大小從字節(jié)調整 4 為 8 字節(jié)。最后,我們記錄緩沖區(qū)大小。像這樣運行文件:
node resize_buffer.js
下面是輸出:
The Buffer can be resized!
New Buffer Size: 8
緩沖區(qū)的大小已成功從字節(jié)調整 4 為 8 字節(jié)。也有 SharedArrayBuffer 相同的限制 ArrayBuffers ,但現在我們可以使用以下 grow() 方法將其增長到我們選擇的大小:
// grow_buffer.js
const buffer = new SharedArrayBuffer(4, { maxByteLength: 10 });
if (buffer.growable) {
console.log("The SharedArrayBuffer can grow!");
buffer.grow(8);
}
console.log(`New Buffer Size: ${buffer.byteLength}`);
輸出如下所示:
The SharedArrayBuffer can grow!
New Buffer Size: 8
在示例中,我們檢查 是否 buffer 可增長。如果它的計算結果為 true,我們調用該方法 grow() 以增加 SharedArrayBuffer 8 字節(jié)數。與 ArrayBuffer類似,我們不應該將其增長到 maxByteLength。
5遞歸讀取目錄
目錄通常被認為是樹結構,因為它們包含子目錄,子目錄也包含子目錄。從歷史上看, fs 該模塊readdir()的方法僅限于列出給定目錄的文件內容,并且不會遞歸地遍歷子目錄并列出其內容。因此,開發(fā)人員轉向第三方庫,如readdirp,recursive-readdir,klaw和fs-readdir-recursive。
Node.js v20 為 和 方法添加了一個recursive選項,允許 readdirSync 方法遞歸讀取給定目錄和 readdir 子目錄。
假設有一個類似于以下內容的目錄結構:
├── dir1
│ ├── dir2
│ │ └── file4.txt
│ └── file3.txt
├── file1.txt
└── file2.txt
可以添加 recursive: true 列出所有文件的選項,包括子目錄中的文件,如下所示:
// list_directories.js
import { readdir } from "node:fs/promises";
async function readFiles(dirname) {
const entries = await readdir(dirname, { recursive: true });
console.log(entries);
}
readFiles("data"); // <- "data" is the root directory name
運行文件后,輸出將匹配以下內容:
[
'dir1',
'file1.txt',
'file2.txt',
'dir1/dir2',
'dir1/file3.txt',
'dir1/dir2/file4.txt'
]
如果不將選項傳遞給 readdir() 該方法,輸出將 recursive 如下所示:
[ 'dir1', 'file1.txt', 'file2.txt' ]
雖然這是一個次要的補充,但它可以幫助我們減少項目中的依賴關系。
6單個可執(zhí)行實驗性應用程序
我們將在本文中探討的最后一個功能是Node.js v20中引入的實驗性單可執(zhí)行應用程序 (SEA)。它允許我們將應用程序捆綁到Windows上的單個可執(zhí)行文件.exe或可以在macOS / Linux上運行的二進制文件中,而無需用戶在其系統上安裝Node.js。在撰寫本文時,它僅支持使用commons模塊系統的腳本。
讓我們創(chuàng)建一個二進制文件。本節(jié)中的說明僅適用于 Linux。在macOS和Windows上,某些步驟會有所不同,因此最好查閱文檔。首先,創(chuàng)建一個不同的目錄來包含代碼并移動到其中:
mkdir sea_demo && cd sea_demo
在此之后,創(chuàng)建一個 list_items.js 包含以下內容的文件:
const items = ["cameras", "chargers", "phones"];
console.log("The following are the items:");
for (const item of items) {
console.log(item);
}
接下來,創(chuàng)建一個配置文件 sea-config.json ,用于創(chuàng)建可注入可執(zhí)行文件的 Blob:
{ "main": "list_items.js", "output": "sea-prep.blob" }
生成 Blob,如下所示:
node --experimental-sea-config sea-config.json
// Output:
Wrote single executable preparation blob to sea-prep.blob
復制可執(zhí)行文件并為其指定一個適合您的名稱:
cp $(command -v node) list_items
將 Blob 注入二進制文件:
npx postject list_items NODE_SEA_BLOB sea-prep.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
像這樣在系統上運行二進制文件:
The following are the items:
cameras
chargers
phones
(node:41515) ExperimentalWarning: Single executable application is an experimental feature and might change at any time
(Use `list_items --trace-warnings ...` to show where the warning was created)
這負責創(chuàng)建 SEA。如果您想了解如何為 macOS 或 Windows 創(chuàng)建二進制文件,請查看文檔[https://nodejs.org/api/single-executable-applications.html]。
7寫在最后
在這篇文章中,我們探討了 Node.js v20 中引入的一些功能。首先,我們研究了如何使用實驗性權限模型。然后,我們了解了如何使用現在穩(wěn)定的內置測試運行程序。從那里,我們了解了 V8 JavaScript 引擎中可用的新功能。
之后,我們探索了如何遞歸讀取目錄,最后,我們使用實驗性的單一可執(zhí)行應用程序(SEA)功能創(chuàng)建了一個二進制文件,該功能允許用戶在不安裝Node.js的情況下運行Node.js程序。