建工具選型別只看功能清單)
構(gòu)建工具選型別只看功能清單構(gòu)建工具影響本地啟動(dòng)、增量更新、測(cè)試、生產(chǎn)打包和 CI 緩存。官方功能清單能說(shuō)明工具支持什么卻無(wú)法回答倉(cāng)庫(kù)里的自定義 Loader、動(dòng)態(tài)導(dǎo)入、CSS 處理和瀏覽器目標(biāo)能否原樣遷移。選型前先測(cè)量現(xiàn)有鏈路再用同一個(gè)應(yīng)用比較候選方案結(jié)論會(huì)比通用 Benchmark 更接近真實(shí)成本。1. 遷移成本主要藏在三處1.1 自定義生態(tài)與插件鏈Plugin Chain移植斷層國(guó)際化提取、SVG 轉(zhuǎn)換、資源重寫(xiě)和代碼生成等自定義插件可能依賴(lài) Webpack 的特定 Hook。候選工具即使提供相似接口也要逐個(gè)驗(yàn)證輸入、輸出和執(zhí)行階段。盤(pán)點(diǎn)時(shí)記錄插件的業(yè)務(wù)目的不只記錄包名有些插件已經(jīng)沒(méi)有消費(fèi)者可以先刪除有些則適合移到獨(dú)立腳本或編譯器插件中。1.2 Monorepo 體系下的隱式依賴(lài)與 Tree-shaking 破壞Monorepo 常包含 Alias、工作區(qū)軟鏈接、條件導(dǎo)出和未聲明的隱式依賴(lài)。解析規(guī)則變化后可能出現(xiàn)同一庫(kù)被打入多份、CSS 被誤刪或開(kāi)發(fā)環(huán)境能解析而 CI 失敗。遷移前應(yīng)先修正未聲明依賴(lài)再比較模塊圖和最終產(chǎn)物不能只看首頁(yè)是否打開(kāi)。1.3 CI 持久化緩存Persistent Cache失靈本地?zé)岣驴觳淮?CI 冷構(gòu)建或遠(yuǎn)程緩存同樣有效。緩存鍵要覆蓋源碼、鎖文件、工具版本、環(huán)境和構(gòu)建參數(shù)CI Runner 也要實(shí)際保存和恢復(fù)對(duì)應(yīng)目錄。比較時(shí)分開(kāi)記錄冷構(gòu)建、熱更新、無(wú)改動(dòng)重建和常見(jiàn)改動(dòng)后的增量構(gòu)建。2. 先測(cè)量現(xiàn)有構(gòu)建收集構(gòu)建階段耗時(shí)、模塊數(shù)量、轉(zhuǎn)換次數(shù)、壓縮時(shí)間、峰值內(nèi)存和緩存命中。再選幾種代表性改動(dòng)例如只改業(yè)務(wù)模塊、改公共依賴(lài)、改樣式 Token 和升級(jí)鎖文件觀察哪些階段被重新執(zhí)行。若瓶頸來(lái)自一個(gè)自定義插件或錯(cuò)誤的緩存鍵直接修復(fù)可能比遷移整條鏈路更合算。3. Webpack 構(gòu)建性能診斷 Plugin 實(shí)現(xiàn)下面的 Webpack Plugin 記錄模塊從buildModule到succeedModule的墻鐘時(shí)間可用于發(fā)現(xiàn)候選慢模塊。import { Compiler, Compilation } from webpack; export interface ModuleBuildTimeInfo { moduleName: string; durationMs: number; } export class WebpackBuildProfilerPlugin { private moduleTimes: Mapstring, number new Map(); private slowModules: ModuleBuildTimeInfo[] []; private thresholdMs: number; constructor(options: { thresholdMs?: number } {}) { this.thresholdMs options.thresholdMs || 100; // 默認(rèn)追蹤耗時(shí) 100ms 的模塊 } public apply(compiler: Compiler) { const pluginName WebpackBuildProfilerPlugin; // 1. 監(jiān)聽(tīng) Module 構(gòu)建開(kāi)始 compiler.hooks.compilation.tap(pluginName, (compilation: Compilation) { compilation.hooks.buildModule.tap(pluginName, (module: any) { const modulePath module.userRequest || module.rawRequest || unknown-module; this.moduleTimes.set(modulePath, Date.now()); }); // 2. 監(jiān)聽(tīng) Module 構(gòu)建結(jié)束計(jì)算耗時(shí) compilation.hooks.succeedModule.tap(pluginName, (module: any) { const modulePath module.userRequest || module.rawRequest || unknown-module; const startTime this.moduleTimes.get(modulePath); if (startTime) { const duration Date.now() - startTime; if (duration this.thresholdMs) { this.slowModules.push({ moduleName: modulePath, durationMs: duration, }); } this.moduleTimes.delete(modulePath); } }); }); // 3. 構(gòu)建完成輸出診斷報(bào)告 compiler.hooks.done.tap(pluginName, (stats) { const totalTime stats.endTime - stats.startTime; console.log(\n Webpack 構(gòu)建耗時(shí)診斷報(bào)告 ); console.log(?? 構(gòu)建總耗時(shí): ${(totalTime / 1000).toFixed(2)}s); console.log(?? 發(fā)現(xiàn) ${this.slowModules.length} 個(gè)編譯耗時(shí)超過(guò) ${this.thresholdMs}ms 的慢模塊:\n); // 按耗時(shí)降序排列前 10 個(gè)慢模塊 const topSlowModules this.slowModules .sort((a, b) b.durationMs - a.durationMs) .slice(0, 10); topSlowModules.forEach((item, index) { console.log( ${index 1}. [${item.durationMs}ms] ${item.moduleName}); }); console.log(\n); }); } } module.exports WebpackBuildProfilerPlugin;4. 示例診斷插件的限制該插件用模塊路徑作為 Map Key。如果同一路徑在并行構(gòu)建或不同編譯上下文中重復(fù)出現(xiàn)開(kāi)始時(shí)間可能互相覆蓋。模塊構(gòu)建失敗時(shí)也沒(méi)有在failedModule清理記錄。slowModules在 Watch 模式的多輪構(gòu)建之間沒(méi)有重置報(bào)告會(huì)混入之前的數(shù)據(jù)。生產(chǎn)診斷應(yīng)使用穩(wěn)定的模塊實(shí)例標(biāo)識(shí)并在每次 compilation 開(kāi)始時(shí)初始化狀態(tài)。Date.now()適合粗略觀察不適合做精細(xì)階段分析閾值也應(yīng)根據(jù)項(xiàng)目基線配置。模塊墻鐘時(shí)間包含調(diào)度等待不一定等于某個(gè) Loader 的純執(zhí)行時(shí)間。發(fā)現(xiàn)慢模塊后還需要結(jié)合 Stats、CPU Profile 或 Loader 自身計(jì)時(shí)繼續(xù)定位。診斷工具本身也會(huì)增加開(kāi)銷(xiāo)因此不必在每次普通構(gòu)建都開(kāi)啟。候選工具的比較表應(yīng)由同一倉(cāng)庫(kù)、同一機(jī)器或 Runner、同一緩存條件生成。除了時(shí)間還要核對(duì) JS/CSS 行為、Source Map、Chunk、資源路徑、瀏覽器兼容和部署結(jié)果。沒(méi)有這些實(shí)測(cè)不寫(xiě)看似精確的兼容率和提升比例。5. 打包工具選型與工程治理 Checklist做決定前完成以下評(píng)估定位瓶頸確認(rèn)時(shí)間花在轉(zhuǎn)換、壓縮、模塊解析、插件還是緩存恢復(fù)保留冷構(gòu)建與增量基線。評(píng)估原地優(yōu)化只對(duì)已確認(rèn)的瓶頸嘗試緩存、并行或替換轉(zhuǎn)換器并用測(cè)試證明產(chǎn)物沒(méi)有變化。盤(pán)點(diǎn)插件與語(yǔ)法為每個(gè)自定義 Loader、Plugin、動(dòng)態(tài)導(dǎo)入和 CSS 處理準(zhǔn)備遷移用例。比較產(chǎn)物核對(duì)模塊圖、公共依賴(lài)、Chunk、Source Map、CSS 順序、資源路徑和目標(biāo)瀏覽器不只比較壓縮體積。規(guī)劃遷移邊界可以先遷移獨(dú)立應(yīng)用或測(cè)試鏈路。開(kāi)發(fā)與生產(chǎn)采用雙軌時(shí)要評(píng)估兩套解析規(guī)則帶來(lái)的差異與維護(hù)成本不能默認(rèn)雙軌更穩(wěn)。構(gòu)建工具選型的結(jié)果應(yīng)是一份帶證據(jù)的取舍當(dāng)前瓶頸是什么候選方案解決了什么哪些插件需要重寫(xiě)出現(xiàn)問(wèn)題怎樣退回。功能清單用于篩選候選真實(shí)項(xiàng)目的構(gòu)建與發(fā)布才負(fù)責(zé)最后決定。