原視頻地址: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沙箱