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