H5原生组件web Component
Web Component 是一种用于构建可复用用户界面组件的技术,开发者可以创建自定义的 HTML 标签,并将其封装为包含逻辑和样式的独立组件,从而在任何 Web 应用中重复使用。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>web Component原生组件</title>
</head>
<body>
<m-button type="primary">webComponent</m-button>
<template id="m-btn">
<button class="m-button">
<slot>Default</slot>
</button>
</template>
<template id="m-btn">
<style type="text/css">
.m-button {
width: 100%;
border: 1px solid #ebebeb;
}
</style>
<div class="m-collapse">
<slot></slot>
</div>
</template>
<script type="text/javascript">
class MButton extends HTMLElement {
constructor() {
super()
let btnTmpl = document.getElementById('m-btn') // 定义模板并获取模板
let shadow = this.attachShadow({ mode: 'open' }) // 配置 devtools 是否可查看 DOM 结构,open / close
let cBtnTmpl = btnTmpl.content.cloneNode(true) // copy 模板便于重用
cBtnTmpl.querySelector('.m-button').addEventListener('click', this.onClick)
shadow.appendChild(cBtnTmpl) // 模板挂载 Shadow DOM
}
static get observedAttributes() {
return ['type'] // 监控 type 属性是否改变
}
connectedCallback() {
// 组件首次挂载时调用
}
attributeChangedCallback(key, oldValue, newValue) {
// 组件更新时调用,key 为属性名,oldValue, newValue 为属性值
}
disconnectedCallback() {
// 组件移除时调用
}
}
</script>
</body>
</html>
Shadow DOM
Shadow DOM 是 DOM nodes 的附属树。这种 Shadow DOM 子树可以与某宿主元素相关联,但并不作为该元素的普通子节点,而是会形成其自有的作用域;Shadow DOM 中的根及其子节点也不可见。
- 不使用Shadow DOM
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Components</title>
<style>
h1 {
font-size: 20px;
color: yellow;
}
</style>
</head>
<body>
<div></div>
<hello-world></hello-world>
<h1>Hello World! 外部</h1>
<script type="module">
class HelloWorld extends HTMLElement {
constructor() {
super();
// 关闭 shadow DOM
// this.attachShadow({ mode: 'open' });
const d = document.createElement('div');
const s = document.createElement('style');
s.innerHTML = `h1 {
display: block;
padding: 10px;
background-color: #eee;
}`
d.innerHTML = `
<h1>Hello World! 自定义组件内部</h1>
`;
this.appendChild(s);
this.appendChild(d);
}
tag = 'hello-world'
say(something) {
console.log(`hello world, I want to say ${this.tag} ${something}`)
}
}
window.customElements.define('hello-world', HelloWorld);
const hw = document.querySelector('hello-world');
hw.say('good');
</script>
</body>
</html>
- 使用 Shadow DOM
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Components</title>
<style>
h1 {
font-size: 20px;
color: yellow;
} </style>
</head>
<body>
<div></div>
<hello-world></hello-world><h1>Hello World! 外部</h1>
<script type="module"> class HelloWorld extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
h1 {
font-size: 30px;
display: block;
padding: 10px;
background-color: #eee;
}
</style><h1>Hello World! 自定义组件内部</h1>
`;
}
tag = 'hello-world'
say(something) {
console.log(`hello world, I want to say ${this.tag} ${something}`)
}
}
window.customElements.define('hello-world', HelloWorld);
const hw = document.querySelector('hello-world');
hw.say('good');
</script>
</body>
</html>
HTML templates 和 slot
元素允许开发者在 HTML 中定义一个模板,其中可以包含任意的 HTML 结构、文本和变量占位符。此元素及其内容不会在 DOM 中呈现,但仍可使用 JavaScript 去引用它。
微前端
回顾微前端的历史,最早的时候我们是利用 iframe 嵌入一个网页,这就是微前端的雏形。虽然接入时方便快捷,但它也存在一系列缺点,如:
- 路由状态丢失,刷新一下,iframe 的 url 状态就丢失了
- dom 割裂严重,弹窗只能在 iframe 内部展示,无法覆盖全局
- 通信非常困难,只能通过 postmessage 传递序列化的消息
- 白屏时间太长,对于有性能要求的应用来说无法接受
微前端的特点
路由隔离、js隔离、css隔离、预加载机制、通信机制、多微应用激活
import microApp from '@micro-zoe/micro-app';
microApp.start();export function MyPage () {
return (<div>
<h1>子应用</h1>
<micro-app
name='app1' // name(必传):应用名称
url='http://localhost:3000/' // url(必传):应用地址,会被自动补全为http://localhost:3000/index.html
baseroute='/my-page' // baseroute(可选):基座应用分配给子应用的基础路由,就是上面的 `/my-page`
></micro-app>
</div> )}
js隔离(沙箱)
export class SnapShot {
proxy: Window & typeof globalThis
constructor () {
this.proxy = window
}
// 沙箱激活
active () {
// 创建一个沙箱快照
this.snapshot = new Map()
// 遍历全局环境
for (const key in window) {
this.snapshot[key] = window[key]
}
}
// 沙箱销毁
inactive () {
for (const key in window) {
if (window[key] !== this.snapshot[key]) {
// 还原操作
window[key] = this.snapshot[key]
}
} }
}
microApp 使用过程中碰到的问题
- webpack-dev-server中添加headers解决父应用引入子应用不同域名跨域问题
headers: {
'Access-Control-Allow-Origin': '*',
}
原理解析
当调用 microApp.start() 后,会注册一个名为 micro-app 的自定义 webComponent 标签。我们可以从 中拿到子应用的线上入口地址