<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端構(gòu)建系統(tǒng)淺析

          共 6775字,需瀏覽 14分鐘

           ·

          2024-08-07 08:45

          開發(fā)者編寫JavaScript代碼,而瀏覽器運行JavaScript代碼。從根本上說,前端開發(fā)不需要構(gòu)建步驟。那么,為什么現(xiàn)代前端需要構(gòu)建步驟呢?

          隨著前端代碼庫越來越龐大,以及開發(fā)者體驗越來越重要,直接將JavaScript源碼傳輸給客戶端會帶來兩個主要問題:

          1. 不支持的語言特性:由于JavaScript在瀏覽器中運行,而瀏覽器種類繁多、版本各異,每增加一種語言特性,能運行你JavaScript的客戶端數(shù)量就會減少。此外,像JSX這樣的語言擴展不是有效的JavaScript,任何瀏覽器都無法運行。

          2. 性能問題:瀏覽器必須單獨請求每個JavaScript文件。在一個大型代碼庫中,這可能導致成千上萬次的HTTP請求來渲染一個頁面。在HTTP/2之前,這還會導致成千上萬次的TLS握手。

            另外,可能需要幾次連續(xù)的網(wǎng)絡(luò)往返才能加載所有JavaScript。例如,如果index.js導入page.js,而page.js又導入button.js,那么需要三次連續(xù)的網(wǎng)絡(luò)往返才能完全加載JavaScript。這被稱為瀑布問題。

            源文件由于長變量名和空白縮進字符等原因,也可能不必要地變大,增加帶寬使用和網(wǎng)絡(luò)加載時間。

          前端構(gòu)建系統(tǒng)處理源代碼并生成一個或多個優(yōu)化后的JavaScript文件,便于傳輸給瀏覽器。最終的可分發(fā)文件通常是人類難以閱讀的。

          構(gòu)建步驟

          前端構(gòu)建系統(tǒng)通常包括三個步驟:轉(zhuǎn)譯、打包和壓縮。

          某些應(yīng)用程序可能不需要所有三個步驟。例如,較小的代碼庫可能不需要打包或壓縮,而開發(fā)服務(wù)器可能為了性能跳過打包和/或壓縮。此外,還可以添加自定義步驟。

          有些工具實現(xiàn)了多個構(gòu)建步驟。尤其是打包工具通常實現(xiàn)所有三個步驟,僅使用打包工具就足以構(gòu)建簡單的應(yīng)用程序。復(fù)雜的應(yīng)用程序可能需要專門的工具來分別執(zhí)行每個構(gòu)建步驟,以提供更大的功能集。

          轉(zhuǎn)譯

          轉(zhuǎn)譯通過將用現(xiàn)代JavaScript標準編寫的代碼轉(zhuǎn)換為舊版本的JavaScript標準來解決不支持的語言特性問題。如今,ES6/ES2015是一個常見的目標版本。

          框架和工具也可能引入轉(zhuǎn)譯步驟。例如,JSX語法必須轉(zhuǎn)譯為JavaScript。如果一個庫提供了Babel插件,這通常意味著它需要一個轉(zhuǎn)譯步驟。此外,像TypeScript、CoffeeScript和Elm這樣的語言必須轉(zhuǎn)譯為JavaScript。

          CommonJS模塊(CJS)也必須轉(zhuǎn)譯為瀏覽器兼容的模塊系統(tǒng)。自從2018年瀏覽器廣泛支持ES6模塊(ESM)后,通常建議轉(zhuǎn)譯為ESM。由于ESM的導入和導出是靜態(tài)定義的,因此更容易優(yōu)化和進行樹搖。

          目前常用的轉(zhuǎn)譯器有Babel、SWC和TypeScript Compiler。

          1. Babel(2014)是標準的轉(zhuǎn)譯器:一個用JavaScript編寫的單線程轉(zhuǎn)譯器,速度較慢。許多需要轉(zhuǎn)譯的框架和庫通過Babel插件實現(xiàn),因此Babel必須成為構(gòu)建過程的一部分。然而,Babel難以調(diào)試且常常令人困惑。
          2. SWC(2020)是一個用Rust編寫的多線程快速轉(zhuǎn)譯器。它聲稱速度比Babel快20倍,因此被較新的框架和構(gòu)建工具使用。它支持轉(zhuǎn)譯TypeScript和JSX。如果你的應(yīng)用程序不需要Babel,SWC是一個更好的選擇。
          3. TypeScript Compiler(tsc)也支持轉(zhuǎn)譯TypeScript和JSX。它是TypeScript的參考實現(xiàn),也是唯一功能全面的TypeScript類型檢查器。然而,它非常慢。雖然TypeScript應(yīng)用程序必須使用TypeScript Compiler進行類型檢查,但在構(gòu)建步驟中,使用其他轉(zhuǎn)譯器會更高效。

          如果你的代碼是純JavaScript并且使用ES6模塊,可以跳過轉(zhuǎn)譯步驟。

          對于某些不支持的語言特性,另一個解決方案是polyfill。polyfill在運行時執(zhí)行,實現(xiàn)在執(zhí)行主應(yīng)用程序邏輯之前任何缺失的語言特性。然而,這增加了運行時開銷,有些語言特性無法用polyfill實現(xiàn)。參見core-js。

          所有打包工具本質(zhì)上都是轉(zhuǎn)譯器,因為它們解析多個JavaScript源文件并生成一個新的打包JavaScript文件。在此過程中,它們可以選擇在生成的JavaScript文件中使用哪些語言特性。有些打包工具還可以解析TypeScript和JSX源文件。如果你的應(yīng)用程序有簡單的轉(zhuǎn)譯需求,可能不需要單獨的轉(zhuǎn)譯器。

          打包

          打包解決了需要進行多次網(wǎng)絡(luò)請求和瀑布問題。打包工具將多個JavaScript源文件連接成一個JavaScript輸出文件,稱為bundle,而不改變應(yīng)用程序行為。該bundle可以通過瀏覽器在一次網(wǎng)絡(luò)往返請求中高效加載。

          目前常用的打包工具有Webpack、Parcel、Rollup、esbuild和Turbopack。

          1. Webpack(2014)在2016年左右獲得了巨大的人氣,后來成為標準的打包工具。與當時流行的Browserify不同,Webpack開創(chuàng)了“加載器”這一概念,通過導入轉(zhuǎn)換源文件,使Webpack能夠協(xié)調(diào)整個構(gòu)建流程。

            加載器允許開發(fā)者在JavaScript文件中透明地導入靜態(tài)資源,將所有源文件和靜態(tài)資源組合成一個依賴關(guān)系圖。使用Gulp時,每種類型的靜態(tài)資源必須作為單獨的任務(wù)進行構(gòu)建。Webpack還支持開箱即用的代碼分割,簡化了其設(shè)置和配置。

            Webpack速度較慢且是單線程的,用JavaScript編寫。它高度可配置,但其眾多配置選項可能令人困惑。

          2. Rollup(2016)利用了ES6模塊在瀏覽器中的廣泛支持以及它帶來的優(yōu)化,尤其是樹搖。它生成的bundle大小遠小于Webpack,導致Webpack后來也采用了類似的優(yōu)化。Rollup是一個單線程的打包工具,用JavaScript編寫,性能僅略優(yōu)于Webpack。

          3. Parcel(2018)是一個低配置的打包工具,旨在開箱即用,為構(gòu)建過程的所有步驟和開發(fā)者工具需求提供合理的默認配置。它是多線程的,速度比Webpack和Rollup快得多。Parcel 2在底層使用SWC。

          4. Esbuild(2020)是一個為并行性和性能優(yōu)化而架構(gòu)的打包工具,用Go編寫。它的性能比Webpack、Rollup和Parcel高出數(shù)十倍。Esbuild實現(xiàn)了一個基本的轉(zhuǎn)譯器和一個壓縮工具。然而,它的功能不如其他打包工具,提供的插件API有限,不能直接修改AST。可以在傳遞給esbuild之前對源文件進行轉(zhuǎn)換,而不是使用esbuild插件修改源文件。

          5. Turbopack(2022)是一個支持增量重建的快速Rust打包工具。該項目由Vercel構(gòu)建,并由Webpack的創(chuàng)建者領(lǐng)導。目前處于測試階段,可以在Next.js中選擇使用。

          如果你的模塊很少或網(wǎng)絡(luò)延遲很低(例如在本地環(huán)境中),可以跳過打包步驟。一些開發(fā)服務(wù)器在開發(fā)服務(wù)器中也選擇不打包模塊。

          代碼拆分

          默認情況下,客戶端React應(yīng)用會被轉(zhuǎn)換為一個bundle。對于有很多頁面和功能的大型應(yīng)用,bundle可能非常大,抵消了打包的原始性能優(yōu)勢。

          通過將bundle拆分成多個較小的bundle,或稱為代碼拆分,解決了這個問題。一種常見的方法是將每個頁面拆分為一個單獨的bundle。在HTTP/2下,共享依賴項也可以被分解到它們自己的bundle中,以避免重復(fù),幾乎沒有成本。此外,大型模塊可以拆分為單獨的bundle,并按需延遲加載。

          代碼拆分后,每個bundle的文件大小大大減小,但現(xiàn)在需要額外的網(wǎng)絡(luò)往返,從而可能重新引入瀑布式加載問題。代碼拆分是一個權(quán)衡。

          文件系統(tǒng)路由器,由Next.js流行起來,優(yōu)化了代碼拆分的權(quán)衡。Next.js為每個頁面創(chuàng)建單獨的bundle,只包括該頁面導入的代碼。在加載一個頁面時,會并行預(yù)加載該頁面使用的所有bundle。這優(yōu)化了bundle大小而不會重新引入瀑布式加載問題。文件系統(tǒng)路由器通過為每個頁面創(chuàng)建一個入口點(pages/**/*.jsx),而不是傳統(tǒng)客戶端React應(yīng)用的單個入口點(index.jsx)來實現(xiàn)這一點。

          搖樹

          一個bundle由多個模塊組成,每個模塊包含一個或多個導出。通常,一個給定的bundle只使用其導入模塊的一個子集。打包工具可以在搖樹過程中移除未使用的模塊和導出。這樣優(yōu)化了bundle大小,提升了加載和解析時間。

          搖樹依賴于對源文件的靜態(tài)分析,因此當靜態(tài)分析變得更加困難時,搖樹的效率會受到影響。兩個主要因素影響搖樹的效率:

          1. 模塊系統(tǒng): ES6模塊具有靜態(tài)導入和導出,而CommonJS模塊具有動態(tài)導入和導出。因此,打包工具在搖樹ES6模塊時可以更加積極和高效。
          2. 副作用: package.jsonsideEffects屬性聲明了一個模塊在導入時是否具有副作用。當存在副作用時,由于靜態(tài)分析的限制,未使用的模塊和導出可能無法被搖樹。

          靜態(tài)資源

          靜態(tài)資源,如CSS、圖片和字體,通常在打包步驟中被添加到可分發(fā)文件中。它們也可能在壓縮步驟中被優(yōu)化文件大小。

          在Webpack之前,靜態(tài)資源在構(gòu)建管道中與源代碼分開構(gòu)建,作為一個獨立的構(gòu)建任務(wù)。為了加載靜態(tài)資源,應(yīng)用必須通過它們在可分發(fā)文件中的最終路徑引用它們。因此,常常需要根據(jù)URL約定仔細組織資源(例如 /assets/css/banner.jpg/assets/fonts/Inter.woff2)。

          Webpack的 loader 允許從JavaScript中導入靜態(tài)資源,將代碼和靜態(tài)資源統(tǒng)一到一個依賴圖中,簡化了它們的組織和加載。盡管如此,將靜態(tài)資源捆綁在JavaScript文件中會增加bundle大小,最好將靜態(tài)資源分離。

          代碼壓縮

          代碼壓縮主要是解決文件過大的問題。壓縮工具可以在不改變代碼功能的情況下,減少文件的大小。對于JavaScript和CSS等代碼,壓縮工具可以縮短變量名、去除空白和注釋、刪除無用代碼,并優(yōu)化語言特性使用。對于其他靜態(tài)資源,壓縮工具也能優(yōu)化文件大小。通常,壓縮工具會在構(gòu)建過程的最后一步運行。

          目前常用的JavaScript壓縮工具包括Terser、esbuild和SWC。Terser是從不再維護的uglify-es分支出來的,用JavaScript編寫,因此速度較慢。而esbuild和SWC除了壓縮功能外,還有其他功能,并且速度比Terser更快。

          常用的CSS壓縮工具有cssnano、csso和Lightning CSS。cssnano和csso是純CSS壓縮工具,用JavaScript編寫,因此速度較慢。Lightning CSS則是用Rust編寫的,聲稱速度比cssnano快100倍。此外,Lightning CSS還支持CSS轉(zhuǎn)換和打包功能。

          開發(fā)工具

          基本的前端構(gòu)建管道可以生成優(yōu)化的生產(chǎn)發(fā)布版。然而,有許多工具可以增強基本構(gòu)建管道,提升開發(fā)體驗。

          元框架

          前端領(lǐng)域在選擇合適的工具包時常常令人困惑。例如,上述五種打包工具中,你應(yīng)該選擇哪一種?

          元框架提供了一組經(jīng)過精選的工具包,包括構(gòu)建工具,它們可以協(xié)同工作,實現(xiàn)特定的應(yīng)用模式。例如,Next.js專注于服務(wù)器端渲染(SSR),而Remix則專注于漸進增強。

          元框架通常提供預(yù)配置的構(gòu)建系統(tǒng),省去了自己拼湊的麻煩。它們的構(gòu)建系統(tǒng)既有生產(chǎn)環(huán)境的配置,也有開發(fā)服務(wù)器的配置。

          與元框架類似,Vite等構(gòu)建工具也提供預(yù)配置的構(gòu)建系統(tǒng),適用于生產(chǎn)和開發(fā)環(huán)境。不同的是,它們不強制特定的應(yīng)用模式,適用于一般的前端應(yīng)用。

          源映射(Sourcemaps)

          構(gòu)建管道生成的發(fā)布版對大多數(shù)人來說是難以閱讀的。這使得調(diào)試錯誤變得困難,因為錯誤的追蹤指向的是不可讀的代碼。

          源映射解決了這個問題,將發(fā)布版中的代碼映射回其原始源碼位置。瀏覽器和調(diào)試工具(如Sentry)使用源映射來恢復(fù)并顯示原始源碼。在生產(chǎn)環(huán)境中,源映射通常對瀏覽器隱藏,只上傳到調(diào)試工具,以避免公開源碼。

          構(gòu)建管道的每一步都可以生成源映射。如果使用多個構(gòu)建工具,源映射將形成一個鏈條(例如:source.js -> transpiler.map -> bundler.map -> minifier.map)。要找到壓縮代碼對應(yīng)的源碼,必須遍歷源映射鏈條。

          然而,大多數(shù)工具無法解釋源映射鏈條;它們最多只期望每個文件有一個源映射。因此,源映射鏈條必須被壓平成一個源映射。預(yù)配置的構(gòu)建系統(tǒng)會解決這個問題(如Vite的combineSourcemaps函數(shù))。

          熱重載(Hot Reload)

          開發(fā)服務(wù)器通常提供熱重載功能,當源代碼改變時,自動重新構(gòu)建新包并重新加載瀏覽器。雖然這比手動重建和重新加載要好得多,但仍然有點慢,并且所有客戶端狀態(tài)在重新加載時都會丟失。

          模塊熱替換(Hot Module Replacement)改進了熱重載,通過在運行的應(yīng)用程序中替換更改的包進行原位更新。這保留了未更改模塊的客戶端狀態(tài),并減少了代碼更改到應(yīng)用更新之間的延遲。

          然而,每次代碼更改都會觸發(fā)導入它的所有包的重建。這使得重建時間相對于包大小呈線性增長。因此,在大型應(yīng)用中,模塊熱替換可能會因為重建成本的增加而變慢。

          Vite倡導的無打包開發(fā)服務(wù)器模式則不打包開發(fā)服務(wù)器,而是直接向瀏覽器提供每個源碼文件對應(yīng)的ESM模塊。在這種模式下,每次代碼更改只觸發(fā)一個模塊在前端的替換。這樣,刷新時間復(fù)雜度相對于應(yīng)用大小幾乎是恒定的。然而,如果模塊很多,初始頁面加載時間可能會變長。

          單一倉庫(Monorepos)

          在擁有多個團隊或多個應(yīng)用的組織中,前端可能會被拆分成多個JavaScript包,但保留在一個倉庫中。在這種架構(gòu)下,每個包都有自己的構(gòu)建步驟,共同形成包的依賴圖。應(yīng)用程序位于依賴圖的根部。

          單一倉庫工具負責協(xié)調(diào)依賴圖的構(gòu)建。它們通常提供增量重建、并行處理和遠程緩存等功能。通過這些功能,大型代碼庫也能享受小型代碼庫的構(gòu)建時間。

          標準的單一倉庫工具如Bazel,支持多種語言、復(fù)雜的構(gòu)建圖和隔離執(zhí)行。然而,前端JavaScript生態(tài)系統(tǒng)是最難完全整合到這些工具中的,目前幾乎沒有先例。

          幸運的是,針對前端的單一倉庫工具存在,但它們?nèi)狈azel等工具的靈活性和穩(wěn)健性,特別是隔離執(zhí)行。

          目前常用的前端單一倉庫工具是Nx和Turborepo。Nx更成熟,功能更豐富,而Turborepo是Vercel生態(tài)系統(tǒng)的一部分。過去,Lerna是將多個JavaScript包鏈接在一起并發(fā)布到NPM的標準工具。2022年,Nx團隊接管了Lerna,現(xiàn)在Lerna在后臺使用Nx進行構(gòu)建。

          趨勢

          最后,來說一說前端構(gòu)建的趨勢。

          較新的構(gòu)建工具使用編譯語言編寫,注重性能。2019年前端構(gòu)建非常慢,但現(xiàn)代工具大大加快了速度。然而,現(xiàn)代工具的功能較少,有時與庫不兼容,因此舊代碼庫往往難以輕松切換到它們。

          服務(wù)器端渲染(SSR)在Next.js興起后變得更受歡迎。SSR對前端構(gòu)建系統(tǒng)沒有引入任何根本性的不同。SSR應(yīng)用也必須向瀏覽器提供JavaScript,因此它們執(zhí)行相同的構(gòu)建步驟。

          本文譯自:https://sunsetglow.net/posts/frontend-build-systems.html


          瀏覽 105
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美日韩激情四射 | 欧美成人无码一区二区三区 | 超嫩俩小younv合集 | 北条麻妃网址 | 精品久久久久久久成人热 91 |