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/guide
qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台,在经过一批线上应用的充分检验及打磨后,我们将其微前端内核抽取出来并开源,希望能同时帮助社区有类似需求的系统更方便的构建自己的微前端系统,同时也希望通过社区的帮助将 qiankun 打磨的更加成熟完善。
目前 qiankun 已在蚂蚁内部服务了超过 200+ 线上应用,在易用性及完备性上,绝对是值得信赖的。
1. 主应用搭建
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跟主应用配置保持一致
首页
关于
(
)} />
(
About Page
)}>
);
}
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(
,
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 (
首页Link> 关于Link> hello homeh1>}>Route> hello abouth1>}>Route>BrowserRouter>
);
}
2.3 飞冰微前端实战
官方接入指南 https://micro-frontends.ice.work/docs/guide
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( , props.container);
}
export function unmount(props) {
ReactDOM.unmountComponentAtNode(props.container);
}
if (!isInIcestark()) {
ReactDOM.render( , document.getElementById('root'));
}
if (isInIcestark()) {
registerAppEnter(() => {
ReactDOM.render( , getMountNode());
})
registerAppLeave(() => {
ReactDOM.unmountComponentAtNode(getMountNode());
})
} else {
ReactDOM.render( , 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',
},
微前端部署
微前端部署实践总结
更多干货只在公号:「前端进阶之旅」分享