<link rel="stylesheet" href="/_merged_assets/_static/search/noscript.css">
Custom Elements Manifest Custom Elements Manifest Analyzer Blog Playground Toggle darkmode

Getting Started

Custom Elements Manifest is a file format that describes custom elements. This format will allow tooling and IDEs to give rich information about the custom elements in a given project. You can find the repository for the specification of the schema here.

✨ Try it out in the online playground! ✨

Install

npm i -D @custom-elements-manifest/analyzer
yarn add -D @custom-elements-manifest/analyzer
pnpm add -D @custom-elements-manifest/analyzer

Usage

custom-elements-manifest analyze

or

cem analyze

Demo

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);
{
  "schemaVersion": "1.0.0",
  "readme": "",
  "modules": [
    {
      "kind": "javascript-module",
      "path": "fixtures/-default/package/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": "fixtures/-default/package/my-element.js"
          }
        }
      ]
    }
  ]
}

Support

@custom-elements-manifest/analyzer by default supports standard JavaScript, and vanilla web components. Dedicated web component libraries can be supported through the use of plugins. Currently, support for LitElement, Stencil and Catalyst is provided in this project via plugins. You can enable them by using the CLI flags --litelement, --fast, --stencil, --catalyst and --catalyst respectively, or loading the plugin via your custom-elements-manifest.config.js.

TL;DR:

Support for other web component libraries can be done via custom plugins, feel free to create your own for your favourite libraries.

Documenting your components

For all supported syntax, please check the fixtures folder.

@custom-elements-manifest/analyzer is able to figure out most of your components API by itself, but for some things it needs a little help, including the following: CSS Shadow Parts, CSS Custom Properties and Slots. You can document these using JSDoc.

import { LitElement, html, css } from 'lit-element';

/**
 * @slot container - You can put some elements here
 *
 * @cssprop --text-color - Controls the color of foo
 * @cssproperty --background-color - Controls the color of bar
 *
 * @csspart bar - Styles the color of bar
 */
class MyElement extends LitElement {
  static get styles() {
    return css`
      :host {
        color: var(--text-color, black);
        background-color: var(--background-color, white);
      }
    `;
  }

  /**
   * @attr
   * @reflect
   */
  foo = 'foo';

  /**
   * @internal
   */
  _privateThing = 1;

  constructor() {
    super();
    /** @type {boolean} - disabled state */
    this.disabled = true;
  }

  get disabled() {
    /* etc */
  }
  set disabled(val) {
    /* etc */
  }

  fire() {
    /** @type {FooEvent} foo-event - description */
    this.dispatchEvent(new FooEvent('foo-changed'));
  }

  render() {
    return html`
      <div part="bar"></div>
      <slot name="container"></slot>
    `;
  }
}

/** @type {boolean} - This will show up in the custom-elements.json too */
export const someVariable = true;

customElements.define('my-element', MyElement);
{
  "schemaVersion": "1.0.0",
  "readme": "",
  "modules": [
    {
      "kind": "javascript-module",
      "path": "fixtures/-default/package/my-element.js",
      "declarations": [
        {
          "kind": "class",
          "description": "",
          "name": "MyElement",
          "cssProperties": [
            {
              "description": "Controls the color of foo",
              "name": "--text-color"
            },
            {
              "description": "Controls the color of bar",
              "name": "--background-color"
            }
          ],
          "cssParts": [
            {
              "description": "Styles the color of bar",
              "name": "bar"
            }
          ],
          "slots": [
            {
              "description": "You can put some elements here",
              "name": "container"
            }
          ],
          "members": [
            {
              "kind": "field",
              "name": "foo",
              "type": {
                "text": "string"
              },
              "default": "'foo'",
              "reflects": true,
              "attribute": "foo"
            },
            {
              "kind": "field",
              "name": "disabled",
              "type": {
                "text": "boolean"
              },
              "description": "disabled state",
              "default": "true"
            },
            {
              "kind": "method",
              "name": "fire"
            }
          ],
          "events": [
            {
              "name": "foo-changed",
              "type": {
                "text": "FooEvent"
              },
              "description": "foo-event - description"
            }
          ],
          "attributes": [
            {
              "name": "foo",
              "type": {
                "text": "string"
              },
              "default": "'foo'",
              "fieldName": "foo"
            }
          ],
          "superclass": {
            "name": "LitElement",
            "package": "lit-element"
          },
          "tagName": "my-element",
          "customElement": true
        },
        {
          "kind": "variable",
          "name": "someVariable",
          "type": {
            "text": "boolean"
          },
          "default": "true",
          "description": "This will show up in the custom-elements.json too"
        }
      ],
      "exports": [
        {
          "kind": "js",
          "name": "someVariable",
          "declaration": {
            "name": "someVariable",
            "module": "fixtures/-default/package/my-element.js"
          }
        },
        {
          "kind": "custom-element-definition",
          "name": "my-element",
          "declaration": {
            "name": "MyElement",
            "module": "fixtures/-default/package/my-element.js"
          }
        }
      ]
    }
  ]
}

