webpack 5 模块联邦实现微前端疑难问题解决

2023-11-18

webpack 5 模块联邦实现微前端疑难问题解决

说明

webpack 5 新增 Module Federation(模块联邦)功能,他可以帮助将多个独立的构建组成一个应用程序,不同的构建可以独立的开发与部署。

借助模块联邦我们可以一定程度的实现微前端

概念

1. 什么是微前端?
  • 微前端将微服务理念扩展到前端开发,一般来讲一个微服务架构中会有多个后端团队开发不同的业务服务,而前端通常只有一个团队,集中维护一个 SPA 单页应用,随着时间累加,前端团队维护的 SPA 会随着业务增长越来越大,变得难以维护(项目启动耗时、CI\CD 耗时等);
  • 微前端可以帮助我们像后端一样将 SPA 应用按照业务拆分为多个可独立维护部署的应用,这样一方面我们可以实现,哪个业务改变就近更新哪个业务的前端;又可以帮助搭建从前端到后端单一业务领域的团队
  • 可以理解为微前端是一种将多个可独立交付的小型前端应用聚合为一个整体的架构风格
2. 什么样的产品适合微前端?
  • 大型前端项目
  • 多个项目间跨应用模块共享
  • 有拆分出多个独立的子系统,独立部署维护的需求
3. 模块联邦 module Federation 能干什么?
  • 模块联邦提供了可以在当前应用中远程加载其他服务器上应用的能力
  • 使用 module Federation 可以实现一个去中心话的应用部署群,每个应用都单独部署,每个应用都可以引用其他应用,也能被其他应用所引用(js 级别)
  • 借助第三方工具可实现跨技术栈开发 (react + vue 2)
  • 可以用来实现前端项目平滑迭代(旧项目是一个应用,新项目是一个应用,两者之间通过远程连接,这样我们可以逐渐丰富 新项目来替换旧项目的内容)
  • 又可以帮助搭建从前端到后端单一业务领域的团队
4. 什么是本地模块与远程模块,什么是 host \ remote?
  • host:消费其他 remote 的 webpack 构建(即使用远程模块的应用)
  • remote: 被 host 消费的 webpack 构建(即提供可用的模块给其他应用使用)
  • host 与 remote 是相对的,一个应用既可以是 host 有可以是 remote,因此 模块联邦实现的微前端又是去中心化的。
  • 本地模块即为我们本地开发时当前项目内的模块
  • 远程模块不属于当前构建,它是在运行时被加载进来的 remote 应用提供的模块
5. 模块联邦的负面影响
  • 运行时加载远程模块等逻辑,可能导致一定的性能问题
  • 本地开发需要开启多个端口的服务,比较麻烦
  • 按需加载第三方依赖比较难实现
  • 比起传统 spa 项目结构上有些复杂
  • 迭代时的版本控制需要更多关注

模块联邦 Api

// webpack.config.js
export default {
  plugins: [
    new ModuleFederationPlugin({
      name: "app_two", // 当前应用名
      filename: "remoteEntry.js", // 外部应用引用当前应用模块时的加载入口
      exposes: {
        Search: "./src/Search" // 输出给外部应用使用的模块
      },
      remotes: {
        appOne: 'appOne@http://localhost:3003/remoteEntry.js' // 当前应用会用到的远程应用地址
      }
      shared: [ // 与远程模块共享的模块,与远程模块共同配置,这样在页面中就只会加载一次这个library, 用来避免重复加载第三方依赖
        "react", 
        "react-dom"
      ]
    })
  ]
};

// /index.js 注意,入口一定要动态引入模块
import('./bootstrap');

// /bootstrap.js
ReactDOM.render(
  <App/>
  document.getElementById('root'),
);

// /App.js 中使用远程应用的模块 import('远程应用/远程应用模块')
const AppOne = React.lazy(() => import('appOne/Button'));

shared 说明


/ **
 *应该在共享范围内共享的模块的高级配置。
 * /
