Web Component -- 即将爆发的原生的 UI 组件化标准

news2025/1/11 2:25:26

6d61e2981ab63aa65bdf16c155933bd1.png

Web Component 概述

Web Component 是一种用于构建可复用用户界面组件的技术,开发者可以创建自定义的 HTML 标签,并将其封装为包含逻辑和样式的独立组件,从而在任何 Web 应用中重复使用。

每个 Web Component 都具有自己的 DOM 和样式隔离,避免了全局 CSS 和 JavaScript 的冲突问题。它还支持自定义事件和属性,可以与其他组件进行通信和交互。

不同于 Vue/React 等社区或厂商的组件化开发方案,Web Component 被定义在标准的 HTML 和 DOM 标准中。它由一组相关的 Web 平台 API 组成,也可以与现有的前端框架和库配合使用。

Web Component 的兼容性良好,可以在现代浏览器中直接使用,也可以通过 polyfill 兼容到旧版浏览器(IE11 理论上可以兼容,出于初步调研的考虑,本文不对兼容性作过多探讨)

同类组件化方案比较

Pros技术Cons
可以异构Micro Frontend需要主应用、对子应用有侵入、样式统一困难
模块级的多项目在运行时共享Module Federation主要依赖webpack5,既有项目改造成本未知;实现异构引用需要借助其他插件
模块级动态共享Vue :is + 动态import依赖vue技术栈
可以异构、完全解耦、对原有开发方法改造极小Web CompnentIE兼容性仅11可通过Polyfill支持

TL;DR

5fe3a27da5368c7d216be3edaf49788c.jpeg 实例:用异构系统共建 web components
https://gitee.com/tonylua/web-component-test1/tree/master

Web Component 关键特性

Custom Elements(自定义元素)

是 Web 标准中的一项功能,它允许开发者自定义新的 HTML 元素,开发者可以使用 JavaScript 和 DOM API,使新元素具有自定义的行为和功能

4.13.1.1 Creating an autonomous custom element

This section is non-normative.

For the purposes of illustrating how to create an autonomous custom element, let's define a custom element that encapsulates rendering a small icon for a country flag. Our goal is to be able to use it like so:

<flag-icon country="nl"></flag-icon>

To do this, we first declare a class for the custom element, extending HTMLElement:

class FlagIcon extends HTMLElement {
  constructor() {
    super();
    this._countryCode = null;
  }

  static observedAttributes = ["country"];

  attributeChangedCallback(name, oldValue, newValue) {
    // name will always be "country" due to observedAttributes
    this._countryCode = newValue;
    this._updateRendering();
  }
  connectedCallback() {
    this._updateRendering();
  }

  get country() {
    return this._countryCode;
  }
  set country(v) {
    this.setAttribute("country", v);
  }

  _updateRendering() {
    ...
  }
}

We then need to use this class to define the element:

customElements.define("flag-icon", FlagIcon);
  • 继承自基类 HTMLElement

  • 自定义的元素名称需符合 DOMString 标准,简单来说就是必须带短横线

  • 其中 observedAttributes 声明的属性才能被 attributeChangedCallback() 监听

  • 完整生命周期方法说明为:

class MyCustomElement extends HTMLElement {
  constructor() {
    super();
    // 在构造函数中进行初始化操作
    // 用 this.appendChild(...) 等挂载到dom中
    // 用 addEventListener() 绑定事件到 this.xxx 上
  }
  connectedCallback() {
    // 元素被插入到文档时触发,等价于 vue 的 mounted
  }
  disconnectedCallback() {
    // 元素从文档中移除时触发,等价于 vue 的 beforeDestory / destoyed
  }
  attributeChangedCallback(attributeName, oldValue, newValue) {
    // 元素的属性被添加、移除或更改时触发,等价于 vue 的 beforeUpdate / updated
  }
}

除了继承 HTMLElement,也可以继承其既有子类,并在使用是采用原生标签(被继承类) + is 语法,如:

// Create a class for the element
class WordCount extends HTMLParagraphElement {
  constructor() {
    // Always call super first in constructor
    super();

    // Constructor contents omitted for brevity
    // …
  }
}

// Define the new element
customElements.define("word-count", WordCount, { extends: "p" });
<p is="word-count"></p>

Shadow DOM

DOM 编程模型令人诟病的一个方面就是缺乏封装,不同组件之间的逻辑和样式很容易互相污染。

