您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
快速实现一个基于 Vue.js 的微前端应用
 
作者:月光宝盒造梦师
   次浏览      
 2020-7-29 
 
编辑推荐:
本文主要讲解了对微前端的理解。以及如何快速的实现一个基于 Vue.js 的简单易用的微前端。
本文来自于博客园,由火龙果软件Anna编辑推荐。

缘起何处

我们团队在公司内部主要为国内某 TOP3 建筑公司编写 ERP 中后台应用,由于是建筑领域,其 ERP 系统十分庞杂。在我于 2018 年底入职时,该系统就已编写了三年。但是由于需求方依然在提需求,所以依然在编写新应用,维护,迭代和重构。

团队内部后端技术栈后端主要是 Java,前端主要是 Vue.js,CI/CD 是 Jenkins + GitLab,监控系统使用 Sentry,包管理器使用的是 Nexus (支持 npm & maven)。

因为团队中很多 CRUD 界面都是后端同学自己码了之后调接口。甚至很多同学在使用的时候连 Vue.js 文档都没看过。不要问为什么,问就是 Vue.js 好上手。

当时团队内部的前端编写模式是,原始项目 A 使用 Vue-CLI2 创建。现在需求方提交新模块 B 的需求给到产品,当产品交付原型图后,项目 Leader 复制项目 A 改改配置成了新的项目 B。

由于在我们的项目中存在许多不同 domain,比如 /notify, /workflow, /construction, /appMgt, /form, /auth, /print, /pan, /market, /hr, /asset 等。之前我们使用 nginx 反向代理实现 domain 分发到对应项目。在应用布局中顶部菜单直接分成了 notify.example.com, workflow.example.com 来实现不同项目 domain 的分发。

随着项目越来越多,暴露的问题越发明显:

技术债堆积

代码重复和冗余

相同依赖的版本问题

重复依赖的加载和执行

项目个数跟维护成本正相关

公共代码的更新需要复制粘贴并构建所有项目

找到问题,接下来就是解决问题了。在2018 年恰逢其时地了解到 Micro-FrontEnds 概念后,替我们指明了方向,我们也需要在项目中实现类似微前端的概念来解决上述问题。

于是,开始在社区调研。当时写微前端的文章并不多,实践的团队也比较少,留下印象最深刻的是这篇phodal/microfrontends文章,文中提到了微前端的各种实现方式、实现成本、工程成本等问题,比较全面。

真正意义上的微前端应该是框架无关的,现在社区中首推 Single-SPA。但是在 18 年的时候,Single-SPA 还未完全 Production Ready,于是我们决定实现我们团队内部的微前端框架。

因为技术栈很统一,所以无需做到框架无关,我们最终的选择是微前端:微应用化。它不仅完美解决了上述问题,同时实施成本低、技术难度小、维护成本低。

微应用化

在多 domain 时通过 nginx 反向代理的情况下,维护公共代码很痛苦。将各个业务模块拆成子应用后,其各个业务的公共部分则被拆成独立的应用。社区称之为基座应用,或者主应用。

我们将所有 domain 即子应用的公共部分封装到主应用中,单独维护,同时发布到内部 npm 私仓。以给子应用在开发环境中将其作为依赖安装。在需要修改公共代码或者提供公共服务的时候,只需要重新构建和部署主应用即可,解决了一大痛点。

图片摘自phodal/microfrontends

不过我们的实现细节上有所不同,我们将 URL Change 交由 VueRouter 的 beforeHook 处理,将 应用管理服务、Loader、install 等交由 VueMfe 处理。

主应用 App

主运行时本身也是一个独立完整的应用,独立运行、开发和部署。我们的项目中,主应用包含了下列内容(通常都包含了以下内容):

公共依赖

在我们的项目中使用 UMD 引入公共依赖,同时维护了一份公共的 Webpack externals 配置,以避免主应用和各个子应用在打包时重复构建公共依赖。

公共配置,比如:Webpack 配置,Env Variables 配置,App 配置等

公共插件,比如:ProgressBar, MicroFrontend, LazyLoad, Vuex, VueRouter, Element-UI 等

公共服务,比如:Utils, Http, Socket, Storage 等

公共数据,比如:Auth, Config, Message 等 Vuex modules,我们通过 Vuex 实现全局 Store 共享,借助其 dynamic-module-registration 的能力,实现子应用之间共享数据的注册和销毁。

公共鉴权和校验,比如:路由权限校验 Router before/after Hook、用户角色校验 AuthManager.hasAuth(authKey) 等公共状态校验

公共资源,比如:样式、字体、图标、图片、Theme 变量

公共组件,比如:<ContentBlock />,<FilePreview /> 等

公共布局,比如:<DefaultContainer />, <DetailContrainer /> 等

公共路由,比如:/index, /error/401,/error/404 等

子应用 SubApp

在提出了所有公共代码之后,子应用变成了纯业务代码的容器被主应用在运行时加载执行。因此在启动子应用之前需要先启动主应用,以拥有主应用运行时的能力。

在开发环境下,将子应用的入口设置为主应用,兼容主应用的 Webpack 配置,将 devServer 的 contentBase 也设置为主运行时的 public 目录,以保持主应用/子应用开发和生产环境下的一致性。

vue.config.js:

子应用通过主应用的中心化路由,动态加载执行。而在 Vue.js 中,如何实现中心化路由呢?Vue-Router本身提供了router.addRoutes(routes: Array<RouteConfig>)的API,但是这个 API 有一个很致命的缺点,就是不支持嵌套路由,而在实际业务中,子应用通常都是某个 Layout 下的嵌套路由。具体可以参考这个 ISSUE 的讨论,Dynamically add child routes to an existing route,根据 vuejs/rfcs Dynamic routing ,官方团队也正在征集社区意见和实现这个功能。

