如何在 Svelte 中创建 Web 组件 编译为自定义元素的组件不会发出事件 #3119

2025-06-10

如何在 Svelte 中创建 Web 组件

编译为自定义元素的组件不会发出事件 #3119

在本文中,我们将了解如何使用 Svelte 框架创建 Web 组件。
在开始编写代码之前,我们先来了解一下什么是 Web 组件。

Web 组件简介

Web 组件是一组 Web 平台 API,允许您创建新的自定义、可重用且封装的 HTML 标签,用于网页和 Web 应用。自定义组件和小部件基于 Web 组件标准,可在现代浏览器上运行,并且可以与任何兼容 HTML 的 JavaScript 库或框架一起使用。

Web 组件基于四个主要规范:

自定义元素

自定义元素提供了一种构建功能齐全的 DOM 元素的方法。通过定义自定义元素,开发者可以告知解析器如何正确构造元素,以及该类的元素应如何响应更改。自定义元素包含其自身的语义、行为和标记,并且可以跨框架和浏览器共享。

影子 DOM

Shadow DOM 规范定义了如何在 Web 组件中使用封装的样式和标记。它能够隐藏标记的结构、样式和行为,并将其与页面上的其他代码隔离,从而避免不同部分发生冲突。

ES 模块

ES 模块规范以一种基于标准、模块化且高性能的方式定义了 JS 文档的包含和重用。JavaScript 规范定义了模块的语法,以及其处理模型中一些与主机无关的部分。该规范定义了其处理模型的其余部分:如何通过 type 属性设置为“module”的 script 元素引导模块系统,以及如何获取、解析和执行模块。

HTML模板

HTML 模板元素规范定义了如何声明在页面加载时未使用但可以在运行时实例化的标记片段。

Web Components技术可以单独使用,也可以集体使用。

如何使用 Web 组件?

使用 Web 组件非常简单。例如,可以使用 Polymer 发布的 Web 组件库中现有的组件,例如以下组件:

https://www.webcomponents.org/element/@polymer/paper-button

从一个简单的网页开始:

<!doctype html>
<html>
  <head>
    <title>This is the title of the webpage!</title>
  </head>
  <body>
      <h1>Test Page</h1>
      <p>This is an example paragraph.</p>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

可以导入包含 Web 组件的脚本来开始使用库组件,就好像这是一个简单的 HTML 元素一样。

<html>
  <head>
    <title>This is the title of the webpage!</title>
    <script type="module" src="https://npm-demos.appspot.com/@polymer/paper-button@3.0.1/paper-button.js?@polymer/paper-button@3.0.1"></script>
  </head>
  <body>
      <h1>Test Page</h1>
      <p>This is an example paragraph.</p>
      <paper-button raised class="indigo">button</paper-button>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

什么是 Svelte?

Svelte 是由 Rich Harris 编写的 JavaScript 框架。Svelte 应用程序不包含框架引用。React
、Vue 或 Angular 等传统框架的大部分工作都在浏览器中完成,而 Svelte 则将这些工作转移到构建应用时进行的编译步骤中。Svelte
会生成操作 DOM 的代码,从而提升客户端的运行时性能。

Svelte 不使用虚拟 DOM 差异等技术,而是编写代码,在应用程序状态发生变化时精确更新 DOM。

Svelte 实现的 TodoMVC 压缩包大小为 3.6kb。相比之下,React 加上 ReactDOM(不含任何应用代码)压缩包大小约为 45kb。浏览器仅评估 React 所需的时间大约是 Svelte 启动并运行交互式 TodoMVC 所需的时间的 10 倍。

  • Svelte 简介:没有框架的框架

如何制作简单精简的 Web 应用程序

要创建一个新的 svelte 项目,我们可以从官方模板https://github.com/sveltejs/template开始。

要在 my-svelte-project 目录中创建新项目、安装其依赖项并启动服务器,您可以键入以下命令:

npx degit sveltejs/template my-svelte-project
cd my-svelte-project
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

通过访问 URL http://localhost:5000,您将看到 hello-world Web 应用程序。

在这个例子中,我们将创建一个时钟组件,您可以从此链接复制文件 app.svelte 的内容:https://svelte.dev/examples#clock

