微应用

164 阅读2分钟

微应用

使用

主应用

  • 注册
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, () => ())