前言
微前端系列分为 上 / 下 两篇,本文为 上篇 次要还是理解微前端的由来、概念、作用等,以及基于已有的微前端框架进行实际,并理解微前端的外围性能所在,而在下篇 2022 你还不会微前端吗 (下) — 揭秘微前端外围原理 中次要就是通过自定义实现一个微前端框架来加深了解。
微前端是什么?
微前端 是一种相似于 微服务 的概念,因而要想更好的理解微前端,就必须先理解一下微服务。
微服务
微服务架构 是将一个宏大的业务零碎依照业务模块拆分成 若干个独立的子系统 ,每个子系统都是一个独立的利用,它是一种将利用构建成一系列按 业务畛域 划分模块的、小的自治服务的软件架构形式,提倡将 简单的单体利用 拆分成 若干个性能繁多、松偶合的服务,目标是升高开发难度、加强扩展性、便于麻利开发,及继续集成与交付流动。
与微服务绝对的另一个概念是传统的 单体式应用程序 (Monolithic application),单体式利用外部蕴含了 所有须要的服务 ,且各个服务功能模块具备 强耦合性(相互依赖),导致难以进行拆分和扩容。
简略来说,单体式应用程序 其实就是一台服务器解决了须要所有的性能, 微服务 就是将性能依照业务模块划分成了不同的独立服务,各个微服务间通过 HTTP 协定进行通信,通过注册核心观测微服务状态。
微服务 概念次要存在于后端开发,但这个概念是不是和你据说过的 微前端 很像了。
微前端
随着大前端的疾速倒退 和 SPA
的大规模利用,也带来了新的问题,而这些问题都催化出了 微前端 的概念:
- 我的项目性能一直增多、体积一直增大(
巨石利用
),导致打包工夫成正比例增长,是否能保障更好的我的项目扩大
- 前端技术更新太快,一个我的项目历经一两年兴许就须要进行我的项目降级,甚至是切换技术栈,但仍须要老我的项目的代码,是否能进行
新老版本的兼容
- 团队技术栈不一,又须要保障同一我的项目的开发,是否能保障不同团队的
独立开发
- SPA 我的项目的任何变动都需执行残缺的打包、部署,是否能保障不同内容
独立部署
微前端 是一种相似于 微服务 的架构,是一种由独立交付的 多个前端利用 组成整体的架构格调,将前端利用分解成一些更小、更简略的可能 独立开发、测试、部署 的利用,而对外体现仍是 单个内聚的产品。
微前端框架
微前端框架的外围
一个微前端框架至多要保障如下的外围性能:
-
技术栈无关
- 主框架不限度接入子利用的技术栈,微利用具备齐全自主权
-
独立开发、独立部署
- 微利用仓库独立,前后端可独立开发,部署实现后主框架主动实现同步更新
-
增量降级
- 在面对各种简单场景时,通常很难对一个曾经存在的零碎做全量的技术栈降级或重构,而微前端是一种十分好的施行渐进式重构的伎俩和策略
-
独立运行时
- 每个微利用之间状态隔离,运行时状态不共享
single-spa
single-spa
是一个将多个单页面利用聚合为一个整体利用的JavaScript
微前端框架。
外围原理
在 基座 (主) 利用 中注册所有 App
的路由,single-spa
保留各子利用的路由映射关系,充当微前端控制器 Controler,当对应的 URL
变换时,除了匹配 基座利用 自身的路由外,还会匹配 子利用 路由并加载渲染子利用。
子利用会通过如下过程:
- 下载 (
loaded
) - 初始化 (
initialized
) - 挂载 (
mounted
) - 卸载 (
unmounted
)
single-spa
还会通过 生命周期 为这些过程提供对应的 钩子函数。
qiankun
qiankun
是一个基于 single-spa
的 微前端 实现库,目标是提供更简略、无痛的构建一个生产可用微前端架构零碎。
包含在 single-spa
文档中也有举荐应用 qiankun
:
single-spa 实际
创立子利用
Vue3 子利用
为了疾速的创立利用,这里通过 vue create vue3-micro-app
来疾速创立技术栈为 Vue3
的 子利用。
以下在子利用中的解决形式可用
single-spa-vue
来简化
页面成果如下
子利用入口文件
为了子利用既能够独立运行,也能够在基座利用中运行,须要在子利用入口文件进行一些批改,具体如下:
-
将本来的初始化内容封装在自定义的
render
函数中,目标是能够在不同的环境执行初始化操作- 若以后在基座利用中进行渲染,则其页面内容对应的挂载容器须要指定为基座容器中对应的
DOM
节点 - 当
window.singleVue3
不存在时意味着是子利用独立运行,此时间接依照本来的初始化形式进行即可,即间接调用render()
函数
- 若以后在基座利用中进行渲染,则其页面内容对应的挂载容器须要指定为基座容器中对应的
- 子利用必须导出
bootstrap、mount、unmount
等生命周期函数,且其返回值类型要为fullfilled
状态的Promise
,否则后续操作不会执行 -
定义
instance
变量存储实例对象,不便在以后子利用在基座利用中被切换时能够执行真正的卸载子利用// main.ts import {createApp} from 'vue' import type {App as AppType} from 'vue' import App from './App.vue' import router from './router' let instance: AppType function render(container?: string) {instance = createApp(App) instance.use(router).mount(container || '#micro-vue-app') } // 当 window.singleVue3 不存在时,意味着是子利用独自运行 if (!window.singleVue3) {render(); } // 子利用必须导出 以下生命周期 bootstrap、mount、unmount export const bootstrap = () => {return Promise.resolve() }; export const mount = (props: any) => {render(props.container); return Promise.resolve()}; export const unmount = () => {instance.unmount(); return Promise.resolve()};
为什么要将
x.mount('#app')
换成x.mount('#micro-vue-app')
?
如果你明确 子利用 在 基座利用 中的渲染形式就不难理解了,因为以后这个子利用的挂载容器的 id="app"
而基座利用中的默认挂载容器也是 id="app"
,这显然会导致抵触,初始化渲染时会渲染基座利用自身,然而当你切换到 vue3
的子利用时,就会发现以后子利用的内容整个笼罩了基座利用的内容,因为此时子利用在进行挂挂载的时候,会把曾经渲染 基座利用 的容器再一次作为 子利用 的容器进行渲染,于是内容就会被齐全替换成子利用的内容。
基座利用被子利用替换成果如下:
路由配置
路由模式为 hash
模式,默认路由配置:
import {createRouter, createWebHashHistory, RouteRecordRaw} from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
const router = createRouter({history: createWebHashHistory(),
routes
})
export default router
打包配置
在 vue.config.js
下必须将打包后的输入格局指定为 umd
格局:
module.exports = {
configureWebpack: {
output: {
library: 'singleVue3',
libraryTarget: 'umd',
globalObject: 'window',
},
devServer: {port: 5000,},
},
}
React 子利用
相似的,这里通过 npx create-react-app react-micro-app
来疾速创立技术栈为 React
的 子利用。
以下在子利用中的解决形式可用
single-spa-react
来简化
页面成果如下
子利用入口文件
此局部核心内容和上述的 vue3
子利用统一,不在额定阐明,入口文件代码如下:
// index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'
let root = null
function render(props = {}) {
const container = document.getElementById(props.container ? props.container.slice(1) : 'root',
)
if(!container) return
root = ReactDOM.createRoot(container)
root.render(
<React.StrictMode>
<App {...props} />
</React.StrictMode>,
)
}
// 当 window.singleReact 不存在时,意味着是子利用独自运行
if (!window.singleReact) {render()
}
// 子利用必须导出 以下生命周期 bootstrap、mount、unmount
export const bootstrap = () => {return Promise.resolve()
}
export const mount = (props) => {render(props)
return Promise.resolve()}
export const unmount = () => {root.unmount()
return Promise.resolve()}
路由配置
路由模式为 hash
模式,自定义路由配置:
<Router>
<Switch>
<Route path="/" exact>
<Home />
</Route>
<Route path="/about" exact>
<About />
</Route>
</Switch>
</Router>
打包配置
对于 React
利用来讲,要么是一开始就是自定义 webpack
相干配置,要么是基于 crate-react-app
内置的 webpack
配置进行批改,因为下面是通过 crate-react-app
的形式创立的我的项目,因而能够基于其内置的配置文件进行批改:
不想通过
eject
的形式去批改配置文件,可通过react-app-rewired
进行重写
- 执行
npm run eject
将内置的webpack
配置像裸露进去,会生成一个scripts
目录 和config
目录 - 这里只须要关注
config
目录即可,因为webpack.config.js
在该目录下 -
为其中的
output
配置增加上如下两行配置项即可(需重新启动)output: { library: 'singleReact', libraryTarget: 'umd', globalObject: 'window', .... }
创立基座利用
基座利用的技术栈这里抉择
Vue2
,同样能够通过vue create vue2-main-app
的形式创立对应的基座利用:
路由配置
为了让 基座利用 的路由看起来更好看,这里抉择 history
模式(也可选 hash
模式),接着配置具体路由:
-
基座利用的路由
- 配置对应的路由门路
- 指定对应组件作为路由渲染视图,如
HomeView
组件
-
微利用的路由
- 只须要设定对应的路由门路,不须要指定对应的具体组件
import Vue from 'vue' import VueRouter, {RouteConfig} from 'vue-router' import HomeView from '../views/HomeView.vue' Vue.use(VueRouter) const routes: Array<RouteConfig> = [ { path: '/', name: 'home', component: HomeView }, { path: '/vue3-micro-app', name: 'about', }, { path: '/react-micro-app', name: 'about', } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router
子利用挂载容器
基座利用中的
<router-view />
是为了渲染基座利用本身的路由视图,而子利用是不能通过<router-view />
来渲染,因而,咱们须要在基座利用中设定一个Dom
节点专门用于渲染子利用的视图,这里就将子利用的内容挂载在<div id="micro-content"></div>
的节点中:<!-- 主内容 --> <main class="content"> <!-- 基座利用路由视图渲染 --> <router-view /> <!-- 子利用挂载容器 --> <div id="micro-content"></div> </main>
菜单配置
菜单配置理论就是指定路由的跳转,具体蕴含内容如下:
Home
菜单渲染的视图内容是 基座利用 中对应的HomeView
组件Vue3-micro-app
菜单渲染视图是名为vue3-micro-app
的 子利用-
Home
菜单渲染视图是基名为react-micro-app
的 子利用基座利用注册子利用
在 基座利用 中尚未注册 子利用 时的页面成果如下:
为了切换菜单路由时,对应的子利用可能被正确的渲染在基座利用中,须要咱们在基座利用中注册子利用:
- 通过
pnpm install single-spa -S
装置single-spa
-
通过
single-spa
中提供的registerApplication()
和start()
函数实现注册和启动,该逻辑可抽离到registerApplication.ts
中// registerApplication.ts import {registerApplication, start} from 'single-spa'; // 子利用 export const applications = [{ name: 'singleVue3', async activeWhen() {await loadScript('http://localhost:5000/js/chunk-vendors.js'); await loadScript('http://localhost:5000/js/app.js'); return window.singleVue3 }, app(location: Location) {return location.pathname.startsWith('/vue3-micro-app') }, customProps: {container: '#micro-content'} }, { name: 'singleReact', async activeWhen() {await loadScript('http://localhost:3000/static/js/main.js'); return window.singleReact }, app(location: Location) {return location.pathname.startsWith('/react-micro-app') }, customProps: {container: '#micro-content'} }] // 加载子利用 script export const loadScript = async (url: string) => {await new Promise((resolve, reject) => {const script = document.createElement('script'); script.src = url; script.onload = resolve; script.onerror = reject; document.head.appendChild(script) }); } // 注册子利用 export const registerApps = (apps: any[] = applications) => { apps.forEach(({ name, activeWhen, app, customProps = {}}) => registerApplication(name, activeWhen, app, customProps) ); start();}
- 在
main.ts
中导入并执行registerApplication()
即可
成果预览
源码地址
其中蕴含了 子利用 在基座利用中 特定地位的渲染 ,以及 子利用 本身路由的切换时的成果,能够看出子利用路由和主利用路由互不影响。
qiankun 实际
有了后面 single-spa
的根底,通过 qiankun
来实现微前端更加简略了,因为 qiankun
自身就是基于 single-spa
实现的微前端架构零碎,目标是提供更简略、简洁的形式接入。
上面咱们还是应用上述的三个我的项目来通过 qiankun
的模式来实现微前端。
配置基座利用
在 vue2-main-app
的入口文件 registerApplication.ts
中应用 qiankun
进行简略配置即可,相比于下面 single-spa
的形式来说更简略:
// registerApplication.ts
import {registerMicroApps, start} from 'qiankun';
// 默认子利用
export const applications = [
{
name: 'singleVue3', // app name registered
entry: 'http://localhost:5000',
container: '#micro-content',
activeRule: '/vue3-micro-app',
},
{
name: 'singleReact', // app name registered
entry: 'http://localhost:3000',
container: '#micro-content',
activeRule: '/react-micro-app',
},
]
// 注册子利用
export const registerApps = (apps: any[] = applications) => {registerMicroApps(applications);
start();}
配置子利用
子利用 局部该导出的生命周期还是要导出,值得注意的是生命周期中的 container
曾经是对应基座利用中的 实在 DOM
节点,而不是 CSS
选择器,因而只须要进行简略的批改即可,具体如下所示:
vue3-micro-app 子利用
// src/main.ts
import {createApp} from 'vue'
import type {App as AppType} from 'vue'
import App from './App.vue'
import router from './router'
let instance: AppType
function render(container?: string) {instance = createApp(App)
// 这里的 container 曾经是对应基座利用中的实在 DOM 节点,而不是 CSS 选择器
instance.use(router).mount(container || '#micro-vue-app')
}
// 当 window.singleVue3 不存在时,意味着是子利用独自运行
if (!window.singleVue3) {render();
}
// 子利用必须导出 以下生命周期 bootstrap、mount、unmount
export const bootstrap = () => {return Promise.resolve()
};
export const mount = (props: any) => {render(props.container);
return Promise.resolve()};
export const unmount = () => {instance.unmount();
return Promise.resolve()};
react-micro-app 子利用
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'
let root = null
function render(props = {}) {
// 这里的 container 曾经是对应基座利用中的实在 DOM 节点,而不是 CSS 选择器
const container = props.container || document.getElementById('root')
if(!container) return
root = ReactDOM.createRoot(container)
root.render(
<React.StrictMode>
<App {...props} />
</React.StrictMode>,
)
}
// 当 window.singleReact 不存在时,意味着是子利用独自运行
if (!window.singleReact) {render()
}
// 子利用必须导出 以下生命周期 bootstrap、mount、unmount
export const bootstrap = () => {return Promise.resolve()
}
export const mount = (props) => {render(props)
return Promise.resolve()}
export const unmount = () => {root.unmount()
return Promise.resolve()}
子利用配置 CORS
后面说过基座利用是须要将子利用的入口文件加载到以后利用下来执行的,这个过程第一步就是申请对应的入口文件,因为浏览器 同源策略 的限度,咱们必须要在子利用中配置以后子利用的资源是容许被跨域申请的。
子利用没有配置 CORS 产生跨域
vue3-micro-app 配置 CORS
在 vue.config.js
中配置 devServer
既可,其中的 devServer
能够配置所有合乎 webpack-dev-server
的选项:
module.exports = {
publicPath: '//localhost:5000',
configureWebpack: {
output: {
library: 'singleVue3',
libraryTarget: 'umd',
globalObject: 'window',
},
devServer: {
port: 5000,
headers: {'Access-Control-Allow-Origin': '*',},
},
},
}
react-micro-app 配置 CORS
因为之前是通过 npm run eject
的形式裸露进去和 webpack
相干的配置,在查看对应的 config\webpackDevServer.config.js
配置发现其外部曾经默认做了 CORS
配置
最初
通过以上的实际,上面简略地对微前端框架核心内容进行本人的了解:
-
技术栈无关
- 任何一个子利用不管应用什么技术栈,最终都会被编译为
JavaScript
代码,因而真正在执行时无论基座利用还是子利用都曾经是同一种语言模式
- 任何一个子利用不管应用什么技术栈,最终都会被编译为
-
独立开发、独立部署
- 子利用实质上就是广泛应用的
spa
单页面利用,因而当然能够领有独立代码仓库进行关联,可独立公布运行,也可作为子利用运行,只须要做好不同环境的兼容即可
- 子利用实质上就是广泛应用的
-
增量降级
- 子利用可能独立开发部署,天然反对本身利用的性能的独立扩大,又或者是接入新的子利用
-
独立运行时
- 保障多个子利用在基座利用中运行时,本身的状态不受其余子利用的影响
本篇文章就这里就完结了,下一篇文章再去聊聊微前端的实现原理,以及通过本人实现一个微前端的形式加深了解。
心愿本篇文章能对你有所帮忙!!!
参考
- 微前端架构的几种技术选型
- 2022 年你必须要会的微前端
- 什么叫做微服务?它和传统的我的项目之间有什么区别?
- 什么是微服务架构?