前端模塊化開(kāi)發(fā)那點(diǎn)歷史

2019-08-14 14:35 更新

最近不斷有人問(wèn)及,想起前些天跟 @dexteryy 等人的討論:dexteryy/OzJS#10 當(dāng)時(shí)有過(guò)簡(jiǎn)單總結(jié),重新梳理如下。

寫在前面

  1. 不談什么:傳統(tǒng)的模塊化開(kāi)發(fā)方式,比如文件拆分、全局變量、命名空間,以及 YUI3 式的模塊化開(kāi)發(fā)方式。有興趣的可閱讀:#547

  2. 談什么: 關(guān)于 CommonJS、AMD、Node.js、CMD 等相關(guān)的故事與未來(lái)趨勢(shì),很有意思。

  3. 不一定精準(zhǔn):本文是基于史實(shí)的扯淡,因此部分文字特別是時(shí)間都是模糊記憶,不一定精準(zhǔn)。關(guān)于流派、趨勢(shì)則是個(gè)人在社區(qū)的感受,不代表客觀看法。(看法都是主觀的,呵呵)

CommonJS 社區(qū)

大概 09 年 - 10 年期間,CommonJS 社區(qū)大牛云集。CommonJS 原來(lái)叫 ServerJS,推出 Modules/1.0 規(guī)范后,在 Node.js 等環(huán)境下取得了很不錯(cuò)的實(shí)踐。

09年下半年這幫充滿干勁的小伙子們想把 ServerJS 的成功經(jīng)驗(yàn)進(jìn)一步推廣到瀏覽器端,于是將社區(qū)改名叫 CommonJS,同時(shí)激烈爭(zhēng)論 Modules 的下一版規(guī)范。分歧和沖突由此誕生,逐步形成了三大流派:

  1. Modules/1.x 流派。這個(gè)觀點(diǎn)覺(jué)得 1.x 規(guī)范已經(jīng)夠用,只要移植到瀏覽器端就好。要做的是新增Modules/Transport 規(guī)范,即在瀏覽器上運(yùn)行前,先通過(guò)轉(zhuǎn)換工具將模塊轉(zhuǎn)換為符合 Transport 規(guī)范的代碼。主流代表是服務(wù)端的開(kāi)發(fā)人員。現(xiàn)在值得關(guān)注的有兩個(gè)實(shí)現(xiàn):越來(lái)越火的 component 和走在前沿的 es6 module transpiler。

  2. Modules/Async 流派。這個(gè)觀點(diǎn)覺(jué)得瀏覽器有自身的特征,不應(yīng)該直接用 Modules/1.x 規(guī)范。這個(gè)觀點(diǎn)下的典型代表是 AMD 規(guī)范及其實(shí)現(xiàn) RequireJS。這個(gè)稍后再細(xì)說(shuō)。

  3. Modules/2.0 流派。這個(gè)觀點(diǎn)覺(jué)得瀏覽器有自身的特征,不應(yīng)該直接用 Modules/1.x 規(guī)范,但應(yīng)該盡可能與 Modules/1.x 規(guī)范保持一致。這個(gè)觀點(diǎn)下的典型代表是 BravoJS 和 FlyScript 的作者。BravoJS 作者對(duì) CommonJS 的社區(qū)的貢獻(xiàn)很大,這份 Modules/2.0-draft 規(guī)范花了很多心思。FlyScript 的作者提出了 Modules/Wrappings 規(guī)范,這規(guī)范是 CMD 規(guī)范的前身??上У氖?BravoJS 太學(xué)院派,F(xiàn)lyScript 后來(lái)做了自我閹割,將整個(gè)網(wǎng)站(flyscript.org)下線了。這個(gè)故事有點(diǎn)悲壯,下文細(xì)說(shuō)。

AMD 與 RequireJS

再來(lái)說(shuō) AMD 規(guī)范。真正的 AMD 規(guī)范在這里:Modules/AsynchronousDefinition。AMD 規(guī)范一直沒(méi)有被 CommonJS 社區(qū)認(rèn)同,核心爭(zhēng)議點(diǎn)如下:

執(zhí)行時(shí)機(jī)有異議

看代碼

Modules/1.0:

var a = require("./a") // 執(zhí)行到此處時(shí),a.js 才同步下載并執(zhí)行

AMD:

define(["require"], function(require) {  // 在這里,模塊 a 已經(jīng)下載并執(zhí)行好
  // ...
  var a = require("./a") // 此處僅僅是取模塊 a 的 exports})

AMD 里提前下載 a.js 是瀏覽器的限制,沒(méi)辦法做到同步下載,這個(gè)社區(qū)都認(rèn)可。

但執(zhí)行,AMD 里是 Early Executing,Modules/1.0 里是第一次 require 時(shí)才執(zhí)行。這個(gè)差異很多人不能接受,包括持 Modules/2.0 觀點(diǎn)的也不能接受。

這個(gè)差異,也導(dǎo)致實(shí)質(zhì)上 Node 的模塊與 AMD 模塊是無(wú)法共享的,存在潛在沖突。

模塊書(shū)寫風(fēng)格有爭(zhēng)議

AMD 風(fēng)格下,通過(guò)參數(shù)傳入依賴模塊,破壞了 就近聲明 原則。比如:

define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {    // 等于在最前面申明并初始化了要用到的所有模塊

   if (false) {       // 即便壓根兒沒(méi)用到某個(gè)模塊 b,但 b 還是提前執(zhí)行了
       b.foo()
   }

})

還有就是 AMD 下 require 的用法,以及增加了全局變量 define 等細(xì)節(jié),當(dāng)時(shí)在社區(qū)被很多人不認(rèn)可。

最后,AMD 從 CommonJS 社區(qū)獨(dú)立了出去,單獨(dú)成為了 AMD 社區(qū)。有陣子,CommonJS 社區(qū)還要求 RequireJS 的文檔里,不能再打 CommonJS 的旗幟(這個(gè) CommonJS 社區(qū)做得有點(diǎn)小氣)。

脫離了 CommonJS 社區(qū)的 AMD 規(guī)范,實(shí)質(zhì)上演化成了 RequireJS 的附屬品。比如

  1. AMD 規(guī)范里增加了對(duì) Simplified CommonJS Wrapper 格式的支持。這個(gè)背后是因?yàn)?RequireJS 社區(qū)有很多人反饋想用 require 的方式,最后 RequireJS 作者妥協(xié),才有了這個(gè)半殘的 CJS 格式支持。(注意這個(gè)是偽支持,背后依舊是 AMD 的運(yùn)行邏輯,比如提前執(zhí)行。)

  2. AMD 規(guī)范的演進(jìn),離不開(kāi) RequireJS。這有點(diǎn)像 IE…… 可能是我的偏見(jiàn)。

AMD 的流行,很大程度上取決于 RequireJS 作者的推廣,這有點(diǎn)像 less 因 Bootstrap 而火起來(lái)一樣。但火起來(lái)的東西未必好,比如個(gè)人覺(jué)得 stylus 就比 less 更優(yōu)雅好用。

關(guān)于 AMD 和 RequireJS,暫且按下不表。來(lái)看另一條暗流:Modules/2.0 流派。

Modules/2.0

BravoJS 的作者 Wes Garland 有很深厚的程序功底,在 CommonJS 社區(qū)也非常受人尊敬。但 BravoJS 本身非常學(xué)院派,是為了論證 Modules/2.0-draft 規(guī)范而寫的一個(gè)項(xiàng)目。學(xué)院派的 BravoJS 在實(shí)用派的 RequireJS 面前不堪一擊,現(xiàn)在基本上只留存了一些美好的回憶。

這時(shí),Modules/2.0 陣營(yíng)也有一個(gè)實(shí)戰(zhàn)派:FlyScript。FlyScript 拋去了 Modules/2.0 中的學(xué)究氣,提出了非常簡(jiǎn)潔的 Modules/Wrappings 規(guī)范:

module.declare(function(require, exports, module)
{   var a = require("a"); 
   exports.foo = a.name; 
});

這個(gè)簡(jiǎn)潔的規(guī)范考慮了瀏覽器的特殊性,同時(shí)也盡可能兼容了 Modules/1.0 規(guī)范。悲催的是,F(xiàn)lyScript 在推出正式版和官網(wǎng)之后,RequireJS 當(dāng)時(shí)正直紅火。期間 FlyScript 作者 khs4473 和 RequireJS 作者 James Burke 有過(guò)一些爭(zhēng)論。再后來(lái),F(xiàn)lyScript 作者做了自我閹割,將 GitHub 上的項(xiàng)目和官網(wǎng)都清空了,官網(wǎng)上當(dāng)時(shí)留了一句話,模糊中記得是

我會(huì)回來(lái)的,帶著更好的東西。

這中間究竟發(fā)生了什么,不得而知。后來(lái)我有發(fā)郵件給 @khs4473 詢問(wèn),khs 給了兩點(diǎn)挺讓我尊重的理由,大意是

  1. 我并非前端出身,RequireJS 的作者 James Burke 比我更懂瀏覽器。

  2. 我們應(yīng)該協(xié)同起來(lái)推動(dòng)一個(gè)社區(qū)的發(fā)展,即便它不是你喜歡的。

這兩句話對(duì)我影響很大。也是那之后,開(kāi)始仔細(xì)研究 RequireJS,并通過(guò)郵件等方式給 RequireJS 提出過(guò)不少建議。

再后來(lái),在實(shí)際使用 RequireJS 的過(guò)程中,遇到了很多坑。那時(shí) RequireJS 雖然很火,但真不夠完善。期間也在尋思著 FlyScript 離開(kāi)時(shí)的那句話:“我會(huì)回來(lái)的,帶著更好的東西”

我沒(méi) FlyScript 的作者那么偉大,在不斷給 RequireJS 提建議,但不斷不被采納后,開(kāi)始萌生了自己寫一個(gè) loader 的念頭。

這就是 Sea.js。

Sea.js 借鑒了 RequireJS 的不少東西,比如將 FlyScript 中的 module.declare 改名為 define 等。Sea.js 更多地來(lái)自 Modules/2.0 的觀點(diǎn),但盡可能去掉了學(xué)院派的東西,加入了不少實(shí)戰(zhàn)派的理念。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)