declare interface SharedConfig  {
 / **
  *直接在异步请求后面包含提供的和后备模块。这也允许在初始加载中使用此共享模块。所有可能的共享模块也都需要急切。
  * /
 eager?: boolean;
/ **
  *应提供共享范围的提供的模块。如果在共享作用域中找不到共享模块或版本无效,则还充当回退模块。默认为属性名称。
  * /
 import?: DevTool;
/ **
  *软件包名称,用于从描述文件中确定所需的版本。仅当无法根据请求自动确定包名称时才需要。
  * /
 packageName?: string;
/ **
  *共享范围中来自模块的版本要求。
  * /
 requiredVersion?: DevTool;
/ **
  *在共享范围内的此键下查找模块。
  * /
 shareKey?: string;
/ **
  *共享范围名称。
  * /
 shareScope?: string;
/ **
  *在共享范围内仅允许共享模块的单个版本(默认情况下处于禁用状态)。
  * /
 singleton?: boolean;
/ **
  *如果版本无效,则不接受共享模块(默认为是,如果本地后备模块可用并且共享模块不是单例,否则为no,如果未指定所需的版本,则无效)。
  * /
 strictVersion?: boolean;
/ **
  *所提供模块的版本。将替换较低的匹配版本,但不会替换较高的版本。
  * /
 version?: DevTool;
}

开发中遇到的问题

1. 怎么仿照微服务中的服务发现,实现对不同应用版本的运行时管理?

由于 关于“远程依赖应用的引用”是在 build 打包时,打包到代码中的固定值(url),为了通过文件名区分是否有更新,我们需要给remoteEntry.[contenthash].js 加上 hash,;这个时候如果, “远程依赖应用”有版本更新,那么使用这个“远程依赖应用“的应用也要更新(否则拿到的时是期的资源),如此一来我们 “只发布有改动的应用” 这个目标就没办法达成了。

为了实现这个目标,最好是将 remoteEntry 的确切地址(url)在项目运行时注入,这样就避免了改动一个应用,其他应用也跟着更新的窘境
参考案例

除此之外还可以借助 __webpack_init_sharing__ __webpack_share_scopes__ 实现 “动态远程容器” 参考 通过这套方案我们可以实现:项目线上运行时,动态决定要渲染哪些远程应用模块

2. 模块联邦对 Tree shaking 有什么影响
  • 因为模块联邦中的各个应用是各自打包的,没有办法综合所有应用来做 Tree shaking(仅能各自应用各自 Tree-shaking );
  • 这样会造成 应用间对于某个依赖的冗余引用,例如多个应用都使用了 Antd 的 Button 组件,就会在每个应用中都打包一份 Button 组件
  • 如果把依赖提取为公共依赖,则只能全量引用,同样造成代码体积过大(可以成为公共依赖的都需要是全量的,例如 react react-dom 等);
  • 考虑到以上问题,我们可以使用一个麻烦一点的方案:再增加一个”库应用“,这个应用专门用来做需要 Tree-shaking 依赖的打包,所有关于这个依赖的引用都要指向这个项目中,麻烦点在于需要确定是否能都通过脚本实现自动化追加,以及 TS 项目中类型检查怎么办
  • 注意: 模块联邦中不可使用 webpack 的 module.noParse 来处理工具库的解析,否则会引发模块引用的 报错
3. 模块联邦对 context 使用有什么影响?
  • 使用 context, 我们可以避免通过中间元素来向下传递 props
  • Context 在模块联邦的模块(应用)之间无法自动传递
  • 因此需要在每个应用的入口处重新提供一个 Provider 来将与上层同样的context 数据传递下去
4. 模块联邦对状态管理有什么影响?
  • 与 context 类似,例如 redux 这样的状态管理工具内部也是使用的 context 实现
  • 以 redux 为例,一个 SPA 项目中我们会借助 react-redux 提供的 Provider 来将业务组件包裹,然后通过 connect 将 store 中的状态释放到被包裹的组件(及其子组件)中。
  • 对于 模块联邦中的各个应用来说,即使一个应用充当了另外一个含有 Provider 应用的子组件,store 也不会传递下去,这是因为两个应用是分别构建的。
  • 为了所有模块联邦中的应用都共用一套状态,我们可以在每个应用的顶层都通过 Provider 包裹一下,然后给每个应用都传入 store 这样就可以实现连接了
5. 模块联邦中 Typescript 类型检查怎么用,eslint 会有影响么?

