第三方 Web Components (Beta) — LWC 外部组件集成指南

完整指南:在 LWC 中使用第三方 Web Components。涵盖三种导入方式(静态资源/LWC 模块/直接注册)、lwc:external 指令、time-elements 和 popup-info 完整示例、已知问题(ESM/npm/ID引用/Experience Builder/customized built-in)与使用指南、自定义元素全生命周期(constructor/connected/disconnected/observedAttributes/attributeChangedCallback/adoptedCallback)、Shadow Root 附加(open vs closed 模式)、计数器/属性回调/随机方块三大实战示例、数据传递(attribute vs property + lwc:spread)、插槽、事件处理(大小写限制 + addEventListener)、Shadow DOM 与 Light DOM 样式方案、移动就绪组件。...

📅 2026/7/19 ✍️ ponybai 🏷️ lwc, salesforce, web-components

第三方 Web Components(Beta)

第三方 Web Components
Beta 服务——受 Beta Services Terms 约束。必须先启用 Lightning Web Security(LWS)——Lightning Locker 不支持自定义元素。

使用第三方 Web Components 可以节省你用 LWC 重建相同组件的时间。第三方 Web Components 在 LWC 模板中作为原生 Web Components渲染。虽然可以用 iframe 或 lwc:dom="manual",但推荐使用 lwc:external 指令渲染。

Salesforce 不提供对第三方 Web Components 的支持。第三方组件可来自各种 JavaScript 框架或纯 Vanilla JS。MDN web components 和 webcomponents.org 提供了可用的示例。如果是第三方 JavaScript (非 Web Components),参见第三方 JavaScript 库章节。

第三方 Web Components —— 概述与 lwc:external

lwc:external 概述

使用第三方组件前,先检查 AppExchange 的 LWC 应用/组件和基础组件是否有现成方案。

三种导入方式

  1. 上传为静态资源——用 loadScript 加载。单文件最大 5 MB,组织上限 250 MB
  2. 添加为 LWC 模块——放在 lwc 文件夹,配 .js-meta.xml。JS 文件最大 1 MB
  3. 直接定义和注册——在 JS 中用 customElements.define()

lwc:external 指令

lwc:external 告诉 LWC 框架:此元素是外部自定义元素,不由 LWC 引擎管理。添加此指令的第三方组件渲染为原生 Web Components,可以正常使用属性和事件。

示例:上传 Web Component 为静态资源

静态资源示例

此示例使用 time-elements 库(提供 local-time、relative-time、time-ago、time-until 组件),功能类似 lightning-relative-date-time 基础组件。

<!-- externalExample.html -->
<p>Relative time: <relative-time datetime={date} lwc:external></relative-time></p>

// externalExample.js
import { loadScript } from "lightning/platformResourceLoader";
import timeElements from "@salesforce/resourceUrl/timeElements";

isCmpInitialized = false;
async renderedCallback() {
  if (this.isCmpInitialized) return;
  this.isCmpInitialized = true;
  try {
    await loadScript(this, timeElements);  // 加载 IIFE 格式的库
    this.initializeComponent();
  } catch (error) { this.error = error; }
}
initializeComponent() { this.date = new Date(); }

关键点:① 在 renderedCallback 中加载(确保 DOM 就绪)② isCmpInitialized 守卫防止重复加载 ③ loadScript 目前不支持 ESM——必须用 IIFE 或 UMD 等旧格式。④ 第三方组件标签上必须加 lwc:external

示例:添加 Web Component 为 LWC 模块

LWC 模块示例

此示例使用 MDN 的 popup-info 组件(功能类似 lightning-helptext)。创建完整 LWC 组件,在其中定义和注册自定义元素:

<!-- popupInfo.html -->
<popup-info lwc:external img={logo}
    data-text="Here's some popup text for your logo'"></popup-info>

// popupInfo.js
import trailheadLogo from "@salesforce/resourceUrl/trailhead_logo";

