MontoApp(四)- Web Components 方案
本节继续介绍微前端的另一种方案:Web Components。它是根据浏览器原生支持的组件化方案来封装的微应用。其实现方式类似于动态 script 方案,
仓库地址:tutorial/web-components-cookie 分支
1. 主应用
还是老样子,启动一个node服务:
app.listen(port.main, host);
然后配置请求微服务列表的接口:
app.post("/microapps", function (req, res) {
console.log("main cookies: ", req.cookies);
// 设置一个响应的 Cookie 数据
res.cookie("main-app", true);
// 这里可以是管理后台新增菜单后存储到数据库的数据
// 从而可以通过管理后台动态配置微应用的菜单
res.json([
{
name: "micro1",
id: "micro1",
// 自定义元素名称
customElement: "micro-app-1",
// 这里暂时以一个入口文件为示例
script: `http://${host}:${port.micro}/micro1.js`,
style: `http://${host}:${port.micro}/micro1.css`,
prefetch: true,
},
{
name: "micro2",
id: "micro2",
customElement: "micro-app-2",
script: `http://${host}:${port.micro}/micro2.js`,
style: `http://${host}:${port.micro}/micro2.css`,
prefetch: true,
},
]);
});
主应用的 html 页面与 动态 script 基本页面一致,这里只展示不同之处:
hashChangeListener() {
// Web Components 方案
// 微应用的插槽
const $slot = document.getElementById("micro-app-slot");
window.addEventListener("hashchange", () => {
this.microApps?.forEach(async (microApp) => {
// Web Components 方案
const $webcomponent = document.querySelector(
`[micro-id=${microApp.id}]`
);
if (microApp.id === window.location.hash.replace("#", "")) {
console.time(`fetch microapp ${microApp.name} static`);
// 加载 CSS 样式
microApp?.style &&
!this.hasLoadStyle(microApp) &&
(await this.loadStyle(microApp));
// 加载 Script 标签
microApp?.script &&
!this.hasLoadScript(microApp) &&
(await this.loadScript(microApp));
console.timeEnd(`fetch microapp ${microApp.name} static`);
// 动态 Script 方案
// window?.[microApp.mount]?.("#micro-app-slot");
// Web Components 方案
// 如果没有在 DOM 中添加自定义元素,则先添加处理
if (!$webcomponent) {
// Web Components 方案
// 自定义元素的标签是微应用先定义出来的,然后在服务端的接口里通过 customElement 属性进行约定
const $webcomponent = document.createElement(
microApp.customElement
);
$webcomponent.setAttribute("micro-id", microApp.id);
$slot.appendChild($webcomponent);
// 如果已经存在自定义元素,则进行显示处理
} else {
$webcomponent.style.display = "block";
}
} else {
this.removeStyle(microApp);
// 动态 Script 方案
// window?.[microApp.unmount]?.();
// Web Components 方案
// 如果已经添加了自定义元素,则隐藏自定义元素
if ($webcomponent) {
$webcomponent.style.display = "none";
}
}
});
});
}
2. 子应用改造
子应用也启动一下:
// 这里捎带手配置子应用 ajax 请求的跨域
app.use((req, res, next) => {
// 跨域请求中涉及到 Cookie 信息传递,值不能为 *,必须是具体的地址信息
res.header("Access-Control-Allow-Origin", `http://你的域名.com:${port.main}`);
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
res.header("Allow", "GET, POST, OPTIONS");
// 允许跨域请求时携带 Cookie
res.header("Access-Control-Allow-Credentials", true);
next();
});
app.use(
express.static(path.join("public", "micro"), {
etag: true,
lastModified: true,
})
);
app.post("/cors", function (req, res) {
console.log("micro cookies: ", req.cookies);
// 增加 SameSite 和 Secure 属性,从而使浏览器支持 iframe 子应用的跨站携带 Cookie
// 注意 Secure 需要 HTTPS 协议的支持
const cookieOptions = { sameSite: "none", secure: true };
// 设置一个响应的 Cookie 数据
res.cookie("micro-app", true, cookieOptions);
res.json({
hello: "true",
});
});
app.listen(port.micro, host);
子应用的入口js要改造一下,暴露出来生命周期钩子:
class MicroApp2Element extends HTMLElement {
constructor() {
super();
}
// [生命周期回调函数] 当 custom element 自定义标签首次被插入文档 DOM 时,被调用
connectedCallback() {
console.log(`[micro-app-2]: 执行 connectedCallback 生命周期回调函数`);
// 挂载应用
// 相对比动态 Script,组件内部可以自动进行 mount 操作,不需要对外提供手动调用的 mount 函数,从而防止不必要的全局属性冲突
this.mount();
}
// [生命周期回调函数] 当 custom element 从文档 DOM 中删除时,被调用
disconnectedCallback() {
console.log(
`[micro-app-2]: 执行 disconnectedCallback 生命周期回调函数`
);
// 卸载处理
this.unmount();
}
mount() {
const $micro = document.createElement("h1");
$micro.textContent = "微应用2";
// 将微应用的内容挂载到当前自定义元素下
this.appendChild($micro);
// 新增 Ajax 请求,用于请求 micro1.js 所在的服务
// 需要注意 micro1.js 动态加载在主应用 localhost:4000 下,因此请求是跨域的
window
.fetch("http://30.120.112.54:3000/cors", {
method: "post",
credentials: "include"
})
.then((res) => res.json())
.catch((err) => {
console.error(err);
});
}
}
window.customElements.define("micro-app-2", MicroApp2Element);
对比于动态Script方案,Web Components方案有以下特点:
- 不需要对外抛出全局API(挂载与卸载),Web Components 天然支持,可复用性更强。
- 更标准化,Web Components的能力后续会随着w3c标准逐步提升。
- 也是动态 script 的优点:可以非常便捷的进行移植和组件替换
当然,Web Components还是存在一些不足:
- 兼容性问题。对旧版本浏览器兼容性不好。
- 有一定上手难度,需要额外学习Web Components相关知识。
- 子应用是侵入式的