chunchao
源码开源,仅仅为了让大家学习微前端的工作模式而已,实际项目中,我们有使用Paas模式,web components,git submodule等模式都可以实现微前端,当然业内肯定有独特的、优于这些模式的微前端实现推荐你先看我之前的几篇文章,这样才能更好的阅读本文
如果你有什么问题想跟我交流,可以加入我们的专业微前端交流群/技术交流群
export async function loadApp() {
const shouldMountApp = Apps.filter(shouldBeActive);
console.log(shouldMountApp, 'shouldMountApp');
fetch(shouldMountApp[0].entry)
.then(function (response) {
return response.text();
})
.then(function (text) {
const dom = document.createElement('div');
dom.innerHTML = text;
const subapp = document.querySelector('#subApp-content');
subapp && subapp.appendChild(dom);
});
}
dom
节点,渲染到基座的对应子应用节点中script
、style
标签样式隔离、沙箱隔离并不是难题,这里不着重实现,可以参考shadow dom,qiankun的proxy隔离代理window实现
fetch
去加载·
script、style`标签,然后用key-value形式缓存在一个对象中(方便缓存第二次直接获取),他们的fetch还可以用闭包传入或者使用默认的fetch,这里不做过多源码解析
subapp1
subapp1
script
标签,需要加载,根据加载方式,分为html内部的和通过script标签引入的
export async function loadApp() {
const shouldMountApp = Apps.filter(shouldBeActive);
const App = shouldMountApp.pop();
});
script src="/index.js"
,但是读取script标签的src属性,会自动+上主应用的前缀,所以要考虑下如何处理const url = window.location.protocol+"//"+window.location.host
const res = await Promise.all(paromiseArr);
console.log(res, 'res');
if (res && res.length > 0) {
res.forEach((item) => {
const script = document.createElement('script');
script.innerText = item;
subapp.appendChild(script);
});
}
为了优雅一些,我们把脚本抽离成单独function,今天由于简单点,乞丐版,为了给你们学习,所以不讲究太多,都用js写代码了,就不追求稳定和美观了
完整的loadApp函数:
export async function loadApp() {
const shouldMountApp = Apps.filter(shouldBeActive);
const App = shouldMountApp.pop();
fetch(App.entry)
.then(function (response) {
return response.text();
})
.then(async function (text) {
const dom = document.createElement('div');
dom.innerHTML = text;
const entryPath = App.entry;
const scripts = dom.querySelectorAll('script');
const subapp = document.querySelector('#subApp-content');
const paromiseArr =
scripts &&
Array.from(scripts).map((item) => {
if (item.src) {
const url = window.location.protocol + '//' + window.location.host;
return fetch(`${entryPath}/${item.src}`.replace(url, '')).then(
function (response) {
return response.text();
}
);
} else {
return Promise.resolve(item.textContent);
}
});
subapp.appendChild(dom);
const res = await Promise.all(paromiseArr);
if (res && res.length > 0) {
res.forEach((item) => {
const script = document.createElement('script');
script.innerText = item;
subapp.appendChild(script);
});
}
});
}
抽离脚本处理函数:
在loadApp函数中,插入dom后加载脚本
subapp.appendChild(dom);
handleScripts(entryPath,subapp,dom);
export async function handleScripts(entryPath,subapp,dom) {
const scripts = dom.querySelectorAll('script');
const paromiseArr =
scripts &&
Array.from(scripts).map((item) => {
if (item.src) {
const url = window.location.protocol + '//' + window.location.host;
return fetch(`${entryPath}/${item.src}`.replace(url, '')).then(
function (response) {
return response.text();
}
);
} else {
return Promise.resolve(item.textContent);
}
});
const res = await Promise.all(paromiseArr);
if (res && res.length > 0) {
res.forEach((item) => {
const script = document.createElement('script');
script.innerText = item;
subapp.appendChild(script);
});
}
}
export async function loadApp() {
const shouldMountApp = Apps.filter(shouldBeActive);
const App = shouldMountApp.pop();
fetch(App.entry)
.then(function (response) {
return response.text();
})
.then(async function (text) {
const dom = document.createElement('div');
dom.innerHTML = text;
const entryPath = App.entry;
const subapp = document.querySelector('#subApp-content');
subapp.appendChild(dom);
handleScripts(entryPath, subapp, dom);
});
}
同理,我们此时要来一个复用,获取所有的style标签,以及link标签,而且是rel="stylesheet"的,这样的我们需要用fetch拉取回来,插入到subapp container中
首先在subApp1子应用中+上style标签和样式内容
subapp1
subapp1
handleScripts(entryPath, subapp, dom);
handleStyles(entryPath, subapp, dom);
export async function handleStyles(entryPath, subapp, dom) {
const arr = [];
const styles = dom.querySelectorAll('style');
const links = Array.from(dom.querySelectorAll('link')).filter(
(item) => item.rel === 'stylesheet'
);
const realArr = arr.concat(styles,links)
const paromiseArr =
arr &&
Array.from(realArr).map((item) => {
if (item.rel) {
const url = window.location.protocol + '//' + window.location.host;
return fetch(`${entryPath}/${item.href}`.replace(url, '')).then(
function (response) {
return response.text();
}
);
} else {
return Promise.resolve(item.textContent);
}
});
const res = await Promise.all(paromiseArr);
if (res && res.length > 0) {
res.forEach((item) => {
const style = document.createElement('style');
style.innerHTML = item;
subapp.appendChild(style);
});
}
}
这里可以做个promise化,如果加载失败可以报个警告控制台,封装框架大都需要这个,否则无法debug.我这里做乞丐版,目前就不做那么正规了,设计框架原则大家不能忘记哈
问题也暴露出来了,那么现在我们在子应用中写的样式代码,污染到了基座全局,这样是不可以的,因为每个子应用应该是沙箱环境
我们今天主要是实现乞丐版,为了让大家能了解微前端如何工作的,这里也是开放了源码
https://github.com/JinJieTan/chunchao
,记得给个star
哦https://qianduan.life
,感觉对你有帮助,可以右下角点个在看
,关注一波公众号:[前端巅峰
]