原文: https://juejin.cn/post/7113503219904430111?share_token=a2d6b49c-d8ce-4448-acd3-d71bbc6e228d
作者:小白

背景

随着SPA大规模的应用,紧接着就带来一个新问题:一个规模化应用需要拆分。

一方面功能快速增加导致打包时间成比例上升,而紧急发布时要求是越短越好,这是矛盾的。另一方面当一个代码库集成了所有功能时,日常协作绝对是非常困难的。而且最近十多年,前端技术的发展是非常快的,每隔两年就是一个时代,导致同志们必须升级项目甚至于换一个框架。但如果大家想在一个规模化应用中一个版本做好这件事,基本上是不可能的。

最早的解决方案是采用iframe的方法,根据功能主要模块拆分规模化应用,子应用之间使用跳转。但这个方案最大问题是导致页面重新加载和白屏。

那有什么好的解决方案呢?微前端这样具有跨应用的解决方案在此背景下应运而生了!

微前端的概念

微前端是什么:微前端是一种类似于微服务的架构,是一种由独立交付的多个前端应用组成整体的架构风格,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的应用,而在用户看来仍然是内聚的单个产品。有一个基座应用(主应用),来管理各个子应用的加载和卸载。

f135ab0912746bd6.png

所以微前端不是指具体的库,不是指具体的框架,不是指具体的工具,而是一种理想与架构模式。

微前端的核心三大原则就是:独立运行、独立部署、独立开发

微前端的优势

采用微前端架构的好处就是,将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性。

实现微前端的几种方式

  • single-spaqiankun

  • 基于WebComponent的micro-app

  • webpack5实现的Module Federation

微前端框架的分类

Single-spa

single-spa是一个很好的微前端基础框架,而qiankun框架就是基于single-spa来实现的,在single-spa的基础上做了一层封装,也解决了single-spa的一些缺陷。

首先我们先来了解该如何使用single-spa来完成微前端的搭建。

single-spa.jpg

Single-spa实现原理

首先在基座应用中注册所有App的路由,single-spa保存各子应用的路由映射关系,充当微前端控制器Controler,。URL响应时,匹配子应用路由并加载渲染子应用。上图便是对single-spa完整的描述。

有了理论基础,接下来,我们来看看代码层面时如何使用的。

以下以Vue工程为例基座构建single-spa,在Vue工程入口文件main.js完成基座的配置。

基座配置

