qiankunjs实现原理
根据官网搭建一个main主应用
- 安装qiankun yarn add qiankun
- 改造main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { registerMicroApps, start } from 'qiankun'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
registerMicroApps([
{
name: 'app-vue1',
entry: '//localhost:1001',
container: '#container',
activeRule: '/app-vue1'
},
{
name: 'app-vue2',
entry: '//localhost:1002',
container: '#container',
activeRule: '/app-vue2'
}
])
start()
两个app-vue子应用
- 改造main.js
import './public-path'
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
let instance = null
function render (props = {}) {
const { container } = props
instance = new Vue({
router,
store,
render: (h) => h(App)
}).$mount(container ? container.querySelector('#app') : '#app')
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
mount({})
}
export async function bootstrap () {
console.log('[vue] vue app bootstraped')
}
export async function mount (props) {
console.log('[vue] props from main framework', props)
render(props)
}
export async function unmount () {
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
}
- public-path.js
/* eslint-disable */
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
- vue-config.js 把打包输出改为umd格式,用于匹配qiankun
'use strict'
const packageName = require('./package.json').name
module.exports = {
devServer: {
open: true, // 自动启动浏览器
host: 'localhost', // localhost
port: 1002, // 端口号
headers: {
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`
}
}
}
这样,独立运行子应用会发现,window上导出了三个方法,这三个方法是提供给main调用的,下面会讲解
路由改造
const router = new VueRouter({
mode: 'history',
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue2' : process.env.BASE_URL,
routes
})
手写qiankun
把main应用的两个方法改造为自己写的方法
// import { registerMicroApps, start } from 'qiankun'
import { registerMicroApps, start } from './micro-fe'
- 新建 micro-fe文件夹,创建index.js
- 导出两个方法registerMicroApps和start
- 存储apps,并监听路由变化
import { rewiteRouter } from './rewite-router'
let _apps = []
export const getApps = () => _apps
export const registerMicroApps = (apps) => {
_apps = apps
}
export const start = ()=>{
rewiteRouter()
}
监听路由变化rewiteRouter
- micro-fe文件夹下创建rewite-router.js
- popstate 监听路由前进后退
- pushState 监听路由切换
- replaceState监听路由重置
/* eslint-disable */
export const rewiteRouter = () => {
window.addEventListener('popstate', () => {
// console.log('popstate 前进后退')
handleRouter()
})
const rawPushState = window.history.pushState
window.history.pushState = (...args) => {
rawPushState.apply(window.history, args)
// console.log('pushState 路由变化')
}
const rawReplaceState = window.history.replaceState
window.history.replaceState = (...args) => {
rawReplaceState.apply(window.history, args)
// console.log('replaceState 路由变化')
}
}
路由变化同时,执行 handleRouter方法
- 在rewite-router.js添加handleRouter方法
- 同时记录两次路由变化prevRoute和nextRoute
/* eslint-disable */
import { handleRouter } from './handle-router'
let prevRoute = ''
let nextRoute = window.location.pathname
export const getPrevRoute = () => prevRoute
export const getNextRoute = () => nextRoute
export const rewiteRouter = () => {
// console.log('重写路由监听')
prevRoute = nextRoute
nextRoute = window.location.pathname
window.addEventListener('popstate', () => {
// console.log('popstate 前进后退')
prevRoute = nextRoute
nextRoute = window.location.pathname
handleRouter()
})
const rawPushState = window.history.pushState
window.history.pushState = (...args) => {
prevRoute = window.location.pathname
rawPushState.apply(window.history, args)
nextRoute = window.location.pathname
handleRouter()
// console.log('pushState 路由变化')
}
const rawReplaceState = window.history.replaceState
window.history.replaceState = (...args) => {
prevRoute = window.location.pathname
rawReplaceState.apply(window.history, args)
nextRoute = window.location.pathname
handleRouter()
// console.log('replaceState 路由变化')
}
}
- micro-fe文件夹下创建handle-router.js
import { getApps } from './index'
import { importHtml } from './import-html'
import { getPrevRoute, getNextRoute } from './rewite-router'
export const handleRouter = async () => {
// 匹配子应用
const apps = getApps()
// console.log(apps)
const prevApp = apps.find(item => {
return getPrevRoute().startsWith(item.activeRule)
})
const app = apps.find(item => getNextRoute().startsWith(item.activeRule))
console.log(app)
if (prevApp) {
await unmount(prevApp)
}
if (!app) return false
const container = document.querySelector(app.container)
// console.log(container)
const { template, execScropts } = await importHtml(app.entry)
container.appendChild(template)
window.__POWERED_BY_QIANKUN__ = true
window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ = app.entry + '/'
const appExport = await execScropts()
console.log(appExport)
app.bootstrap = appExport.bootstrap
app.mount = appExport.mount
app.unmount = appExport.unmount
await bootstrap(app)
await mount(app)
}
async function bootstrap (app) {
app.bootstrap && (await app.bootstrap())
}
async function mount (app) {
app.mount && (await app.mount({
container: document.querySelector(app.container)
}))
}
async function unmount (app) {
app.unmount && (await app.unmount({
container: document.querySelector(app.container)
}))
}
import-html导出模板和执行模板里面的js
- micro-fe文件夹下创建import-html.js
/* eslint-disable */
import {
fetchResource
} from './fetch-resource'
export const importHtml = async entry => {
const html = await fetchResource(entry)
const template = document.createElement('div')
template.innerHTML = html
const scripts = template.querySelectorAll('script')
function getExternalScripts() {
return Promise.all(Array.from(scripts).map(script => {
// console.log(script)
const src = script.getAttribute('src')
if (!src) {
return Promise.resolve(script.innerHTML)
} else {
console.log(`${entry}${src}`)
return fetchResource(src.startsWith('http') ? src : `${entry}${src}`)
}
}))
}
async function execScropts() {
const scripts = await getExternalScripts()
const module = {
exports: {}
}
const exports = module.exports
scripts.forEach(srcipt => {
eval(srcipt)
})
return module.exports
}
return {
template,
getExternalScripts,
execScropts
}
}
- micro-fe文件夹下创建fetch-resource.js
export const fetchResource = url => fetch(url).then(res => res.text())