介绍:自定义元素清单

2025-06-07

介绍:自定义元素清单

这个想法web-components.json最初是由 VS Code 团队的 Pine 在 Web 组件 GitHub 存储库的
这个GitHub 问题中提出的,最初的目标是让 IDE 能够更好地支持自定义元素。

八位字节

开发人员往往有很多不同的意见,标准化往往需要时间。三年多过去了,我们很高兴终于能够与大家分享:自定义元素清单🎉

自定义元素清单是一种描述项目中自定义元素的文件格式。此格式允许工具和 IDE 提供有关给定项目中自定义元素的丰富信息。它custom-elements.json包含项目中自定义元素的元数据,包括它们的属性、方法、特性、继承、插槽、CSS 阴影部分、CSS 自定义属性以及模块导出。如果您有兴趣关注该架构的规范或为其做出贡献,可以在此处找到代码库:webcomponents/custom-elements-manifest

值得注意的是,自定义元素清单架构是一项社区标准其讨论是公开进行的,任何人都可以访问。来自各种利益相关者的工程师参与了该架构的讨论,例如:Adobe、Stencil、Google、Open Web Components、ING 等等。

例子

以下是一个例子:

class MyElement extends HTMLElement {
  static get observedAttributes() {
    return ['disabled'];
  }

  set disabled(val) {
    this.__disabled = val;
  }
  get disabled() {
    return this.__disabled;
  }

  fire() {
    this.dispatchEvent(new Event('disabled-changed'));
  }
}

customElements.define('my-element', MyElement);
Enter fullscreen mode Exit fullscreen mode

将产生以下结果custom-elements.json

