什么是微前端
微前端简单来说就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。
为什么要使用微前端
- 不同团队间开发同一个应用技术栈不同怎么破?
- 希望每个团队都可以独立开发,独立部署怎么破?
- 项目中还需要老的应用代码怎么破?
我们是不是可以将一个应用划分成若干个子应用,将子应用打包成一个个的lib。当路径切换时加载同的子应用。这样每个子应用都是独立的,技术栈也不用做限制了!从而解决了前端协同开发问题
ok,完事之后教大家怎么使用这个微前端qiankun。
https://qiankun.umijs.org/zh/guide //qiankun官网,里面写的很详细怎么用
那我们怎么自己来实现呢?微前端的运行原理其实就是4点
1.监视路由变化
2.匹配子应用
3.加载子应用
4.渲染子应用
其实我们用qiankun的话,需要的就是这2个方法,也就是说只要我们能实现这2个方法,就能实现跟qiankun一样的效果
import {registerMicroApps, start} from 'qiankun';
1.首先我们创建一个js文件,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 = () => {
window.addEventListener("popstate", () => {
// popstate 触发的时候,路由已经完成导航了
prevRoute = nextRoute; // 之前的
nextRoute = window.location.pathname; //最新的
handleRouter();
});
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();
};
};
2.再创建一个js文件,fetch-resource.js
这个文件主要是返回我们的页面text
export const fetchResource = url => fetch(url).then(res => res.text());
3.再创建一个js文件,import-html.js
这个文件为什么需要?
1.客户端渲染需要通过执行 js 来生成内容
2.浏览器出于安全考虑, innerHtml中的 script 不会加载执行
3.所以需要手动加载子应用的script,执行script中的代码,我们可以用eval或者new Function
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) => {
const src = script.getAttribute("src");
if (!src) {
return Promise.resolve(script.innerHTML);
} else {
return fetchResource(src.startsWith("http") ? src : `${url}${src}`);
}
})
);
}
// 获取并执行所有的 script脚本代码
async function execScripts() {
const scripts = await getExternalScripts();
//手动构造一个 CommonJS 模块环境
const module = { exports: {} };
const exports = module.exports;
scripts.forEach((code) => {
// eval执行的代码可以访问外部变量
eval(code);
});
return module.exports;
}
return {
template,
getExternalScripts,
execScripts,
};
};
4.再创建一个js文件,handle-router.js
这个文件就是要实现我们qiankun里面的生命周期事件
import { importHTML } from "./import-html";
import { getPrevRoute, getNextRoute } from "./rewrite-router";
// 处理路由变化
import { getApps } from "./index";
export const handleRouter = async () => {
const apps = getApps();
// 卸载上一个路由应用
const prevApp = apps.find((item) => {
return getPrevRoute().startsWith(item.activeRule);
});
// 获取下一个路由应用
const app = apps.find((item) => getNextRoute().startsWith(item.activeRule));
// 如果有上一个应用,则先销毁
if (prevApp) {
await unmount(prevApp);
}
if (!app) return;
// 3.加载子应用
const { template, getExternalScripts, execScripts } = await importHTML(
app.entry
);
const container = document.querySelector(app.container);
container.appendChild(template);
// 配置全局环境变量
window.__POWERED_BY_QIANKUN__ = true;
window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ = app.entry;
const appExports = await execScripts();
app.bootstrap = appExports.bootstrap;
app.mount = appExports.mount;
app.unmount = appExports.unmount;
await bootstrap(app);
await mount(app);
};
async function bootstrap(app) {
app.bootstrap && (await app.bootstrap());
}
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),
}));
}
5.再创建一个js文件,index.js
不用多说就是入口文件啦
import { handleRouter } from "./handle-router";
import { rewriteRouter } from './rewrite-router';
let _apps = [];
export const getApps = () => _apps;
export const registerMicroApps = (apps) => {
_apps = apps;
};
export const start = () => {
// 1.监视路由变化
// hash 路由: window.onhashchange
// history 路由
// history.go history.back history.forward 使用window.onpopstate事件
// pushState, replaceState 需要通过函数重写的方式进行劫持,因为popstate只能监听浏览器的前进后退
rewriteRouter();
//初始执行匹配
handleRouter();
};
完事之后我们直接引入
import {registerMicroApps, start} from './micro-fe/index';
到此为止我们就实现了跟qiankun一样的效果啦,不过有一个问题,就是样式冲突的问题,这个就不写了,qiankun里面是如何解决的呢?
start({
sandbox: {
strictStyleIsolation: true, // 使用shadow dom解决样式冲突
experimentalStyleIsolation: true // 通过添加选择器范围来解决样式冲突
}
}); // 开启应用
还有一个问题就是如果图片出不来,src目录下面加一个文件public-path.js,然后引入。
if(window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
如果还有什么不懂的,可以去我的github网站上面,有源码,把3个服务全部开启即可。
github项目地址https://github.com/lsh555/qiankun