鉴于这个原因,Web components 的一个重要属性就是封装——可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离。其中,Shadow DOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM 附加到一个元素上

Shadow DOM 是 DOM nodes 的附属树。这种 Shadow DOM 子树可以与某宿主元素相关联,但并不作为该元素的普通子节点,而是会形成其自有的作用域;Shadow DOM 中的根及其子节点也不可见。

相比于以前为了实现封装而只能使用 <iframe> 实现的情况,Shadow DOM 无疑是一种更优雅的创建隔离 DOM 树的方法。

Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样。

outside_default.png

这里,有一些 Shadow DOM 特有的术语需要我们了解:

  • Shadow host:一个常规 DOM 节点,Shadow DOM 会被附加到这个节点上。

  • Shadow tree:Shadow DOM 内部的 DOM 树。

  • Shadow boundary:Shadow DOM 结束的地方,也是常规 DOM 开始的地方。

  • Shadow root: Shadow tree 的根节点。

你可以使用同样的方式来操作 Shadow DOM,就和操作常规 DOM 一样——例如添加子节点、设置属性,以及为节点添加自己的样式(例如通过 element.style 属性),或者为整个 Shadow DOM 添加样式(例如在 <style> 元素内添加样式)。不同的是,Shadow DOM 内部的元素始终不会影响到它外部的元素(除了 :focus-within),这为封装提供了便利。

注意,不管从哪个方面来看,Shadow DOM 都不是一个新事物——在过去的很长一段时间里,浏览器用它来封装一些元素的内部结构。以一个有着默认播放控制按钮的 <video> 元素为例。你所能看到的只是一个 <video> 标签,实际上,在它的 Shadow DOM 中,包含了一系列的按钮和其他控制器。Shadow DOM 标准允许你为你自己的元素(custom element)维护一组 Shadow DOM。

基本用法

可以使用 Element.attachShadow() 方法来将一个 shadow root 附加到任何一个元素上。它接受一个配置对象作为参数,该对象有一个 mode 属性,值可以是 open 或者 closed

let shadow = elementRef.attachShadow({ mode: "open" });
let shadow = elementRef.attachShadow({ mode: "closed" });

open 表示可以通过页面内的 JavaScript 方法来获取 Shadow DOM,例如使用 Element.shadowRoot 属性:

let myShadowDom = myCustomElem.shadowRoot;

如果你将一个 Shadow root 附加到一个 Custom element 上,并且将 mode 设置为 closed,那么就不可以从外部获取 Shadow DOM 了——myCustomElem.shadowRoot 将会返回 null。浏览器中的某些内置元素就是如此,例如<video>,包含了不可访问的 Shadow DOM。

如果你想将一个 Shadow DOM 附加到 custom element 上,可以在 custom element 的构造函数中添加如下实现(目前,这是 shadow DOM 最实用的用法):

let shadow = this.attachShadow({ mode: "open" });

将 Shadow DOM 附加到一个元素之后,就可以使用 DOM APIs 对它进行操作,就和处理常规 DOM 一样。

var para = document.createElement('p');
shadow.appendChild(para);
etc.

注意:

  • 要使用 Chrome 调试器检查 Shadow DOM,需要选中调试器的 Preferences / Elmenets 下的 show user agent shadow DOM 框*;比如对于上文提到的 <video>,在打开该调试选项后,就能在元素面板中看到 <video> 下挂载的 shadow tree

  • 一些比较旧的资料中会出现 attachShadow() 的前身 createShadowRoot(),语义基本相同;createShadowRoot()已经被废弃,它是在 Shadow DOM v0 规范中引入的。Shadow DOM 的最新版本是 v1,是 Web 标准的一部分。

HTML templates 和 slot

<template> 元素允许开发者在 HTML 中定义一个模板,其中可以包含任意的 HTML 结构、文本和变量占位符。此元素及其内容不会在 DOM 中呈现,但仍可使用 JavaScript 去引用它。

<template id="my-paragraph">
  <p>My paragraph</p>
</template>

上面的代码不会展示在你的页面中,直到你用 JavaScript 获取它的引用,然后添加到 DOM 中,如下面的代码:

let template = document.getElementById("my-paragraph");
let templateContent = template.content;
document.body.appendChild(templateContent);