如果我们的项目使用 lerna 做一个monorepo 的仓库,每个子 package 代表着一个 应用,预期这些应用都会用 TS 编写,而且 TS 的配置应该是一样的,所以,我按照如下配置

  • 整个仓库只有最外层会有 tsconfig.json 以及 .eslintrc.js 配置文件,所有子package 中都会使用外层的配置
  • .eslintrc.js 中引用正确的 ts 的配置文件路径即可
  • 因为应用之间的模块引用会是一个类似webpack alias 的形式,引用的远程模块是跨包的,也需要ts 提供的属性提示,所以我们要给 tsconfig.json 中配置一下路径解析,否则 ts 找不到模块位置
  • 如下 如果请求模块 @module/library/antd 则会映射到 /modules/library/antd/index 这个文件上,ts 的 paths 会提供一定的路径解析支持,但是没办法更细致的解析(如:没有办法把 @module/library/antd 解析到 /modules/library/src/antd.ts)因此,我们开发时就不要在每个子 package 中创建 src 文件夹了,而是直接展开写,这样我们就不用在增加远程模块时频繁的改动 tsconfig.json 文件了
  // tsconfig.json 配置
{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "@shell/*": ["./shells/*"],
          "@module/*": ["./modules/*"]
        },
    }
}

// remote 模块 /modules/library/antd/index.tsx
export { Button } from 'antd';
export { Row } from 'antd';

// host 中调用 remote 模块
import { Button,Row } from '@module/library/antd';
6. 模块联邦对 code split 有什么影响?
  • Runtime chunk 不会被提取出来了(会造成解析错误)runtimeChunk 用来将运行时代码提取出来,避免因运行时代码改变导致的其他chunk hash 改变,从而影响浏览器缓存的问题。
7. 模块联邦对样式文件有什么要求?
  • 每个普通应用在 build 后都会生成各自的 main-style.css 如果使用了 css-module 则不用担心样式覆盖和引用问题
  • 如果是 global(或者不使用 css-module) 的样式则存在覆盖问题,因此建议使用 css-module (dev 开发和 build 两个环境都会样式覆盖,行为表现一致,这样可以确保,我们开发时什么表现,build 到生成环境后还是什么表现)
  • 如果是 ”库应用“ (例如 antd)的打包则需要注意,收到 babel-plugin-import 插件的影响 在 ”库应用“ 中类似 export {Button} from ‘antd’; 这种写法,会导致 样式文件输出不了 ISSUE
  • 目前没找到更好的方案,只能先用下边的方案来做
// Error1: 会导致不输出样式文件
// export { Button, Row } from 'antd'; 
 
// Error2: 不能直接在 import {Button} 后 export {Button}; webpack 会报错
// import { Button, Row } from 'antd'; 
// export { Button, Row }; 


// Error3: export default 可以实现, 但是使用时不方便
// 例如只能这样使用:
//     import antd from '@module/library/antd';
//     const {Button} = antd;
// export default { Button, Row }; 

// 可实现方案,编写库应用时有点繁琐,但是使用时简单 例: import {Button} from '@module/library/antd';
import { Button as _Button, Row as _Row } from 'antd';
 
export const Button = _Button;
export const Row = _Row;
8. 模块联邦对 路由有什么影响?
  • 基本没什么影响,react-router 的 Hash 路由可以正常使用
9. DLL 与模块联邦结合?
  • 两者可以结合使用
  • 但是因为 DLL 与其他业务代码是分开打包的,使用时又是一起使用的,这样会有一个问题:DLL 打包模块中的 chunkid moduleId与 业务代码打包模块中的 chunkid 重复了,这会导致模块识别报错。解决的方法是将 DLL 打包的 moduleId chunkId 生成方式由 (deterministic => named), 减少重复的可能
10. React 技术栈与 Vue 技术栈结合能否实现
  • 借助 vuera 可以实现在 react 项目中引用 vue 组件,或者在 Vue 项目中引入 React 组件,但是优越 vuera 这个项目缺少维护,目前仅在 vue 2.x 中试验成功。
  • 再结合模块联邦,我们可以实现某个远程应用通过 Vue 技术栈实现(单独一个 仓库维护),这样可以保证其他远程应用的干净
