十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Umi 4 中的 MFSU:比 Vite 还快的 Webpack 提速方案原理与实践

Umi 4 中的 MFSU:比 Vite 还快的 Webpack 提速方案原理与实践 Umi 4 中的 MFSU比 Vite 还快的 Webpack 提速方案原理与实践【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umiUmi 4 同时内置 webpack 与 vite 两种构建方式而 MFSUModule Federation Speed Up是官方给出的第三条路基于 webpack 5 的 Module Federation 能力把应用代码与应用依赖的编译拆开让依赖只编译一次、热更新只重编业务代码。本文以 Umi 仓库官方博客的实测对比为核心结合 packages/mfsu 源码与 MFSU 官方指南讲清 MFSU 的两大策略normal / eager、两种依赖构建工具webpack / esbuild的原理、配置参数与踩坑解法读完即可在自己的 Umi 4 项目中开箱使用并在「既要 Webpack 功能与生态又想要 Vite 速度」时做出理性选择。一、为什么会有 MFSUWebpack 慢就去改他MFSU 诞生的初衷可以用一句编辑按语概括Change the code, dont Workaround!面对 webpack 启动与热更新偏慢的问题Umi 团队没有选择绕过或妥协而是直接改造 webpack 的工作方式。该方案在 Umi 4 中默认开启官方明确表示它面向的是既要 Webpack 功能与生态又想要 Vite 速度的开发者。其核心思路是分而治之docs/docs/docs/guides/mfsu.md将应用源代码的编译与应用依赖的编译分离把变动较小的应用依赖构建为一个Module Federation 的 remote 应用应用热更新时不再重新编译依赖只编译业务代码从而大幅缩短热更新时间。从源码看packages/mfsu/src/mfsu/mfsu.ts 中的MFSU类正是这一方案的中枢它接收strategy: eager | normal、buildDepWithESBuild、shared、include、exclude等选项向应用 webpack 配置注入ModuleFederationPlugin将依赖注册为名为mf的 remote同时通过BuildDepPlugin与DepBuilder在后台完成依赖包的独立构建。二、官方实测两个示例、四种模式、四个维度Umi 官方博客记录了 Umi 4 开发完成后的首轮对比实验方法论设计得很克制结论是MFSU with esbuild 在几个典型场景下数据领先。2.1 对比矩阵两个示例大型的全量ant-design-pro与小型的libs example四种模式webpack、webpack MFSU、webpack MFSU with esbuild mode、Vite in umi四个维度无缓存的冷启动、有缓存的热启动、修改代码后的热更新、页面打开速度。原文档中的速度对比图ant-design-pro 与 libs example 两组直观展示了各模式耗时差异。读者可在仓库的 examples 目录下各 example 中执行npm run dev手动复现验证其中 examples/mfsu-independent 与 examples/mfsu-independent-e2e 是独立的 MFSU 演示项目。2.2 统计口径防止误读原文档特别交代了几点统计前提引用结论时必须同时引用这些口径物理缓存所有 webpack 相关模式全部开启物理缓存Vite 集成对比的是 Umi 中集成后的 Vite并经开发者确认基本排除误用可能其大段时间消耗在预编译依赖上公平性Ant Design Pro 中包含 less 样式这是 esbuild 无法加速的部分对四种模式的影响是公平的硬件与采样数据为本地 13-inch M12022重启电脑后跑 5 次的平均值热更新维度Vite 的热更速度未纳入统计——由于 esm 特性改动后需等请求被处理完才算结束无法统计但肯定很快。2.3 结论与意外MFSU with esbuild 数据领先尤其受益于 esbuild 对依赖预编译的高效页面打开速度四个模式差不多原以为 Vite 请求多会导致页面变慢实测并未出现作者也坦言有可能项目还不够复杂。三、MFSU 的两种策略normal 与 eagerMFSU 最关键的问题是如何把应用代码实际使用的依赖分析出来。根据分析方式的不同官方提供两种策略对应源码中的两个类StrategyCompileTimenormal与StaticAnalyzeStrategyeager二者都实现了 packages/mfsu/src/mfsu/mfsu.ts 中定义的IMFSUStrategy接口init/shouldBuild/getBabelPlugin/getBuildDepPlugConfig/loadCache/getCacheFilePath/getDepModules/refresh/writeCache。3.1 normal 策略编译时分析mfsu: { strategy: normal, }现代前端代码在生产前都要经过转译transpile转译器如 babel会在代码中插入新的依赖——这些依赖在项目代码层面不可见只有通过转译插件才能收集到。normal 策略的工作方式是先对应用源码单独编译编译的同时收集项目本身依赖与编译插入的依赖项目代码编译完成后用收集结果继续构建依赖部分的代码整个过程是串行的以 React 引用构建为例见官方指南流程图。对应源码为 packages/mfsu/src/mfsu/strategyCompileTime.tsgetBabelPlugin()返回[awaitImport, getAwaitImportCollectOpts()]通过 babel 插件在转译时用onCollect回调把匹配/未匹配的 import 写入depInfo.moduleGraph依赖构建完成后才继续应用代码编译。3.2 eager 策略静态扫描mfsu: { strategy: eager, }与编译时分析不同eager 策略先读取项目中所有源代码文件用静态分析获取依赖这个过程非常快——官方文档给出的数据是在一个 17 万行代码、1400 多个文件的项目中分析一次只需 700ms 左右。快速分析的代价是收集到的依赖会缺失后续代码编译插入的依赖这部分依赖最终与项目代码一起编译打包。分析完成后Umi 拿着依赖信息并行执行项目代码编译与依赖编译官方指南配有 eager 并行流程图。对应源码为 packages/mfsu/src/mfsu/strategyStaticAnalyze.ts其getBuildDepPlugConfig()通过beforeCompile钩子在编译前发起buildDeps()并用onFileChange监听 js/ts 源码变更、在改动时重新触发静态分析srcCodeCache.handleFileChangeEvents实现依赖信息的增量维护。3.3 如何选择官方给出的选择建议docs/docs/docs/guides/mfsu.md不使用 Module Federation、依赖变动不频繁建议先尝试 esbuild 构建monorepo 项目推荐normal策略并配合开启monoreporedirect配置项目较大、代码基数大推荐eager策略并行编译对冷启动改善明显项目刚启动、频繁改动依赖推荐eager策略其他类型项目可随意选择。一句话总结权衡normal 收集的依赖完整、依赖与应用完全分离但构建串行eager 耗时构建并行、冷启动收益大但部分运行时依赖要与项目代码一起编译。四、两种依赖构建工具webpack 与 esbuildMFSU 支持用 webpack 或 esbuild 来构建依赖默认使用 webpack与 webpack 生态兼容性最好开启 esbuild 只需一行配置mfsu: { esbuild: true, }两者的差异在 packages/mfsu/src/depBuilder/depBuilder.ts 中一目了然——DepBuilder类提供了三条构建路径buildWithWebpack以依赖清单生成ModuleFederationPlugin配置exposes每个依赖、filename: remoteEntry.js、合并 vendor chunk用 webpack 编译器执行buildWithESBuild通过getESBuildEntry生成esbuild-entry.js作为入口调用umijs/bundler-esbuild的build打包出mf-va_remoteEntry产物buildWithWorkereager 策略下把依赖构建放到worker_threads中执行与项目代码编译真正并行。配置文档docs/docs/docs/api/config.md补充了该开关的代价esbuild 模式让首次启动更快但二次编译没有物理缓存、会稍慢一些因此更适合依赖比较稳定的项目。五、MFSU 配置参数全解mfsu配置的类型与默认值如下docs/docs/docs/api/config.md类型{ esbuild: boolean; mfName: string; cacheDirectory: string; strategy: normal | eager; include?: string[]; chainWebpack: (memo, args) void; exclude?: Arraystring | RegExp }默认值{ mfName: mf, strategy: normal }默认开启配置mfsu: false可关闭。参数说明默认值esbuild依赖预编译是否走 esbuild首次启动更快但二次编译无物理缓存稍慢falsemfNameremote 库的全局变量名微前端中为防主/子应用冲突时配置mfcacheDirectory自定义缓存目录node_modules/.cache/mfsuchainWebpack链式修改依赖的 webpack 配置基于 webpack-chain—runtimePublicPath让 mf 加载文件的 publicPath 使用window.publicPath—strategy依赖编译时机normal为 babel 编译分析后构建 MF 远端包eager为静态分析并和项目代码同时构建normalinclude仅eager模式生效补偿静态分析不到的依赖如{ include: [react] }—exclude手动排除不走 MFSU 的依赖字符串全词匹配或正则如{ exclude: [vant] }/{ exclude: [/vant/] }—典型用法// 用 esbuild 做依赖预编译 mfsu: { esbuild: true, } // 关闭 mfsu 功能 mfsu: false; // 链式修改依赖构建的 webpack 配置 mfsu: { chainWebpack(memo, args) { memo.plugin(hello).use(Plugin, [...args]); return memo; } }源码层面packages/mfsu/src/mfsu/mfsu.ts 中的构造函数会依据strategy选择StaticAnalyzeStrategy或StrategyCompileTimeeager 模式在未提供srcCodeCache时会回退到 normal 并打印 warn 日志packages/mfsu/src/constants.ts 定义了默认临时目录.mfsu、mf-va_/mf-dep_/mf-static/前缀与remoteEntry.js产物名。依赖分析结果通过 packages/mfsu/src/depInfo.ts 的DepInfo与ModuleGraph进行快照snapshotDeps、缓存读写loadCache/writeCache与变更判断hasDepChanged缓存文件默认落在node_modules/.cache/mfsu。六、常见问题与解法官方指南整理了五大典型坑docs/docs/docs/guides/mfsu.md均给出可复制的配置解法6.1 依赖缺失eager 模式构建失败报错形如error - [MFSU][eager] build worker failed AssertionError [ERR_ASSERTION]: filePath not found of lodash.capitalize检查对应依赖如lodash.capitalize是否已安装。6.2 React 多实例问题浏览器出现多个 React 实例报错根因是复杂场景下 React 被多次打包、运行时产生多个实例。解法通过 Module Federation 的shared配置让依赖单例共享mfsu: { shared: { react: { singleton: true, }, }, },其他依赖出现多实例也可用类似方式解决。若同时开启了 MF 插件必须配置shared详见 MF 文档 中的和 MFSU 一起使用。6.3 externals script 兼容问题若项目依赖 a、a 依赖 b且 b 配置了 script 类型 externalsexternals: { b: [script https://cdn/b.js, b] }开启 MFSU 后import * as b from b拿到的会是PromiseModule而非 Module。这是 webpack 未处理好 externals script 与 module federation 兼容性所致。解法是只在生产环境开启该 externalsexternals: { ...(process.env.NODE_ENV production ? {b: [script https://cdn/b.js, b]} : {}) }6.4 依赖环问题场景 1monorepo 回环项目依赖 A、A 依赖 B、B 又是项目 monorepo 的子包形成源码→A→源码的环。建议用exclude把 B 排除出 MFSUmfsu: { exclude: [ B ] }场景 2依赖引用.umi内部产物某依赖不合理的引用了.umi目录内容如依赖 Bigfish 插件相关功能开启 MFSU 后可能编译失败同样将其配置进mfsu.exclude。6.5 worker 兼容问题项目代码若需在 Worker 中使用必须把 Worker 需要的依赖加入mfsu.exclude。原因是 Module Federation 通过window对象共享模块Worker 环境中无法使用 MF 模块只能靠排除绕过。七、总结MFSU 是 Umi 4 默认开启的提速方案用分而治之把依赖从应用编译中剥离再通过 Module Federation 按需加载配合 normal/eager 两种依赖分析策略与 webpack/esbuild 两种依赖构建器覆盖了从冷启动、热启动到热更新的全链路提速。官方实测表明MFSU with esbuild 在典型场景下优于纯 webpack 与 Umi 内置 Vite而页面打开速度四种模式基本持平。对开发者而言项目依赖稳定优先esbuildmonorepo 优先normalmonoreporedirect大项目或频繁改依赖优先eager。遇到多实例、依赖环、worker、externals script 等边界问题时MFSU 官方指南 与 mfsu 配置文档 中的解法均可在当前仓库直接验证。【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表