class PopUpInfo extends HTMLElement {
  constructor() {
    super();
    this.shadow = this.attachShadow({ mode: "closed" });  // 创建 shadow root
  }
  connectedCallback() {
    // 用 createElement 构建 DOM:wrapper > icon(img) + info(span)
    const wrapper = document.createElement("span");
    const icon = document.createElement("span");  icon.setAttribute("tabindex", 0);
    const info = document.createElement("span");  info.textContent = this.getAttribute("data-text");
    const img = document.createElement("img");
    img.src = this.hasAttribute("img") ? this.getAttribute("img") : trailheadLogo;
    // 注入 scoped CSS styles
    const style = document.createElement("style");
    style.textContent = `.wrapper { position: relative; } .info { ... }`;
    this.shadow.appendChild(style);
    // 组装 DOM 树
  }
}
customElements.define("popup-info", PopUpInfo);  // 注册(名称必须含连字符)
export default class PopupInfo extends LightningElement { logo = trailheadLogo; }

虽然不推荐用 JS 操作 DOM,但第三方 Web Components 通常使用 createElementappendChild 等 Web API 构建内部结构——这是自定义元素的标准做法。更好的替代方案:用模板指令(如 lwc:if)条件显示组件。

已知问题与使用指南

已知问题

已知问题

  • loadScript 不支持 ESM:<script type="module"> 不兼容——必须用 IIFE 或 UMD 预打包格式
  • 不支持 npm 依赖/编译打包:LWC 目前不支持从 npm 导入
  • 不支持 document.getElementById:Shadow DOM 中无法访问全局 HTML 文档——改用 template refs
  • Experience Builder 站点不支持:启用 LWS 时暂不支持
  • LWS 不支持 customized built-in elements:使用 extends 选项的自定义元素不被支持(WebKit/Safari 也不支持)

使用指南

  • 数据传递:默认设为属性(attributes),仅当属性存在时才设为属性(properties)
  • 插槽:Synthetic shadow 不支持第三方组件的 slotting
  • 嵌套 template:LWC 模板仅支持根级 <template>——需要嵌套时用 document.createElement('template')
  • 事件名:声明式绑定仅支持小写事件名——非小写/含连字符的事件名用 addEventListener()
  • 属性观察:渲染后用 observedAttributes() + attributeChangedCallback() 观察属性变化
  • 不支持动态组件创建:lwc:external 不兼容 lwc:component
  • 注册时机:注册前或注册后渲染——组件升级行为有差异(先注册→收到动态值;后注册→收不到)

使用自定义元素 —— 定义与注册

定义与注册

自定义元素必须:① 定义一个继承 HTMLElement 的类;② 用 customElements.define(name, constructor) 注册——名称必须包含连字符且页面内唯一。使用 <template> 标签是可选的,可用 document.createElement("template") 创建。

完整的自定义元素结构

class MyCustomElement extends HTMLElement {
  constructor() { super(); /* 初始状态、默认值、事件监听器、shadow root */ }
  connectedCallback() { /* 元素添加到文档 */ }
  disconnectedCallback() { /* 元素从文档移除 */ }
  static get observedAttributes() { return [/* 要监控变化的属性名数组 */]; }
  attributeChangedCallback(name, oldValue, newValue) { /* 被观察的属性被修改 */ }
  adoptedCallback() { /* 元素移动到新文档 */ }
}
customElements.define("my-custom-element", MyCustomElement);

自定义元素可以在LWC 类定义之前组件包内的单独 JS 文件中定义和注册——以保持代码分离和可维护。

附加 Shadow Root 到自定义元素

附加 Shadow Root

自定义元素通过 attachShadow({ mode: 'open' | 'closed' }) 附加 shadow root:

// Open 模式 —— 可从外部访问 shadowRoot
this.attachShadow({ mode: "open" });
// 渲染:
//         #shadow-root (open) | 
// Closed 模式 —— shadowRoot 返回 null,阻止外部 JS 访问
this._shadow = this.attachShadow({ mode: 'closed' });  // 保存引用到私有变量
this._shadow.innerHTML = "
"; // 通过保存的引用操作 // 仍可在内部通过保存的引用查询和更新: this._shadow.querySelector('div').textContent = "Hello closed mode";
注意:Closed 模式不能完全阻止访问——只是让 shadowRoot 属性返回 null。组件内部仍可通过保存的引用操作 shadow DOM。创建 closed 模式元素时必须保存引用(如 this._shadow)。

