一、前端(架构)发展史
最初,前端是没有架构的,因为功能简单的代码没有架构可言。通过操作DOM就能完成的工作,不需要复杂的设计模式和代码管理机制,也就不需要架构来支撑起应用。前端开发的发展历史分为以下几个阶段:
一旦前端应用需要从后端获取数据,就意味着前端应用在运行时是动态地渲染内容的,这便是 Model(模型)UI 层解耦。jQuery 能够提供 DOM 操作方法和模板引擎等。这时的开发人员需要做下面两个事情:
由于 HTML 的动态生成、模板的独立与分离,前端应用开始变得复杂。后端的 MVC 架构进一步影响了前端开发,便诞生了一系列早期的 MVC 框架,如 Backbone,Knockout,等等。
与此同时,在 Ryan Lienhart Dahl 等人开发了 Node.js 之后,前端的软件工程便不断地改善:
随着单页面应用的流行,前后端分离架构也成为行业内的标准实践。由此,前端进入了一个新的时代,要考虑的内容也越来越多:
系统变得越来越复杂,架构在前端的作用也变得越来越重要。MVC 满足不了开发人员的需求,于是采用了组件化架构。
而组件化 + MV* 也无法应对大型的前端应用。

微前端便又出现在我们的面前,它解决了以下问题:
复杂的前端应用发展了这么久,也出现了一系列需要演进的应用——考虑重写、迁移、重构,等等。
二、什么是微前端架构
微前端不是单纯的前端框架或者工具,而是一套架构体系,这个概念最早在 2016 年底被提出,可以参考在 Google 上搜索 Micro-Frontends, 排名靠前的 https://micro-frontends.org 的博客文章,提出了早期的微前端模型。
Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. – Micro Frontends
微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将单页面前端应用由单一的单体应用转变为把多个小型前端应用聚合为一的应用。各个前端应用还可以独立开发、独立部署。
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
微前端的实现意味着对前端应用的拆分。拆分应用的目的并不只是为了在架构上好看,它还可以提升开发效率。比如10万行的代码拆解成10个项目,每个项目1万行代码,要独立维护每个项目就会容易得多。而我们只需要实现应用的自治,即实现应用的独立开发和独立部署,就可以在某种程度上实现微前端架构的目的。
三、微前端的核心价值
- 拆分巨型应用

- 遗留系统增量迁移
迁移是一项非常耗时且艰难的任务,比如有一个管理系统使用 AngularJS 开发维护已经有三年时间,但是随时间的推移和团队成员的变更,无论从开发成本还是用人需求上,AngularJS 已经不能满足要求,于是团队想要更新技术栈,想在其他框架中实现新的需求,但是现有项目怎么办?直接迁移是不可能的,在新的框架中完全重写也不太现实。
使用微前端架构就可以解决问题,在保留原有项目的同时,可以完全使用新的框架开发新的需求, 然后再使用微前端架构将旧的项目和新的项目进行整合。这样既可以使产品得到更好的用户体验, 也可以使团队成员在技术上得到进步,产品开发成本也降到的最低。
不重写原有系统,同时抽出人力来开发新的业务,这对业务人员来说,是一个相当有吸引力的特性,而且对技术人员来说,也是一件相当不错的事情。人生苦短,请尽量不重写。
- 独立发布
在目前的单页应用架构中,使用组件构建用户界面,应用中的每个组件或功能开发完成或者bug修 复完成后,每次都需要对整个产品重新进行构建和发布,任务耗时操作上也比较繁琐。
在使用了微前端架构后,可以将不能的功能模块拆分成独立的应用,此时功能模块就可以单独构建单独发布了,构建时间也会变得非常快,应用发布后不需要更改其他内容应用就会自动更新,这意味着你可以进行频繁的构建发布操作了。

