iT邦幫忙

2024 iThome 鐵人賽

DAY 7
0
Modern Web

Rust 的戰國時代:探索網頁前端工具的前世今生系列 第 7

Day 07:網頁前端工具的前世今生 (五) - 模組化最終章,一口氣說完 NPM、Browserify、Webpack、ESM

  • 分享至 

  • xImage
  •  

今天將用 ESM 收尾關於 JavaScript 模組化的歷史,並帶出以前 NPM、Browserify、Webpack 等工具如何成為複雜的現代網頁開發中一個利器,應該也算是第一篇聊 Webpack 歷史的中文文章。

day 07 banner

(Photo by Jr Korpa)

前言

關於 JavaScript 模組化的歷史不小心扯得有點太多,今天期待能簡潔做一些收尾,快快進到此系列的核心主題!

NPM (2009)

前面在 CommonJS 的章節中有稍微提過 Node.js 的歷史,這裡可以簡單根據 Node.js 紀錄片中對 NPM 作者 —— Isaac Schlueter 的訪談,記錄一些可以了解下的 fun fact。

在 Node.js 出現前,當時任職於 Yahoo 的 Isaac 和同事們就曾嘗試將 JavaScript 移植到 server side。儘管他一直知道有一個名為 Node.js 的新技術,也嘗試過最初的 v0.0.2 版,但並不滿意。直到後來發展到 v0.0.6 版時,在社群的推薦下,Isaac 實作拿 Node.js 開發後發現跟他的想像一致,因此也決定加入社群一起開發。

而當年他們有個線上討論群組,如果要去發佈與使用別人做好的套件其實很陽春,大概會像這樣的步驟:

  • 將套件丟到 GitHub、雲端位置或寄信附檔
  • 要使用或測試的人去下載一包 zip 檔
  • 解壓縮後複製到自己的 repo 裡
  • 根據文件自己去編譯出執行檔
  • 引入並測試、繼續開發

但這樣的流程似乎不夠聰明,也因此他參考其他語言的套件管理工具的方式,創造了 NPM 這個偉大的發明,讓後人在分享與使用各種 JavaScript 套件都能無痛引入。

Browserify (2011)

Browserify

昨天提到許多瀏覽器的模組化標準正爭執的如火如荼,而與此同時 James Halliday (substack) 這位老兄有另一種想法,NPM 的這些套件這麼方便,瀏覽器沒辦法用實在太可惜了,不如就像魔法師一般,寫個工具來讓瀏覽器也可以用 CommonJS 的方式載入現成的模組吧,因此 Browserify 就誕生了!

參考個官網上的 hello world 範例,當你用 npm install uniq 安裝了一個模組後,可以在你的前端程式中寫像是 CommonJS 的程式:

// main.js
var unique = require('uniq');

var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];

console.log(unique(data));

但如果你今天直接在 HTML 載入這個 main.js 瀏覽器會認不得 require 的語法,因此你可以用 Browserify 做轉譯:

browserify main.js -o bundle.js

轉譯後你可以得到這樣的結果 (經過簡化),即可讓瀏覽器去順利執行:

// bundle.js
(function(){
  ...
})()({
  1: [function(require, module, exports) {
    // uniq 模組相關程式碼
    // ...
    module.exports = unique;
  }, {}],
  2: [function(require, module, exports) {
    // main.js
    var unique = require('uniq');

    var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];
    console.log(unique(data));
  }, {"uniq": 1}]
}, {}, [2]);

Browserify 的運作原理可以這樣理解,可以看得到後續其他 bundler 的影子:

  • 從 entry point 的 JavaScript 檔案開始解析
  • 將程式碼轉成 AST (抽象語法樹) 如下圖
  • 遍歷 AST 去確認所有的 require 載入模組的地方
  • 遞迴地分析依賴模組中層層的關係,找出依賴關係圖
  • 最後將上述的依賴關係做打包並輸出

AST

