Client Side 模組化的百家爭鳴
Client Side 模組化的百家爭鳴 (2011-2012)
前言
昨天在 CommonJS 的介紹中最後提到 require
在載入模組是同步的,當模組還沒被載入完成前,後續的操作都會被阻斷。不知道你有沒有想過,為什麼 server side 同步載入模組可以被接受,但 client side 就會有問題呢?
因為在古早時期,大多時候對 server side 來說,所需要載入的模組通常都被放在同一個檔案系統中,因此載入速度基本上不成問題,且有機會做一些預先加載的快取處理。
上面指的是 ESM 尚未出現前,在 ESM 標準出現後,Node.js 在 v13.2.0 後能穩定支援 ESM,這也讓非同步模組載入能在 Node.js 中做到。
但對瀏覽器環境就不一樣了,大多時候你需要載入的模組可能來自各處的遠端伺服器,中間的網路延遲時間不可控,如果每載入一個模組就卡住後面的頁面加載、渲染等工作,體驗上就會很差,所以通常在瀏覽器端會期待能做到非同步加載。
百家爭鳴
因爲上述這個問題,CommonJS 社群在想要將模組化標準推向瀏覽器這件事出現了意見分歧,詳細的演進可見這篇文章,簡單紀錄的話就是分成幾派:
- Modules/1.x:延伸原本的標準,透過轉譯的方式來達到
- 最有名的實踐是 Component.js 與 node-module-transpiler
- 前者在 2015 年停止維護,並建議可以改用 Webpack
- 後者後來更名成 es6-module-transpiler,從文件上可以看到後來被併入 Babel 中
- Modules/Async:認為應該將重點放在解決非同步載入這件事上,代表的選手是 RequireJs 與 AMD 標準
- Modules/2.0:希望兼容 1.0 但做一套新標準,從古至今的選手有 BravoJS、FlyScript、Sea.js (CMD)
AMD
- 不是超跌半導體的那個 AMD,這裡指的是非同步模組定義 (Asynchronous Module Definition),由 James Burke 等開發者在 2011 年提出,並利用 RequireJS 來實踐。
- 冷知識:其他使用此標準的實踐還有 curl.js、Dojo Toolkit。
此標準的主要概念是「依賴前置」,可能有點難懂,可以看以下這個範例:
參考上面的註解說明可以這樣理解一個模組載入的過程:
- 用
require
去載入homePageModule
這個模組。 - script loader 會透過在第二個參數中去識別會需要載入哪些依賴模組,像這裡的
jquery
、lib/math
,來進行非同步載入。 - 當依賴模組載入完成後,才會透過定義中的第三個參數的 callback function 來執行對應的模組方法,達到非同步模組載入的需求,而不阻塞後續的其他操作。
但 CommonJS 社群並不接受這樣的做法,主要有兩個不認同的地方:
- 需要定義了這個全域變數
define
來做模組定義 - 在參數中傳入 dependency array 來載入依賴模組時,沒辦法做到按需載入,較違反了 Principle of Proximity,可參考下面的範例
因此後來 AMD 社群自己另外獨立出去發展。儘管不被原本的社群認同,RequireJS 與 AMD 的概念仍被廣泛接受影響了一些函式庫的模組化標準,甚至也蓋過了其他有深厚技術底蘊的門派如 BravoJS、FlyScript 聲量。
CMD
- 通用模組定義 (Common Module Definition),由 Sea.js 來實踐,作者為阿里巴巴的開發者玉伯,也就是前面提到那篇文章的作者
- 當時玉伯嘗試在 RequireJS 社群貢獻,但有些建議沒被採納因此後來自己做了另一套標準
- 與 AMD 的差異主要是關於依賴模組的引入與執行寫法風格,這部份可以參考黃玄投影片中的範例如下圖:
UMD
- Universal Module Definition
- GitHub Repo.
- 另一個想整合 CommonJS 與 AMD 的後起之秀,但比較少被討論,這邊就不贅述
小結
今天簡單點到了在模組化歷史中,曾經為了將模組化標準搬上瀏覽器而有了這段百家爭鳴的現象,一直以來我沒有搞很懂的非同步模組載入這件事為什麼在瀏覽器上會有問題,也在這次比較深入理解 AMD 的歷史後豁然開朗,希望我的筆記們也有幫助到正在閱讀的你。明天也終於快進到「前端工具」這個重點了,敬請期待!