Supported JSDoc

JSDocDescription
@attr,
@attribute
Documents attributes for your custom element
@prop,
@property
Documents properties for your custom element
@part,
@csspart
Documents your custom elements Shadow Parts
@slotDocuments the Slots used in your components
@cssprop,
@cssproperty
Documents CSS Custom Properties for your component
@fires,
@event
Documents events that your component might fire
@tag,
@tagname
Documents the name of your custom element
@summaryDocuments a short summary
@internal,
@ignore
To omit documentation of internal details
@defaultDocuments the default value for a property
/**
 * @attr {boolean} disabled - disables the element
 * @attribute {string} foo - description for foo
 *
 * @csspart bar - Styles the color of bar
 *
 * @slot - This is a default/unnamed slot
 * @slot container - You can put some elements here
 *
 * @cssprop --text-color - Controls the color of foo
 * @cssproperty [--background-color=red] - Controls the color of bar
 *
 * @prop {boolean} prop1 - some description
 * @property {number} prop2 - some description
 *
 * @fires custom-event - some description for custom-event
 * @fires {Event} typed-event - some description for typed-event
 * @event {CustomEvent} typed-custom-event - some description for typed-custom-event
 *
 * @summary This is MyElement
 *
 * @tag my-element
 * @tagname my-element
 */
class MyElement extends HTMLElement {}

How it works

@custom-elements-manifest/analyzer will scan the source files in your project, and run them through the TypeScript compiler to gather information about your package. Construction of the custom-elements.json happens in several phases:

Collect phase

During the collect phase, @custom-elements-manifest/analyzer goes through the AST of every module in your package. You can use this phase to collect any information that you may need in a later stage. All modules in a project are visited before continuing to the analyzePhase.

Analyze phase

During the analyze phase, @custom-elements-manifest/analyzer goes through the AST of every module in your package, and gathers as much information about them as possible, like for example a class and all its members, events, attributes, etc. During this phase it also gathers a modules imports, imports are not specified in custom-elements.json, but are required for the second phase, and then deleted once processed.

During the module Link phase you can link information together about a current module. For example, if a module contains a class declaration, and a customElements.define call, you can already link the components tagName to the classDoc. You'll also have access to a modules imports during this phase.

During the package link phase, we'll have all the information we need about a package and its custom elements, and we can start joining them together. Examples of this are:

  • Finding a CustomElement's tagname by finding its customElements.define() call, if present
  • Applying inheritance to classes (adding inherited members/attributes/events etc)

Usage in the browser

You can also run the analyzer in the browser. You can import it like so:

<html>
  <body>
    <script type="module">
      import { ts, create, litPlugin } from '@custom-elements-manifest/analyzer/browser/index.js';
      // or
      import { ts, create, litPlugin } from 'https://unpkg.com/@custom-elements-manifest/analyzer/browser/index.js';

      const modules = [ts.createSourceFile(
        'src/my-element.js',
        'export function foo() {}',
        ts.ScriptTarget.ES2015,
        true,
      )];

      const manifest = create({
        modules,
        plugins: [...litPlugin()],
        context: {dev: false},
      });

      console.log(manifest);
    </script>
  </body>
</html>

Because typescript doesn't follow semver, and may do breaking changes on minor or patch versions, typescript is bundled with the analyzer to avoid any typescript version mismatches.