11. 模块联邦对日常 dev 开发会有什么影响?
  • 模块联邦项目中,可能需要有多个应用需要同时启动,联合调试,且需要 HMR 来帮助刷新浏览器,例如:我测试的项目中会有一个 host 主应用,其余有若干个远程应用
  • webpack 支持 MultiCompiler (即多个 webpack 配置文件一起执行)但是 webpack-dev-server 对 MultiCompiler 的支持却不太好
  • 截止 2021.04.07 webpack-dev-server 的最新发行版本还是 v3.x.x ,这个版本有一个问题,就是对 MultiCompiler 支持不好(会导致只有一个 compiler 有 HMR 支持,其他构建没有实时刷新),而 webpack-dev-server@4.0.0-beta.2 虽然支持 MultiCompiler,但是对于模块联邦会出现模块解析失败问题
  • 经过多次尝试最终提供一个解决方案如下
    • 主应用通过 webpack-dev-server@4.0.0-beta.2 来实现源码编译、源码改动监控、编译后文件映射到页面、页面实时刷新
    • 其他应用 直接使用 webpack 的 watch 来实现源码编译、源码改动监控, 至于编译后文件映射到页面、页面实时刷新 这两项则借助注意用的 DevServer 实例顺便实现
    • 注意:非主应用的实时刷新的全量的,主应用的实时刷新是增量的
// 1. 前提:所有构建的 webpacCofnig 都要禁用 webpack.HotModuleReplacementPlugin()因为现阶段的 HMR 只能支持一个构建,多个构建会出错
// webpack.config.js
const cofnig = {
    plugins: [
        // new webpack.HotModuleReplacementPlugin(), // 禁用
    ]
}

// 2. 主应用开启 hmr
// /shells/admin/bootstrap.js 中加入如下内容
if (module.hot) {
  module.hot.accept();
}

// 3. 除主应用外,其他应用通过 MultiCompiler 一起构建, 并通过 webpack watch 监控源码变化实时编译到指定目录(buildPath)
const compiler = webpack([module1Config, module2config, ...]);
compiler.watch({}, (err, stats) => {
  console.log(stats);
});

// 4. 主应用借助 webpack-dev-server 来实现 源码监控、实时编译、文件映射到页面、页面实时刷新
const server = new WebpackDevServer(webpack(hostConfig), {
    transportMode:'ws',
    hot: true,
    // 5. 借助主应用 webpack-dev-server 的 static 配置,将其他应用的构建结果纳入“监控及映射到页面”的范围
    static: [
        {directory: path.join(buildPath, module1Static), publicPath: path.join(publicPath, module1Static)},
        {directory: path.join(buildPath, module2Static), publicPath: path.join(publicPath, module2Static)},
    ],
});

server.listen(customPort, customHost);
  • 遗留问题1:经过如上配置后留下一个问题:webpack-dev-server 提供的 proxy 只能在 主应用中使用,其他应用中没法使用。但是,还使用这个方案的原因是:我们的 proxy mock server 代理应该不会使用 webpack-dev-server 实现,所以这个功能是低概率使用的,而且即使使用也不会上到生成环境,影响不大。
  • 遗留问题2:非主应用的应用由于是通过 webpack 直接构建出来的,其编译结果会存在真实的文件(即在项目中生成一个编译后结果的文件夹,而 webpack-dev-server 会将编译后结果存放到虚拟内存中,虚拟内存要比真正文件读写性能要好)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

