ARTICLE DETAIL

资讯详情

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

Module Federation 高频面试题 + 满分答案

Module Federation 高频面试题 + 满分答案 一、Module Federation 是什么【★★★★★】核心思路一句话Module Federation 解决的是“多个独立项目之间怎么复用模块”的问题而且这个模块可以在运行时从另一个项目加载而不是构建时就打进自己的 Bundle。1. 举例说明假设公司有两个完全独立的前端项目项目 A www.a.com 项目 B www.b.com项目 B 做了一个非常成熟的DatePicker。传统做法项目 B ↓ npm publish ↓ 项目 A npm install ↓ Webpack 构建 ↓ DatePicker 被打进项目 A 的 BundleModule Federation 可以变成项目 A ↓ 用户访问 A ↓ 运行时加载 B 的模块 ↓ 直接使用 B 提供的 DatePicker所以它解决的不是单纯的“代码复用”。因为 npm 本身就能代码复用。真正有区别的是npm 模块复用发生在构建阶段 Module Federation 模块复用可以发生在运行阶段2. 技术定义Webpack Module Federation 允许多个独立构建在运行时提供和消费模块并且可以对公共依赖进行共享。这里有三个关键词独立构建 ↓ 运行时 ↓ 模块提供 / 消费3. 三个层次不要混这是面试时非常重要的一点。Module Federation │ ├── 解决的问题 │ 多个独立项目之间如何复用模块 │ ├── 核心能力 │ 运行时提供 / 消费远程模块 │ └── 关键实现 远程模块的解析和加载可以延迟到运行时所以不要简单回答“Module Federation 就是代码共享。”也不要简单回答“Module Federation 就是运行时加载。”前者太宽泛后者只是实现特征。最准确的表达是价值是独立构建之间的模块共享特点是可以运行时提供和消费远程模块关键实现是把远程模块的解析和加载延迟到运行时。二、为什么需要 Module Federation【★★★★★】核心思路一句话因为大型系统中经常存在多个独立构建的项目而传统 npm 共享模块必须经过“发布 → 安装 → 构建 → 部署”Module Federation 可以把远程模块的使用延迟到运行时。传统 npm公共项目 ↓ npm publish ↓ 业务项目 npm install ↓ Webpack 构建 ↓ 模块进入业务项目 Bundle ↓ 部署如果公共项目更新公共项目 v1 ↓ 发布 v2 ↓ 业务项目仍然使用 v1业务项目必须重新npm install ↓ 重新构建 ↓ 重新部署Module Federation公共项目 ↓ 构建 Remote ↓ 部署 remoteEntry.js ↓ 业务项目运行 ↓ 运行时加载 Remote ↓ 获取模块因此Remote 更新 ↓ 重新部署 Remote ↓ Host 不需要因为 Remote 的业务代码变化而重新构建注意这不是说 Host 永远不需要重新构建。如果 Host 自己修改了代码、Remote 地址、版本策略、共享依赖配置等仍然可能需要重新构建。三、Module Federation 和 npm 有什么区别【★★★★★】核心思路一句话npm 是构建时共享Module Federation 是运行时共享。npmModule Federation模块获取构建时运行时模块是否进入 Host Bundle通常会Remote 模块不会直接进入 Host BundleRemote 更新通常需要 Host 更新依赖并重新构建可以独立更新 Remote发布方式npm 包Remote 独立部署典型特点版本固定、依赖明确独立部署、运行时加载最容易被追问的问题“那 npm 也能代码共享Module Federation 到底特殊在哪里”npm 解决的是“把别人写好的代码拿到我的项目里然后一起构建”Module Federation 解决的是“我的项目已经构建完成以后运行时还能去另一个独立构建中获取模块”。四、Host、Remote、Shared 分别是什么【★★★★★】核心思路一句话Host 是使用方Remote 是提供方Shared 是多个构建之间需要共同使用的依赖。Module Federation Host 使用远程模块 │ │ ↓ Remote 提供远程模块 ┌───────────────┐ │ Shared │ │ React / ReactDOM │ └───────────────┘Host消费远程模块的应用。例如importButtonfromdesignSystem/Button;这里的业务应用就是 Host。Remote对外提供模块的独立构建。例如exposes:{./Button:./src/Button.jsx}表示Remote 对外提供 designSystem/Button ↓ src/Button.jsxSharedShared 解决的是Host 和 Remote 都依赖 React 时如何避免各自带一份 React以及如何处理版本问题。注意Remote 模块共享 ≠ Shared 依赖共享这是两个问题。五、Remote 到底是什么【★★★★☆】核心思路一句话Remote 不是一个特殊的“子应用”而是一个能够对外提供模块的独立 Webpack 构建。例如App B │ ├── src │ ├── Button.jsx │ └── DatePicker.jsx │ └── Webpack ↓ remoteEntry.js ↓ 对外提供 Button / DatePicker因此Remote 的本质是“模块提供方”。它不一定是微前端子应用。甚至两个完全无关的网站www.a.com www.b.com也可以B 提供 DatePicker A 运行时加载 DatePicker这仍然是 Module Federation。六、remoteEntry.js 是什么【★★★★★】核心思路一句话remoteEntry.js 是 Remote对外暴露模块的运行时入口Host 先找到它才能进一步找到 Remote 提供的具体模块。例如 RemotenewModuleFederationPlugin({name:appB,filename:remoteEntry.js,exposes:{./DatePicker:./src/DatePicker.jsx}})构建后dist/ ├── remoteEntry.js ├── xxx.js └── xxx.chunk.jsHostremotes:{appB:appBhttps://b.com/remoteEntry.js}运行时Host ↓ https://b.com/remoteEntry.js ↓ Remote Container ↓ 找到 ./DatePicker ↓ 加载 DatePicker 对应 Chunk ↓ 返回模块注意remoteEntry.js不是 DatePicker 本身。它更像Remote 的“运行时入口 模块获取入口”它里面包含Webpack Runtime和Container相关代码用来告诉 Host我有哪些模块可以提供 这些模块怎么获取 需要哪些 Chunk 怎么初始化 Shared七、exposes 和 remotes 有什么区别【★★★★★】核心思路一句话exposes 是“我提供什么”remotes 是“我要从谁那里拿什么”。Remoteexposes:{./Button:./src/Button.jsx,./Table:./src/Table.jsx}意思我提供 Button TableHostremotes:{designSystem:designSystemhttps://cdn.xxx.com/remoteEntry.js}意思我知道 designSystem 在哪里然后importButtonfromdesignSystem/Button;就是Host ↓ designSystem ↓ Button八、Module Federation 运行时到底是怎么加载远程模块的【★★★★★】这是高级面试最值得展开的一题。核心思路一句话Host 发现自己需要远程模块后由 Webpack Runtime 动态加载 Remote 的 remoteEntry再通过 Container 获取暴露模块如果模块对应的 Chunk 还没加载就继续动态加载 Chunk。完整流程业务代码 │ │ import(appB/DatePicker) ↓ Webpack Runtime │ │ 加载 remoteEntry.js ↓ Remote Container │ │ container.init(...) ↓ 初始化 Shared │ │ container.get(./DatePicker) ↓ 寻找 DatePicker │ │ ├── 模块已经存在 │ ↓ │ 直接返回 │ └── 模块所在 Chunk 未加载 ↓ 动态加载 Chunk ↓ Chunk 执行 ↓ 注册模块 ↓ container.get() ↓ 返回 DatePicker九、import(appB/DatePicker)为什么浏览器能识别【★★★★☆】核心思路一句话浏览器根本不认识appB/DatePicker这是 Webpack Module Federation 在构建阶段识别的模块标识最终由 Webpack Runtime 完成远程加载。浏览器原生理解的是import(./DatePicker.js)而import(appB/DatePicker)不是浏览器直接解析的 URL。Webpack 在构建时知道appB ↓ https://b.com/remoteEntry.js于是把它转换成 Federation Runtime 的远程模块加载逻辑。所以源码 ↓ import(appB/DatePicker) ↓ Webpack 构建 ↓ 生成 Federation Runtime ↓ 浏览器执行 Runtime ↓ 加载 remoteEntry.js十、container.get()和container.init()是干什么的【★★★★★】核心思路一句话init()负责初始化共享依赖环境get()负责获取 Remote 暴露出来的具体模块。可以把 Container 理解成constcontainer{init(shareScope){// 初始化共享依赖},get(moduleName){// 获取远程模块}}实际 Webpack 生成代码会复杂得多这里是理解它的核心模型。运行awaitcontainer.init(__webpack_share_scopes__.default);然后constfactoryawaitcontainer.get(./DatePicker);constDatePickerfactory();所以init() ↓ 先处理 Shared get(./DatePicker) ↓ 再获取具体 Remote Module十一、Shared React 到底放在哪里【★★★★★】核心思路一句话shared 不是把 React 放进一个公共文件夹而是让不同 Webpack Runtime 在运行时通过 Share Scope 协调“谁提供 React、谁复用 React、使用哪个版本”。例如shared:{react:{singleton:true},react-dom:{singleton:true}}概念上可以理解为Share Scope │ ├── react │ ├── 18.2.0 │ └── 18.3.1 │ └── react-dom ├── 18.2.0 └── 18.3.1Runtime 会根据配置和版本要求选择可用版本。最重要的一点Remote 模块共享 ↓ container.get() React 依赖共享 ↓ Share Scope这是两条不同的链。十二、为什么 Shared React 不会出现React is undefined【★★★★☆】核心思路一句话因为 Webpack 会把共享依赖的初始化和远程模块执行组织成异步依赖关系Remote 不是拿到代码后立刻无条件执行而是在需要 Shared 时先完成相应初始化。概念流程加载 Remote ↓ container.init(shareScope) ↓ 建立 Shared 依赖环境 ↓ container.get(./Button) ↓ Button 执行 ↓ 获取 React ↓ React.createElement(...)因此不是下载 Button ↓ 立刻执行 Button ↓ 突然发现 React 不存在而是准备依赖 ↓ 初始化共享环境 ↓ 获取模块 ↓ 执行模块不过不能说它“绝对不会 undefined”。如果存在Remote 加载失败 Shared 配置错误 React 版本不兼容 初始化失败 运行时冲突依然可能报错。十三、singleton: true是什么意思【★★★★★】核心思路一句话singleton 的核心目的是让一个 Share Scope 中的依赖尽量只使用一个共享实例React 这种要求单实例的库尤其重要。例如shared:{react:{singleton:true},react-dom:{singleton:true}}为什么 React 特别需要关注因为如果Host └── React A Remote └── React B可能出现React Context Hooks Provider Component跨不同 React 实例工作的问题。尤其可能导致经典错误Invalid hook call所以实际项目通常会对 React / ReactDOM 采用 singleton 策略。十四、如果 Host 和 Remote 的 React 版本不一样怎么办【★★★★☆】核心思路一句话Webpack 会根据 Shared 配置和版本要求进行共享依赖选择但“能不能安全共存”最终取决于版本兼容性和项目配置不能简单理解成自动解决所有版本冲突。例如Host React 18.3 Remote React 18.2可以通过 Shared 配置声明版本要求。但如果Host React 18 Remote React 17就不能只靠singleton:true认为问题自动消失。需要考虑版本兼容 requiredVersion strictVersion singleton 实际依赖关系十五、Remote 更新以后为什么 Host 可以不重新构建【★★★★★】核心思路一句话因为 Host Bundle 里保存的是“去哪里找 Remote”而不是 Remote 的完整业务代码。传统 npmHost ↓ npm install Remote v1 ↓ Remote v1 进入 Host Bundle ↓ Host 部署Remote 更新Remote v2 ↓ Host Bundle 里面还是 v1所以必须重新构建。Module FederationHost ↓ 知道 Remote 地址 ↓ https://b.com/remoteEntry.jsRemoteremoteEntry.js ↓ DatePicker ↓ 对应 Chunk因此Remote 更新 ↓ Remote 重新部署 ↓ Host 下次运行时加载新的 Remote但是注意“不需要重新构建 Host”不等于“自动永远拿最新版本”。还会受到浏览器缓存 CDN 缓存 remoteEntry 缓存策略 版本化 URL 灰度发布 兼容性影响。十六、为什么 remoteEntry.js 可以更新而 Host 不需要重新构建【★★★★☆】核心思路一句话因为 Host 保存的是 Remote 的访问地址而不是 Remote 的具体代码。例如remotes:{appB:appBhttps://b.com/remoteEntry.js}Host 保存的是https://b.com/remoteEntry.js不是DatePicker 的源代码所以Host │ │ 地址没变 ↓ remoteEntry.js │ │ 内容已经更新 ↓ 新版本 Remote Module这就是为什么Remote 可以独立部署十七、Module Federation 的完整运行流程是什么【★★★★★】这是建议直接背下来的流程图。用户访问 Host │ ↓ Host Bundle 执行 │ ↓ import(appB/DatePicker) │ ↓ Webpack Runtime │ ↓ 加载 remoteEntry.js │ ↓ Remote Container │ ↓ container.init() │ ↓ 初始化 Share Scope │ ↓ container.get(./DatePicker) │ ↓ 找到 DatePicker 对应 Chunk │ ↓ 动态加载 Chunk │ ↓ Chunk 执行 │ ↓ 注册 Remote Module │ ↓ 获取 Shared React │ ↓ 执行 DatePicker │ ↓ React 渲染一句话串起来Host 运行 → 请求 Remote Entry → 初始化共享依赖 → 获取暴露模块 → 按需加载对应 Chunk → 获取共享 React → 执行远程模块。十八、Webpack Module Federation 和微前端是什么关系【★★★★★】核心思路一句话微前端是一种架构思路Module Federation 是一种模块运行时共享技术两者不是同一个东西。微前端 │ └── 解决“大型前端系统怎么拆分和独立交付” Module Federation │ └── 解决“独立构建之间怎么运行时共享模块”可以微前端 Module Federation但也可以普通 Webpack 项目 Module Federation例如公司设计系统团队 ↓ 提供 Button / Table / DatePicker ↓ Module Federation ↓ 多个完全独立的业务网站消费这些网站不需要组成一个微前端应用。所以Module Federation 可以作为微前端的技术实现之一但 Module Federation 本身不是微前端。十九、Module Federation 和动态 import 有什么区别【★★★★☆】核心思路一句话动态 import 解决的是“当前构建中的代码什么时候加载”Module Federation 解决的是“另一个独立构建提供的代码怎么在运行时加载”。动态 importimport(./UserPanel)通常是当前项目 ↓ Webpack 构建 ↓ UserPanel 被拆成 Chunk ↓ 运行时动态加载 ChunkModule Federationimport(appB/UserPanel)是当前项目 ↓ 发现这是 Remote ↓ 加载 Remote Entry ↓ 找到 Remote Module ↓ 加载对应 Chunk所以dynamic import 当前 Bundle 内的代码分包加载 Module Federation 跨独立构建的运行时模块加载二十、Module Federation 和普通 Webpack Code Splitting 有什么区别【★★★★☆】核心思路一句话Code Splitting 是一个应用自己的代码拆成多个 ChunkModule Federation 是多个独立构建之间共享和加载模块。Code SplittingApp ├── main.js ├── user.js └── product.js这些 Chunk都属于同一个构建Module FederationApp A ↓ App B 的 remoteEntry ↓ App B 的 Chunk这里A 和 B 属于不同构建这是本质区别。二十一、Module Federation 如何处理缓存问题【★★★★☆】核心思路一句话remoteEntry 负责找到 Remote真正业务 Chunk 通常可以使用内容哈希做长期缓存而 remoteEntry / manifest 则需要重点设计更新策略。典型生产策略remoteEntry.js ↓ 较短缓存 / 合理缓存策略 xxx.[contenthash].js ↓ 长期缓存因为remoteEntry 负责“告诉我新版本在哪里” contenthash Chunk 负责“真正的代码内容”如果 remoteEntry 长时间被 CDN 缓存Remote 已经发布 v2 ↓ 用户仍然拿到 v1 remoteEntry ↓ 仍然使用旧版本因此生产环境需要认真设计缓存 版本 CDN 灰度 回滚 兼容性二十二、Module Federation 有哪些问题【★★★★★】核心思路一句话Module Federation 最大的代价是把原本构建阶段发现的问题部分推迟到了运行阶段。主要问题1. 运行时失败remoteEntry 加载失败 Chunk 加载失败 CDN 故障 网络异常以前构建失败现在可能变成线上运行失败2. 版本兼容Host v1 Remote v2接口、React、公共依赖可能不兼容。3. 调试复杂传统代码 ↓ 构建 ↓ BundleModule FederationHost ↓ Remote Entry ↓ Remote Chunk ↓ Shared ↓ 不同 Runtime排查链更长。4. 部署耦合变成运行时契约虽然独立部署但不是完全没有依赖Host 仍然依赖Remote 地址 Remote 暴露模块 模块 API Shared 依赖 版本兼容因此独立部署不等于完全解耦。二十三、如果 Remote 加载失败怎么办【★★★★☆】核心思路一句话把 Remote 当成一个运行时外部依赖处理必须有超时、降级、监控和版本回滚策略。例如Host ↓ 加载 Remote ↓ 失败 ├── 否 → 正常渲染 │ └── 是 ↓ 重试 ↓ 仍然失败 ↓ 降级 UI ↓ 上报监控例如asyncfunctionloadRemote(){try{returnawaitimport(appB/ProductDetail);}catch(error){reportError(error);return{default:()商品详情暂时不可用};}}生产环境还可以增加超时 重试 备用 Remote 版本回滚 错误监控 熔断 / 降级二十四、如何设计 Module Federation 的版本管理【★★★★☆】核心思路一句话不要把 Remote 当成“永远只有一个最新版本”而应该把 Remote 当成有版本的运行时依赖。例如Remote ├── v1 ├── v2 └── v3配合Manifest ↓ 版本选择 ↓ 灰度 ↓ 回滚生产环境重点考虑Remote 版本 Shared 版本 兼容性 缓存 灰度 回滚二十五、Module Federation 最容易被问的“源码原理题”【★★★★★】如果面试官继续往底层追可以记住这条链import(appB/Button) ↓ Webpack Runtime ↓ Remote Loading ↓ 加载 remoteEntry.js ↓ Remote Container ↓ container.init() ↓ Share Scope ↓ container.get(./Button) ↓ 加载 Button Chunk ↓ Webpack Chunk Runtime ↓ 注册 Module ↓ 返回 Module Factory ↓ 执行 ModuleWebpack Runtime 中常见的核心概念__webpack_require__ __webpack_require__.f __webpack_require__.l webpackChunk... __webpack_share_scopes__ container.init() container.get()不需要一上来背源码。真正应该理解的是谁负责加载 ↓ Webpack Runtime 加载什么 ↓ remoteEntry.js 谁提供模块 ↓ Remote Container 怎么拿模块 ↓ container.get() 公共依赖怎么处理 ↓ Share Scope 具体代码在哪里 ↓ Remote Chunk二十六、完整最小示例Remote提供 DatePicker// webpack.config.jsconst{ModuleFederationPlugin}require(webpack).container;module.exports{mode:production,plugins:[newModuleFederationPlugin({// Remote 在运行时的名字name:appB,// Remote 对外提供的运行时入口filename:remoteEntry.js,// 对外暴露模块exposes:{// 外部使用// appB/DatePicker//// 实际对应// ./src/DatePicker.jsx./DatePicker:./src/DatePicker.jsx},// 与 Host 共享 Reactshared:{react:{singleton:true},react-dom:{singleton:true}}})]};Host消费 DatePicker// webpack.config.jsconst{ModuleFederationPlugin}require(webpack).container;module.exports{mode:production,plugins:[newModuleFederationPlugin({name:appA,// 告诉 Webpack// appB 的 remoteEntry 在哪里remotes:{appB:appBhttps://b.com/remoteEntry.js},// 与 Remote 共享 Reactshared:{react:{singleton:true},react-dom:{singleton:true}}})]};业务代码importReact,{Suspense}fromreact;// appB/DatePicker 不是浏览器直接解析的 URL。// Webpack Module Federation 会把它识别成 Remote Module。constDatePickerReact.lazy(()import(appB/DatePicker));exportdefaultfunctionApp(){return(Suspense fallback{divLoading.../div}DatePicker//Suspense);}二十七、把整个例子串起来App A │ │ │ import(appB/DatePicker) ↓ Webpack Runtime │ │ ↓ https://b.com/remoteEntry.js │ ↓ Remote Container │ ├───────────────┐ │ │ ↓ ↓ container.init() container.get() │ │ ↓ ↓ Share Scope DatePicker │ │ ↓ ↓ React DatePicker Chunk │ ↓ 执行模块代码 │ ↓ 使用共享 React │ ↓ React 渲染这张图就是 Module Federation 的核心。二十八、面试追问链建议按照下面的顺序准备① Module Federation 是什么 ↓ ② 它解决什么问题 ↓ ③ 和 npm 有什么区别 ↓ ④ 为什么可以运行时加载 ↓ ⑤ Host / Remote 是什么 ↓ ⑥ remoteEntry.js 是什么 ↓ ⑦ exposes / remotes 是什么 ↓ ⑧ import(appB/Button) 怎么实现 ↓ ⑨ container.get() 做什么 ↓ ⑩ container.init() 做什么 ↓ ⑪ shared 是什么 ↓ ⑫ React 到底共享在哪里 ↓ ⑬ singleton 为什么重要 ↓ ⑭ React 版本冲突怎么办 ↓ ⑮ Remote 更新为什么 Host 可以不重新构建 ↓ ⑯ 缓存怎么办 ↓ ⑰ Remote 加载失败怎么办 ↓ ⑱ 和微前端什么关系 ↓ ⑲ 和 dynamic import / Code Splitting 什么区别 ↓ ⑳ 生产环境怎么做版本、灰度、回滚二十九、最终“满分答案”简单说Module Federation 解决的是多个独立项目之间怎么复用模块的问题。传统 npm 是在构建阶段把依赖安装到项目里再一起打包Module Federation 则允许一个项目构建完成以后在运行时从另一个独立构建中加载模块。Webpack 中提供模块的一方叫 Remote使用模块的一方叫 Host。Remote 通过exposes暴露模块Host 通过remotes找到 Remote 的remoteEntry.js。运行时 Host 先加载 Remote Entry再通过 Container 的init()初始化共享依赖通过get()获取暴露模块如果模块对应的 Chunk 没有加载就继续动态加载 Chunk最后执行远程模块。另外shared解决的是公共依赖共享比如 Host 和 Remote 都使用 React 时可以通过 Share Scope 和singleton等配置尽量保证使用兼容的共享 React 实例。所以理解 Module Federation 最关键的是区分三件事它的价值是独立构建之间共享模块它的核心特点是运行时提供和消费远程模块它的关键实现是把远程模块的解析和加载从构建阶段延迟到了运行阶段。它可以用于微前端但本身不等于微前端。三十、100 分评分标准如果面试题是“你讲一下 Webpack Module Federation。”60 分只回答“Module Federation 是 Webpack 的模块联邦可以实现模块共享。”问题太宽泛没说和 npm 的区别没说运行时没说独立构建75 分能说Host Remote Shared exposes remotes remoteEntry说明知道基本配置。85 分能讲清npm 构建时共享 VS Module Federation 运行时共享并能解释remoteEntry container.get container.init Chunk95 分还能继续解释Share Scope singleton React 版本 缓存 Remote 更新 运行时失败并且能够把整个加载过程串起来。100 分最终能把下面这条因果链讲完整为什么需要 ↓ 独立构建之间需要共享模块 为什么不用 npm ↓ npm 主要是构建时共享 Module Federation 特殊在哪里 ↓ 运行时提供 / 消费远程模块 怎么做到 ↓ Remote Entry Container Webpack Runtime 具体怎么拿 ↓ container.get() 依赖怎么办 ↓ Share Scope Shared 为什么能独立部署 ↓ Host 不把 Remote 业务代码直接打进自己的 Bundle 代价是什么 ↓ 把一部分问题从构建阶段推到了运行阶段 生产怎么办 ↓ 版本 缓存 监控 降级 灰度 回滚这一条链真正理解了Module Federation 这类题基本就是一个完整的技术方案。这套题里最值得重点背的是第 1、3、8、11、15、17、18、29 题。它们基本构成了面试官从“概念”一路追到“底层原理和生产实践”的完整链路。
返回列表