模板(Template)本身就是有用的,而与 web 组件(web component)一起使用效果更好。我们定义一个 web 组件使用模板作为阴影(shadow)DOM 的内容,叫它 <my-paragraph>

customElements.define(
  "my-paragraph",
  class extends HTMLElement {
    constructor() {
      super();
      let template = document.getElementById("my-paragraph");
      let templateContent = template.content;

      const shadowRoot = this.attachShadow({ mode: "open" });
      shadowRoot.appendChild(templateContent.cloneNode(true));
    }
  },
);

使用 <slot> 则能进一步展示不同的自定义内容:

<template id="my-paragraph">
  <p><slot name="my-text">My default text</slot></p>
</template>

...

<my-paragraph>
  <ul slot="my-text">
    <li>Let's have some different text!</li>
    <li>In a list!</li>
  </ul>
</my-paragraph>

CSS Scoping(局部作用域的 CSS)

The CSS scoping module defines the CSS scoping and encapsulation mechanisms, focusing on the Shadow DOM scoping mechanism.

根据 Shadow DOM 作用域机制,CSS scoping 模块定义了 CSS 作用域和封装机制

CSS styles are either global in scope or scoped to a shadow tree. Globally scoped styles apply to all the elements in the node tree that match the selector, including custom elements in that tree, but not to the shadow trees composing each custom element. Selectors and their associated style definitions don't bleed between scopes.

CSS 样式分为全局和 shadow tree 局部两种。全局样式应用于节点树中与选择器匹配的所有元素,包括该树中的自定义元素,但不应用于组成每个自定义元素的shadow tree。选择器及其关联的样式定义也不会在作用域之间流通。

Within the CSS of a shadow tree, selectors don't select elements outside the tree, either in the global scope or in other shadow trees. Each custom element has its own shadow tree, which contains all the components that make up the custom element (but not the custom element, or "host", itself).

在 shadow tree 的 CSS 中,选择器不会影响树外部的元素 -- 无论是全局作用域还是其他 shadow tree。每个自定义元素都有自己的 shadow tree,它包含组成自定义元素的所有组件(但不包含自定义元素或“宿主”本身)。

:host 伪类

在 shadow DOM  内部,要想为“宿主” shadow host 本身添加样式,可以用 CSS 选择器 :host

:host {
  /* ... */
}

:host 选择器还有一种函数式的用法,接收一个选择器参数,该参数表示 shadow host 本身具备特定的状态或样式时才生效,如:

:host(:hover) {
  background-color: #ccc;
}

:host(.active) {
  color: red;
}

:host(.footer) { // 宿主元素包含footer样式名时
  color : red; 
}

:host-context 伪类

:host(selector) 用法类似的还有 :host-context() 伪类,但所谓 context 的语意指的是,作为其参数的选择器指向的是 shadow host 宿主元素的上下文环境,也就是其作为哪个祖先元素的后代时才生效,如:

// 当宿主是 h1 后代时
:host-context(h1) {
  font-weight: bold;
}

// 当 .dark-theme 类应用于主文档 body 时
:host-context(body.dark-theme) p {
  color: #fff;
}

::part 伪元素

用于在父页面指定 shadow DOM 内部使用了对应 part 属性元素的样式:

<html>
<head>
 <template id="template">
  My host element!
  <span part="sp">xxx</span>
 </template>
 <style>
  #host::part(sp) {
   background-color: aqua;
  }
 </style>
</head>
<body>
 <div id="host"></div>
 <script type="text/javascript">
  var template = document.querySelector('#template')
  var root = document.querySelector('#host').attachShadow({ mode: "open" });
  root.appendChild(template.content);
 </script>
</body>
</html>

::part() 在遵循 Shadow DOM 封装性的同时,提供了一个安全指定内部样式的途径。

但这不是唯一的手段,另一种“穿透”方法是通过 CSS 自定义变量

<html>
<head>
   <template id="template">
    <style>
     span {
      background-color: var(--sp-color, red);
     }
    </style>
    My host element will have a blue border!
    <span part="sp">xxx</span>
   </template>
   <style>
    #host {
     --sp-color: blue; // 生效
    }
   </style>
</head>
<body>
   <div id="host"></div>
   <script type="text/javascript">
    var template = document.querySelector('#template')
    var root = document.querySelector('#host').attachShadow({ mode: "open" });
    root.appendChild(template.content);
   </script>
</body>
</html>

::slotted 伪元素