为了解决这个问题,需要我们自己打补丁增强一下 VueRouter 的 addRoutes 功能,实现支持动态嵌套路由、动态加载应用等功能。这便是 vue-mfe 的由来。

动态嵌套路由

vue-mfe 内部维护了一套独立的pathList和pathMap,虽然独立维护,会增加内存开销成本,好处是不会对VueRouter本身的功能造成任何影响。

当调用 router.addRoutes(routes: RouteConfig[], parentPath: string)时,深度优先找到 parentPath 所在的旧路由 oldRoute,并将其 children 与新的 routes 合并后生成新的路由 options: newRouterOptions。

再使用 options: newRouterOptions 重新实例化 new VueRouter(options: newRouterOptions),拿到新的 router.matcher 并将其赋值给app.$router.matcher 以达到支持动态嵌套路由、动态更新应用路由注册表的目的。

动态加载应用

使用VueMfe.createApp(AppConfig)注册微前端主应用App,初始化 Router,刷新 VueMfe 内部路由注册表pathList和pathMap。

注册 beforeEach 钩子,拦截路由to是否已存在于当前路由中,若不存在则认为这是一个需要被动态加载的子应用。

执行getAppPrefix(to)获取当前路由的子应用prefix前缀,执行 install 方法。

install会尝试优先获取 SubApp 自身的 resources 配置 config.resources[prefix],其次取主应用的 resources 配置。如果都获取不到,则会抛出无法找到 prefix资源的异常。

获取到 SubApp 资源后,广播加载开始LOAD_START事件,开始安装 SubApp 的静态资源和路由,执行 SubApp 的 init 初始化方法,加载成功后广播加载成功LOAD_SUCCESS事件。

执行 next(to) 跳转到用户访问的路由prefix实现完整闭环。

构建子应用

因为不同的 App 由不同的 webpack build context 构建,无法共享 chunkId 和 moduleId。所以需要将子应用打包成 umd 格式的 library,暴露 SubApp 的配置项到 root 全局变量供 VueMfe 安装。而且后续其他资源控制权则继续交由 webpack 控制。

而在 19 年末有了 webpack5 提供的 module-federation ,正式为了解决这个问题提出,但目前还是 beta 版本。新曙光,而且很多大佬已经开始了探索。后续,会继续跟上 webpack5 的升级。

构建步骤:

将子应用打包成 umd 格式的 library。

构建的入口必须是 export default VueMfe.createSubApp(SubAppConfig)的文件,以保证 root 的 全局变量是 SubAppConfig 供 VueMfe 直接安装。

如果有使用 CDN 则将 CDN 地址配置到 SubApp 或者 App 的 resources 即可。

在我们团队中,在更新到 vue-cli3 之后,因为 cli3 封装了所有的 webpack 配置,通过 service api 形式暴露,所以写了一个插件 vue-cli-plugin-mfe 来构建子应用。我们分别拆分了 3 个 command:

build构建 umd 格式文件

upload上传构建后的文件到 CDN

publish发布应用通知更新前端资源

build 的主要代码如下:

1.删除了 vue-cli3 自带的相关插件,这些插件对主应用生效即可,子应用并不需要:

api.chainWebpack((config) => {
config.plugins
.delete("html") // for cli-3.2+
.delete("html-index") // for cli-3.5+
.delete("prefetch")
.delete("prefetch-index")
.delete("preload")
.delete("preload-index")
.delete("workbox")
.delete("workbox-index")
.delete("copy")
.delete("pwa")
.end()
})

2.配置子应用打包成 umd 格式,及其全局变量名称

api.configureWebpack({
// 打包入口
entry: "./src/portal.entry.js" || options.entry, // options.entry
devtool: args.disableSourceMap ? false : "source-map", // disable all
output: {
path: api.resolve(args.output),
library: {
root: "__domain__app__" + camelizedName,
amd: packageName,
commonjs: packageName,
},
libraryTarget: "umd",
filename: "js/" + camelizedName + "-[chunkhash:8].umd.js",
// libraryExport: name,
chunkLoadTimeout: 120000,
crossOriginLoading: "anonymous",
},
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
name: camelizedName + "-" + "chunk-vendors",
// eslint-disable-next-line no-useless-escape
test: /[\\\/]node_modules[\\\/]/,
priority: -10,
chunks: "initial",
},
common: {
name: camelizedName + "-" + "chunk-common",
minChunks: 2,
priority: -20,
chunks: "initial",
reuseExistingChunk: true,
},
},
},
},
plugins: [
args.downloadUrl &&
new WebpackRequireFrom({
path: args.downloadUrl + args.name + "/",
}),
new WebpackManifest(),
new WebpackArchiver({
source: api.resolve(args.output),
destination: outputPath,
format: "tar",
}),
].filter(Boolean),
})

回顾历程

截止目前为止,团队内已使用微前端架构 1 年多了。虽然不够完美,或者一些概念在日新月异的前端领域可能已经过时。但是这套方案,在团队内部切切实实解决了开篇提到的种种问题

 
   
次浏览       
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]
 
最新文章
如何设计高扩展的在线网页制作平台
electron入门心得
使用 Electron 构建桌面应用
VUE.JS组件化开发实践
深入理解JSCore
最新课程
HTML 5 + CSS3 原理与开发应用
Web前端高级工程师必备技能实战
Vue大型项目开发实战
React原理与实践
Vue.js进阶与案例实践
更多...   
成功案例
中交集团 构建Web自动化测试框架
某著名电信公司 Vue.js进阶与案例
国电通网络技术 HTML5+CSS3 +web前端框
移动通信 移动互联网应用开发原理
某电力行 android开发平台最佳
更多...