编译为自定义元素(又名 Web 组件)

Svelte 组件也可以使用 customElement: true 编译器选项编译为自定义元素(也称为 Web 组件)。您应该使用svelte:options元素为组件指定标签名称

<svelte:options tag="my-element">
Enter fullscreen mode Exit fullscreen mode

默认情况下,自定义元素在编译时会使用 accessors: true 进行编译,这意味着所有 props 都会作为 DOM 元素的属性公开。为了避免这种情况,请将 accessors={false} 添加到svelte:options

要构建自定义元素,我们必须:

  • 将 customElement: true 添加到 rollup.config.js 文件:
    plugins: [
        svelte({
            customElement: true,
Enter fullscreen mode Exit fullscreen mode
  • 添加 App.svelte
<svelte:options tag="svelte-clock">
Enter fullscreen mode Exit fullscreen mode

如果你没有定义这个 svelte:option,编译器会用以下消息警告你

svelte plugin: No custom element 'tag' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag="my-thing"/>. To hide this warning, use <svelte:options tag={null}/
Enter fullscreen mode Exit fullscreen mode
  • 运行“npm run build”

在开发过程中(npm run dev),实时重新加载将被启用。这意味着对自定义元素或 HTML 所做的任何更改都将立即反映在浏览器中。

一旦 Web 组件准备就绪,我们就可以运行“npm run build”,它将在 public/bundle.js 文件中编译自定义元素的精简版、可用于生产环境的版本。
编译器将负责创建 Shadow DOM、应用属性 (attribute) 以及定义自定义元素。

为了测试创建的 Web 组件,我们可以使用 http-server。
安装方法如下:

npm install http-server -g
Enter fullscreen mode Exit fullscreen mode

然后我们可以在公共目录中创建index.html,导入bundle.js并声明自定义元素“svelte-clock”:

<!doctype html>
<html>
  <head>
    <title>This is the title of the webpage!</title>
    <script src="bundle.js"></script>
  </head>
  <body>
      <h1>Test Page</h1>
      <p>This is an example paragraph.</p>
      <svelte-clock/>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

执行以下命令我们可以看到组件的运行情况:

> http-server -p 8080 -c-1 public/
Starting up http-server, serving public/
Available on:
  http://127.0.0.1:8080
Enter fullscreen mode Exit fullscreen mode

Svelte Web 组件:结论

特性

自定义元素接受的任何 props 都会在编译时自动转换为元素属性。建议使用小写的属性名,因为像 camelCase 或 PascalCase 这样的命名约定在 HTML 中不起作用。

为了测试,我们可以向自定义元素添加一个简单的属性。

<script>
    ...
    export let clocktitle = "Svelte Clock"
    ...
</script>
...
<h1>{clocktitle}</h1>
...
Enter fullscreen mode Exit fullscreen mode

在我们的 index.html 中,我们现在可以设置值

<svelte-clock clocktitle="My Clock"></svelte-clock>
Enter fullscreen mode Exit fullscreen mode

活动

从包装为 web 组件的 Svelte 3 内部发出的自定义事件不会像普通 DOM 事件那样冒泡到 web 组件本身(默认情况下,自定义事件不会超出 shadowDom 的边界),并且不能以模板中的通常方式处理。

<svelte-clock custom-event="handler()">    
Enter fullscreen mode Exit fullscreen mode

编译为自定义元素的组件不会发出事件 #3119

用于监听事件 on:mycustomevent 的原生 Svelte 语法不适用于导出到自定义元素的 Svelte 组件分派的事件。

可能与此有关?https://github.com/sveltejs/svelte/blob/a0e0f0125aa554b3f79b0980922744ee11857069/src/runtime/internal/Component.ts#L162-L171

这是一个复制存储库:

https://github.com/vogloblinsky/svelte-3-wc-debug

svelte3-raw

仅使用 Svelte 语法的示例。内部组件会触发自定义事件“message”。应用组件使用 on:message 监听该事件。

有用 !

//Inner.svelte
<script>
    import { createEventDispatcher } from 'svelte';

    const dispatch = createEventDispatcher();

    function sayHello() {
        console.log('sayHello in child: ', 'Hello!');
        dispatch('message', {
            text: 'Hello!'
        });
    }
</script>

<button on:click={sayHello}>
    Click to say hello
</button>
//App.svelte
<script>
    import Inner from './Inner.svelte';

    function handleMessage(event) {
        console.log('handleMessage in parent: ', event.detail.text);
    }
</script>

<Inner on:message={handleMessage}/>

svelte3-wc

仅使用 Svelte 语法并将组件导出到 Web 组件的示例。内部组件会触发自定义事件“message”。应用组件使用 on:message 监听该事件。

相同的语法不起作用。

//Inner.svelte
<svelte:options tag="inner-btn"/>
<script>
    import { createEventDispatcher } from 'svelte';

    const dispatch = createEventDispatcher();

    function sayHello() {
        console.log('sayHello in child: ', 'Hello!');
        dispatch('message', {
            text: 'Hello!'
        });
    }
</script>

<button on:click={sayHello}>
    Click to say hello
</button>
//App.svelte
<svelte:options tag="my-app" />
<script>
    import Inner from './Inner.svelte';

    function handleMessage(event) {
        console.log('handleMessage in parent: ', event.detail.text);
    }
</script>

<inner-btn on:message={handleMessage}/>

Vanilla JS 在 public/index.html 中运行良好

const button = document
                    .querySelector('my-app')
                    .shadowRoot.querySelector('inner-btn');

                button.$on('message', e => {
                    console.log('handleMessage in page');
                });

为了使其跨越 shadowDom 的边界,我们必须创建一个自定义事件,正如 svelte v2 文档中所述。您可以使用 CustomEvent API 在 Svelte 组件中创建自定义事件。定义自定义事件后,您可以通过调用 this.dispatchEvent(event) 来调度该事件,以响应组件中的更改。
自定义事件无法通过生命周期方法调度。例如,如果您尝试在 onMount 生命周期方法中调度自定义事件,则该事件将不会被调度。

要添加事件,我们可以添加一个按钮:

<button on:click="{dispatchSavedDateEvent}">Save Date</button>
Enter fullscreen mode Exit fullscreen mode

当被点击时我们可以发出一个自定义事件:

function dispatchSavedDateEvent(e) {
   console.log("[dispatchSecondIsElapsedEvent] time: ", time);
   // 1. Create the custom event.
   const event = new CustomEvent("savedData", {
     detail: time,
     bubbles: true,
     cancelable: true,
     composed: true // makes the event jump shadow DOM boundary
   });

   // 2. Dispatch the custom event.
   this.dispatchEvent(event);
 }
Enter fullscreen mode Exit fullscreen mode

Event 接口的只读组合属性返回一个布尔值,指示事件是否会跨越影子 DOM 边界传播到标准 DOM。

另一种方法是利用 createEventDispatcher

import { createEventDispatcher } from 'svelte'; 
const dispatch = createEventDispatcher();
...
dispatch('second', {
       text: '10 seconds elapsed!'
     });
...
Enter fullscreen mode Exit fullscreen mode

在index.html中我们必须通过以下方式订阅新事件:

document.querySelector('svelte-clock')
    .$on('second', (e) => { console.log("[index.html][second]", e)})
Enter fullscreen mode Exit fullscreen mode

导入

要导入 Svelte 组件,我们必须使用标签声明每个嵌套元素

<svelte:option tag="my-nested-element”>
Enter fullscreen mode Exit fullscreen mode

将子组件声明为自定义元素,这些元素也可供用户使用。
嵌套元素使用与父元素相同的 Shadow DOM,因此无法将嵌套元素的 Shadow DOM 模式设置为“关闭”。

使用 Svelte.js 创建 Web 组件的主要优势在于最终组件的尺寸非常小。在我们的小示例中,打包在 bundle.js 中的 Web 组件仅重 7170 字节,与其他框架创建的 Web 组件相比,我们的 Web 组件体积小了数十倍,并且在浏览器中的执行速度也更快。

鏂囩珷鏉ユ簮锛�https://dev.to/silvio/how-to-create-a-web-components-in-svelte-2g4j
PREV
编写同事会喜欢的 Git 提交消息
NEXT
10 个值得关注的开源文档框架