主应用开发
subApps.js
// 子应用列表
let list = []
export const getList = () => list
export const setList = (data) => {
if (Array.isArray(data)) {
list = data;
return
}
list.push(data)
}
export const registerMicroApps = (apps) => {
// 注册子应用
setList(apps)
}
rewriteRouter.js
import { patchRouter } from '../util';
import { turnApp } from './routerHandler';
export const rewriteRouter = () => {
window.history.pushState = patchRouter(window.history.pushState, 'micro_push');
window.history.replaceState = patchRouter(window.history.replaceState, 'micro_replace');
// 添加路由跳转事件监听
window.addEventListener('micro_push', turnApp);
window.addEventListener('micro_replace', turnApp);
window.onpopstate = async function () {
await turnApp()
}
}
util/index.js
// 改变全局事件状态
export const patchRouter = (event, ListerName) => {
return function () {
// 创建一个自定义事件
const e = new Event(ListerName);
// 让event来代替本函数执行
event.apply(this, arguments);
// 通过dispatchEvent来触发自定义事件
window.dispatchEvent(e);
};
routerHandler.js
export const turnApp = async () => {
console.log("路由被切换了...")
}
start.js
export const start = async () => {
// 获取子应用列表
const appList = getList()
if (!appList.length) {
throw Error('子应用列表为空,请查看是否正确注册');
} else {
// 跳转到第一个子应用
const app = currentApp();
if (app) {
const { pathname, hash } = window.location
const url = pathname + hash
window.history.pushState(url, app.name, url || app.activeRule)
}
// 将当前子应用做标记
window.__CURRENT_SUB_APP__ = app.activeRule;
}
}
util/index.js
// 获取当前应用
export const currentApp = () => {
const currentRouter = window.location.pathname.match(/(\/\w+)/)[0];
return filterApp('activeRule', currentRouter);
};
export const filterApp = (key, rule) => {
const currentApp = getList().filter(app => app[key] === rule);
return currentApp.length ? currentApp[0] : false;
};
mainLifecycles.js
let mainLifecycle = {}
export const getMainLifecycle = () => mainLifecycle
export const setMainLifecycle = (data) => {
mainLifecycle = data
}
// 注册子应用
registerMicroApps(
leftNav.navList,
// 生命周期
{
beforeLoad: [
app => {
app.loading.openLoading()
// 每次改动,都将头部和底部显示出来,不需要头部和底部的页面需要子应用自己处理
headerState.changeHeader(true)
footerState.changeFooter(true)
console.log('开始加载 -- ', app.name);
},
],
mounted: [
app => {
console.log('加载完成 -- ', app.name);
setTimeout(() => {
app.loading.closeLoading()
}, 200)
},
],
destoryed: [
app => {
console.log('卸载完成 -- ', app.name);
},
],
},
{
}
);
util/index.js
// 查看当前路由是否有变化
export const isTurnChild = () => {
const { pathname, hash } = window.location
const url = pathname + hash
// 当前路由无改变。
const currentPrefix = url.match(/(\/\w+)/g)
if (
currentPrefix &&
(currentPrefix[0] === window.__CURRENT_SUB_APP__) &&
hash === window.__CURRENT_HASH__
) {
return false;
}
window.__ORIGIN_APP__ = window.__CURRENT_SUB_APP__;
const currentSubApp = window.location.pathname.match(/(\/\w+)/)
if (!currentSubApp) {
return false
}
// 当前路由以改变,修改当前路由
window.__CURRENT_SUB_APP__ = currentSubApp[0];
// 判断当前hash值是否改变
window.__CURRENT_HASH__ = hash
return true;
};
// 根据 路由 查找子应用
export const findAppByRoute = (router) => {
return filterApp('activeRule', router);
};
// 根据 name 查找子应用
export const findAppByName = (name) => {
return filterApp('name', name);
};
export const filterApp = (key, rule) => {
const currentApp = getList().filter(app => app[key] === rule);
return currentApp.length ? currentApp[0] : false;
};
routerHandler.js
import { lifecycle } from "../lifecycle/lifecycle"
import { isTurnChild } from "../util"
export const turnApp = async () => {
// 查看当前路由是否有变化
if (isTurnChild()) {
// 路由变化,同步修改子应用
await lifecycle()
}
}
fetchResources.js
// 获取页面资源
export const fetchUrl = url => fetch(url).then(async res => res.text())
const div = document.createElement('div');
div.innerHTML = await fetchUrl(url);
htmlLoader.js
import {fetchUrl} from '../util/fetchResources';
import {sandbox} from '../sandbox/sandbox';
import {findAppByName} from '../util';
const cache = {};
// 解析html
export const parseHtml = async (url, appName) => {
if (cache[appName]) {
return cache[appName];
}
const div = document.createElement('div');
let scriptsArray = [];
div.innerHTML = await fetchUrl(url);
const [scriptUrls, scripts, elements] = getResources(div, findAppByName(appName));
const fetchedScript = await Promise.all(scriptUrls.map(url => fetchUrl(url)));
scriptsArray = scripts.concat(fetchedScript);
cache[appName] = [elements, scriptsArray];
return [elements, scriptsArray];
}
// 解析 js 内容
export const getResources = (root, app) => {
const scriptUrls = [];
const scripts = [];
function deepParse(element) {
const children = element.children;
const parent = element.parentNode;
// 处理位于 link 标签中的 js 文件
if (element.nodeName.toLowerCase() === 'script') {
const src = element.getAttribute('src');
if (!src) {
// 直接在 script 标签中书写的内容
let script = element.outerHTML;
scripts.push(script);
} else {
if (src.startsWith('http')) {
scriptUrls.push(src);
} else {
// fetch 时 添加 publicPath
scriptUrls.push(`http:${app.entry}/${src}`);
}
}
if (parent) {
let comment = document.createComment('此 js 文件已被微前端替换');
// 在 dom 结构中删除此文件引用
parent.replaceChild(comment, element);
}
}
// 处理位于 link 标签中的 js 文件
if (element.nodeName.toLowerCase() === 'link') {
const href = element.getAttribute('href');
if (href.endsWith('.js')) {
if (href.startsWith('http')) {
scriptUrls.push(href);
} else {
// fetch 时 添加 publicPath
scriptUrls.push(`http:${app.entry}/${href}`);
}
}
}
for (let i = 0; i < children.length; i++) {
deepParse(children[i]);
}
}
deepParse(root);
return [scriptUrls, scripts, root.outerHTML];
}
// 加载和渲染html
export const htmlLoader = async (app) => {
const {
container: cantainerName, entry, name
} = app
let [dom, scriptsArray] = await parseHtml(entry, name);
let container = document.querySelector(cantainerName);
if (!container) {
throw Error(` ${name} 的容器不存在,请查看是否正确指定`);
}
container.innerHTML = dom;
scriptsArray.map((item) => {
sandbox(item, name);
});
}
export const performScriptForFunction = (script) => {
new Function(script).call(window, window);
}
export const performScriptForEval = (script) => {
eval(script)
}