【深度好文】我在做前端構建過程中的思考

Jothy發表於2019-07-19
作者:支付寶前端 玄寂
寫在最前:歡迎來到「微貸前端技術」專欄,我們將與大家分享前端各領域的高質量技術文章,包括但不限於移動端、小程式、互動技術/資料視覺化、Node.js 全棧/中後臺、基礎架構、個人思考,不限於原創與翻譯。我們也歡迎優質文章的投稿。
編者按:本文作者曾負責過內部某產品(以下簡稱 X)的「構建」流程的重構工作,在重構過程中深挖了許多知識點(為這種刨根問底的精神點贊?),盡數濃縮在這篇五千字長文中,通篇讀完絕對能帶給你滿滿的乾貨!P.S.: 我們假定你已經瞭解 Webpack 以及前端構建相關概念。以下為正文。

在我的理解中,對 X 的構建經歷了以下兩大過程:

  1. Webpack 化
  2. 去 Webpack 化
乍一看就好像一開始走錯了路,但其實,並沒有清晰的界限可以判定某一技術變更是對的還是錯的,因為所有的決定都取決於當下的狀態。


Webpack 化

先來說一說為什麼 Webpack 化。 個人覺得 Webpack 最大的魅力是視萬物為 module 的能力。它高階的擴充套件能力,帶來了極高的個性化能力;極其活躍的社群或多或少可以讓我們少走路,也少走彎路;另外不得不著重提一下 CRA,CRA 在優化開發體驗上下的功夫非常深,目前絕大多數基於 Webpack 的上層封裝,都會有 CRA 的影子。 總而言之,Webpack :
  • 優異的 Add-on 設計
  • 成熟的社群配套
  • 活躍的社群氛圍
  • CRA 提供了優異的開發體驗模組
這些對於新生業務來說有著致命的誘惑。

另外由於之前對 Ant tool 的維護,我成為了 Webpack 死忠粉。所以我們自然而然地採用了 Webpack 做為構建的核心,也基於它的 Add-on 能力做了足夠多的個性化能力輸出。

一切看上去都沒什麼大問題,除了 Webpack 內建的快取優化方案坑了我們一次之外,沒有任何的意外。

然而凡事都有個但是,隨著 X 一步步發展,開發者的個性化需求也見漲。不出所料,越來越多的同學希望我們能開放核心的構建能力、更多的引數配置,因此 webpack.config.js 又被搬上了檯面。如果業務取向穩定可控,那麼 webpack.config.js 絕對是滋生惡魔的來源,關於這一塊探討我在 2 年前寫過一篇文章——構建工具的發展和未來的選擇,這裡就不再展開了,我擔憂的根本問題是這給後續帶來的維護、穩定性、安全性問題。 由於 Webpack 內建支援 pollyfill nodejs buildin 的模組,作為社群大庫他們自然會使用一些 module,通常他們也會提供一個 dist 目錄,以儲存可以純跑在瀏覽器端的檔案。而作為開發者,在我所看到的上千的專案中,幾乎 90% 的開發者都沒有這個意識,他們習慣「拿來即用」。

這又牽出了另外一個話題,很有趣。我應該曾有 2 次在對外分享中問過大家一個問題:如下程式碼,涉及了幾種模組規範?(大家可以在評論區發表自己的看法)

import a from 'a';

const hello = require('./hello');

const d = {
  x: 1,
};
module.exports.c = function c() {};
exports.b = {};
export { d };複製程式碼

讓人驚訝的是,能回答上來的人寥寥無幾。

不知道大家有沒有想過元罪是什麼?是 Webpack 這一類 universal 方案麼? 還是 NPM 把前端模組也引入了進來了?這是一個開放的命題,沒有標準答案。

回到正題,因為 Webpack 強大的包容性(當然這也怪我們當初沒限死),慢慢的我們發現在業務中,居然出現了一類 cheerio 的依賴,看到越來越多的案例把傳統 PC、Node 開發思路用到了 X 之上。就我個人來說,這是我不想看到的。

另外,在除錯環節,X 的研發流程中使用者會設定編譯模式(即只除錯固定頁),Webpack 貪婪式的編譯模式(全量構建)是否真的契合 X 的除錯模式?

此外,隨著 Web-IDE 的盛行,我們也琢磨著把編譯流程整合到瀏覽器端,Webpack 的厚重讓可能性顯得比較渺茫。

不過最最最重要的原因還是,Webpack 的效率仍略顯侷促,特別是在與友商的對比下。不過在這裡要給 Webpack 抱不平的是,Webpack 的能力是遠大於友商的,而這種能力,就像現在的手機,它的算力是過剩的,而這種過剩的算力導致了時間的上累。

調研之路,友商帶來的啟發

友商在安全的防護上做的是相當到位的,我很難通過 hack 的方式來窺視整個流程。
探究的過程中留給我最深刻的印象是對於剋制的理解。產品層是剋制的,功能是剋制的,開放度是剋制的。要真正在浮躁的網際網路中做到如此「剋制」還是挺難的。

