我也是看视频学的,在这分享一下源码,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沙箱