//main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { registerApplication, start } from 'single-spa'Vue.config.productionTip = falseconst mountApp = (url) => {return new Promise((resolve, reject) => {const script = document.createElement('script')script.src = urlscript.onload = resolvescript.onerror = reject// 通过插入script标签的方式挂载子应用const firstScript = document.getElementsByTagName('script')[0]// 挂载子应用firstScript.parentNode.insertBefore(script, firstScript)})
}const loadApp = (appRouter, appName) => {// 远程加载子应用return async () => {//手动挂载子应用await mountApp(appRouter + '/js/chunk-vendors.js')await mountApp(appRouter + '/js/app.js')// 获取子应用生命周期函数return window[appName]}
}// 子应用列表
const appList = [{// 子应用名称name: 'app1',// 挂载子应用app: loadApp('http://localhost:8083', 'app1'),// 匹配该子路由的条件activeWhen: location => location.pathname.startsWith('/app1'),// 传递给子应用的对象customProps: {}},{name: 'app2',app: loadApp('http://localhost:8082', 'app2'),activeWhen: location => location.pathname.startsWith('/app2'),customProps: {}}
]// 注册子应用
appList.map(item => {registerApplication(item)
})// 注册路由并启动基座
new Vue({router,mounted() {start()},render: h => h(App)
}).$mount('#app')

构建基座的核心是:配置子应用信息,通过registerApplication注册子应用,在基座工程挂载阶段start启动基座。

子应用配置

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import singleSpaVue from 'single-spa-vue'Vue.config.productionTip = falseconst appOptions = {el: '#microApp',router,render: h => h(App)
}// 支持应用独立运行、部署,不依赖于基座应用
// 如果不是微应用环境,即启动自身挂载的方式
if (!process.env.isMicro) {delete appOptions.elnew Vue(appOptions).$mount('#app')
}
// 基于基座应用,导出生命周期函数
const appLifecycle = singleSpaVue({Vue,appOptions
})// 抛出子应用生命周期
// 启动生命周期函数
export const bootstrap = (props)  => {console.log('app2 bootstrap')return appLifecycle.bootstrap(() => { })
}
// 挂载生命周期函数
export const mount = (props) => {console.log('app2 mount')return appLifecycle.mount(() => { })
}
// 卸载生命周期函数
export const unmount = (props) => {console.log('app2 unmount')return appLifecycle.unmount(() => { })
}

配置子应用为umd打包方式

//vue.config.js
const package = require('./package.json')
module.exports = {// 告诉子应用在这个地址加载静态资源,否则会去基座应用的域名下加载publicPath: '//localhost:8082',// 开发服务器devServer: {port: 8082},configureWebpack: {// 导出umd格式的包,在全局对象上挂载属性package.name,基座应用需要通过这个// 全局对象获取一些信息,比如子应用导出的生命周期函数output: {// library的值在所有子应用中需要唯一library: package.name,libraryTarget: 'umd'}}

配置子应用环境变量

// .env.micro
NODE_ENV=development
VUE_APP_BASE_URL=/app2
isMicro=true

子应用配置的核心是用singleSpaVue生成子路由配置后,必须要抛出其生命周期函数

用以上方式便可轻松实现一个简单的微前端应用了。

那么我们有single-spa这种微前端解决方案,为什么还需要qiankun呢?

相比于single-spaqiankun他解决了JS沙盒环境,不需要我们自己去进行处理。在single-spa的开发过程中,我们需要自己手动的去写调用子应用JS的方法(如上面的 createScript方法),而qiankun不需要,乾坤只需要你传入响应的apps的配置即可,会帮助我们去加载。

Qiankun

Qiankun的优势

  • 基于 single-spa 封装,提供了更加开箱即用的 API。

  • 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。

  • HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。

  • 样式隔离,确保微应用之间样式互相不干扰。

  • JS 沙箱,确保微应用之间 全局变量/事件 不冲突。

  • 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。

基座配置

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();

子应用配置

以 create react app 生成的 react 16 项目为例,搭配 react-router-dom 5.x。

1.在 src 目录新增 public-path.js,解决子应用挂载时,访问静态资源冲突

if (window.__POWERED_BY_QIANKUN__) {__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}

2.设置 history 模式路由的 base

<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>

3.入口文件 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'));}

4.修改 webpack 配置

安装插件 @rescripts/cli,当然也可以选择其他的插件,例如 react-app-rewired

npm i -D @rescripts/cli

根目录新增 .rescriptsrc.js

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;return config;},
};

以上对Qiankun的使用可以看出,与single-spa使用过程很相似。不同的是,Qiankun的使用过程更简便了。一些内置的操作交由给Qiankun内部实现。这是一种IOC思想的实现,我们只管面向容器化开发,其他操作交给Qiankun框架管理。

Micro-app

micro-app并没有沿袭single-spa的思路,而是借鉴了WebComponent的思想,通过CustomElement结合自定义的ShadowDom,将微前端封装成一个类WebComponent组件,从而实现微前端的组件化渲染。并且由于自定义ShadowDom的隔离特性,micro-app不需要像single-spaqiankun一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改webpack配置,是目前市面上接入微前端成本最低的方案。

WebComponent的概念

WebComponent是HTML5提供的一套自定义元素的接口,WebComponent是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的 web 应用中使用它们。以上是MDN社区对WebComponent的解释。

  • Custom elements(自定义元素): 一组 JavaScript API,允许您定义 custom elements 及其行为,然后可以在您的用户界面中按照需要使用它们。

  • Shadow DOM(影子 DOM) :一组 JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。

  • HTML templates(HTML 模板):  <template> 和 <slot> 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。

接下来用一个小例子更快来理解WebComponent的概念。

