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沙箱