webpack 5 模块联邦实现微前端疑难问题解决 的相关文章

  • webpack5.x 遇到的问题

    更新问题1 异常 xff1a Compiling RuleSet failed Exclamation mark separated loader lists has been removed in favor of the 39 use
  • vue-cli2 老项目webpack3升级webpack5过程总结

    文章目录 背景一 webpack5环境要求二 升级步骤1 脚手架webpack cli2 升级webpack包3 更新webpack相关插件3 1 不推荐或被移除的插件3 2 升级babel到7版本3 3 更新插件 4 修改配置4 1 新增
  • webpack5基本教程-1

    基本使用 Webpack 是一个静态资源打包工具 它会以一个或多个文件作为打包的入口 将我们整个项目所有文件编译组合成一个或多个文件输出出去 输出的文件就是编译好的文件 就可以在浏览器段运行了 我们将 Webpack 输出的文件叫做 bun
  • 微前端总结

    微前端 核心价值 微前端架构具备以下几个核心价值 技术栈无关 主框架不限制接入应用的技术栈 微应用具备完全自主权 独立开发 独立部署 微应用仓库独立 前后端可独立开发 部署完成后主框架自动完成同步更新 增量升级 在面对各种复杂场景时 我们通
  • micro-app在vue-element-admin中一些使用研究

    1 简述 本文承接上一篇micro app在vue element admi中的搭建 对micro app在vue element admin中的一些平时开发中常用的功能做了一些研究 本文代码 2 路由 关于路由 这边从两方面进行研究 一方
  • webpack5 (三)

    webpack 高级配置 其实就是对 webpack 进行优化 让代码在编译 运行时性能更好 1 提升开发体验 2 提升打包构建速度 3 减少代码体积 4 优化代码运行性能 一 提升开发体验 sourcemap 在编译打包后所有的 css
  • 微前端的出现的背景和意义

    目录 微前端是什么 大规模 Web 应用的困局 传统 Web 应用的利与弊 背景和意义总结 微前端是什么 微前端是一种类似于微服务的架构 是一种由独立交付的多个前端应用组成整体的架构风格 将前端应用分解成一些更小 更简单的能够独立开发 测试
  • 使用 vite 代替 webpack 搭建 react 前端开发环境

    说明 在大型前端项目中 我们一般会使用 webpack Rollup 等工具进行模块整合 但是庞大的代码量会使得我们在开发阶段花费更多的时间在 代码改动 gt 页面渲染 这个阶段 即使使用 HMR 这个问题也没有完全的解决 项目代码量达到一
  • webpack5进阶-学习笔记

    学习连接 https www bilibili com video BV1964y1k7Hm p 19 spm id from pageDriver 1 区分环境打包 1 1 通过环境变量区分 执行webpack命令时可携带环境变量 并在w
  • webpack5后台管理

    1 系统基于panjiachen后台管理系统模板 升级webpack5 2 github地址 https chengmanxiang webpack5 vue2 admin 3 线上地址 webpack5 vue2
  • 微前端--qiankun原理概述

    demo放最后了 一 微前端 一 微前端概述 微前端概念是从微服务概念扩展而来的 摒弃大型单体方式 将前端整体分解为小而简单的块 这些块可以独立开发 测试和部署 同时仍然聚合为一个产品出现在客户面前 可以理解微前端是一种将多个可独立交付的小
  • webpack 5 模块联邦实现微前端疑难问题解决

    webpack 5 模块联邦实现微前端疑难问题解决 说明 webpack 5 新增 Module Federation 模块联邦 功能 他可以帮助将多个独立的构建组成一个应用程序 不同的构建可以独立的开发与部署 借助模块联邦我们可以一定程度
  • 第二十一章 webpack5原理loader概述

    简介 loader其实是一个函数 用来帮助 webpack 将不同类型的文件转换为 webpack 可识别的模块 loader的分类以及执行顺序 1 分类 pre 前置loader normal 普通loader inline 内联load
  • 如何使用 Vite 构建多个包,类似于 Webpack 中的多编译器模式(多配置)

    我有一个应用程序 它有一个主 index html 和 main js 但也有外部脚本 库 从 main js 内部以编程方式注入到 index html 中 所有这些脚本 库 的构建方式都不同 因为它们需要不同的别名 插件 加载器和文件夹
  • 如何使webpack 5.x src路径是项目真正的src路径而不是webpack配置文件根路径

    我在配置中配置 webpack src 路径 如下所示 path resolve dirname src 看来webpack没有找到真正的项目src路径 源路径是 web pack 配置路径 现在我得到这样的路径 Users xiaoqia
  • 将 Next.js v10.1.13 更新到 webpack5 时,收到警告 Can't resolve 'fsevents' in chokidar

    npm install next react react dom并运行 Node js v12 创建最简单pages index tsx export default function PageHome props return lt gt
  • 如何修复 Chunk.modulesIterable 的弃用警告?

    我是的维护者外部 svg sprite loader https github com karify external svg sprite loader我注意到 当将它与 webpack 5 一起使用时 我收到以下警告 DEP WEBPA
  • 如何在 Webpack 5 中为 jsonwebtoken 填充缓冲区

    我正在升级到 Webpack 5 并且 jsonwebtoken 包存在问题 https github com auth0 node jsonwebtoken https github com auth0 node jsonwebtoken
  • 如何让 mini-css-extract-plugin 与 webpack 5 一起使用?

    我正在尝试提取库中的 css 文件 我已经读过执行此操作的方法是使用 mini css extract plugin 难道我做错了什么 还有其他方法可以提取我的库的 css 文件吗 下面我使用提供的相同文件创建了一个简单的测试项目https
  • Webpack 5“dependOn”和目标:“es5”似乎不兼容

    我在使用 dependOn 条目参数时无法让 Webpack 5 输出 es5 兼容代码 我使用 Babel 来转译我的代码 效果很好 但除非我将 webpack 目标设置为 es5 否则 webpack 本身会输出不兼容的代码 我正在使用

随机推荐