一个存在组件内交互的WebComponent

// 基于HTMLElement自定义组件元素
class CounterElement extends HTMLElement {// 在构造器中生成shadow节点constructor() {super();this.counter = 0;// 打开影子节点// 影子节点是为了隔离外部元素的影响const shadowRoot = this.attachShadow({ mode: 'open' });// 定义组件内嵌样式const styles = `#counter-increment {width: 60px;height: 30px;margin: 20px;background: none;border: 1px solid black;}`;// 定义组件HTMl结构shadowRoot.innerHTML = `<style>${styles}</style><h3>Counter</h3><slot name='counter-content'>Button</slot><span id='counter-value'>; 0 </span>;<button id='counter-increment'> + </button>`;// 获取+号按钮及数值内容this.incrementButton = this.shadowRoot.querySelector('#counter-increment');this.counterValue = this.shadowRoot.querySelector('#counter-value');// 实现点击组件内事件驱动this.incrementButton.addEventListener("click", this.decrement.bind(this));}increment() {this.counter++this.updateValue();}// 替换counter节点内容,达到更新数值的效果updateValue() {this.counterValue.innerHTML = this.counter;}
}// 在真实dom上,生成自定义组件元素
customElements.define('counter-element', CounterElement);

有了对WebComponent的理解,接下来,我们更明白了Micro-app的优势。

micro-app的优势

d879637b4bb34253.png
  • 使用简单

    我们将所有功能都封装到一个类WebComponent组件中,从而实现在基座应用中嵌入一行代码即可渲染一个微前端应用。

    同时micro-app还提供了js沙箱样式隔离元素隔离预加载数据通信静态资源补全等一系列完善的功能。

  • 零依赖

    micro-app没有任何依赖,这赋予它小巧的体积和更高的扩展性。

  • 兼容所有框架

    为了保证各个业务之间独立开发、独立部署的能力,micro-app做了诸多兼容,在任何技术框架中都可以正常运行。

基座的简易配置

基座存在预加载子应用、父子应用通信、公共文件共享等等

// index.js
import React from "react"
import ReactDOM from "react-dom"
import App from './App'
import microApp from '@micro-zoe/micro-app'const appName = 'my-app'// 预加载
microApp.preFetch([{ name: appName, url: 'xxx' }
])// 基座向子应用数据通信
microApp.setData(appName, { type: '新的数据' })
// 获取指定子应用数据
const childData = microApp.getData(appName)microApp.start({// 公共文件共享globalAssets: {js: ['js地址1', 'js地址2', ...], // js地址css: ['css地址1', 'css地址2', ...], // css地址}
})

分配一个路由给子应用

// router.js
import { BrowserRouter, Switch, Route } from 'react-router-dom'export default function AppRoute () {return (<BrowserRouter><Switch><Route path='/'><micro-app name='app1' url='http://localhost:3000/' baseroute='/'></micro-app></Route></Switch></BrowserRouter>)
}

子应用的简易配置

// index.js
import React from "react"
import ReactDOM from "react-dom"
import App from './App'
import microApp from '@micro-zoe/micro-app'const appName = 'my-app'// 子应用运行时,切换静态资源访问路径
if (window.__MICRO_APP_ENVIRONMENT__) {__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__
}// 基子应用向基座发送数据
// dispatch只接受对象作为参数
window.microApp.dispatch({ type: '子应用发送的数据' })
// 获取基座数据
const data = window.microApp.getData() // 返回基座下发的data数据//性能优化,umd模式
// 如果子应用渲染和卸载不频繁,那么使用默认模式即可,如果子应用渲染和卸载非常频繁建议使用umd模式
// 将渲染操作放入 mount 函数 -- 必填
export function mount() {ReactDOM.render(<App />, document.getElementById("root"))
}// 将卸载操作放入 unmount 函数 -- 必填
export function unmount() {ReactDOM.unmountComponentAtNode(document.getElementById("root"))
}// 微前端环境下,注册mount和unmount方法
if (window.__MICRO_APP_ENVIRONMENT__) {window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
} else {// 非微前端环境直接渲染mount()
}