在自定义组件内部指定该样式后,仅有 被外部成功填充的slot 才会被匹配到,使用默认值的 slot 上则不会生效。

优先级

  • 对于“宿主”元素,外部样式优先级高于内部的 :host

  • 如果要覆盖父页中设置的样式,则必须在宿主元素上内联完成

  • 外部 ::part 样式优先级高于内部定义

观察以下例子,优先级 blur > green > red:

<head>
 <template id="template">
  <style>
   :host {
    border: 1px solid red;
    padding: 10px;
    line-height: 50px;
   }
  </style>
  My host element will have a blue border!
 </template>
 <style>
  #host {
   border-color: green;
  }
 </style>
</head>
<body>
 <div id="host" style="border-color: blue;"></div>
 <script type="text/javascript">
  var template = document.querySelector('#template')
  var root = document.querySelector('#host').attachShadow({ mode: "open" });
  root.appendChild(template.content);
 </script>
</body>

Event retargeting(事件的重定向)

当 shadow DOM 中发生的事件在外部被捕获时,将会以其 host 元素作为目标。

<user-card></user-card>

<script>
customElements.define('user-card', class extends HTMLElement {
  connectedCallback() {
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `<p>
      <button>Click me</button>
    </p>`;
    this.shadowRoot.firstElementChild.onclick =
      e => alert("Inner target: " + e.target.tagName);
  }
});

document.onclick =
  e => alert("Outer target: " + e.target.tagName);
</script>

打印出:
Inner target: BUTTON
Outer target: USER-CARD

外部文档并不需要知道自定义组件的内部情况 -- 从它的角度来看,事件总是发生在自定义组件上,除非事件发生在 slot 的元素上

<user-card id="userCard">
<span slot="username">John Smith</span>
</user-card>

<script>
customElements.define('user-card', class extends HTMLElement {
  connectedCallback() {
    this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `<div>
      <b>Name:</b> <slot name="username"></slot>
    </div>`;

    this.shadowRoot.firstElementChild.onclick =
      e => alert("Inner target: " + e.target.tagName);
  }
});

userCard.onclick = e => alert(`Outer target: ${e.target.tagName}`);
</script>

打印出:
Inner target: BUTTON
Outer target: SPAN

从 Shadow DOM 内部触发事件

如果要发送自定义事件,可以使用 CustomEvent,注意要设置冒泡和 composed

this._shadowRoot.dispatchEvent(
  new CustomEvent("weather-fetched", {
    bubbles: true,
    composed: true,
    detail: json,
  })
);

HTML imports

Web Component 标准中被废弃的一个草案(有开源替代方案),用于引入自定义组件的结构和完整定义,从而可以直接在主页面 html 中引用:

<link rel="import" href="module-my-comp.html">

<my-comp />

Web Component 开发框架

除了原生开发方法,社区中大量既有/特有开发语言,都可以转译为 Web Component

Polymer

Google 推出的 Web Components 库,支持数据的单向和双向绑定,兼容性较好,跨浏览器性能也较好;在语法层面,Polymer 也最接近 Web Components 的原生语法。

import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';
import '@polymer/iron-icon/iron-icon.js'; // 一个图标库

class IconToggle extends PolymerElement {
  static get template() {
    return html`
      <style>
        :host {
          display: inline-block;
        }
        iron-icon {
          fill: var(--icon-toggle-color, rgba(0,0,0,0));
          stroke: var(--icon-toggle-outline-color, currentcolor);
        }
        :host([pressed]) iron-icon {
          fill: var(--icon-toggle-pressed-color, currentcolor);
        }
      </style>
      <!-- shadow DOM goes here -->
      <iron-icon icon="[[toggleIcon]]"></iron-icon>
    `;
  }
  static get properties () {
    return {
      toggleIcon: {
        type: String
      },
      pressed: {
        type: Boolean,
        notify: true,
        reflectToAttribute: true,
        value: false
      }
    };
  }
  constructor() {
    super();
    this.addEventListener('click', this.toggle.bind(this));
  }
  toggle() {
    this.pressed = !this.pressed;
  }
}

customElements.define('icon-toggle', IconToggle);

Lit

Google 在 2019 年宣布停止对 Polymer 的进一步开发,转向支持 Web Components 规范更好的 Lit;这也是目前社区中被推荐较多的一个

The Polymer library is in maintenance mode. For new development, we recommend Lit. -- Google

