我們很高興地宣布為Workers和Pages推出改進(jìn)的Node.js兼容性預(yù)覽。更廣泛的兼容性讓您可以使用更多NPM包,并在編寫Workers時(shí)充分利用JavaScript生態(tài)系統(tǒng)的優(yōu)勢(shì)。
我們最新版本的Node.js兼容性結(jié)合了我們先前努力實(shí)現(xiàn)的最佳特征。Cloudflare Workers以某種形式支持Node.js已有相當(dāng)長(zhǎng)一段時(shí)間了。我們于2021年首次宣布對(duì)polyfill的支持,后來隨著時(shí)間的推移不斷擴(kuò)展對(duì)部分Node.js API的內(nèi)置支持。
最新的改進(jìn)將帶來更多便捷:
-您可在Workers上使用更多NPM包。
-您可以使用不是用node:prefix導(dǎo)入Node.js API的包
-您可以在Workers上使用更多Node.js API,包括async_hooks、Buffer、dns、os和events上的大多數(shù)方法。許多其他方法(例如fs或process)都可以使用模擬方法來導(dǎo)入。
如要嘗試,請(qǐng)將以下標(biāo)志添加到wrangler.toml,并使用Wrangler部署您的Worker:
compatibility_flags=【"nodejs_compat_v2"】
無法使用nodejs_compat導(dǎo)入的包(即使是作為另一個(gè)包的依賴)現(xiàn)已可以加載。其中包括流行的包,例如body-parser、jsonwebtoken、{}Got、passport、md5、knex、mailparser、csv-stringify、cookie-signature、stream-slice等。
對(duì)于所有啟用了現(xiàn)有nodejs_compat兼容性標(biāo)志且兼容性日期為2024-09-23或之后的Workers,此行為很快將成為默認(rèn)。在您試用改進(jìn)的Node.js兼容性過程中,歡迎通過在GitHub提交問題來分享您的反饋。
Workerd不是Node.js
要了解這些最新變化,我們先來簡(jiǎn)單了解一下Workers運(yùn)行時(shí)與Node.js的不同之處。
Node.js主要專為直接在主機(jī)操作系統(tǒng)上運(yùn)行的服務(wù)而構(gòu)建,是服務(wù)器端JavaScript的先驅(qū)。因此,它包括與主機(jī)交互所需的功能(例如process或fs),以及各種實(shí)用模塊(例如crypto)。
Cloudflare Workers在一個(gè)名為workerd的開源JavaScript/Wasm運(yùn)行時(shí)上運(yùn)行。雖然Node.js和workerd都是在V8上構(gòu)建的,但workerd設(shè)計(jì)為在共享進(jìn)程中運(yùn)行不受信任的代碼,暴露綁定以便與其他Cloudflare服務(wù)進(jìn)行互操作,包括JavaScript原生RPC,并盡可能使用Web標(biāo)準(zhǔn)API。
Cloudflare幫助建立了WinterCG(Web互操作運(yùn)行時(shí)社區(qū)小組),其旨在提高JavaScript運(yùn)行時(shí)之間以及運(yùn)行時(shí)與Web平臺(tái)的互操作性。您可以僅使用Web標(biāo)準(zhǔn)API構(gòu)建許多應(yīng)用,但是當(dāng)您想從NPM導(dǎo)入依賴于Node.js API的依賴項(xiàng)時(shí),該怎么辦呢?
例如,如果您在未開啟Node.js兼容性的情況下嘗試導(dǎo)入pg(一個(gè)PostgreSQL驅(qū)動(dòng)程序)……
當(dāng)運(yùn)行wrangler dev以構(gòu)建您的Worker時(shí),將看到如下錯(cuò)誤:
發(fā)生這種情況是因?yàn)閜g包從Node.js導(dǎo)入events模塊,而workerd默認(rèn)情況下不提供該模塊。
我們?nèi)绾螌?shí)現(xiàn)這一點(diǎn)?
我們的第一種方法——構(gòu)建時(shí)polyfills
Polyfill是為原生不支持某項(xiàng)功能的運(yùn)行時(shí)添加功能的代碼。這些代碼通常用于為舊版瀏覽器提供現(xiàn)代JavaScript功能,但也可以用于服務(wù)器端運(yùn)行時(shí)。
在2022年,我們?yōu)閃rangler添加了功能,如果您在wrangler.toml中設(shè)置node_compat=true,則可以將一些Node.js API的polyfill實(shí)現(xiàn)注入Worker中。以下代碼在開啟該標(biāo)志時(shí)可以正常工作,但在關(guān)閉時(shí)則不行:
這些polyfill本質(zhì)上就是在部署Worker時(shí)由Wrangler添加到Worker的額外JavaScript代碼。這個(gè)行為由esbuild-plugins/node-globals-polyfill支持實(shí)現(xiàn),后者本身使用rollup-plugin-node-polyfills。
這允許您導(dǎo)入和使用一些NPM包,例如pg。然而,許多模塊無法用足夠快的代碼進(jìn)行polyfill,或者根本無法被polyfill。
例如,Buffer是用于處理二進(jìn)制數(shù)據(jù)的常見Node.js API。存在支持它的polyfill,但JavaScript通常并沒有針對(duì)它在內(nèi)部執(zhí)行的操作進(jìn)行優(yōu)化,例如copy、concat、子字符串搜索或轉(zhuǎn)碼。雖然可以用純JavaScript實(shí)現(xiàn),但如果底層運(yùn)行時(shí)可以使用來自不同語言的基元,那么速度還會(huì)更快。其他流行的API,如Crypto、AsyncLocalStorage和Stream,也存在類似的限制。
我們的第二種方法——在Workers運(yùn)行時(shí)中原生支持一些Node.js API
2023年,我們開始將一部分Node.js API直接添加到Workers運(yùn)行時(shí)中。您可以通過向Worker添加nodejs_compatcompatible標(biāo)志來啟用這些API,但不能同時(shí)將polyfills與node_compat=true一起使用。
此外,在導(dǎo)入Node.js API時(shí),必須使用node:prefix:
由于這些Node.js API直接內(nèi)置于Workers運(yùn)行時(shí)中,因此可以用C++編寫,這使得它們比JavaScript polyfill更快。像AsyncLocalStorage這樣的API(不能在不影響安全性和性能的情況下進(jìn)行polyfill)可以原生提供。
要求node:prefix使導(dǎo)入更加明確,并與現(xiàn)代Node.js約定保持一致。不幸的是,現(xiàn)有的NPM包可能不使用node:導(dǎo)入。例如,回顧一下上面的示例,如果您在帶有nodejs_compat標(biāo)志的Worker中導(dǎo)入流行程序包pg,您仍然會(huì)看到以下錯(cuò)誤:
即使您啟用了nodejs_compat兼容性標(biāo)志,許多NPM包仍然不能在Workers中運(yùn)行。您必須在較小的高性能API集(以許多NPM包無法訪問的方式暴露)之間進(jìn)行選擇,亦或是在較大的不完整、性能較低的API集之間進(jìn)行選擇。而在Node.js中暴露為全局變量的API,例如process,仍然只能通過將其作為模塊導(dǎo)入來訪問。
新方式:混合模型
如果我們可以做到兩全其美并能順利運(yùn)行,那會(huì)如何?
-在Workers運(yùn)行時(shí)中直接實(shí)現(xiàn)的Node.js API子集
-適用于其他大多數(shù)Node.js API的Polyfill
-不需要node:prefix
-一個(gè)簡(jiǎn)單的選擇使用方式
改進(jìn)后的Node.js兼容性就能做到這一點(diǎn)。
我們來看看兩行代碼,看起來相似,但在啟用nodejs_compat_v2后,內(nèi)部行為有所不同:
第一行從workerd中的JavaScript模塊導(dǎo)入Buffer,該模塊由C++代碼支持。其他各種Node.js模塊也類似地以Typescript和C++的組合實(shí)現(xiàn),包括AsyncLocalStorage和Crypto。這樣允許編寫與Node.js行為相匹配的高性能代碼。
請(qǐng)注意,導(dǎo)入buffer時(shí)不需要node:prefix,即使用node:buffer代碼也能工作。
第二行導(dǎo)入net,Wrangler使用一個(gè)名為unenv的庫自動(dòng)polyfill。Polyfill和內(nèi)置運(yùn)行時(shí)API現(xiàn)在可以協(xié)同工作。
在以前的版本中,當(dāng)您設(shè)置node_compat=true時(shí),Wrangler會(huì)在能夠的情況下為每個(gè)Node.js API添加polyfill,即使您的Worker及其依賴項(xiàng)都沒有使用該API。當(dāng)您啟用nodejs_compat_v2compatible_flag時(shí),Wrangler只會(huì)為您的Worker或其依賴項(xiàng)實(shí)際使用的Node.js API添加polyfill。結(jié)果是,即使使用了polyfill,Worker也會(huì)很小。
對(duì)于某些Node.js API,Workers運(yùn)行時(shí)中尚未提供原生支持,也沒有polyfill實(shí)現(xiàn)。在這些情況下,unenv會(huì)“模擬”接口。這意味著它會(huì)將該模塊及其方法添加到您的Worker,但調(diào)用該模塊的方法要么不執(zhí)行任何操作,要么拋出錯(cuò)誤,并顯示類似以下的消息:
【unenv】is not implemented yet!
這比看起來更為重要。因?yàn)槿绻粋€(gè)Node.js API被“模擬”,那么依賴它的NPM包仍然可以被導(dǎo)入。請(qǐng)考慮以下代碼:
以前,即使啟用了現(xiàn)有的nodejs_compat兼容性標(biāo)志,嘗試導(dǎo)入my-module也會(huì)在構(gòu)建時(shí)失敗,因?yàn)闊o法解析fs模塊?,F(xiàn)在,fs模塊可以解析,不依賴于未實(shí)現(xiàn)的Node.js API的方法可以奏效,而那些確實(shí)拋出錯(cuò)誤的方法會(huì)給出更具體的錯(cuò)誤信息——一個(gè)運(yùn)行時(shí)錯(cuò)誤,表明某個(gè)特定的Node.js API方法尚不支持,而不是構(gòu)建時(shí)錯(cuò)誤,指明模塊無法被解析。
這就是為什么某些包從“在Workers上甚至不加載”轉(zhuǎn)變?yōu)椤翱梢约虞d,但有一些不受支持的方法”。
依然缺少Node.js的某個(gè)API?模塊別名來幫忙
假設(shè)您需要一個(gè)NPM包在Workers上工作,其依賴于尚未在Workers運(yùn)行時(shí)中實(shí)現(xiàn)的Node.js API,或作為unenv中的polyfill。您可以使用模塊別名來實(shí)現(xiàn)剛好足夠正常工作的API。
例如,假設(shè)您需要工作的NPM包調(diào)用fs.readFile。您可以通過將以下內(nèi)容添加到Worker的wrangler.toml來為fs模塊起別名:
然后,在fs-polyfill.js文件中,您可以定義自己對(duì)fs模塊的任何方法的實(shí)現(xiàn):
以下代碼之前拋出錯(cuò)誤信息“【unenv】readFile is not implemented yet!”,現(xiàn)在則可以正常運(yùn)行:
您還可以使用模塊別名來提供一個(gè)在Workers上不起作用的NPM包的實(shí)現(xiàn),即使您只是間接依賴該NPM包(作為您的Worker的某個(gè)依賴項(xiàng)的依賴項(xiàng))。
例如,cross-fetch等一些NPM包依賴于node-fetch,后者在fetch()API在內(nèi)置到Node.js之前提供其polyfill。Workers中不需要node-fetch包,因?yàn)閒etch()API由Workers運(yùn)行時(shí)提供。node-fetch在Workers不能工作,因?yàn)樗蕾嚨膆ttp和https模塊中的Node.js API目前不受支持。
您可以為node-fetch的所有導(dǎo)入設(shè)置別名,使其直接指向使用流行的nolyfill包內(nèi)置于Workers運(yùn)行時(shí)的fetch()API:
在這種情況下,您的替代模塊只需重新導(dǎo)出Workers運(yùn)行時(shí)內(nèi)置的fetch API即可:
向unenv回報(bào)貢獻(xiàn)
Cloudflare正在為unenv積極做貢獻(xiàn)。我們認(rèn)為unenv正在以正確的方式解決跨運(yùn)行時(shí)兼容性問題——它會(huì)根據(jù)您使用的API和針對(duì)的運(yùn)行時(shí),僅向您的應(yīng)用程序添加必要的polyfill。該項(xiàng)目支持也workerd之外的各種運(yùn)行時(shí),并且已經(jīng)被包括Nuxt和Nitro在內(nèi)的其他流行項(xiàng)目使用。我們要感謝Pooya Parsa和unenv的維護(hù)者,并鼓勵(lì)生態(tài)系統(tǒng)中的更多人采用或貢獻(xiàn)。
下一步
目前,您可以通過在wrangler.toml中設(shè)置nodejs_compat_v2標(biāo)志來啟用改進(jìn)的Node.js兼容性。我們從9月23日起使該新行為成為使用nodejs_compat標(biāo)志時(shí)的默認(rèn)行為。這將需要更新compatibility_date。
我們對(duì)即將到來的Node.js兼容性變化感到興奮,同時(shí)也十分鼓勵(lì)您即刻就進(jìn)行嘗試。查看文檔,了解如何為你的Workers選擇加入。如果有任何反饋或錯(cuò)誤,請(qǐng)通過提交問題告知我們。這樣可以幫助我們識(shí)別支持中的任何錯(cuò)漏,確保盡可能多的Node.js生態(tài)系統(tǒng)能夠在Workers上運(yùn)行。