设置子应用路由

import { BrowserRouter, Switch, Route } from 'react-router-dom'export default function AppRoute () {return (// 设置基础路由,子应用可以通过window.__MICRO_APP_BASE_ROUTE__获取基座下发的baseroute,// 如果没有设置baseroute属性,则此值默认为空字符串<BrowserRouter basename={window.__MICRO_APP_BASE_ROUTE__ || '/'}>...</BrowserRouter>)
}

以上便是Micro-app的用法

Module Federation

Module Federation是Webpack5提出的概念,module federation用来解决多个应用之间代码共享的问题,让我们更加优雅的实现跨应用的代码共享。

MF想做的事和微前端想解决的问题是类似的,把一个应用进行拆分成多个应用,每个应用可独立开发,独立部署,一个应用可以动态加载并运行另一个应用的代码,并实现应用之间的依赖共享。

为了实现这样的功能, MF在设计上提出了这几个核心概念。

Container

一个被 ModuleFederationPlugin 打包出来的模块被称为 Container。通俗点讲就是,如果我们的一个应用使用了 ModuleFederationPlugin 构建,那么它就成为一个 Container,它可以加载其他的 Container,可以被其他的 Container 所加载。

Host&Remote

从消费者和生产者的角度看 ContainerContainer 又可被称作 Host 或 Remote

Host:消费方,它动态加载并运行其他 Container 的代码。

Remote:提供方,它暴露属性(如组件、方法等)供 Host 使用

可以知道,这里的 Host 和 Remote 是相对的,因为 一个 Container 既可以作为 Host,也可以作为 Remote

Shared

一个 Container 可以 Shared 它的依赖(如 react、react-dom)给其他 Container 使用,也就是共享依赖。

微信图片_20220626184254.png

微信图片_20220626184305.png

以上是webpack5与之前版本的模块管理对比图

微应用配置

通过webpack5的配置达成微应用的效果

// 配置webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
new ModuleFederationPlugin({name: "appA",//出口文件filename: "remoteEntry.js",//暴露可访问的组件exposes: {"./input": "./src/input",},//或者其他模块的组件//如果把这一模块当作基座模块的话,//这里应该配置其他子应用模块的入口文件remotes: {appB: "appB@http://localhost:3002/remoteEntry.js",},//共享依赖,其他模块不需要再次下载,便可使用shared: ['react', 'react-dom'],
})

以上便是我对微应用架构的理解,以及微应用架构技术的演变过程。不难看出,这些技术的演变都朝着易用性和可拓展性的方向演进。其中技术也有其时代的局限性,不过思想和技术总是在不断进步的。这几类技术选型都有其优缺点,各有千秋,我们可以根据不同的需要选择不同的技术来构建应用。

下列是本文写作时的参考资料:

single-spa: zh-hans.single-spa.js.org/docs/gettin…

qiankun: qiankun.umijs.org/zh/guide

WebComponent: developer.mozilla.org/zh-CN/docs/…

micro-app: cangdu.org/micro-app/d…

【微前端】1404- 常用微前端架构的几种技术选型相关推荐

  1. 广州蓝景分享—目前微前端架构的几种技术选型,你了解吗?

    各位编程的小伙伴,今天广州蓝景继续跟大家分享前端技术相关文章:微前端架构的几种技术选型,你了解吗?随着SPA大规模的应用,紧接着就带来一个新问题:一个规模化应用需要拆分. 一方面功能快速增加导致打包时 ...

  2. 聊一聊关于微前端架构的几种技术选型

    背景 随着SPA大规模的应用,紧接着就带来一个新问题:一个规模化应用需要拆分. 一方面功能快速增加导致打包时间成比例上升,而紧急发布时要求是越短越好,这是矛盾的.另一方面当一个代码库集成了所有功能时, ...

  3. 聊一聊关于微前端架构的几种技术选型(转载,侵权必删)

    背景 随着SPA大规模的应用,紧接着就带来一个新问题:一个规模化应用需要拆分. 一方面功能快速增加导致打包时间成比例上升,而紧急发布时要求是越短越好,这是矛盾的.另一方面当一个代码库集成了所有功能时, ...

  4. 微服务架构下该如何技术选型?

    点击上方"服务端思维",选择"设为星标" 回复"669"获取独家整理的精选资料集 回复"加群"加入全国服务端高端社群「后 ...

  5. 微服务架构下该如何技术选型呢?

    点击上方"程序猿技术大咖",关注并选择"设为星标" 回复"加群"获取入群讨论资格! 一.前言 为了实现基于微服务开发的产品,或者说为了将单体 ...

  6. 你真的了解传统IT的集中式三层架构及主流开发技术选型?!

    [引言] 最近在看一本书<未来架构 从服务化到云原生>,关于技术架构这部分,结合书中内容,有一些认知和感悟. 互联网的技术架构正在经历由集中式->分布式->云平台的发展历程中. ...

  7. 多租户系统技术实现mysql_SaaS “可配置”和“多租户”架构的几种技术实现方式...

    1.数据存储方式的选择 多租户(Multi-Tenant ),即多个租户共用一个实例,租户的数据既有隔离又有共享,说到底是要解决数据存储的问题. 常用的数据存储方式有三种. 方案一:独立数据库 一个T ...

  8. 前端开发入门:前端构建工具百度FIS

    FIS是什么? FIS全称为Front-end Integrated Solution,即前端集成解决方案.前端团队发展大多会经历规范设计.技术选型.系统拆分.性能优化等阶段,其中的技术需求可以总结为 ...

  9. 大数据平台架构技术选型与场景运用

    内容来源:2017年5月6日,大眼科技CTO张逸在"魅族技术开放日第八期--数据洞察"进行<大数据平台架构技术选型与场景运用>演讲分享.视频地址:https://mp. ...

最新文章

  1. linux的网络命令整理 更新中
  2. phpmyadmin 无法载入 mysql 扩展_phpMyAdmin“无法载入mysql扩展,请检查PHP配置”解决方法...
  3. powershell.exe直接运行命令
  4. Javascript模块化编程系列一: 模块化的驱动
  5. 251f与ips屏显示器对比_8百左右预算,2020年PS平面设计/摄影后期显示器推荐/选购指南(全高清+高色域屏)...
  6. mysql oracle优缺点_oracle 的优缺点
  7. 【长篇连载】桌面管理演义 尾声
  8. 微信分享#后面被截断,导致安卓分享失效
  9. paip.基于HTML gui界面的javascript JS实现SLEEP。。
  10. leg引擎适合什么系统的服务器,第一讲 LEG引擎服务端更新BLUE引擎
  11. 【C++】多态/虚函数/虚表指针,虚表
  12. 布线可视化管理12芯OM3室内多模光缆
  13. 微信微粒贷开通什么条件?微粒贷开通方法及流程
  14. CSAPP 存储器山数据的测量以及绘制,Cache lab part A:Cache simulator
  15. 企业如何利用BaaS平台快速部署区块链应用落地
  16. 在WIN7、WIN10操作系统用WebDAV映射网络驱动器需要的操作
  17. HTML、纯JS实现语音播放,实现定时刻播放
  18. html轮播鼠标悬停效果,jQuery图片轮播加悬停效果
  19. ##如何通过写博客赚钱
  20. 硬件大厂技术一面题目

热门文章

  1. 3步!PDF Converter Master for Mac6.2中文激活版满足您一切pdf转换需求!
  2. java局域网组建与维护题_局域网组建与维护习题(有答案).doc
  3. vue网页端Chrome浏览器打开麦克风和摄像头
  4. Office2016零售版(Retail)转批量授权(VOL)方法
  5. RK3588去掉锁屏设置永不灭屏
  6. 什么是Cookie?Cookie的特征都有哪些?Cookie如何存储与获取?
  7. 【Vue】template 模板用法
  8. linux动态调频响应时间,CPU动态调频(linux 3.14.0)一
  9. 怎样的目标管理能真正实现目标?做到这3点就对了
  10. 什么是分布式数据库?