接下來我們單純從技術層面來說一說。注意:以下都是我個人的理解,未必對。

友商的技術選型,按我的理解應該遵循四點:輕,快,可管控,夠用就好。這幾點在框架、DSL、構建服務等方面均有體現。

框架層面,友商相比螞蟻,做的還是很薄的,螞蟻背靠 react 生態,但這很有可能是把雙刃劍?我們是否做好了開放所帶來的的管控問題?我覺得這些都是棘手的問題。而友商的輕薄,雖然在管控性上做的比較極致,但是面對開發者天馬行空的需求,或許他們的問題更多的是如何來支援。典型的案例就是 NPM 支援,在螞蟻這套技術體系下,我們是純天然就支援現有的前端研發鏈路的,所以在開發習慣的延續性上基本沒有任何問題,但友商最初的做法是,讓開發者在 X 的生態外自己建立一個 NPM 使用流程,這也就是為什麼在社群裡面會有很多這類方案的原因,而後續友商釋出支援 NPM,但仔細琢磨,其實友商 NPM 更加偏向於是 component,而不是標準前端意義上的模組,比如不支援 nodejs module shim 就可以看出。反觀我們的 NPM 目前已被玩壞。

另外在梳理過程中發現,友商在處理 *xml 檔案和 *css 檔案時,它是解耦的,貪婪的。稍微深入就可以看到,他有專門的二進位制編譯工具負責此類檔案的編譯,利用編譯工具可以批處理 *xml 和 *css 檔案。這和我們之前基於 Webpack 的方案有著巨大的差異,我們的編譯本質上是有上下文的,即比如 component 樣式會有作用域提升,進而影響 page,另外這當中大量依賴了 Webpack 的語法分析 和 loader 機制,與此同時我們還依賴了一個雖不屬於構建卻又能影響構建的外部過程,所以從根本上來說,在現有的技術架構上我們很難真正意義上超越友商。
另外,友商在框架層應該就考慮了模組載入,所以友商對於模組的載入模式優化或者內部模組間的管控,比如控制 exports 有著更加靈活的空間,但我們在這一層依賴 Webpack 提供的 runtime,正因為這種模式,如果友商想要往比如 Web-IDE 靠攏,其實更加可行。對於這一層的認知主要是我接觸到了 Stackblitz,以及之後慢慢了解到了 Systemjs。這部分內容我放到下節。

通過對友商的學習,看到他們的產品沒有厚重的堆積感,我感受到了小而美的力量。


CodeSandbox、Stackblitz 帶來的啟發

對於我來說,我的命題就是儘可能的讓開發者以最快的速度來完成開發環境的初始化。我做過非常多基於 Webpack 的嘗試,熟人常知的 happypack,cache-loader,thread-loader,hardsource,以及如何儘可能的讓快取增加有效性,以及甚至基於 Webpack 的 memory fs hack 了一套自己的邏輯等等等等,但效果並不讓人滿意,甚至很多優化反而會導致很奇怪的問題。

很神奇的是,我有一次無意看到了友商在 worker 端程式碼的載入過程,它並沒有真正意義上的 bundle 過程,而是對所有的 worker 進行了全量的 http 請求,我的第一個反應是 http 有同源策略,為什麼他們還敢這樣做,難道併發量不會成為瓶頸嗎?這種方式我見不到相對 Webpack 的優勢到底是什麼?!

這困擾了我很長時間,直至 Stackblitz 走到了我的眼前——最初就是那篇 turboCDN 文章。我基本上挖墳了所有有關 Stackblitz 的 Issue Twitter。實際上 CodeSandbox 在這一塊上也用著類似的方案,但 CodeSandbox 的問題是,他在這塊的實現在當時與其業務實現深度耦合,所以我更加傾選擇 systemjs,至少它是獨立的,且有自己的生態。隨著瞭解的深入,我逐漸對他如何在瀏覽器內實現偽 bundle 和 NPM 如何跑在瀏覽器端有了一定的瞭解。當時我的最大想法就是,我可以基於 systemjs 來實現一套動態和按需的載入方案,在本地開發階段省去所有 bundle 過程,檔案只有在真正用到時再進行編譯,甚至通過一些方式,比如在瀏覽器端實現 fs,那麼這套方案就可以被移植到 Web-IDE 上。基於這樣的構想,我開始了很長時間的嘗試,從 18 年 10 月初我寫下第一行程式碼,代號被取為 Gravity,更多 Gravity 設計的內容我會放在下一段 。但在瀏覽器端實現 fs 和利用 web worker 來實現 compile 上我碰了很多壁,因為瀏覽器端沒有 nodejs 環境,我要解決所有的 pollyfill 的問題,以及檔案 resolve 的差異性。然而時間上並不允許我做過多的技術性探究,所以第一階段我退而求其次,把這部分內容架設在了本地,通過啟動一個 koajs 例項來解決,即瀏覽器端發生資源請求時,通過中介軟體(所處 nodejs 環境)來實現真正的編譯過程。僅僅只是這一步嘗試,就把我原先在 mac 上花費的 40s+ 編譯時間降到了 8s 左右,說實話我自己都沒法相信。