import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
  static styles = css`p { color: blue }`;

  @property()
  name = 'Somebody';

  render() {
    return html`<p>Hello, ${this.name}!</p>`;
  }
}
<simple-greeting name="World"></simple-greeting>

React

react 在 v17 版本之后,增加了对于在 React 组件中使用 web component 的支持:

If you render a tag with a dash, like, React will assume you want to render a custom HTML element. In React, rendering custom elements works differently from rendering built-in browser tags:

All custom element props are serialized to strings and are always set using attributes. Custom elements accept class rather than className, and for rather than htmlFor. If you render a built-in browser HTML element with an is attribute, it will also be treated as a custom element.

import React, { useState }  from 'react';
import './alert.js';

export default function App() {
  const [show, setShow] = useState(true);

  return (
    <div>
      <button onClick={() => setShow(!show)}>toggle alert</button>

      <x-alert hidden={show} status="success" closable oncloseChange={() => setShow(!show)}>
        This is a Web Component in React
      </x-alert>
    </div>
  );
}

而如果想将标准 react 组件包装为 web component,可以在 react 工程中直接结合 web component 原生语法、使用 React 完成节点渲染,并导出成独立组件。

比如 Github上这个例子:

import * as React from "react";
import * as ReactDom from "react-dom";
import { FetchData } from "./fetch-data";

class StandaloneComponent extends HTMLElement {
  mountPoint!: HTMLSpanElement;
  name!: string;

  connectedCallback() {
    const mountPoint = document.createElement("span");
    this.attachShadow({ mode: "open" }).appendChild(mountPoint);

    const name = this.getAttribute("name");
    if (name) {
      ReactDom.render(<FetchData name={name} />, mountPoint);
    } else {
      console.error("You must declare a name!");
    }
  }
}
export default StandaloneComponent;

window.customElements.get("standalone-component") ||
  window.customElements.define("standalone-component", StandaloneComponent);

另一种更方便的方式是依靠 react 社区中的工具,常见的如:

  • react-web-component

  • direflow

  • react-shadow-root

  • react-to-web-component

import r2wc from '@r2wc/react-to-web-component';
import Checklist from './components/checklist/Checklist';

const wcChecklist = r2wc(Checklist, { props: { items: "json" } });

customElements.define("r2w-checklist", wcChecklist);

Vue3

Polymer 是另一个由谷歌赞助的项目,事实上也是 Vue 的一个灵感来源。Vue 的组件可以粗略的类比于 Polymer 的自定义元素,并且两者具有相似的开发风格。最大的不同之处在于,Polymer 是基于最新版的 Web Components 标准之上,并且需要重量级的 polyfills 来帮助工作 (性能下降),浏览器本身并不支持这些功能。相比而言,Vue 在支持到 IE9 的情况下并不需要依赖 polyfills 来工作。

...

Vue implements a content distribution API inspired by the Web Components spec draft, using the <slot> element to serve as distribution outlets for content.

-- vue2官方文档

源自 Vue 2.x 时代对  Web Components 的关注,Vue 3 更进一步,原生支持了将 Vue 3 组件导出为 Web Components

Vue 提供了一个和定义一般 Vue 组件几乎完全一致的 defineCustomElement 方法来支持创建自定义元素。这个方法接收的参数和 defineComponent 完全相同。但它会返回一个继承自 HTMLElement 的自定义元素构造器:

<my-vue-element></my-vue-element>
import { defineCustomElement } from 'vue'

const MyVueElement = defineCustomElement({
  // 这里是同平常一样的 Vue 组件选项
  props: {},
  emits: {},
  template: `...`,

  // defineCustomElement 特有的:注入进 shadow root 的 CSS
  styles: [`/* inlined css */`]
})

// 注册自定义元素
// 注册之后,所有此页面中的 `<my-vue-element>` 标签
// 都会被升级
customElements.define('my-vue-element', MyVueElement)

// 你也可以编程式地实例化元素:
// (必须在注册之后)
document.body.appendChild(
  new MyVueElement({
    // 初始化 props(可选)
  })
)

...

官方的 SFC 工具链支持以“自定义元素模式”导入 SFC (需要 @vitejs/plugin-vue@^1.4.0vue-loader@^16.5.0)。一个以自定义元素模式加载的 SFC 将会内联其 <style> 标签为 CSS 字符串,并将其暴露为组件的 styles 选项。这会被 defineCustomElement 提取使用,并在初始化时注入到元素的 shadow root 上。