自定义元素 —— 导入与在 LWC 中使用

导入与使用

在组件包内导入:将自定义元素定义在单独 JS 文件中,在 LWC JS 中导入:

// myCustomElement.js
class MyCustomElement extends HTMLElement {
  constructor() { super(); this.attachShadow({ mode: "closed" }).innerHTML = "
Hello
"; } } customElements.define("my-custom-element", MyCustomElement); // myComponent.js import "./myCustomElement"; // 副作用导入——执行定义和注册 export default class MyComponent extends LightningElement {} <!-- myComponent.html --> <my-custom-element lwc:external></my-custom-element>

组件包结构:myComponent/ 目录含 myComponent.htmlmyComponent.jsmyComponent.js-meta.xmlmyCustomElement.js

示例:带生命周期回调的计数器按钮

计数器按钮

完整示例——每次点击按钮标签计数递增,展示自定义元素的完整生命周期:

class MyCounter extends HTMLElement {
  count = 0;
  handler = () => { this.count++; this._shadow.firstElementChild.innerHTML = this.count; };
  constructor() {
    super();
    this._shadow = this.attachShadow({ mode: "closed" });
    this._shadow.innerHTML = `Button:`;
  }
  connectedCallback() {
    this._shadow.firstElementChild.addEventListener("click", this.handler);
  }
  disconnectedCallback() {
    this._shadow.firstElementChild.removeEventListener("click", this.handler);  // 清理!
  }
}
customElements.define("my-counter", MyCounter);

渲染输出:<my-counter>#shadow-root (closed) | "Button:" <button>0</button>。每次点击 → count++ → 按钮标签更新。注意 disconnectedCallback 中的事件清理——防止内存泄漏。

示例:带属性变化回调的计数器

属性变化回调

observedAttributes + attributeChangedCallback 监控属性变化:

static get observedAttributes() { return ["count"]; }

get count() { return this.getAttribute("count"); }
set count(val) { this.setAttribute("count", val); }

attributeChangedCallback(prop, oldVal, newValue) {
  if (prop === "count") { this.renderButton(); /* 重新绑定事件 */ }
}

renderButton() { this._shadow.innerHTML = ``; }

父组件通过属性传初始值:<my-counter count="0" lwc:external></my-counter>。当父组件更新 count 属性 → attributeChangedCallback 触发 → 按钮重新渲染。

示例:带条件显示的随机方块

随机方块

此示例源自 MDN 生命周期回调示例,定义在单独 customSquare.js 文件中。使用 lwc:if 而非 appendChild/removeChild 条件显示——推荐做法,因为这也会正确触发自定义元素的 connected/disconnected 回调:

// customSquare.js —— 导出 random 工具函数供 LWC 使用
class Square extends HTMLElement {
  static get observedAttributes() { return ["color", "size"]; }
  updateStyle() {
    this._shadow.querySelector("style").textContent = `
      div { width: ${this.getAttribute("size")}px; height: ...; background-color: ${this.getAttribute("color")}; }`;
  }
}
customElements.define("custom-square", Square);
export { random };  // 也可导出工具函数

// myCustomSquare.js —— LWC 组件控制显示/更新/移除
handleAdd() { this.showSquare = true; }
handleUpdate() { this.size = random(50, 200); this.color = `rgb(...)`; }
handleRemove() { this.showSquare = false; }

<template lwc:if={showSquare}>
  <custom-square lwc:external size={size} color={color}></custom-square>
</template>

向自定义元素传递数据 —— 属性与 Property

传递数据

向第三方组件传递数据时,LWC 默认设为属性(attributes),仅当对应的 property 存在时才设为属性(properties)

通过 Attribute 传递

渲染后属性变更默认被忽略。要观察属性变化以重新渲染:

static observedAttributes = ["myAttr"];
attributeChangedCallback(attr, oldVal, newVal) {
  if (attr === "myAttr") { this.shadow.getElementById("myElement").myAttr = newVal === "true"; }
}
set myAttr(bool) { this.setAttribute("myAttr", bool.toString()); }
get myAttr() { return this.getAttribute("myAttr") === "true"; }

通过 Property 传递(配合 lwc:spread)

// 自定义元素内部
set message(value) { this._message = value; }
get message() { return this._message; }

// LWC 父组件通过 lwc:spread 传递
props = { message: "Hello custom element" };
<c-message lwc:external lwc:spread={props}></c-message>

向子组件传递数据与自定义元素中的插槽

子组件与插槽

传递数据到子组件

父→子用 lwc:spread,子组件内嵌自定义元素,属性用 @api 暴露:

<!-- myApp.html -->  <c-cmp lwc:spread={myProps}></c-cmp>
// myApp.js  myProps = { name: "Guest", greeting: "Hello" };

<!-- myCmp.html -->  <c-custom-el lwc:external>{greeting}, {name}</c-custom-el>
// myCmp.js  @api name; @api greeting;

向自定义元素插槽传递标记

行为类似 LWC 插槽——但 Synthetic shadow 不支持第三方组件的 slotting。使用 <slot> 元素在自定义元素内部创建插入点:

// 自定义元素:this.attachShadow({ mode: "closed" }).innerHTML = ``;
<c-custom-slot lwc:external><div class="slotted">slot content</div></c-custom-slot>

命名插槽也同样支持——<slot name="myslot"> + <p slot="myslot">

第三方 Web Components 中的事件与注意事项

事件与注意事项

事件处理

声明式事件绑定仅支持小写事件名。对于含大写或连字符的事件名,必须用 addEventListener() 编程式添加:

// 在 constructor 中添加事件监听器
this.addEventListener("click", this.handleClick);
handleClick() {
  this.dispatchEvent(new CustomEvent("lowercaseevent"));  // ✅ 小写
  this.dispatchEvent(new CustomEvent("camelEvent"));      // 需 addEventListener
}

组件升级行为

未注册的自定义元素渲染为 HTMLUnknownElement 实例(类似 span 或 div——无额外属性/方法)。组件升级后的行为差异:未升级→LWC 在挂载和更新时设置属性;延迟升级→设属性而非 property;升级后→如有 property 则设 property 而非 attribute。

附加样式到自定义元素 —— Shadow DOM

Shadow DOM 样式

Shadow DOM 中样式不继承——自定义元素的样式不泄露,外部样式也不穿透。但可通过以下方式自定义:

通过 CSS 自定义属性覆盖

第三方组件作者可能暴露 CSS 自定义属性作为样式钩子:

// 第三方组件内部
style.textContent = `.someSelector { color: var(--my-title-color, #AA4A44) }`;

// 你的 LWC CSS 中覆盖
:host { --my-title-color: blue; }  // CSS 自定义属性穿透 Shadow DOM!

直接为宿主元素设置样式

/* 用自定义元素选择器直接样式化宿主 */
c-custom-el { background-color: red; }
Closed 模式注意:this.refs.myEl.shadowRoot 在 closed 模式下返回 null——无法从外部访问 shadow DOM。查找第三方组件的可用 CSS 自定义属性和 shadow parts 应查阅其文档或源码。

附加样式 —— Light DOM 与移动就绪组件

Light DOM 样式

Light DOM 自定义元素样式

如果第三方组件使用 Light DOM(不使用 attachShadow),样式从根文档级联——容器组件的样式穿透到嵌套的自定义元素。反过来,Light DOM 自定义元素的样式也应用到其容器(直到遇到 shadow 边界)。

<custom-element class="highlight" lwc:external></custom-element>
/* CSS */
.highlight { color: red; }  // 直接通过 class 样式化

要限制样式作用域,使用 *.scoped.css 文件。

移动就绪组件

LWC 支持桌面和移动设备。移动环境不仅仅是屏幕更小——还有新的限制扩展能力(如离线支持、设备功能访问)。构建跨移动体验的组件请参阅 Mobile and Offline Developer Guide——进行移动开发时的重要参考。使用 lightning/mobileCapabilities 模块检测设备功能。

感谢阅读本指南。如需继续学习,请参阅下一章:使用 Lightning 基础组件。