- 允许单个团队做出技术决策
因为微前端构架与框架无关,当一个应用由多个团队进行开发时,每个团队都可以使用自己擅长的 技术栈进行开发,也就是它允许适当的让团队决策使用哪种技术,从而使团队协作变得不再僵硬。
- 聚合前端应用,更利于用户体验
四、微前端也不是银弹
微前端帮助开发者解决了实际的问题,但是对于每个业务来说,是否适合使用微前端,以及是否正确的使用微前端,还是需要遵循以下一些原则:
- 微前端最佳的使用场景是一些 B 端的管理系统,既能兼容集成历史系统,也可以将新的系统集成进来,并且不影响原先的交互体验。
- 整体的微前端不仅仅是只将系统集成进来,而是整个微前端体系的完善,这其中就包括:
只有将整个能力体系搭建完善,才能说是整个微前端体系流程的完善。
除此之外,它也有一系列的缺点:
当发现使用微前端反而使效率变低,简单的变更复杂那就说明微前端并不适用。
五、如何实现微前端
从技术实践上,微前端架构可以采用以下几种方式进行:
(1)路由分发式。通过 HTTP 服务器的反向代理功能,将请求路由到对应的应用上。
(2)前端微服务化。在不同的框架之上设计通信和加载机制,以在一个页面内加载对应的应用。
(3)微应用。通过软件工程的方式,在部署构建环境中,把多个独立的应用组合成一个单体应用。
(4)微件化。开发一个新的构建系统,将部分业务功能构建成一个独立的 chunk 代码,使用时只需要远程加载即可。
(5)前端容器化。将 iframe 作为容器来容纳其他前端应用。
(6)应用组件化。借助于 Web Components 技术,来构建跨框架的前端应用。
实施的方式虽然多,但都是依据场景而采用的。在有些场景下,可能没有合适的方式;在有些场景下,则可以同时使用多种方案。
六、微前端架构模式
从微前端应用间的关系来看分为两种:基座模式(管理式)、自组织式,分别对应两种不同的架构模式:
就当前而言,基座模式实施起来比较方便,方案上也是蛮多的。
不论哪种方式,都需要提供一个查找应用的机制,在微前端中称为服务的注册表模式。和微服务架构相似,不论哪种微前端方式,都需要有一个应用注册表的服务,它可以是一个固定值的配置文件,如JSON文件,或者是一个可动态更新的配置,又或者是一种动态的服务。
它主要做以下一些事情:
应用在部署的时候,可以在注册表服务中注册。如果基于注册表来管理应用,那么使用基座模式来开发就比较方便。
中心化:基座模式
在这种模式的微前端架构中,基座承担了微前端应用的基础与技术核心。基座模式,是由一个主应用和一系列业务子应用构成的系统,并由这个主应用来管理其他子应用,包括从子应用的生命周期管理到应用间的通信机制。
基座模式中的主应用,类似于 API Gateway 的概念,它作为系统的统一入口,负责将对应的请求指向对应的服务。子应用,则是负责各个子模块的业务实现,其架构如图所示。

这个主应用,既可以只带有单纯的基座功能,也可以带有业务功能。它所处理的业务功能指的是核心部分的业务功能,如:
作为应用的基础核心,它还需要:
要实现这种模式的微前端架构,只需要设计好对应的应用加载机制即可,因此在实施的时候也比较方便。
去中心化:自组织模式
去中心化自组织模式指的是,系统内部各子系统之间能自行按照某种规则形成一定的结构或功能。采用这种模式可以使系统内的各种前端应用,都各自拥有一个小型的基座管理功能,也相当于每个应用都可以是基座。
在采用基座模式时,用户要想访问A应用需要先加载主应用,然后才能加载A应用。采用自组织模式时,用户想要访问A应用则只访问A应用,不需要加载主应用,这也因此使它拥有了更高的自主性。
不过多数时候,我们并不需要自组织模式的微前端架构,因为它设计起来复杂、拥有大量的重复代码。
七、微前端框架方案
目前业界已经有不少框架来帮助开发者轻松的集成微前端架构,其中比较主流的有:
八、qiankun
qiankun 介绍

