1.背景公司发展到一定程度。随着业务分支的不断增加,B端和C端项目的数量也将增加。由于历史原因,新老技术栈(Vue/react)并存,不利于组件素材的分离和统一(一种通用组件需要适配多套技术栈),同时也增加了开发人员跨项目开发的适应成本。因此,技术栈融合是提高前端平台系统开发效率的重要一环。说到技术栈迁移,我们首先想到的就是微前端方案。在隔离方面,微前端确实是一个很好的解决方案,但是对于一些复杂的核心模块,往往需要很长的迁移周期,并且随着模块的不断迭代,使得整个项目的迁移进度逐渐拉长。最终,核心痛点可能仍未彻底解决。基于以上背景,我们需要解决两个问题:更顺畅的技术栈迁移:不仅是新页面,旧页面的需求也可以用react开发,实现代码块级别的迁移。跨技术栈开发:MF组件开发需要将react组件转为vue组件,将react组件嵌入到同一个界面中。2.跨技术栈开发vuereact-combined[1]提供了更通用的组件转换方案,使用applyReactInVue在vue指定的生命周期渲染React组件,区分update阶段和create阶段,减少不必要的dom构建,同时监听上层的$attrs和$listenters,通过中间层reactComponentWrapper获取的reactInstance透传相应的state和method。React->Vue转换源码可以参考这里[2]同时,vuereact-combined[3]内部也会进行vuex和router转换,让react组件获取全局state和router实例,满足路由跳转和身份验证相关的要求。以下是它支持的转换特性:同一个项目下混合开发?好了,看起来一切都解决了,但是有一些问题无法回避:第一,React和Vue的依赖和编译babel生态不一样。如果有相同的依赖,就需要找一个兼容两个技术栈的版本,构建成本成倍增加。在本地开发和在线建设中,需要分别投入精力进行优化。一个文件夹里既有.vue,也有.tsx,结构比较乱,不利于维护。3.技术栈迁移更顺畅3.1页面级微前端在技术栈迁移和效率提升上存在局限性提到技术栈迁移,我们首先想到的是微前端,比如QianKun、SingleSPA、MicroApp,他们可以做平台内基于大业务模块的项目级拆分,与技术栈无关。从本质上解决了项目维护成本和施工优化成本随项目不断增加的问题。请注意,尽管可以跨技术堆栈开发页面级微前端,但它们只能进行增量修改。新页面我们可以使用内部统一的技术栈,但是业务迭代中有相当一部分需求是基于旧页面进行改造的,我们还是需要基于之前的技术栈进行开发,除非是全量重构。然后按照常规方法,花时间单独迁移核心模块。阻力可想而知。业务在不断推进,重构对用户无动于衷,需要测试资源的回归,不管业务的提升与否,结果短期内并没有明显的正面效果。所以,结果只能是旧模块不变,技术栈的统一任重而道远。3.2网络组件?Webcomponents是chrome推广的原生组件API,即不依赖技术栈开发组件,实现组件的高复用率。作为组件级的共享是更好的解决方案,但是由于原生API、状态管理、组件通信,需要开发者自己实现。因为技术栈迁移场景与技术栈是迁入还是迁入相关,所以组件转换会有较高的成本。3.3在ModuleFederation的代码块级别引入MF本质上是webpack提供的一种能力,可以让开发者在一个JavaScript应用中动态加载和运行另一个JavaScript应用的代码,实现应用之间的依赖共享。具体原理可参考ModuleFederation原理分析[4]。这样我们就可以逐步迁移旧的Vue项目,同时降低重构的成本。基于之前微前端与模块化的对比,考虑到模块逻辑的复杂性和迁移成本,我们决定采用基于ModuleFederation的模块化开发,使得复杂模块的迁移更加顺畅,能够平衡技术迁移和下层迁移同一个模块。为了业务发展的节奏,两者尽可能松耦合,实现渐进迁移。下面介绍了实施模块化迁移解决方案的要点。4.实现方案4.1组件转换首先,我们需要将开发者的react组件转换为vue组件。每次reactmicro工程发生变化,我们都需要遍历工程,找到.jsx/.tsx文件,并声明其对应的.vue文件,.vue文件中做了什么?会引入基于vuereact的react文件,透传变量和方法。这些.vue文件的用户不会感知到它们,因此它们将被存储在一个临时目录(.mfveat)中。.vue文件的代码模板如下:4.2生成组件Expose映射上一节提到的.vue文件会生成一个从expose组件的地址到ModuleFederation要使用的文件地址的映射。4.3ModuleFederation的动态注入大家都知道ModuleFederation是写在构建配置文件中的。公开决定微应用程序公开哪些组件。但是在本地开发的时候,我们的业务代码和导出都变了。如果每次都修改exposes,就得重启项目,效率很低。如何解决动态暴露的问题?下面我们就来说说ModuleFederationPlugin的组成。核心是ContainerPlugin(远端)和ContainerReferencePlugin(宿主)。解决方案是在ContainerPlugin的上层封装一个插件mf-veact-plugin,支持暴露。微项目搭建完成后按照上述步骤生成expose.json,然后动态注入mf-veact-plugin。以上三个步骤构成了跨技术栈开发的全链路。用户只需要关心react业务代码,然后通过ModuleFederationHost导入到主工程中即可。5.开发心得5.1刷新监听和MF介绍由于MF和主工程是独立的,那么用户修改React代码后如何触发主工程的刷新呢?每个子工程编译完成后,webpack插件会把更新后的唯一标识写入主工程,主工程会循环监听唯一标识,发生变化时触发页面刷新。最近增加了hotreloading,保证子工程和主工程的通信畅通。webpack-dev-server的作者在最近的版本中更新了hotreload功能,无需手动监控reload。5.2UI库样式降级避免全局污染MF实现了组件级的微前端,但同时也带来了一些问题,因为每个项目可能会使用不同的UI库,而UI库本身也会进行全局样式转换,即不可避免的会影响到其他项目UI库的风格,而MF的组件粒度,很难做到和页面微前端一样的项目风格隔离。这里以Antd为例。Antd中的global.less会格式化全局样式。社区里也有很多讨论,但直到今天都没有任何进展。因为Ant-Design是一种设计语言,antd会引入一套浏览器默认样式重置库global.less,它是从normalize.css[4]fork出来的。因此,这里的解决方案是“收敛base.less,保证外部全局样式不能轻易覆盖antd样式”,从编译的角度解决样式污染问题。1)antd-vue风格污染问题6.总结本文简单介绍了前端领域在迈出统一技术栈的一步之后所经历的痛点和挑战。对比了类开发模型,从解决用户开发痛点的角度阐述了架构设计的原因。最后列出了模块化移植或开发过程中需要注意的问题并给出了解决方案。主要目的还是以更低的成本和更好的开发体验来促进技术栈的统一和迁移。模块化开发是前端领域不可分割的话题。解决技术栈统一问题只是其中的一个分支。同时,模块化的代码隔离和非版本化变更也是我们未来解决优化的方向。组件和平台模块的自动共享一直在前面提到过,希望这次对解决方案的探索能给大家带来一些启发,也欢迎大家在前端平台系统组件通用化的方向上一起交流讨论。参考文章:[1]https://github.com/devilwjp/vuereact-combined[2]https://github.com/devilwjp/vuereact-combined/blob/master/src/applyReactInVue.js[3]https://github.com/devilwjp/vuereact-combined[4]https://juejin.cn/post/6895324456668495880[5]《如何优雅地彻底解决 antd 全局样式问题》https://juejin.cn/post/6844904116288749581[6]《Module Federation原理剖析》https://juejin.cn/post/6895324456668495880
