當前位置:
首頁 > 知識 > 談談Javascript 模塊現狀,並為絲滑過渡做好準備

談談Javascript 模塊現狀,並為絲滑過渡做好準備

我們webpack團隊正在努力進行一些工作,幫助開發者絲滑過渡。為了做到這一點,我們計劃在Node.js的ESM支持成熟後,模擬Node.js導入CJS的方式。

最近在twitter上有很多關於ES Module現狀的討論,尤其是在Node.js上,因為他們計劃引入*.mjs文件擴展名。人們的擔憂和不確定性是情有可原的,因為這個主題很複雜,接下來我會儘力來闡述這個問題。

來自遠古的恐懼

大多數前端開發者應該還記得javascript依賴管理的黑暗時期。那個時候,你需要把一個庫複製粘貼到vendor文件夾,然後作為一個全局變數引入,要自己去按次序組合所有東西,可能還要管理命名空間。

在過去的那些年,我們能深刻體會到公共模塊格式化和中央模塊管理的價值。

在今天,不管是發布還是使用一個庫都要容易得多,只需要使用npm publish和npm install命令就行。這就是人們會那麼緊張兩種模塊系統兼容性問題的原因:他們不想失去已有的舒適區。

接下來我會解釋和總結現有實現的情況,以及為什麼Node生態遷移到ES Module(ESM會那麼難。在最後,總結這些變化對於webpack使用者和模塊作者有什麼影響。

現有實現

目前,ESM有三種方式的實現:

瀏覽器

webpack以及類似的構建工具

Node(未完成,但可能在年底作為一個實驗功能)

為了更好地理解現在的討論,首先要知道ES2015包含兩種模式:

script用於具有全局命名空間的常規腳本

module用於具有明確導入和導出的模塊化代碼

如果你試圖在script標籤使用import或者export 語句,會拋出一個SyntaxError。這種語句在全局環境下沒有任何意義。另一方面,module模式即意味著嚴格模式,禁止使用某些語言特性,比如with語句。因此,需要在腳本被解析和執行之前定義模式。

瀏覽器中的ESM

截至到2017年5月,所有主流瀏覽器都開始做了ESM 的實現工作。不過,大部分仍處於在實驗性質。這裡不會做詳細介紹,因為Jake Archibald已經寫了一篇很厲害的文章:

https://jakearchibald.com/2017/es-modules-in-browsers/

除了一些輕微困難,在瀏覽器中實現起來非常容易,因為以前並沒有模塊系統。想要指定module模式,需要在script標籤添加type="module"屬性,如下所示:

在一個模塊中,當前只能使用有效的URL作為模塊標識符。模塊標識符是用於require或import其他模塊的字元串。為了確保未來兼容CJS模塊標識符,「bare」導入標誌符(如 import "lodash")現在還不支持。模塊標識符必須是絕對URL或者是以/,./, ../開頭:

同樣需要注意的是,一旦處在一個模塊中,每個導入也將被解析為module,而且沒有辦法import一個script。

ESM與webpack

類似webpack這樣的構建工具通常會嘗試用module 模式解析代碼,有問題再切回到script模式。這些工具的生成結果是一段script,通常是在一定程度上模擬CJS和ESM行為的模塊運行時。

我們以這兩個簡單的ESM為例:

webpack使用函數包裝器封裝模塊範圍和對象引用來模擬ESM實時綁定。每次編譯,還包括一個模塊運行時,負責引導和緩存模塊。此外,將模塊標識轉換為數字模塊ID。這樣可以減少打包的大小和引導時間。

這是什麼意思呢?我們來看看編譯輸出:

簡化的webpack輸出,模擬ES Modules行為

結果已經簡化並刪除了一些與此示例無關的代碼。你會發現,webpack在exports對象上將所有export 語句替換成Object.defineProperty,並使用屬性訪問器替換對引入值的所有引用。還要注意每個ESM 開始時的"use strict"指令,這是由webpack自動添加,在ESM中必須是嚴格模式。

這種實現只是模擬,因為它試圖模仿ESM和CJS的行為——但不是與其完全保持一致。比如,這種模擬並不符合某些邊緣情況。看下面這個模塊:

如果你通過加上babel-preset-es2015的Babel 來運行,結果是:

從輸出結果可以看出,Babel假設默認是ESM,因為 module模式即代表嚴格模式,在嚴格模式下會將this初始化為undefined。

然而,使用webpack,結果是:

在引導模塊時,this將指向exports ,與Node.js使用的CJS行為一致。這是因為語法上不確定是script還是module,解析器無法判斷該模塊是ESM還是 CJS。在不明確的時候,webpack會模擬CJS,因為它仍然是最受歡迎的模塊風格。

這種模擬其實已經包含了很多情況,因為模塊作者通常會避免這種代碼。然而,「很多情況」對於像Node.js這樣的平台是不夠的,因為它需要保證所有有效的JavaScript代碼都能正常運行。

Node.js 中的 ESM

Node.js在執行ESM時遇到了麻煩,因為仍然需要支持CJS,語法看起來相似,但運行時行為完全不同。Node.js核心技術委員會(CTC)成員James M Snell撰寫了一篇很好的文章來解釋CJS與ESM之間的差異。

歸結起來,CJS是一個動態模塊系統,ESM是靜態模塊系統。

CJS

允許動態同步 require()

導出僅在模塊執行後才知道

導出可以在模塊初始化後添加,替換和刪除

ESM

只允許靜態同步import

在模塊執行之前,導入和導出已經關聯

導入和導出是不可變的

由於CJS早於ES2015,所以一直在script模式下解析,封裝通過使用函數包裝器實現。在Node.js中載入CJS,實際上會執行與此類似的代碼:

Simplified function wrapper around CommonJS modules in Node.js

問題出現了,將兩個模塊系統集成到同一個運行時時,ESM和CJS之間的循環依賴可能會迅速導致類似死鎖的情況。

而且,由於現有CJS模塊數量龐大,也不能直接放棄對CJS的支持。為了避免Node.js生態的中斷,有兩點已經很明顯:

現有的CJS代碼必須以相同的方式繼續工作

兩個模塊系統都必須同時且儘可能無縫地工作

目前的權衡

2017年3月,經過幾個月的討論,CTC終於找到了一種達成目的的途徑。由於在ES規範和引擎不改變的情況下無法進行無縫集成,CTC決定開始一些權衡之後的實現工作:

1.ESM必須是*.mjs文件擴展名

這是由於上面提及的模糊語法問題,無法通過解析來確切知曉JavaScript代碼是什麼類型。為了Node.js向後兼容的目標,作者需要加入一種新模式。已經有關於各種替代品的討論,但使用不同文件擴展名是解決目前問題的最佳權衡。

2.CJS只能非同步導入ESM import()

Node.js將非同步載入ESM,以便儘可能接近瀏覽器的行為。因此,同步的require()在ESM是不可能的,並且依賴於ESM的每個功能都需要非同步:

3.CJS向ESM暴露一個不可變的默認導出

使用Babel或Webpack,我們通常將CJS重構為ESM,如下所示:

再次,他們的語法看起來很相似,但忽略了CJS中沒有命名導出的事實。只有一個叫做default的導出,等同於在CJS模塊完成計算後一個不可變的module.exports。

從技術上講,有可能將module.exports結構成命名導入,但這需要對標準作更大的變更。這就是為什麼CTC決定現在才去實現這種方式。

4.模塊範圍的變數類似module,require以及__filename在ESM不存在

Node.js和瀏覽器會實現一些ESM的特性,但標準化過程仍在進行中。

鑒於將CJS和ESM集成到一個運行時的工程挑戰,CTC在評估邊緣情況和權衡方面做了非常好的工作。比如使用不同的文件擴展名是就是一個很簡單的解決方案。

實際上,一個文件擴展名可以認為是一個二進位文件如何解釋的提示。如果一個module不是script,我們應該使用不同的文件擴展名。其他工具(如linter或IDE)可以獲取相同信息。

當然,引入新的文件擴展名有成本,但是一旦伺服器和其他應用程序確認*.mjs為JavaScript,我們很快就會忘記這個爭議。

將* .mjs作為

Node.js的Python3?

考慮到所有這些限制,人們可能會問,這種過渡將對現在的生態造成什麼樣的損害。雖然CTC會努力解決問題,但社區如何採用這一點仍然存在很大不確定性。這種不確定性被眾多知名的NPM模塊作者再次強調,他們聲稱將不會在模塊中使用 *.mjs。

Python 3 is killing Python

很難預測社區如何反應,但是應該不會對現在的生態系統造成大破壞,甚至能看到從CJS平穩過渡到 ESM。主要有兩個原因:

1.與CJS嚴格向後兼容

模塊作者不喜歡ESM,除非能保持CJS不被排擠出局。這樣他們自己的代碼不會受到採用ESM的影響,降低遷移到另一個運行時的可能性,讓NPM遷移到新生態變得容易。從CJS到ESM的重構給包維護者帶來額外工作,不能指望所有人都有時間。

2.CJS在ESM中的無縫整合

從ESM導入CJS模塊非常簡單。需要注意的是,CJS 僅導出一個默認值。一旦處於ESM,甚至可能根本不會注意到依賴關係使用的模塊風格,尤其是與在CJS 中使用await import()相比。

由於ESM的這個優點以及其他有點,比如開箱即用的 tree shaking和瀏覽器兼容性,預計在未來幾年內,我們可以看到向ESM的緩慢而穩定的過渡。

CJS的特性,如動態require()和猴子補丁導出,在Node.js社區一直是有爭議的,不比ESM帶來的好處。

這些對我來說意味著什麼?

因為最近這些事情,很容易對目前存在的所有選擇和限制感到困惑。在接下來,整理了開發人員面臨的典型問題以及我們的回答:

現在需要重構現有的代碼嗎?

不需要。Node.js才剛剛開始實現ESM,仍然有大量的工作要做。James M Snell預計至少還需要一年時間,還有很多變化的餘地,所以現在重構是不安全的。

應該在新代碼中使用ESM嗎?

如果你已經有或者打算使用像webpack這樣的構建工具,答案是肯定的。這將更容易完成代碼庫的過渡,並使tree shaking成為可能。但要小心:一旦Node.js支持原生ESM,可能需要重構其中的一些部分。

如果你正在編寫一個庫,答案也是肯定的,你的模塊使用者將受益於tree shaking。

如果你不想進行構建操作,或者正在編寫一個Node.js應用程序,還是用CJS吧。

現在應該使用.mjs嗎?

不要這樣做,目前沒有什麼好處,工具支持依然薄弱。建議一旦原生ESM支持登陸Node.js,儘快開始遷移。記住,瀏覽器只關心MIME類型,而不是文件擴展名。

應該關心瀏覽器兼容性嗎?

是的,需要在一定程度上關注這個問題。不應該在導入語句中省略.js擴展名,因為瀏覽器需要完整的URL,無法像Node.js這樣執行路徑查詢。

同樣,應該避免index.js文件。不過,人們並不會很快在瀏覽器中使用NPM軟體包,因為仍然不能bare導入。

作為庫作者該怎麼辦?

用ESM編寫代碼,並使用Rollup或Webpack轉換成單個CJS模塊,然後在package.json將main欄位指向此CJS包,並將module欄位指向原始ESM。如果還使用ESM之外的其他新語言功能,則應編譯成ES5,並提供CJS和ESM的打包。這樣,庫的用戶仍然可以從tree shaking獲利而無需對代碼進行轉換。

Look at all these tree shaken modules!

總結

關於ES模塊有很多不確定性。由於目前Node.js在實現上的權衡,開發人員擔心可能會破壞Node.js的生態。

這還不會發生,因為兩個原因:CJS的嚴格的後向兼容和CJS在ESM中的無縫集成。

在Node.js發布原生ESM支持之前,應該仍然使用Rollup和Webpack等工具。它們在一定程度上模擬了ESM環境,但要注意它們不完全符合規範。此外,使用打包仍然是個很好的選擇,一旦可以在瀏覽器中使用NPM軟體包。

我們webpack團隊正在努力進行一些工作,幫助開發者絲滑過渡。為了做到這一點,我們計劃在Node.js的ESM支持成熟後,模擬Node.js導入CJS的方式。

文章來源:

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 優才學院 的精彩文章:

端午紀念屈原,程序媛紀念祖師奶!四幕京劇:程序媛阿達傳(上)
阿法狗三十六計之功成身退
端午五美送福利 你會追哪一個?
MockNet:Android 網路介面開發與測試神器

TAG:優才學院 |

您可能感興趣

讓我們來談談死亡·Let』s talk about dying
AppleWatchSeries 3上手,我來談談優缺點!
談談 Oracle Exadata 帶來的業務價值及優勢
談談鎚子系統Smartisan os的使用感受!
體驗Anker Soundcore Flare,談談藍牙音箱能有哪些新玩法
深入談談String.intern在JVM的實現
談談對Linux的Huge Pages與Transparent Huge Pages的認識
談談SimCloud的架構
蘋果iPhone 8Plus好不好?上手體驗完,談談優缺點!
不跑那就走起來吧,談談小米WaklingPad走步機
談談對GS和Spring Drive的看法
用了一段時間的iPhoneXSMax,我來談談優缺點!
借 TicWatch Pro 來談談,我們真的需要一隻智能手錶嗎?
int 和 Integer 有什麼區別?談談 Integer 的值緩存範圍
談談微服務架構中的基礎設施:Service Mesh與Istio
談談.NET Core中基於Generic Host來實現後台任務
iPhone8plus換上華為P30pro,談談真實使用感受
談談OpenStack的八年之癢
談談ASP.NET Core中的ResponseCaching
有沒有一直到現在還在使用iphone6的朋友,談談對iphone6的看法