内容简介:目前的前端领域,单页面应用(SPA)大行其道。而随着时间的推移以及应用功能的丰富,这些应用变得越来越庞大也越来越难以维护。于是“微前端”这一概念应运而生。
本文作者:个推高级前端开发工程师 沈创
目前的前端领域,单页面应用(SPA)大行其道。而随着时间的推移以及应用功能的丰富,这些应用变得越来越庞大也越来越难以维护。于是“微前端”这一概念应运而生。
“微前端”出自2016 年的 ThoughtWorks 技术雷达,指 将项目拆分成一个个可独立运行、独立开发、独立部署的前端微应用,这些微应用可以并行开发、共享组件 。
而微前端的实现方式也分很多种:服务器路由重定向、组合多个独立应用、iFrame、通过Web Components构建等。
微前端的相关概念也 在个推前端中的部分项目(基于Vue框架)中得到应用 。
为什么要进行前端微服务化
之所以强调“部分项目”,是因为任何一种技术或者概念都有其适用场景,微前端也不例外。 针对中小型的项目,使用微前端反而会将事情复杂化 ,因为微前端对项目的开发并不友好。
以个推的业务场景为例:
在A项目线中有10-20个模块,每一个模块中有5-15个不等的页面。而A项目线中所有的产品都是基于这些模块来自由组合的,也就是说:如果按照普通的SPA开发路线,我们可能需要很多分支或者repo来维护这些产品,因为每个产品所需的模块版本会有细微区别。
演变的过程
为了不出现分支混乱、项目庞大、代码冲突、打包麻烦等一系列的问题,借着后端微服务拆分的机会,我们开始对A项目线前端开发和部署方式进行了调整。
最初,我们并没有使用前端微服务的开发和部署方式,而是先把项目中的各个模块拆分成了许多独立的repo,避免团队内的工程师在开发的过程中出现需要pull代码并解决冲突的情况(一个模块一个迭代一般由1-2人完成)。
因此,我们的问题是: 模块拆分后,如何解决开发、打包部署,以及项目中的公共依赖和组件复用的问题 。
拆分后的模块项目目录结构大致如下:
项目中的main.js入口和公共组件被抽离成了一个单独的项目,这里称为main项目。
由于各个子模块项目中仅有当前模块的页面代码和路由、菜单配置,所以dev子模块无法被直接开发。于是我们开发了一个名为lego的CLI工具。开发模块时,开发人员只需要在模块根目录运行“lego dev”命令即可启动一个当前模块的开发服务,开发好的模块都会被发布到我们自己的npm源进行版本的管理。
如果仅仅是对模块进行拆分,那么开发人员单独对模块进行开发时,需要给模块配置对应的运行环境,并且模块与模块之间的相互调用也很麻烦。 而“lego”CLI解决了模块运行环境的问题,运行环境由CLI自动加载,模块开发人员只需要关注模块自身的业务逻辑即可 。
此外,模块还提供了一个config.js文件,可以从npm源配置其他依赖模块,帮助开发人员在开发时更便捷地调用不同模块。使用“lego dev”命令还支持“@self/”路径引入,“@self/”路径指向当前模块的src/文件夹,而“@/”指向main项目的src/文件夹,从而避免了模块开发时import路径的问题。
通过模块的拆分改造,解决了项目庞大、分支混乱的问题,代码冲突的情况也显著减少。但是对于单个产品的打包部署,我们仍然需要从各个模块获取源码,并通过main项目打包成一个独立的产品。即使只修改了某一个模块的一行代码,整个系统也需要重新打包,打包后的整个产品也需要进行回归测试。
针对这一问题,我们思考是否可以直接把模块打包成应用以供调用。
模块打包以及独立部署
我们的理想情况是: 各个模块可以独立开发和部署,然后由产品自身决定加载的模块 。
效果如下:
因此我们需要在模块打包之后,入口(index.js)可以按照需要被注入到main项目中,并且被main项目加载(路由)。
一方面,使用webpack进行打包的项目,代码是基于CommonJS规范的。由于umd规范兼容于CommonJS规范,这使得开发人员可以直接在项目中使用基于umd规范打包后的模块。
另一方面,vue-router和vuex库,都支持动态加载addRouter/registerModule的API。
我们采用过两种方案:
第一种:main项目在Vue实例初始化时,将vue-router和vuex的实例暴露到全局(window),将子模块的路由前缀存储在项目中的路由表。当页面跳转到匹配的子模块的路由时,main项目加载子模块umd.js文件并动态注册router和vuex module,进而渲染页面。
简单DEMO如下图所示:
第二种:子模块umd.js文件先加载,向全局(window)暴露该子模块的路由和vuex信息。Vue实例从window获取路由信息和vuex module、菜单信息等,形成一个独立的产品。
简单DEMO如下图所示:
当然,两种方案都存在一定的缺点:
第一种方案:首先,子模块js文件是在页面跳转之后再进行加载,因此, 在404跳转和路由权限校验的实现上会遇到一些问题 ;其次,在子模块文件加载完成之前以及子模块渲染之前都存在 较长的页面白屏时间 。
第二种方案:无论子模块用户是否会访问到umd入口文件,该文件都需要事先加载。这就要求入口文件需要足够小,意味着子模块无法使用min-chunk-size-plugin插件来对chunk进行合并, 需要开发人员采用手写webpackChunkName或者使用其他 工具 进行合并 。
基于VUE-CLI3的实践
Vue-cli3.x对子模块的打包提供了比较好的支持,使用"vue-cli-service build - target=lib"即可将子模块代码打包成umd规范格式。
但是,需要注意以下几个问题:
-
“--target=lib”的初衷是给发布到npmjs的组件使用,所以打包出的文件是不带hash值的(即使在vue.config.js中配置了chunkName)。我们采取的办法是在执行lego脚手架的打包命令前,修改vue-cli-service源码。
-
使用“--target=lib”打包子模块时,如果没有配置css-in-js,打包出的css文件中的background-image路径有问题。基于此,我们给出两个解决办法:配置css-in-js,或者修改node_modules中vue-cli-service源码再打包。
以上便是个推前端微服务化的开发及部署的实践情况。
在实践中我们发现, 微服务化的接入,很好地解决了项目中遇到的维护难、产品编译部署麻烦等问题 。在模块化拆分时, 我们开发的CLI工具也很好地解决了模块单独开发运行的问题 。
当然,我们的微服务化方案也存在局限。它 比较适合模块之间联系比较紧密的大型项目 ,且没有微前端概念中强调的技术无关性以及团队代码隔离性。
在不久的将来,除了微服务化方案的继续升级, 我们还会接入新的框架,迎接新的挑战 。
想看更多 行业前沿资讯和深度技术干货
赶紧扫码关注 “个推技术学院” 吧
▼
以上所述就是小编给大家介绍的《个推前端微服务化:突破传统 SPA 瓶颈》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。