qiankunjs实现原理

9 篇文章 1 订阅
8 篇文章 0 订阅

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())
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值