第三方 Web Components(Beta)
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
使用第三方组件前,先检查 AppExchange 的 LWC 应用/组件和基础组件是否有现成方案。
三种导入方式
- 上传为静态资源——用
loadScript加载。单文件最大 5 MB,组织上限 250 MB - 添加为 LWC 模块——放在
lwc文件夹,配.js-meta.xml。JS 文件最大 1 MB - 直接定义和注册——在 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 模块
此示例使用 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 通常使用 createElement、appendChild 等 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 到自定义元素
自定义元素通过 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.html、myComponent.js、myComponent.js-meta.xml 和 myCustomElement.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 中样式不继承——自定义元素的样式不泄露,外部样式也不穿透。但可通过以下方式自定义:
通过 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(不使用 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 基础组件。