另外也好奇查了下作者 James Halliday,目前幾乎已經沒什麼資料,連 GitHub 帳號都更換了,有查到在講 Browserify 的議程只有在 LXJS 2013 這一場,可以看到講話風格跟投影片都很 geek,還硬是酸了一下當時風風雨雨的 Node.js

LXJS 2013 - James Halliday

Webpack 的橫空出世 (2012)

參考這份簡報資料,當年的 Webpack 作者 Tobias Koppers 正在寫碩論,其中有個部份是關於網頁應用的優化,當時他需要找一個適合的 bundler,找到一個叫做 modules-webmake 的專案,但其中並沒有做 code splitting,因此他有向作者開了一個 issue 但沒有被重視,因此他決定自己弄髒手來做一個符合他需求的工具,而這就是 Webpack 的起源。

webpack issue

不久後他完成了這個可以做程式分塊的版本,一開始取名叫 modules-webpack 想跟原套件打對台,而在此之後才更名叫做 Webpack。

在發佈後也漸漸地有其他開發者加入貢獻,陸續補上了各種 CommonJS、AMD 等模組化標準支援、各種廣義模組的 loader (style-loader、css-loader 等)、plugin system、compiler、HMR,直到 2014.02 終於釋出正式版 v1.0.0。

另外可能也多少受到 Browserify 啟發,以及其他當時的 task runner 如 Gulp.jsGrunt 跟著發跡,後來 Webpack 也成為了這些工具的集大成者,一直到今日除了作為 bundler 之外,也有完善的 loader 與 plugin 系統、CLI 工具、HMR dev server、code splitting、tree-shaking、支持多種模組化標準等,成為複雜的現代網頁開發中一個利器。

ESM 的一統天下 (2015)

最後也簡單提一下 ESM,其實模組化標準的最終結局的故事大家可能聽過很多了,在 ES6 終於定義了官方的模組化標準後,終於補上 JavaScript 最重要的一塊拼圖,讓後續如非同步加載、動態載入、tree-shaking 的靜態分析等有了穩健的基礎。

只是因為曾經有百家爭鳴的時代,也有 Node.js CommonJS 的流行,因此儘管 ESM 已經出道快十年,目前開發者仍偶爾需要注意多種模組化標準無法兼容的問題。

除了將 Babel 設定好做轉譯外,時至今日許多新工具與新版本仍在朝向統一標準努力,像是:

  • 像是 Node.js 多年來容易讓大家混淆的 .cjs.mjstype: ‘module’ 等等設定,也在今年的 v22 中提到可以用 --experimental-require-module 這個 flag 來幫助漸進式將現有的 CommonJS 遷移至 ESM。詳細說明也可以在參考 C.T 這篇《一篇文搞清楚 Node.js 模組行為,自由運用 CommonJS 與 ESM 模組》,這邊就不贅述
  • 去年強勢登場的 JavaScript runtime — Bun,其中一個特色就是支援 CommonJS 與 ESM 語法在同一個檔案中 (ref)

小結

今天終於一口氣帶到 ESM,不得不說講模組化歷史真的是個大坑,但寫完也是有一點小小成就感,雖然稱不上完美,有許多語法及細節沒提到,但可能就像 Huli 在這篇文章中說的:

「寫作的時候,選擇什麼要講什麼不講也是一門技藝」

就像我原本也是滿腔熱血覺得應該可以再一路講個 Rollup、Parcel,但總覺得再考古下去好像要離 Rust 這個 prefix 越來越遠了,期待下一篇開始可以直接來玩點有趣的實作,我們明天見!

參考資料


上一篇
Day 06:網頁前端工具的前世今生 (四) - Client Side 模組化的百家爭鳴
下一篇
Day 08:現代 bundler、build tool 簡介 (Vite、esbuild、Rspack、Rolldown、Turbopack)
系列文
Rust 的戰國時代:探索網頁前端工具的前世今生12
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言