微应用
使用
主应用
- 注册
registerMicroApps([
{
activeRule: '/app1', // 主应用的路径为这个时渲染app1应用
name: 'app1',
entry: 'http://localhost:8080/', // 应用资源
container: 'qiankun' // app1 放置的dom容器
},
])
- start
start(opts) // 沙箱
子应用
- 根据全局变量,指定是否运行在微应用环境下
if(!window.__DEMO__) {
render() // 自身的渲染
}
const render = (props/* 主应用可以给子应用传一些props */) => {
ReactDOM.render(
<App />,
document.getElementById('app1')
)
}
- export 三个生命周期函数
export const bootstrap = async () => {
console.log('项目启动前')
}
export const mount = async (props) => {
render(props)
}
export const unmount = async (props) => {
console.log('卸载应用')
ReactDOM.unmountComponentAtNode(document.getElementById('app1'))
}
- webpack 的配置
// umd 格式
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[chunkhash:6].js',
library: 'app1',
libraryTarget: 'umd',
},
// 允许跨域
devServer: {
compress: true,
port: 8080,
open:true,
allowedHosts: 'all',
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Headers': '*',
},
}
原理
import { listenRoute, handleRoute } from './routePkg'
let _apps = []
export const getApps = () => _apps
export const registerMicroApps = apps => {
_apps = apps
window.__DEMO__ = true
}
export const start = (opts={}) => {
listenRoute() // 监听路由变化
handleRoute() // 处理路由
}
监听路由
/*
以history为例: 这个也是很多框架实现路由的原理;
window.history.pushState 往历史栈添加一条记录
window.history.replaceState 替换历史栈顶的记录
popstate 浏览器前进和回退
*/
export const listenRoute = () => {
// 劫持
const prePushState = window.history.pushState
const preReplaceState = window.history.replaceState
window.history.pushState = (...args) => {
preRoute = nowRoute
prePushState.apply(window.history, args)
nowRoute = window.location.pathname
// 处理最新的url
handleRoute()
}
window.history.replaceState = (...args) => {
preRoute = nowRoute
preReplaceState.apply(window.history, args)
nowRoute = window.location.pathname
handleRoute()
}
window.addEventListener('popstate', e => {
preRoute = nowRoute
nowRoute = window.location.pathname
handleRoute()
})
}
start过程
import { getApps } from './index'
const request = url => fetch(url).then(res => res.text())
let preRoute = ''
let nowRoute = window.location.pathname
export const handleRoute = async (url = window.location.pathname) => {
// 卸载应用
if (preRoute) {
const preApp = getApps().find(i => preRoute.startsWith(i.activeRule))
if (preApp) {
preApp.unmount && (await preApp.unmount())
}
}
// 渲染新的
const app = getApps().find(i => url.startsWith(i.activeRule))
if (app === undefined) return
// 应用资源、容器
const { entry, container } = app
const wrapper = document.createElement('div')
wrapper.innerHTML = await request(entry)
// 塞入主应用的容器中
document.getElementById(container).appendChild(wrapper)
// 找 script 标签
const scripts = wrapper.getElementsByTagName('script')
if (!scripts) return
// 请求代码
const codes = await Promise.all(Array.from(scripts).map(s => {
// 这里暂时不考虑内联脚本
const src = s.getAttribute('src')
return src ? request(entry + src) : Promise.resolve(s.innerText)
}))
// 制造 umd 环境
const module = { exports: {} }
const exports = module.exports
codes.forEach(c => {
eval(c)
})
const lifes = ['bootstrap', 'mount', 'unmount']
lifes.forEach(i => {
app[i] = module.exports[i]
})
// 渲染应用
await app.bootstrap()
await app.mount()
}
umd
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof exports === 'object')
exports["app1"] = factory();
else
root["app1"] = factory();
})(window, () => ())