{
  "schemaVersion": "1.0.0",
  "readme": "",
  "modules": [
    {
      "kind": "javascript-module",
      "path": "src/my-element.js",
      "declarations": [
        {
          "kind": "class",
          "description": "",
          "name": "MyElement",
          "members": [
            {
              "kind": "field",
              "name": "disabled"
            },
            {
              "kind": "method",
              "name": "fire"
            }
          ],
          "events": [
            {
              "name": "disabled-changed",
              "type": {
                "text": "Event"
              }
            }
          ],
          "attributes": [
            {
              "name": "disabled"
            }
          ],
          "superclass": {
            "name": "HTMLElement"
          },
          "tagName": "my-element"
        }
      ],
      "exports": [
        {
          "kind": "custom-element-definition",
          "name": "my-element",
          "declaration": {
            "name": "MyElement",
            "module": "src/my-element.js"
          }
        }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

潜在用例

为什么要使用自定义元素?

文档和演示

文档查看器应该能够显示有关自定义元素的所有相关信息,例如其标签名称、属性、特性、定义模块、CSS 变量和部分等。

API查看器

api-viewer-element作者:Serhii Kulykov

使用custom-elements.json文件,您可以轻松地使用api-viewer-element等工具为您的组件生成或显示演示,或者为您的组件自动生成Storybook旋钮。可以创建11ty插件来自动为您创建文档站点。

另一个很好的例子是Benny PowersApollo Elements,它使用自定义元素清单来生成文档:

阿波罗

在撰写本文时,我们还在努力为Storybook添加对 Custom Elements Manifest 版本 1.0.0 的支持。您可以在此处跟踪进度:feat: support Custom Elements Manifest v1

框架集成

React 是目前唯一一个自定义元素需要特殊处理的主流框架。React 会将所有数据以 HTML 属性的形式传递给自定义元素,并且如果不使用其他解决方法,就无法监听来自自定义元素的 DOM 事件。

解决方案是创建一个包装器 React 组件来处理这些事情。使用custom-elements.json文件可以自动创建这些包装器组件。

一些组件库(例如FastShoelace)提供了关于如何与特定框架集成的具体说明。自动化此集成层可以让组件库的开发者和库的使用者更轻松地进行开发。

避免在次要版本或补丁版本中发生重大 API 更改

另一个有趣的用例,受到的启发elm-package,是工具能够根据当前custom-elements.json文件的快照检测自定义元素的公共 API 是否已更改,以决定更新的影响,并可能防止补丁或次要版本中的破坏性 API 更改。

代码检查

Linters 能够提供有关您的自定义元素的准确上下文信息。您是否在自定义元素上设置了不受支持的属性?您是否在自定义元素上添加了未触发的事件监听器?使用custom-elements.json,linters 将能够发出警告,并及早发现错误。

编目

它的一个主要用例custom-elements.json是,它使我们能够可靠地检测出包含自定义元素的 NPM 包。这些包可以存储并显示在自定义元素目录中,这实际上相当于重启了 webcomponents.org。通过从 unpkg 等 CDN 导入包中的组件及其文件,该目录能够展示包中所含自定义元素的丰富演示和文档custom-elements.json

还有更多!

我们相信custom-elements.json这将为更多激动人心的新想法和工具打开大门。能想到哪些用例?您是否有想法,但不确定从何入手?欢迎随时通过Lit and Friends 的#open-wc 频道与我们联系,我们非常乐意与您交流并帮助您入门。

我应该如何使用自定义元素清单?

如果您要发布组件或组件库,我们建议人们创建自定义元素清单并将其与组件一起发布到 NPM。

"customElements": "./custom-elements.json"我们建议组件作者在项目的 中添加package.json。这样,工具就可以轻松查找包中是否包含自定义元素清单,并读取其内容。

如果你的软件包使用了Export Maps,请确保在"./customElements"键下也添加你的自定义元素清单。这样,你的清单的使用者就可以轻松地将其导入,如下所示:

import cem from 'my-element/customElements' assert { type: 'json' };
Enter fullscreen mode Exit fullscreen mode

🛠 工具

开发人员不太可能custom-elements.json手动编写文件。因此,在Open Web Components中,我们努力开发了一款工具来帮您完成这项工作!

@custom-elements-manifest/analyzer

@custom-elements-manifest/analyzercustom-elements.json将扫描您项目中的源文件,并为您生成。

您现在可以这样使用它:

npx @custom-elements-manifest/analyzer analyze
Enter fullscreen mode Exit fullscreen mode

✨ 或者在网上游乐场尝试一下!✨

@custom-elements-manifest/analyzer默认支持标准 JavaScript 和原生Web 组件。可以使用插件支持专用的 Web 组件库。目前,本项目通过插件提供对 LitElement、Fast、Stencil 和 Catalyst 的支持。您可以分别使用 CLI 参数--litelement--fast--stencil来启用它们--catalyst,或者通过 加载插件custom-elements-manifest.config.js

TL;DR:

可以通过自定义插件来支持其他 Web 组件库,您可以随意为您最喜欢的库创建自己的插件。

插件

不同的项目通常有不同的需求,以及记录组件的方式。您需要支持自定义 JSDoc、自定义装饰器、自定义库还是自定义其他内容?它@custom-elements-manifest/analyzer拥有丰富的插件系统,允许您扩展其功能,并为您的 .js 文件添加所需的任何额外元数据custom-elements.json

插件通常是一个返回对象的函数,并且有几个您可以选择加入的钩子:

  • collectPhase:首先遍历项目中所有模块的 AST,然后再继续执行analyzePhase。它会为每个模块运行,并允许访问 Context 对象,该对象可用于在各个阶段之间共享数据,同时还允许访问源代码的 AST 节点。这对于收集您在后续阶段可能需要访问的信息非常有用。
  • analyzePhase:针对每个模块运行,并允许访问当前模块的 moduleDoc 以及源代码的 AST 节点。这通常用于 AST 相关的操作。
  • moduleLinkPhase:在模块分析完成后运行,此时模块的所有信息都应该可用。您可以使用此钩子将各个信息片段拼接在一起。
  • packageLinkPhase:在所有模块分析完成并进行后处理后运行。此时所有信息应该可用并链接在一起。

提示:编写自定义插件时,ASTExplorer是你的好朋友 🙂

让我们看一个示例插件:

假设我们有一些源代码,带有自定义@fooJSDoc 注释和一些我们想要添加到我们的信息custom-elements.json

my-element.js

export class MyElement extends HTMLElement {
  /**
   * @foo Some custom information!
   */ 
  message = ''
}
Enter fullscreen mode Exit fullscreen mode

在自定义插件中,我们可以完全访问源代码的 AST,并且可以轻松循环遍历类的任何成员,并查看它是否具有名为 的 JSDoc 标签foo。如果有,我们将描述添加到custom-elements.json

custom-elements-manifest.config.js

export default {
  plugins: [
    {
      name: 'foo-plugin',
      analyzePhase({ts, node, moduleDoc, context}){
        switch (node.kind) {
          case ts.SyntaxKind.ClassDeclaration:
            /* If the current AST node is a class, get the class's name */
            const className = node.name.getText();

            /* We loop through all the members of the class */
            node.members?.forEach(member => {
              const memberName = member.name.getText();

              /* If a member has JSDoc notations, we loop through them */
              member?.jsDoc?.forEach(jsDoc => {
                jsDoc?.tags?.forEach(tag => {
                  /* If we find a `@foo` JSDoc tag, we want to extract the comment */
                  if(tag.tagName.getText() === 'foo') {
                    const description = tag.comment;

                    /* We then find the current class from the `moduleDoc` */
                    const classDeclaration = moduleDoc.declarations.find(declaration => declaration.name === className);
                    /* And then we find the current field from the class */
                    const messageField = classDeclaration.members.find(member => member.name === memberName);

                    /* And we mutate the field with the information we got from the `@foo` JSDoc annotation */
                    messageField.foo = description
                  }
                });
              });
            });
        }
      }
    }
  ]  
}
Enter fullscreen mode Exit fullscreen mode

输出custom-elements.json将如下所示:

{
  "schemaVersion": "1.0.0",
  "readme": "",
  "modules": [
    {
      "kind": "javascript-module",
      "path": "src/bar.js",
      "declarations": [
        {
          "kind": "class",
          "description": "",
          "name": "MyElement",
          "members": [
            {
              "kind": "field",
              "name": "message",
              "default": "",
+             "foo": "Some custom information!"
            }
          ],
          "superclass": {
            "name": "HTMLElement"
          },
          "customElement": true
        }
      ],
      "exports": [
        {
          "kind": "js",
          "name": "MyElement",
          "declaration": {
            "name": "MyElement",
            "module": "src/bar.js"
          }
        }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

要开始开发自定义插件,请查看cem-plugin-template存储库以快速启动和运行,并查看创作插件文档以获取更多深入信息。

结论

我们非常期待看到您使用自定义元素清单构建出什么样的工具。您是否有很棒的工具创意,或者想要添加对库的支持,但又不确定如何开始?欢迎加入Lit and Friends 的#open-wc 频道!

文章来源:https://dev.to/open-wc/introducing-custom-elements-manifest-gkk
PREV
开源 100 天的意外力量
NEXT
从 JavaScript 生成 TypeScript 定义文件