要开启这个模式,只需要将你的组件文件以 .ce.vue 结尾即可:

import { defineCustomElement } from 'vue'
import Example from './Example.ce.vue'

console.log(Example.styles) // ["/* 内联 css */"]

// 转换为自定义元素构造器
const ExampleElement = defineCustomElement(Example)

// 注册
customElements.define('my-example', ExampleElement)

在 Vue 3 中使用其他 Web Component 同样简单,根据编译环境是浏览器、vite 或是 vue cli 等,设置其 isCustomElement 配置函数为 (tag) => tag.includes('-') 后基本就能正常使用了;详见官方文档。

Vue 2

Vue 2 中并不具备 Vue 3 中 defineCustomElement 那样的方法。

webpack

对于大部分基于原生 webpack 的 Vue 2 项目,可以用开源插件 vue-custom-element 达到和 defineCustomElement 类似的效果,如:

Vue.customElement('widget-vue', MyVueComponent, {
  shadow: true,
  beforeCreateVueInstance(root) {
    const rootNode = root.el.getRootNode();

    if (rootNode instanceof ShadowRoot) {
      root.shadowRoot = rootNode;
    } else {
      root.shadowRoot = document.head;
    }
    return root;
  },
});

Vue CLI

而在由 Vue CLI 构建的 Vue 项目中,可以通过为构建命令指定 --target wc 参数,从而将一个单独的入口构建为一个 Web Components 组件:

vue-cli-service build --target wc --name my-element [entry]
  • entry 应该是一个 *.vue 文件。Vue CLI 将会把这个组件自动包裹并注册为 Web Components 组件,无需在 main.js 里自行注册

  • 在 Web Components 模式中,Vue 是外置的。这意味着包中不会有 Vue,即便你在代码中导入了 Vue。这里的包会假设在页面中已经有一个可用的全局变量 Vue

  • 该构建将会产生一个单独的 JavaScript 文件 (及其压缩后的版本) 将所有的东西都内联起来

  • 当这个脚本被引入网页时,会注册自定义组件,其使用 @vue/web-component-wrapper 包裹目标 Vue 组件,并自动代理属性、特性、事件和插槽

  • 也可以设置构建命令打包多个组件或异步组件

<script src="https://unpkg.com/vue"></script>
<script src="path/to/my-element.js"></script>

<!-- 可在普通 HTML 中或者其它任何框架中使用 -->
<my-element></my-element>

🌰实例:用异构系统共建 web components

14ccb6545c1a7b90b68c330125c16483.jpeg https://gitee.com/tonylua/web-component-test1/tree/master

总结

正如以 Flash 为代表的 RIA 技术浪潮极大地刺激了浏览器厂商,从而加速了浏览器的进步并催生了 ES5/ES6 的落地;同样,Angular/React/Vue 等前端组件化开发框架的普及,也让原生的 Web Components 标准不断发展。

Web Components 搭配的 shadow DOM 封装等实用特性,让一直困扰开发者们的样式局部化和事件隔离等问题迎刃而解。

随着现代浏览器兼容性的不断改善和各种开发框架对 Web Components  的主动拥抱,也势必会在不久的将来打破前端开发技术栈之间的壁垒,让整个社区沉淀的服务和能力迎来一次大的整合。

参考资料

  • http://w3c-html-ig-zh.github.io/webcomponents/spec-zh/shadow/

  • https://javascript.info/webcomponents-intro

  • https://www.webcomponents.org/introduction

  • https://juejin.cn/post/7072715334519619598

  • https://juejin.cn/post/7148974524795453476

  • https://juejin.cn/post/7107856163361783816

  • https://www.zhihu.com/question/321832109

  • https://juejin.cn/post/7181088227531915322

  • https://www.jitendrazaa.com/blog/salesforce/introduction-to-html-web-components/

  • https://juejin.cn/post/7168630364246638606

  • https://juejin.cn/post/6976557762377416718

  • https://cn.vuejs.org/guide/extras/web-components.html

  • https://web.dev/custom-elements-best-practices/

  • https://github.com/stcruy/building-a-reusable-vue-web-component

  • https://www.oreilly.com/library/view/modern-javascript/9781491971420/ch05.html

  • https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components

  • https://deepinout.com/css/css-questions/417_css_what_is_the_different_between_host_host_hostcontext_selectors.html

  • https://www.zhangxinxu.com/wordpress/2021/02/css-part-shadow-dom/

  • https://juejin.cn/post/6923957212075261966

  • https://web.dev/custom-elements-best-practices/

  • https://www.abeautifulsite.net/tags/web%20components/

  • https://juejin.cn/post/7010595352550047752

  • https://dev.to/nurlan_tl/tips-to-create-web-components-using-vue-3-ts-vite-3a7a

  • https://itnext.io/react-and-web-components-3e0fca98a593

  • https://www.bitovi.com/blog/how-to-create-a-web-component-with-create-react-app

