一、微前端简介
微前端是一种相似于微服务的架构,它将微服务的理念利用于浏览器端,行将 Web 利用由繁多的单体利用转变为多个小型前端利用聚合为一的利用。各个前端利用能够独立运行、独立开发、独立部署。
微前端的益处
利用自治
。只须要遵循对立的接口标准或者框架,以便于系统集成到一起,相互之间是不存在依赖关系的。繁多职责
。每个前端利用能够只关注于本人所须要实现的性能。技术栈无关
。你能够应用 Angular 的同时,又能够应用 React 和 Vue。
微前端的毛病
- 利用的拆分根底依赖于基础设施的构建,一旦大量利用依赖于同一基础设施,那么保护变成了一个挑战。
- 拆分的粒度越小,便意味着架构变得复杂、保护老本变高。
- 技术栈一旦多样化,便意味着技术栈凌乱
微前端由哪些模块组成
当下微前端次要采纳的是组合式利用路由计划,该计划的外围是“主从”思维,即
包含一个基座(MainApp)利用
和若干个微(MicroApp)利用
,基座利用大多数是一个前端 SPA 我的项目,次要负责利用注册,路由映射,音讯下发等,而微利用是独立前端我的项目,这些我的项目不限于采纳 React,Vue,Angular 或者 JQuery 开发,每个微利用注册到基座利用中,由基座进行治理,然而如果脱离基座也是能够独自拜访,根本的流程如下图所示
是否要用微前端
微前端最佳的应用场景是一些 B 端的管理系统,既能兼容集成历史零碎,也能够将新的系统集成进来,并且不影响原先的交互体验
二、微前端实战
微前端现有的落地计划能够分为三类,自组织模式、基座模式以及模块加载模式
2.1 SingleSpa 实战
官网 https://zh-hans.single-spa.js…
实用场景:我的项目宏大,多个子项目整合在一个大的我的项目中。即便子项目的所用的技术栈不同,比方 vue,react, angular 有相应的 single-spa 的轮子,能够进行整合
1. 构建子利用
首先创立一个 vue 子利用,并通过 single-spa-vue
来导出必要的生命周
vue create spa-vue
npm install single-spa-vue
// main.js
import singleSpaVue from 'single-spa-vue';
const appOptions = {
el: '#vue',
router,
render: h => h(App)
}
// 在非子利用中失常挂载利用
if(!window.singleSpaNavigate){
delete appOptions.el;
new Vue(appOptions).$mount('#app');
}
const vueLifeCycle = singleSpaVue({
Vue,
appOptions
});
// 子利用必须导出以下生命周期:bootstrap、mount、unmount
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
export default vueLifeCycle;
// router.js
// 配置子路由根底门路
const router = new VueRouter({
mode: 'history',
base: '/vue', // 扭转门路配置
routes
})
2. 将子模块打包成类库
//vue.config.js
module.exports = {
configureWebpack: {
// 把属性挂载到 window 上不便父利用调用 window.singleVue.bootstrap/mount/unmount
output: {
library: 'singleVue',
libraryTarget: 'umd'
},
devServer:{port:10000}
}
}
3. 主利用搭建
<div id="nav">
<router-link to="/vue">vue 我的项目 <router-link>
<!-- 将子利用挂载到 id="vue" 标签中 -->
<div id="vue">div>
div>
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import {registerApplication,start} from 'single-spa'
Vue.config.productionTip = false
async function loadScript(url) {return new Promise((resolve,reject)=>{let script = document.createElement('script')
script.src = url
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
}
// 注册利用
registerApplication('myVueApp',
async ()=>{console.info('load')
// singlespa 问题
// 加载文件须要本人构建 script 标签 然而不晓得利用有多少个文件
// 款式不隔离
// 全局对象没有 js 沙箱的机制 比方加载不同的利用 每个利用都用同一个环境
// 先加载公共的
await loadScript('http://localhost:10000/js/chunk-vendors.js')
await loadScript('http://localhost:10000/js/app.js')
return window.singleVue // bootstrap mount unmount
},
// 用户切换到 /vue 下 咱们须要加载方才定义的子利用
location=>location.pathname.startsWith('/vue'),
)
start()
new Vue({
router,
render: h => h(App)
}).$mount('#app')
4. 动静设置子利用 publicPath
if(window.singleSpaNavigate){__webpack_public_path__ = 'http://localhost:10000/'}
2.2 qiankun 实战
文档 https://qiankun.umijs.org/zh/…
- qiankun 是一个基于 single-spa 的微前端实现库,旨在帮忙大家能更简略、无痛的构建一个生产可用微前端架构零碎。
- qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品对立接入平台,在通过一批线上利用的充沛测验及打磨后,咱们将其微前端内核抽取进去并开源,心愿能同时帮忙社区有相似需要的零碎更不便的构建本人的微前端零碎,同时也心愿通过社区的帮忙将 qiankun 打磨的更加成熟欠缺。
- 目前 qiankun 已在蚂蚁外部服务了超过 200+ 线上利用,在易用性及齐备性上,相对是值得信赖的。
1. 主利用搭建
<template>
<!-- 留神这里不要写 app 否则跟子利用的加载抵触
<div id="app">-->
<div>
<el-menu :router="true" mode="horizontal">
<!-- 基座中能够放本人的路由 -->
<el-menu-item index="/">Home</el-menu-item>
<!-- 援用其余子利用 -->
<el-menu-item index="/vue">vue 利用 </el-menu-item>
<el-menu-item index="/react">react 利用 </el-menu-item>
</el-menu>
<router-view />
<!-- 其余子利用的挂载节点 -->
<div id="vue" />
<div id="react" />
</div>
</template>
2. 注册子利用
import {registerMicroApps,start} from 'qiankun'
// 基座写法
const apps = [
{
name: 'vueApp', // 名字
// 默认会加载这个 HTML,解析外面的 js 动静执行(子利用必须反对跨域)entry: '//localhost:10000',
container: '#vue', // 容器
activeRule: '/vue', // 激活的门路 拜访 /vue 把利用挂载到 #vue 上
props: { // 传递属性给子利用接管
a: 1,
}
},
{
name: 'reactApp',
// 默认会加载这个 HTML,解析外面的 js 动静执行(子利用必须反对跨域)entry: '//localhost:20000',
container: '#react',
activeRule: '/react' // 拜访 /react 把利用挂载到 #react 上
},
]
// 注册
registerMicroApps(apps)
// 开启
start({prefetch: false // 勾销预加载})
3. 子 Vue 利用
// src/router.js
const router = new VueRouter({
mode: 'history',
// base 里主利用外面注册的保持一致
base: '/vue',
routes
})
不要遗记子利用的钩子导出。
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
let instance = null
function render() {
instance = new Vue({
router,
render: h => h(App)
}).$mount('#app') // 这里是挂载到本人的 HTML 中 基座会拿到挂载后的 HTML 将其插入进去
}
// 独立运行微利用
// https://qiankun.umijs.org/zh/faq#%E5%A6%82%E4%BD%95%E7%8B%AC%E7%AB%8B%E8%BF%90%E8%A1%8C%E5%BE%AE%E5%BA%94%E7%94%A8%EF%BC%9F
if(!window.__POWERED_BY_QIANKUN__) {render()
}
// 如果被 qiankun 应用 会动静注入门路
if(window.__POWERED_BY_QIANKUN__) {
// qiankun 将会在微利用 bootstrap 之前注入一个运行时的 publicPath 变量,你须要做的是在微利用的 entry js 的顶部增加如下代码:__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// 子利用的协定 导出供父利用调用 必须导出 promise
export async function bootstrap(props) {} // 启动能够不必写 须要导出办法
export async function mount(props) {render()
}
export async function unmount(props) {instance.$destroy()
}
4. 配置 vue.config.js
// vue.config.js
module.exports = {
devServer:{
port:10000,
headers:{'Access-Control-Allow-Origin':'*' // 容许拜访跨域}
},
configureWebpack:{
// 打 umd 包
output:{
library:'vueApp',
libraryTarget:'umd'
}
}
}
5. 子 React 利用
应用 react 作为子利用
// app.js
import logo from './logo.svg';
import './App.css';
import {BrowserRouter,Route,Link} from 'react-router-dom'
function App() {
return (
// /react 跟主利用配置保持一致
<BrowserRouter basename="/react">
<Link to="/"> 首页 </Link>
<Link to="/about"> 对于 </Link>
<Route path="/" exact render={()=>(
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
)} />
<Route path="/about" exact render={()=>(<h1>About Page</h1>)}></Route>
</BrowserRouter>
);
}
export default App;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
function render() {
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
}
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
// 独立运行
if(!window.__POWERED_BY_QIANKUN__){render()
}
// 子利用协定
export async function bootstrap() {}
export async function mount() {render()
}
export async function unmount() {ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}
重写 react 中的 webpack 配置文件 (config-overrides.js)
yarn add react-app-rewired --save-dev
批改 package.json 文件
// react-scripts 改成 react-app-rewired
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
在根目录新建配置文件
// 配置文件重写
touch config-overrides.js
// config-overrides.js
module.exports = {webpack: (config) => {
// 名字和基座配置的一样
config.output.library = 'reactApp';
config.output.libraryTarget = "umd";
config.output.publicPath = 'http://localhost:20000/'
return config
},
devServer: function (configFunction) {return function (proxy, allowedHost) {const config = configFunction(proxy, allowedHost);
// 配置跨域
config.headers = {"Access-Control-Allow-Origin": "*",};
return config;
};
},
};
配置.env 文件
根目录新建.env
PORT=30000
# socket 发送端口
WDS_SOCKET_PORT=30000
路由配置
import {BrowserRouter, Route, Link} from "react-router-dom"
const BASE_NAME = window.__POWERED_BY_QIANKUN__ ? "/react" : "";
function App() {
return (<BrowserRouter basename={BASE_NAME}><Link to="/"> 首页 Link><Link to="/about"> 对于 Link><Route path="/" exact render={() => <h1>hello homeh1>}>Route><Route path="/about" render={() => <h1>hello abouth1>}>Route>BrowserRouter>
);
}
2.3 飞冰微前端实战
官网接入指南 https://micro-frontends.ice.w…
- icestark 是一个面向大型零碎的微前端解决方案,实用于以下业务场景:
- 后盾比拟扩散,体验差异大,因为要频繁跳转导致操作效率低,心愿能对立收口的一个零碎内
- 单页面利用十分宏大,多人合作老本高,开发 / 构建工夫长,依赖降级回归老本高
- 零碎有二方 / 三方接入的需要
icestark 在保障一个零碎的操作体验根底上,实现各个微利用的独立开发和发版,主利用通过 icestark 治理微利用的注册和渲染,将整个零碎彻底解耦。
1. react 主利用编写
$ npm init ice icestark-layout @icedesign/stark-layout-scaffold
$ cd icestark-layout
$ npm install
$ npm start
// src/app.jsx 中退出
const appConfig: IAppConfig = {
...
icestark: {
type: 'framework',
Layout: FrameworkLayout,
getApps: async () => {
const apps = [
{
path: '/vue',
title: 'vue 微利用测试',
sandbox: false,
url: [
// 测试环境
// 申请子利用端口下的服务,子利用的 vue.config.js 外面 须要配置 headers 跨域申请头
"http://localhost:3001/js/chunk-vendors.js",
"http://localhost:3001/js/app.js",
],
},
{
path: '/react',
title: 'react 微利用测试',
sandbox: true,
url: [
// 测试环境
// 申请子利用端口下的服务,子利用的 webpackDevServer.config.js 外面 须要配置 headers 跨域申请头
"http://localhost:3000/static/js/bundle.js",
],
}
];
return apps;
},
appRouter: {LoadingComponent: PageLoading,},
},
};
// 侧边栏菜单
// src/layouts/menuConfig.ts 革新
const asideMenuConfig = [
{
name: 'vue 微利用测试',
icon: 'set',
path: '/vue'
},
{
name: 'React 微利用测试',
icon: 'set',
path: '/react'
},
]
2. vue 子利用接入
# 创立一个子利用
vue create vue-child
// 批改 vue.config.js
module.exports = {
devServer: {
open: true, // 设置浏览器主动关上我的项目
port: 3001, // 设置端口
// 反对跨域 不便主利用申请子利用资源
headers: {
'Access-Control-Allow-Origin' : '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
}
},
configureWebpack: {
// 打包成 lib 包 umd 格局
output: {
library: 'icestark-vue',
libraryTarget: 'umd',
},
}
}
src/main.js
革新
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import {
isInIcestark,
getMountNode,
registerAppEnter,
registerAppLeave,
setLibraryName
} from '@ice/stark-app'
let vue = createApp(App)
vue.use(store)
vue.use(router)
// 留神:`setLibraryName` 的入参须要与 webpack 工程配置的 output.library 保持一致
// 重要 不加不失效 和 vue.config.js 中配置的一样
setLibraryName('icestark-vue')
export function mount({container}) {// ![](http://img-repo.poetries.top/images/20210731130030.png)
console.log(container,'container')
vue.mount(container);
}
export function unmount() {vue.unmount();
}
if (!isInIcestark()) {vue.mount('#app')
}
router
革新
import {getBasename} from '@ice/stark-app';
const router = createRouter({
// 重要 在主利用中的基准路由
base: getBasename(),
routes
})
export default router
3. react 子利用接入
create-react-app react-child
// src/app.js
import {isInIcestark, getMountNode, registerAppEnter, registerAppLeave} from '@ice/stark-app';
export function mount(props) {ReactDOM.render(<App />, props.container);
}
export function unmount(props) {ReactDOM.unmountComponentAtNode(props.container);
}
if (!isInIcestark()) {ReactDOM.render(<App />, document.getElementById('root'));
}
if (isInIcestark()) {registerAppEnter(() => {ReactDOM.render(<App />, getMountNode());
})
registerAppLeave(() => {ReactDOM.unmountComponentAtNode(getMountNode());
})
} else {ReactDOM.render(<App />, document.getElementById('root'));
}
npm run eject
后,革新config/webpackDevServer.config.js
hot: '',
port: '',
...
// 反对跨域
headers: {
'Access-Control-Allow-Origin' : '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
},
微前端部署
微前端部署实际总结
更多干货在公众号「前端进阶之旅」分享