這篇文章會著重在帶大家了解 JavaScript 模組大致的發展歷程,而不會放在各個模組化機制的使用方式,希望能在這前端渾沌的時代,留下一點紀錄。
JavaScript 最早是網頁的腳本語言,單純的被拿來做 DOM 的操作,而隨著時間的推演 JavaScript 逐漸成熟,但一直到 ECMAScript2015 之前都還沒有「標準的」模組化機制。在 ES2015 推出前就發展出了許多套第三方的模組規範,像是 CommonJS, AMD, CMD, UMD...等
ECMAScript
大概在 2009,有個名為 ServerJS 的社群致力於把 JavaScript 移植到 Server 上運行,而其中一個要解決的要務就是 制定模組化機制 ,在推出 Module/1.0 規範後,在 Node.js 等環境下有了不錯的實作。
在 2009 下半年開始,社群中的成員希望把模組化的規範進一步推廣到瀏覽器,便就把社群改名為 CommonJS,但此時社區中大家對於 Modules 的下一個版本產生了激烈的爭論,逐漸分成三大流派:
Modules/1.x: 這流派的觀點主要是我們應該基於現有的規範做一點補足移植到瀏覽器即可,要做的是新增 Modules/transport 規範。也就是在瀏覽器上執行前,通過工具先做一次編譯,轉為符合 Transport 規範的程式碼。目前比較紅的 browserify, webpack, rollup 都是由這套觀點衍生而來。
Modules/async: 這個流派認為瀏覽器本身的特性與伺服器端不同,不應該用同一套規範。這個流派最著名的就是 AMD 及其實作 RequireJS
Modules/2.0: 這個流派的觀點認為瀏覽器本身的特性確實與伺服器不同,不應該直接用 Modules/1.0 的規範,但應該盡可能的與 Modules/1.0 的規範保持一致。這個觀點的主要代表是 BravoJS 與 FlyScript。BravoJS 作者,Wes Garland,對 CommonJS 社群的貢獻很多,其設計的 Modules/2.0-draft 規範花了很多心思。而後 FlyScript 的作者提出了 Modules/Wrappings 規範,也是 CMD 規範的前身。
// sum.js
module.exports = function(a, b) {
return a + b;
}
// index.js
var sum = require('./sum.js');
var result = sum(1, 2)
CommonJS
var sum = require("./sum.js") // 執行到這行,sum.js 才會同步載入並執行
AMD Early Executing
define(["require"], function(require) {
// 在這裡,sum.js 模組已經載入並執行完
// ...
var sum = require("./sum.js") // 此處僅是取得 sum.js 模組的 exports
})
不符合 Principle of Proximity
define(["./sum.js", "test.js"], function(sum, test) {
// 在最前面就先聲明所有要用到的模塊,並初始化
if (false) {
// 即使根本沒有用到 sum,但 sum 還是提前執行了
sum(1, 2)
}
})
我會回來的,帶著更好的東西。
目前官網以及 Github 都已經不在了,這裡還能看到當時大大留下的足跡
之後在 2013 年前後出現了另一套 UMD 是希望能夠整合 CommonJS 與 AMD,會先判定是否在 Server 的環境再決定採用哪套規範,但也導致程式碼複雜,且使用人數不多故在此就略過不提了。
隨著 Browserify 與 Webpack 的推出,加上 NPM 的社群支援,使得近年來大家又開始往 Modules/1.x 的方向在走,而後來 ES6 標準的發佈,大家也開始用 Babel 加上一個模組化工具開始寫 ES6 模組化的語法。
這篇文章會隨著 ES2015(ES6) 標準的逐漸統一,而不再有人注意,但希望透過這篇文章的紀錄讓後來人能一窺 JavaScript 歷史。讀者如果想瞭解 ES2015 的模組化可以期待我的另一篇文章: ES2016 模組化機制