df0436b6f1cbdad15023b1eb57c54069.png

1de8418891ea488e09903f957a64d5ba.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1002743.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一个注解,实现数据脱敏

前言 现在是晚上的凌晨&#xff0c;&#x1f62e;‍&#x1f4a8;哎&#xff0c;文章还没有写完&#xff0c;我要继续加班了。shigen也在开始胡思乱想了&#xff0c;蚂蚁也开源了自己的代码模型&#xff0c;似乎程序员变得更加廉价了。 行业的前途在哪里&#xff0c;我的学长告…

16. 线性代数 - 矩阵的性质

文章目录 神经网络的矩阵/向量矩阵的性质Hi,你好。我是茶桁。 根据上一节课的预告,咱们这节课要进入神经网络中,看看神经网络中的矩阵/向量。然后再来详细了解下矩阵的性质。 毕竟咱们的课程并不是普通的数学课,而是人工智能的数学基础。那为什么人工智能需要这些数学基础…

第74篇:美国APT网络攻击破坏伊朗核设施全过程复盘分析(震网病毒上篇)

Part1 前言 大家好&#xff0c;我是ABC_123。在上大学时&#xff0c;就曾听过美国NSA使用震网病毒&#xff08;Stuxnet&#xff09;攻击了物理隔离的伊朗核设施&#xff0c;病毒在传播过程中使用了多达4个windows系统的0day漏洞&#xff0c;最终导致上千台提纯浓缩铀离心机损坏…

为什么 Elasticsearch 中高基数字段上的聚合是一个坏主意以及如何优化它

Elasticsearch 是分布式搜索和分析引擎&#xff0c;是满足搜索和聚合需求的最受欢迎的选择。 Elasticsearch 提供了 2 种数据类型来存储字符串值&#xff1a; Text&#xff1a;- 在存储到倒排索引之前对这些内容进行分析&#xff0c;并针对全文搜索进行优化。 文本字段不允许…

C 风格文件输入/输出---有格式输入/输出--(std::scanf, std::fscanf, std::sscanf)

C 标准库的 C I/O 子集实现 C 风格流输入/输出操作。 <cstdio> 头文件提供通用文件支持并提供有窄和多字节字符输入/输出能力的函数&#xff0c;而 <cwchar>头文件提供有宽字符输入/输出能力的函数。 有格式输入/输出 从 stdin、文件流或缓冲区读取有格式输入 s…

电脑文件删除没有在回收站怎么恢复?可以参考这些方法

“不慎删除了辛苦搜集的资料&#xff0c;可是在回收站里却找不到它们。这可真是让人困扰。恳请大神能否伸出援手&#xff0c;帮我解决这个问题&#xff1f;” ——在使用电脑的过程中&#xff0c;我们常常会不小心删除一些重要的文件&#xff0c;而这些文件并未进入回收站&…

Spring中加密工具类DigestUtils和BCryptPasswordEncoder

spring中的加密工具类DigestUtils Spring中自带了一个加密工具类&#xff0c;所在的位置的是org.springframework.util.DigestUtils&#xff0c;在spring-core模块中。 该工具类中默认只提供了MD5加密相关的静态方法&#xff0c;同时还有一些获取其他加密算法的私有方法&#…

pdf拆分成多个文件的方法以及注意事项

PDF是一种非常流行的文件格式&#xff0c;但有时候我们需要将一个大的PDF文件拆分成多个小的文件&#xff0c;以便于管理和分享。本文将介绍一些拆分PDF文件的方法以及需要注意的事项。 AdobeAcrobat是一款专业的PDF编辑软件&#xff0c;可以轻松地拆分PDF文件。以下是使用Adob…

读取XML的几种方式

