项目中遇到的问题
需要在react框架中使用一些vue的组件。是重构?还是用其他方式?
几种微前端方案的列举和思考
重构过程比较长,所以重构是一个持续过程,完成一块功能,就迁移到智能版。所以采用微前端的方式集成。主项目不发布,功能热替换。
方案 | 方案核心实现 | 优点 | 缺点 |
|---|---|---|---|
iframe | iframe内嵌页面 | 最简单的实现 | 内外独立的2个系统, 系统间数据共享,通信等很多问题都需要解决 |
微前端 |
| 主项目动态加载子项目 实现了微前端 实现简单,好理解 | 父子项目数据无法直接共享 父项目无法控制子项目渲染的细节。 父子项目有很多约定。 子组件单独无法使用。(如果依赖外层提供的数据的话) |
远程模块(组件) |
| 主项目动态加载子项目 实现了微前端 父子项目数据可以直接通过 和 context传递,不需要额外技术 父页面可以控制子页面的渲染(位置,时机)。甚至可以通用组件再次包装。 可以实现不同系统,页面的多态 | 实现稍微复杂,需要一定理解成本 |
远程模块(组件)一些质疑
为啥不封装npm的业务组件,再彼此系统中使用。
业务组件变化频繁,一般不抽象组件使用。另外npm组件更新,需要使用此组件的系统分别发布升级,会造成线上系统频繁发布,无关的功能也会被频繁发布。
远程模块的改造成本多大,对业务入侵?
使用远程模块方案,只对编译阶段有影响,会额外编译一个目录存放本次业务组件,发布后供其他系统远程获取。自身系统的开发形式和发布形式不约束。但要保证组件是纯组件(尽量不在组件内部依赖全局变量或者数据,保证组件在提供正确 props和context 下即可正常使用),如果有依赖需求,通过 props 或者 context 从外部进行传递。事实上,这点也是我们正常形式开发时要遵守的一点。
为啥不用时老师的方式
会员portm的方式也可以实现。但是正如缺点描述那样,对父子有约定,比如组件渲染的dom id 要约定,子组件的全局数据怎么获取和更新。子组件在开发时路由怎么控制?还有一个问题,子页面的多态无法实现。比如我们组件有2套主题。需要根据父主题而定。通过远程模块,子组件在开发时可以直接根据上下文中的主题内容来渲染自身样式。父组件在渲染时只要包装上context即可。在我们会员系统里,全局的用户信息,菜单权限等,子组件开发时不需要管怎么来的,只需要控制怎么渲染即可。
组件重复的问题?父子应用重复?子应用多个会重复吗?
本着一个原则:微前端尽量减少父子应用之间的耦合。换句话说,单拿出来,应用是个独立的应用,集成后,又可以做为子应用。这是我们设计的理念。在开发时候,开发同学同样不感知我应用最终如何呈现,统一当做一个独立应用开发。所以,父子应用的公共组件不贡献,也不应该共享。(不然会有版本耦合)
子应用独立打包,webpack会抽取子应用组件的公共组件,在我们引入子应用的main脚本时,相关vender会同时被加载。而且只会加载一次。(这点webpack会帮我们实现)
方案设计
整个方案的核心思想就是将webpack 基于组件 code split, 将传统的同项目异步加载,改为异项目异步加载。
图例:
CDN业务bundle组件bundle本地组件远程组件A项目B项目本地组件编译
前置要求:
✅A, B 项目依赖react 版本保持一致。
✅打包后包名要不能重复,可以通过编译控制。
简单的实现方案:
A业务正常开发自身项目。
编译时,除了A正常打包外,在build中打包一个目录。里面是A组件对外提供的业务组件。
打包方式,使用webpack的 code split 即可:
代码块JavaScript// 将组件输出到windowwindow.remoteModule = (name: string) => {if (name === "home") {return import(/* webpackChunkName: "smarts_home" */ './pages/Home');}if (name === "test") {return import(/* webpackChunkName: "smarts_test" */ './pages/Test');}}如下图:
其中,main是入口文件,给B项目依赖。smarts_x.bundle.js 为组件。
B项目中加载后,在入口处动态引入A入口文件(此时,A种的组件还未加载):
代码块JavaScriptfunction loadRemoteModule = (src) => {var script = document.createElement('script');script.charset = 'utf-8';script.timeout = 2000;script.setAttribute('crossorigin', 'anonymous')script.setAttribute('type', 'text/javascript')script.crossorigin="anonymous"document.head.appendChild(script);script.src = src;}B项目异步引入远程模块A中的组件(webpack将动态加载该组件的js文件和它的依赖):
代码块JavaScriptgetRemoteComponent('home').then((Home) => {this.setState({el: Home.default || Home,})})getRemoteComponent = (name: string) => {return Promise.all([window.remoteModule(name),])}render() {const Com = this.state.el;const Context = this.state.context;return <div><hr/><Context value={{user: 'yanggaofei'}}> //指定异步组件的上下文<Com id={1}/> // 传递属性</Context></div>}
重构的技术价值和产出
重构不是简单的新瓶装旧药,也不是无意义的重复建设。基于当前版本现有问题的解决,同时激励自我成长。我们在改造完成后要达到以下目标:
序号 | |
|---|---|
1 | 推行新的react 版本以及新特性在项目落地,沉淀和积累经验。系列文章: 经验积累 (每个人都要有) |
2 | 梳理业务。优化代码中的多处冗余。 |
3 | 积累大型项目微前端整合的经验, 产出通用技术方案/技术博客 |
4 | 对团队代码检查sonar配置的更新 |