qiankun 主应用注册、启动子应用的代码实现


原视频地址:https://www.bilibili.com/video/BV1H34y117fe/

我也是看视频学的,在这分享一下源码,qiankun 配置我就不写了,之前分享有dome,下面直接上代码

在主应用src下新建 micro-fe文件 

micro-fe文件下新建index.js 作为主文件入口

import {
  rewriteRouter
}
from "./rewrite-router";
import {
  handleRouter
}
from "./handle-router";
let _apps = [];

//子应用数据 设置为全局变量
export const getapps = () => _apps;
//  注册子应用
export const registerMicroApps = (apps) => {
  _apps = apps;
}

export const start = (apps) => {
  //微前端的运作原理

  //1.监视路由9变化
  rewriteRouter();

  //初始化执行匹配
  handleRouter();

}

micro-fe文件下新建 handle-router.js

//处理路由变化

import {
  importHTML
} from "./import-html"
import {
  getapps
} from ".";
import {
  getPrevRoute,
  getNextRoute
} from "./rewrite-router";
export const handleRouter = async () => {
  const apps = getapps();
  //获取上一个路由应用
  const prevApp = apps.find(item => {
    return getPrevRoute().startsWith(item.activeRule);
  })

  //获取下一个路由应用
  //find  匹配第一个 startsWith 匹配以什么开头的路径
  const app = apps.find(item => getNextRoute().startsWith(item.activeRule));

  //如果有上一个路由应用,则销毁
  if (prevApp) {
    await unmount(prevApp)
  }

  //2.匹配子应用
  //2.1 获取到当前的路由路径
  //2.2 到apps里查找


  //3.加载子应用
  if (!app) {
    return;
  }
  const {
    template,
    getExternalScripts,
    execScript
  } = await importHTML(app.entry)
  const container = document.querySelector(app.container);
  container.appendChild(template);

  //配置全局环境变量(是子应用运作在qiankun状态)
  window.__POWERED_BY_QIANKUN__ = true;

  //子应用的域名赋值给 主应用的变量
  window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ = app.entry + '/';

  //通过子应用导出的模块 获取
  const appExports = await execScript();
  console.log(appExports);

  //手动加载渲染函数
  app.bootstarp = appExports.bootstarp;
  app.mount = appExports.mount
  app.unmount = appExports.unmount
  await bootstarp(app);
  await mount(app);
  // getExternalScripts().then(script=>{
  //   console.log(script);
  // })
  //请求获取子应用的资源:html、js、css
  // const html = await fetch(app.entry).then(res => res.text());
  // const container = document.querySelector(app.container);
  // container.innerHTML = html;
  //需要注意的是 客户端渲染需要通过执行 javascript 来生成内容,浏览器出去安全考虑
  //innerHTML 中的script 不会加载执行,因此需要手动加载 script
  //eval 或 new function

  //4.渲染子应用
}

async function bootstarp(app) {
  app.bootstarp && (await app.bootstarp())
}

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)
  }))
}

micro-fe文件下新建 rewrite-router.js

import {
  handleRouter
} from "./handle-router";

let prevRoute = ' ' //上一个路由
let nextRoute = window.location.pathname //下一个路由

export const getPrevRoute = () => prevRoute
export const getNextRoute = () => nextRoute

window.getNextRoute = getNextRoute
window.getPrevRoute = getPrevRoute

export const rewriteRouter = () => {

  //hash 路由  window.onhashchange

  //history 路由 有分两种模式
  //一、history.go  history.back  history.forward 使用popstate 事件 :window.onpopstate
  window.addEventListener("popstate", () => {
    //popstate 触发的时候 路由已经完成导航 

    prevRoute = nextRoute; //之前的路由
    nextRoute = window.location.pathname; //最新的路由

    handleRouter();
  })

  //pushState  replaceState 需要通过函数重写的方式进行劫持
  const rawPushState = window.history.pushState;
  window.history.pushState = (...args) => {
    //导航前记录
    prevRoute = window.location.pathname;

    rawPushState.apply(window.history, args); //改变路由历史记录

    //导航后记录
    nextRoute = window.location.pathname;

    handleRouter();
  }
  const rawReplaceState = window.history.replaceState;
  window.history.replaceState = (...args) => {
    //导航前记录
    prevRoute = window.location.pathname;

    rawReplaceState.apply(window.history, args)

    //导航后记录
    nextRoute = window.location.pathname;

    handleRouter();
  }
}

micro-fe文件下新建 import-html.js

import {
  fetchResource
} from "./fetch-resource";
export const importHTML = async (url) => {
  const html = await fetchResource(url);
  const template = document.createElement('div');
  template.innerHTML = html;

  const scripts = template.querySelectorAll("script")
  //获取所有Script 标签的代码 []
  function getExternalScripts() {
    return Promise.all(Array.from(scripts).map(script => {
      //获取到所有的script 标签
      const src = script.getAttribute('src');
      // //如果得到是外联样式js,如果没有得到则是行内样式js
      if (!src) {
        return Promise.resolve(script.innerHTML)
      } else {
        //P判断src链接有没有域名,没有则加上
        return fetchResource(src.startsWith('http') ? src : `${url}${src}`);
      }
    }))
  }

  //获取并执行 所有的Script脚本代码
  async function execScript() {
    const scripts = await getExternalScripts();
    console.log(scripts);

    //手动创建一个common js模块环境
    const module = {
      exports: {}
    };
    const exports = module.exports;

    scripts.forEach(code => {
      //eval 执行的代码可以访问外部变量
      eval(code);
    })
    //获取子应用导出的生命周期函数
    return module.exports;
    // console.log(window['app-vue2-app']);
  }
  return {
    template,
    getExternalScripts,
    execScript
  }
}

micro-fe文件下新建 fetch-resource.js

export const fetchResource = url => fetch(url).then(res => res.text());

做完这些才仅仅是把子应用渲染到主应用上,子应用还没有做css和js隔离,代码和样式会相互影响。

这里是分享下一下子应用的注册和渲染流程。

css样式隔离有shadow dom 和css选择器前面加私有前缀(css选择器前面再加选择器)

js隔离有快照沙箱和JavaScript沙箱

 

相关