一、为什么使用XML 1、便于不同应用程序之间通信。 2、便于不同平台之间通信。 3、便于不同平台之间数据共享。 二、Dom读取 xml文件内容 <?xml version"1.0" encoding"UTF-8"?> <bookstore><book id"1"><name>冰…

Java | 线程的生命周期和安全

不爱生姜不吃醋⭐️ 如果本文有什么错误的话欢迎在评论区中指正 与其明天开始&#xff0c;不如现在行动&#xff01; &#x1f334;线程的生命周期 sleep方法会让线程睡眠&#xff0c;睡眠时间到了之后&#xff0c;立马就会执行下面的代码吗&#xff1f; 答&#xff1a;不会&am…

OSM最新电力数据(2023年9月1日数据)转换总结及与三个月前转换结果对比

sm包含种类繁多&#xff0c;我们这里是只以刚转换的电力设备为例抛砖引玉的进行说明。 首先先看一下转换结果大概368个文件&#xff0c;大约92种类型。当然其中有同一类设备的点、线、面类型&#xff01; 这种带增强的文件&#xff0c;是我在基础规则之外增加的提取规则。是为…

HarmonyOS学习路之方舟开发框架—学习ArkTS语言(状态管理 六)

AppStorage&#xff1a;应用全局的UI状态存储 AppStorage是应用全局的UI状态存储&#xff0c;是和应用的进程绑定的&#xff0c;由UI框架在应用程序启动时创建&#xff0c;为应用程序UI状态属性提供中央存储。 和LocalStorage不同的是&#xff0c;LocalStorage是页面级的&…

SG-Former实战:使用SG-Former实现图像分类任务(一)

摘要 SG-Former是一种新型的Transformer模型&#xff0c;它被提出以降低视觉Transformer的计算成本&#xff0c;同时保持其优秀的性能表现。计算成本的降低使得SG-Former能够更有效地处理大规模的特征映射&#xff0c;从而提高了模型的效率和全局感知能力。 SG-Former的核心思…

算法综合篇专题三:二分法

"寻一颗&#xff0c;未萌的渺小啊&#xff0c;随着青翠未来&#xff0c;升入辽阔云霄~" 现在你有一个"升序"数组&#xff0c;想让你在这个数组里完成查找数字n&#xff0c;在这个数组内的下标&#xff0c;你可以怎么做&#xff1f;这也许是不少友子们初遇…

内存卡中毒了格式化能解决吗?这样清除病毒更有效

内存卡被广泛应用于手机、相机、MP3等数码产品&#xff0c;并且可以存储各种媒体文件&#xff0c;如视频、图片等。然而&#xff0c;在我们使用过程中&#xff0c;您是否因内存卡格式化而导致重要数据丢失感到困扰。对于“内存卡中毒了格式化能解决吗&#xff1f;“的疑惑&…

用于非线性多载波卫星信道的多输入多输出符号速率信号数字预失真器DPD(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【LeetCode刷题篇零】一些基础算法知识和前置技能(下)

数组常用技巧 使用数组代替Map 使用另一个数组来统计每个数字出现的次数&#xff0c;数组的下标作为key, 数组的值作为value&#xff0c; 将数字作为数组的下标索引&#xff0c;数组里的值存储该数字出现的次数&#xff0c;原理有点类似桶排序中使用的计数数组。 比如这里如…

tensorboard可视化文件:events.out.tfevents.

跑了一个开源代码&#xff0c;跑完之后看到生成的文件夹里&#xff0c;出现了events.out.thevents.xxx的格式文件&#xff0c;比较好奇&#xff0c;进行了一番学习~   首先说明文件作用&#xff1a;用于tensorboard显示的可视化文件 使用方法 首先要安装tensorboard&#x…

mvn打包:依赖包和启动包分离

简述 依赖插件&#xff1a; maven-jar-plugin maven-assembly-plugin项目目录结构 assembly.xml <assembly xmlns"http://maven.apache.org/ASSEMBLY/2.1.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://…

zookeeper可视化界面zkui

获取zkui github地址&#xff1a;https://github.com/DeemOpen/zkui 可以使用客户端clone&#xff0c;我这里直接下载zip 解压 编译 如果将包下载到了linux&#xff0c;需要在linux中编译&#xff0c;我的linux没有maven&#xff0c;所以在window编译好&#xff0c;然后复制…