學習 CodeSandbox、Stackblitz 帶來的啟發是,利用純瀏覽器帶來的架構上的變更或許是另外一種出路。

再來談談 bundler 歷史

最近有一篇文章出現在社群,題目是:A Future Without Webpack。如文中所說,這幾年裡,JavaScript 打包過程從一個僅僅針對生產環境的優化,發展到了現在 web 應用開發必要的一個步驟。你喜歡也好,厭惡也罷,你都很難否定一個事實,那就是它大大增加了 web 應用開發的複雜度。而 web 領域一直來它所引以為傲的的點是,view-source 和 easy-to-get-started。這或許現在成為了一種諷刺。

為什麼我們需要 bundler?

把時間倒退到六年前,那會兒我們對於打包的概念應該還是在 grunt 或 gulp 流式的任務處理,對資原始檔的處理也僅僅是壓縮和拼接。而後前端界興起了模組化浪潮,模組化後的程式碼放在哪兒,又如何被引入,相信這個問題是那會兒前端們最為關注的事情,所以又要翻翻老賬 - seajs 有自己的源伺服器來承載模組化後的模組也是理所當然的事情。但大家也都知道,世界上最大的程式碼源服務是 NPM,但如上文中提到的起始時 NPM = “Node.js Package Manager”,它並不真正意義上服務於前端瀏覽器。但是開發者對此的訴求實在是太大了,而眾所周知 Node.js 使用的是 CJS 模組規範,所以不經過打包根本不可能執行於瀏覽器中,而諸多的模組定義,也給了 Browserify, Webpack 等發揮的空間,特別是 Webpack universal 的概念非常契合大眾訴求。

當然作者觀點更多是當下已然 2019 了,我們應該往前看,因為在瀏覽器端已經支援了 ESM。對我而言,我覺得這種想要跳出困局尋求突破的精神是更加值得學習的。另外也帶來一個問題,撇開作者提供的思路或實現,本地 bundle 基於現有的技術架構,能否有所破局。


新技術方案 Gravity

在談新方案 Gravity 前,還要來回首下我在幾年前寫的一篇文章 - 支付寶前端構建工具的發展和未來的選擇,那會兒我們最大的困擾是配置帶來的不可控性。所以那會兒我提出了構建因子以及 preset 的想法。出於對配置的敬畏,在 Gravity 中我把這一套想法完全實現了出來(其實在那篇文章幾個月後就有一版實現,但不幸的是沒有繼續深入就流產了,另外也因為我工作內容而被動調整了)。

所以 Gravity 最 base 的架構思路是讓 Gravity 變成構建工具的工廠,讓各種業務形態的構建變成 Gravity 的一種上層實現。要實現如上這個想法也就意味著,Gravity 必須要有好的外掛機制。這個時候 tapable 自然而然成為了我的最佳選擇,對於 tapable 淵源還要從我解析 Webpack watch 實現說起,當然這不是我們今天的重點。重點是 Webpack 就是基於 tapable 實現出來的,它的靈活性健壯性毋庸置疑,另外我徹徹底底研究過 tapable,真的是喜歡到不行。還有非常重要的一點是,我用 tapable 來設計外掛機制,可以對開發者非常友好,因為幾乎不需要學習。

另外還有一個好處是基於 tapable 我可以非常輕鬆實現一種時序,比如說我現在要實現一個 css 檔案的載入 loader,在上文中我大概說了因為在時間上的原因我並未在一期就嘗試把所有流程都丟到瀏覽器內完成,而是把一部分工作丟給了 koa 的中介軟體,所以在檔案處理上(Webpack 中叫 loader),我實現了一套動態生成中介軟體的方案,原因在於實現一個 css 檔案的載入可能需要經過多個載入器,比如 post-css,css,style,這其中就有時序的問題,所以藉由 tapable 我可以很方便來根據描述檔案(類 Webpack rules 設計)動態建立一個時序,轉而變成一箇中介軟體。

在設計 Gravity loader 時,和 CodeSandbox 一樣,我們把 API 儘可能的往 Webpack 靠了。原因就在於我想要有複用 Webpack loader 的可能性。另外對於上層實現,也會更加友好,因為基本可以做到和 Webpack 長得一樣,用的一樣。

另外還有很多細節我這裡就不在闡述了。

Gravity 會進一步維護,也會在合適的時候開源。它的目標也非常明確,成為真正意義上瀏覽器端方案,在上層實現層可以對接到更多的業務場景。最終通過一個 Web-IDE 把所有的事情都串聯起來。

總結

拋棄偏見,我相信 Cloud IDE 一定是未來,而面向 Web 的架構一定是當務之急。


「微貸前端技術」致力於與你共享高質量的技術文章

歡迎關注我們的專欄,將文章分享給你的好友,共同成長 :-)

相關文章