九、qiankun 主应用
主应用不限技术栈,只需要提供一个容器 DOM,然后注册微应用并 start 即可。
这里以 Vue 2 应用为例演示主应用的搭建。
1、使用 Vue CLI 创建 Vue 2 应用
mkdir qiankun-examples cd qiankun-examples vue create main Vue CLI v4.5.15 ? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, Router ? Choose a version of Vue.js that you want to start the project with 2.x ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? No cd main npm run serve
2、安装 qiankun
npm i qiankun -S # 或者 yarn add qiankun
3、注册微应用并启动
import {
registerMicroApps, start } from 'qiankun' registerMicroApps([ {
name: 'reactApp', entry: '//localhost:3000', container: '#container', activeRule: '/app-react', }, {
name: 'vueApp', entry: '//localhost:8080', container: '#container', activeRule: '/app-vue', }, {
name: 'angularApp', entry: '//localhost:4200', container: '#container', activeRule: '/app-angular', }, ]) // 启动 qiankun start()
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
十、qiankun 微应用
微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。
微应用分为有 webpack 构建和无 webpack 构建项目,有 webpack 的微应用(主要是指 Vue、React、Angular)需要做的事情有:
- 新增 public-path.js 文件,用于修改运行时的 publicPath。什么是运行时的 publicPath ?。
注意:运行时的 publicPath 和构建时的 publicPath 是不同的,两者不能等价替代。
- 微应用建议使用 history 模式的路由,需要设置路由 base,值和它的 activeRule 是一样的。
- 在入口文件最顶部引入 public-path.js,修改并导出三个生命周期函数。
- 修改 webpack 打包,允许开发环境跨域和 umd 打包。
主要的修改就是以上四个,可能会根据项目的不同情况而改变。例如,你的项目是 index.html 和其他的所有文件分开部署的,说明你们已经将构建时的 publicPath 设置为了完整路径,则不用修改运行时的 publicPath (第一步操作可省)。
无 webpack 构建的微应用直接将 lifecycles 挂载到 window 上即可。
1、React 微应用
以 create react app 生成的 react 16 项目为例,搭配 react-router-dom 5.x。
- 在 src 目录新增 public-path.js:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
- 设置 history 模式路由的 base:
<BrowserRouter basename={
window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>
- 入口文件 index.js 修改,为了避免根 id #root 与其他的 DOM 冲突,需要限制查找范围。
import './public-path';import React from 'react';import ReactDOM from 'react-dom';import App from './App'; function render(props) {
const {
container } = props; ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));} if (!window.__POWERED_BY_QIANKUN__) {
render({
});} export async function bootstrap() {
console.log('[react16] react app bootstraped');} export async function mount(props) {
console.log('[react16] props from main framework', props); render(props);} export async function unmount(props) {
const {
container } = props; ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));}
- 修改 webpack 配置
安装插件 @rescripts/cli,当然也可以选择其他的插件,例如 react-app-rewired。
根目录新增 .rescriptsrc.js:
修改 package.json:
npm i -D @rescripts/cli const { name } = require('./package') module.exports = { webpack: (config) => { config.output.library = `${ name}-[name]` config.output.libraryTarget = 'umd' config.output.jsonpFunction = `webpackJsonp_${ name}` config.output.globalObject = 'window' return config }, devServer: (_) => { const config = _ config.headers = { 'Access-Control-Allow-Origin': '*' } config.historyApiFallback = true config.hot = false config.watchContentBase = false config.liveReload = false config.port = 3000 return config } } - "start": "react-scripts start", + "start": "rescripts start", - "build": "react-scripts build", + "build": "rescripts build", - "test": "react-scripts test", + "test": "rescripts test", - "eject": "react-scripts eject" - 解决使用 create-react-app 创建的 React 微应用 socket 连接导致基座报错问题。
安装 cross-env 用来设置跨操作系统的环境变量。
在启动命令中设置开发环境中热更新请求的端口号。
npm i -D cross-end - "start": "rescripts start", + "start": "cross-env WDS_SOCKET_PORT=3000 rescripts start", 2、Vue 2 微应用
以 vue-cli 3+ 生成的 vue 2.x 项目为例,vue 3 版本等稳定后再补充。
- 在 src 目录新增 public-path.js:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
- 入口文件 main.js 修改,为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围。
import './public-path';import Vue from 'vue';import VueRouter from 'vue-router';import App from './App.vue';import routes from './router';import store from './store'; Vue.config.productionTip = false; let router = null;let instance = null;function render(props = {
}) {
const {
container } = props; router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/', mode: 'history', routes, }); instance = new Vue({
router, store, render: (h) => h(App), }).$mount(container ? container.querySelector('#app') : '#app');} // 独立运行时if (!window.__POWERED_BY_QIANKUN__) { render();} export async function bootstrap() {
console.log('[vue] vue app bootstraped');}export async function mount(props) {
console.log('[vue] props from main framework', props); render(props);}export async function unmount() {
instance.$destroy(); instance.$el.innerHTML = ''; instance = null; router = null;}
- 打包配置修改(vue.config.js)
const {
name } = require('./package'); module.exports = {
devServer: {
// 主要 允许跨域 headers: {
'Access-Control-Allow-Origin': '*', }, }, configureWebpack: {
output: {
library: `${
name}-[name]`, libraryTarget: 'umd', // 把微应用打包成 umd 库格式 jsonpFunction: `webpackJsonp_${
name}`, }, }, };
3、Angular 微应用
以 Angular-cli 9 生成的 angular 9 项目为例,其他版本的 angular 后续会逐渐补充。
- 在 src 目录新增 public-path.js 文件,内容为:
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
- 设置 history 模式路由的 base,src/app/app-routing.module.ts 文件:
+ import {
APP_BASE_HREF } from '@angular/common'; @NgModule({
imports: [RouterModule.forRoot(routes)], exports: [RouterModule], // @ts-ignore + providers: [{
provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular' : '/' }] })
- 修改入口文件,src/main.ts 文件。
import './public-path';import {
enableProdMode, NgModuleRef } from '@angular/core';import {
platformBrowserDynamic } from '@angular/platform-browser-dynamic';import {
AppModule } from './app/app.module';import {
environment } from './environments/environment'; if (environment.production) {
enableProdMode();} let app: void | NgModuleRef<AppModule>;async function render() {
app = await platformBrowserDynamic() .bootstrapModule(AppModule) .catch((err) => console.error(err));}if (!(window as any).__POWERED_BY_QIANKUN__) {
render();} export async function bootstrap(props: Object) {
console.log(props);} export async function mount(props: Object) {
render();} export async function unmount(props: Object) {
console.log(props); // @ts-ignore app.destroy();}
- 修改 webpack 打包配置
先安装 @angular-builders/custom-webpack 插件,注意:angular 9 项目只能安装 9.x 版本,angular 10 项目可以安装最新版。
在根目录增加 custom-webpack.config.js ,内容为:
修改 angular.json,将 [packageName] > architect > build > builder 和 [packageName] > architect > serve > builder 的值改为我们安装的插件,将我们的打包配置文件加入到 [packageName] > architect > build > options。
npm i @angular-builders/custom-webpack@9.2.0 -D const appName = require('./package.json').name; module.exports = { devServer: { headers: { 'Access-Control-Allow-Origin': '*', }, }, output: { library: `${ appName}-[name]`, libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${ appName}`, }, }; - "builder": "@angular-devkit/build-angular:browser", + "builder": "@angular-builders/custom-webpack:browser", "options": { + "customWebpackConfig": { + "path": "./custom-webpack.config.js" + } } - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular-builders/custom-webpack:dev-server", - 解决 zone.js 的问题
在父应用引入 zone.js,需要在 import qiankun 之前引入。
将微应用的 src/polyfills.ts 里面的引入 zone.js 代码删掉。
在微应用的 src/index.html 里面的 标签加上下面内容,微应用独立访问时使用。
- import 'zone.js/dist/zone'; <!-- 也可以使用其他的CDN/本地的包 --> <script src="https://unpkg.com/zone.js" ignore></script> - 修正 ng build 打包报错问题,修改 tsconfig.json 文件,参考 issues/431
- "target": "es2015", + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], - 为了防止主应用或其他微应用也为 angular 时, 会冲突的问题,建议给 加上一个唯一的 id,比如说当前应用名称。
src/index.html :
src/app/app.component.ts :
- <app-root></app-root> + <app-root id="angular9"></app-root> - selector: 'app-root', + selector: '#angular9 app-root', 当然,也可以选择使用 single-spa-angular 插件,参考 single-spa-angular的官网 和 angular demo
(补充)angular7 项目除了第 4 步以外,其他的步骤和 angular9 一模一样。angular7 修改 webpack 打包配置的步骤如下:
除了安装 angular-builders/custom-webpack 插件的 7.x 版本外,还需要安装 angular-builders/dev-server。
npm i @angular-builders/custom-webpack@7 -D npm i @angular-builders/dev-server -D
在根目录增加 custom-webpack.config.js ,内容同上。
修改 angular.json, [packageName] > architect > build > builder 的修改和 angular9 一样, [packageName] > architect > serve > builder 的修改和 angular9 不同。
- "builder": "@angular-devkit/build-angular:browser", + "builder": "@angular-builders/custom-webpack:browser", "options": {
+ "customWebpackConfig": {
+ "path": "./custom-webpack.config.js" + } } - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular-builders/dev-server:generic",
十一、非 webpack 构建的微应用
一些非 webpack 构建的项目,例如 jQuery 项目、jsp 项目,都可以按照这个处理。
接入之前请确保你的项目里的图片、音视频等资源能正常加载,如果这些资源的地址都是完整路径(例如 https://qiankun.umijs.org/logo.png),则没问题。如果都是相对路径,需要先将这些资源上传到服务器,使用完整路径。
接入非常简单,只需要额外声明一个 script,用于 export 相对应的 lifecycles。例如:
- 声明 entry 入口
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Purehtml Example</title> </head> <body> <div>
Purehtml Example
</div> </body> + <script src="//yourhost/entry.js" entry></script> </html>
- 在 entry.js 里声明 lifecycles
const render = ($) => {
$('#purehtml-container').html('Hello, render with jQuery'); return Promise.resolve();}; ((global) => {
global['purehtml'] = {
bootstrap: () => {
console.log('purehtml bootstrap'); return Promise.resolve(); }, mount: () => {
console.log('purehtml mount'); return render($); }, unmount: () => {
console.log('purehtml unmount'); return Promise.resolve(); }, };})(window);
你也可以直接参照 examples 中 purehtml 部分的代码
同时,你也需要开启相关资源的 CORS,具体请参照此处
十二、Vite 构建的微应用
目前没有很好的支持,具体可以参考这个官方仓库 issue。
社区还有一个封装好的 vite 插件vite-plugin-qiankun,可以试试。
十三、手动加载微应用
https://qiankun.umijs.org/zh/api#手动加载微应用
如果微应用不是直接跟路由关联的时候,你也可以选择手动加载微应用的方式:
import {
loadMicroApp } from 'qiankun'; loadMicroApp({
name: 'app', entry: '//localhost:7100', container: '#yourContainer', });
十四、统一加载多个应用
多个应用的手动启动比较麻烦,这里我们可以通过 npm scripts 来快速启动多个应用。
- 在所有应用的父目录下配置
npm scripts启动脚本:
cd qiankun-examples npm init -y
"scripts": {
"start": "npm run start:main && npm run start:app1 && npm run app2 && npm run app3", "start:main": "cd main && npm start", "start:app1": "cd app1 && npm start", "start:app2": "cd app2 && npm start", "start:app3": "cd app3 && npm start" }
- 最后执行 qiankun-examples 目录中的 npm start 就可以把所有应用启动起来了。
使用 npm-run-all
使用 npm-run-all 可以把诸如 npm run clean && npm run build:css && npm run build:js && npm run build:html 的一长串命令通过 glob 语法简化成 npm-run-all clean clean build:* 这样精致小巧的模样。此外 npm-run-all 还支持并行运行多个任务。
- 在所有应用的父目录安装 npm-run-all
npm i -D npm-run-all
配置 npm scripts
"scripts": {
"start": "npm-run-all --parallel start:*", "start:main": "cd main && npm start", "start:app1": "cd app1 && npm start", "start:app2": "cd app2 && npm start", "start:app3": "cd app3 && npm start" }
启动所有应用
npm start
需要注意如果脚本退出时返回空值,所有其它子进程都会被 SIGTERM 信号中断,可以用--continue-on-error 参数禁用行为。
npm-run-all 的其它用法示例:
# 这段命令首先按顺序执行 clean lint 两个脚本,然后同时执行 watch:html 和 watch:js 的任务。 npm-run-all clean lint --parallel watch:html watch:js # 顺序运行 a 和 b; # 然后同时运行 c 和 d; # 再依次运行 e 和 f; # 最后同时执行 g, h, i npm-run-all a b --parallel c d --sequential e f --parallel g h i # 或者 npm-run-all a b --parallel c d --serial e f --parallel g h i # 你可以使用 Glob 通配符来匹配任务名称,方便指定多个名称相似的任务,和标准语法不同的是分隔符由 / 改为 : 以适应需要。 npm-run-all --parallel watch:* # 匹配分隔符,所有以 watch: 开头的脚本都会被运行。 npm-run-all --parallel watch: # 在脚本名称后使用双引号包裹来提供参数,甚至还支持用占位符,延迟到运行命令时再提供参数。 npm-run-all build "start-server -- --port {1}" -- 8080
更多使用方式参考npm-run-all 官方稳定。
十五、加载过程的生命周期
registerMicroApps 方法的第二个参数可以配置加载子应用过程的生命周期钩子。
type Lifecycle = (app: RegistrableApp) => Promise<any>;
比如我们利用这些接口给子应用加入加载动画等功能。
npm i nprogress
代码如下
import NProgress from "nprogress"; import "nprogress/nprogress.css"; import {
registerMicroApps, addGlobalUncaughtErrorHandler, start, } from "qiankun"; registerMicroApps(apps, {
// qiankun 生命周期钩子 - 微应用加载前 beforeLoad: (app: any) => {
// 加载微应用前,加载进度条 NProgress.start(); console.log("before load", app.name); return Promise.resolve(); }, // qiankun 生命周期钩子 - 微应用挂载后 afterMount: (app: any) => {
// 加载微应用前,进度条加载完成 NProgress.done(); console.log("after mount", app.name); return Promise.resolve(); }, });
十六、全局异常捕获
/ * 添加全局的未捕获异常处理器 */ addGlobalUncaughtErrorHandler((event: Event | string) => {
console.error(event); const {
message: msg } = event as any; // 加载失败时提示 if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
message.error("微应用加载失败,请检查应用是否可运行"); } });
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/201390.html原文链接:https://javaforall.net
