CEMアナライザープラグイン
CEM(custom-elements.json)とは、パッケージが定義する要素(タグ、attribute、property、イベント、slot)を標準的な形式で記述したものであり、これによりツールはコンポーネントを実行することなく読み取ることができます。アナライザーとそのプラグインAPIについてはcustom-elements-manifest.open-wc.orgを、ファイル形式そのものについてはスキーマと仕様を参照してください。
CEMアナライザー(@custom-elements-manifest/analyzer)は、デフォルトでは static props を1つの静的フィールドとして読み取り、それに対してattributeを出力しません。これにより、Storybookやコードエディタの自動補完のようなツールには読み取るものが何もなくなってしまいます。
私たちの web-component-base/cem-plugin を使うと、CEMアナライザーは各propを型付きのマニフェストattributeと対応する公開フィールドに展開します。以下の手順では、CEMアナライザーをインストールし、ツールが wcb のカスタム要素を扱えるように設定する方法を説明します。
インストール
Section titled “インストール”npm i -D @custom-elements-manifest/analyzerimport { wcbStaticProps } from 'web-component-base/cem-plugin'
export default { globs: ['src/**/*.{js,ts}'], outdir: '.', plugins: [wcbStaticProps()],}そしてアナライザーを実行します。
npx cem analyze生成されるもの
Section titled “生成されるもの”次のようなコンポーネントがあるとします。
import { WebComponent, html } from 'web-component-base'
type CozyButtonProps = { variant: 'primary' | 'ghost' disabled: boolean maxCount: number}
export class CozyButton extends WebComponent<CozyButtonProps> { static props: CozyButtonProps = { variant: 'primary', disabled: false, maxCount: 3, } static shadowRootInit = { mode: 'open' } static styles = ':host { display: inline-block }' get template() { return html`<button>${this.props.variant}</button>` }}customElements.define('cozy-button', CozyButton)custom-elements.json は、propごとに型付きのattributeと対応する公開フィールドを得ます。
| attribute | 型 | フィールド | デフォルト値 |
|---|---|---|---|
variant | string | variant | 'primary' |
disabled | boolean | disabled | false |
max-count | number | maxCount | 3 |
…そして props、shadowRootInit、styles、strictProps、observedAttributes、template は公開サーフェスから取り除かれます。
知っておく価値のある2つの詳細があります。
- 型はデフォルトのリテラルから決まります:
true/false→boolean、数値 →number、object/array →object、それ以外はすべて →string。TypeScriptの型注釈は参照されないため、variantのユニオン型もマニフェストではstringになります。 - attribute名はwcb自身の
getKebabCaseから生成されます。これはobservedAttributesが使うのと同じ関数なので、マニフェスト上の名前がコンポーネントの実際の観測対象からずれることはありません。
Storybook
Section titled “Storybook”Storybookのweb-componentsレンダラーは、Custom Elements Manifestからautodocsとcontrolsを構築します。
Storybookへの組み込み
Section titled “Storybookへの組み込み”import { setCustomElementsManifest } from '@storybook/web-components-vite'import manifest from '../custom-elements.json'
setCustomElementsManifest(manifest)
export default { tags: ['autodocs'] }storyをタグ名に紐づけると、Storybookは残りを推測し、variant にはテキストフィールド、disabled にはトグル、maxCount には数値入力を提供します。
import { html } from 'lit'import '../src/cozy-button.ts'
export default { title: 'Cozy/Button', component: 'cozy-button', // ← argTypesは不要 render: ({ variant, disabled, maxCount }) => html` <cozy-button variant=${variant} ?disabled=${disabled} max-count=${maxCount} ></cozy-button> `,}
export const Default = { args: { variant: 'primary', disabled: false, maxCount: 3 },}完全に動作するセットアップについては、wcbリポジトリ内のstorybook/を参照してください。これはデモコンポーネントに対してこの設定を実際に実行しています。
コードエディタ
Section titled “コードエディタ”custom-elements.json が存在すれば、エディタはプラグインが読み取るのと同じ static props に基づいて、コンポーネントのタグ名やattributeの自動補完を提供できます。そのため、ヒントがコードからずれることはありません。
まず、package.json にマニフェストファイルを宣言します。ルート2の言語サーバーはこのフィールドを通してそれを検出しますし、これは他のマニフェスト駆動のツールが探すエコシステムの慣習でもあります。
{ "customElements": "custom-elements.json"}ルート1: 拡張機能なしのネイティブVS Code
Section titled “ルート1: 拡張機能なしのネイティブVS Code”VS Codeの組み込みHTML言語サービスは、独自のcustom data形式を読み取ります。第2のアナライザープラグインがマニフェストをこの形式に変換するため、両方のファイルを1回の cem analyze 実行から得られます。
npm i -D cem-plugin-vs-code-custom-data-generatorimport { wcbStaticProps } from 'web-component-base/cem-plugin'import { generateCustomData } from 'cem-plugin-vs-code-custom-data-generator'
export default { globs: ['src/**/*.{js,ts}'], outdir: '.', plugins: [wcbStaticProps(), generateCustomData()],}{ "html.customData": ["./vscode.html-custom-data.json"]}VS Codeを再起動すると、HTMLファイル内で <cozy- を入力すると補完され、variant / disabled / max-count がattributeとして提示され、ホバー時にはその型とデフォルト値が表示されます。
注意点: html.customData は .html ファイルにしか適用されません。wcbのコンポーネントは .js / .ts 内の html タグ付きテンプレートでマークアップを記述しますが、これらはHTML言語サービスを通りません。このルートは、あなたのコンポーネントに対してプレーンなHTMLページを書く人にとっては役立ちますが、あなた自身のテンプレート内では機能しません。
ルート2: タグ付きテンプレート向けの言語サーバー拡張機能
Section titled “ルート2: タグ付きテンプレート向けの言語サーバー拡張機能”タグ付きテンプレートの内側で同じ補完を得るには、それらを理解できる拡張機能が必要です。現時点で最も現行に沿った選択肢はCustom Elements Manifest Language Server(pwrs.cem-language-server-vscode)です。これはJSとTSの両方でテンプレートリテラル内のタグ名とattributeを自動補完し、attributeやデフォルト値のホバードキュメントを追加し、.vscode/settings.json を必要とせず、上記の customElements フィールドを通じてマニフェストを検出します。
もう2つの選択肢があり、それぞれの現状を知っておく価値があります。
wc-toolkit/wc-language-server: VS CodeとJetBrainsに対応し、こちらもマニフェスト駆動です。自称アルファ版かつ実験的とのことです。Matsuuu.custom-elements-language-server-project: 古い記事で最もよく見かけるものです。アルファ版であり、そのリポジトリは2026年1月にアーカイブされました。そのため、上記の2つのいずれかを優先してください。
パッケージにマニフェストを同梱する: distPaths()
Section titled “パッケージにマニフェストを同梱する: distPaths()”コンポーネントをnpmに公開する予定ですか? アナライザーは各モジュールの path を、スキャンした正確なファイルで刻印します。つまり globs: ['src/**/*.ts'] の場合、custom-elements.json 内のすべての path は .ts ソースファイルになります。ほとんどのパッケージはビルド済みの dist/ 出力のみを公開します(そもそも実行時に .ts を import することもできません)。そのため、そのマニフェストを読み取る消費者は、tarballに含まれていないパスを解決してしまうことになります。
wcbStaticProps() の後に distPaths() を追加すると、それらのパスをビルド済み出力に書き換えます。ゼロコンフィグでは src/ → dist/ にマッピングし、TypeScriptの拡張子を出力されたJS形式に変換します(.ts → .js、.mts → .mjs、.cts → .cjs)。それ以外の拡張子はそのまま通過するため、.js のソースはディレクトリだけが入れ替わります。
import { wcbStaticProps, distPaths } from 'web-component-base/cem-plugin'
export default { globs: ['src/**/*.ts'], outdir: '.', plugins: [wcbStaticProps(), distPaths()],}異なるレイアウトの場合は、rootDir / outDir をオーバーライドしてください: distPaths({ rootDir: 'lib', outDir: 'dist/esm' })。書き換えられたパスが指す dist/ ファイルが実際に存在するよう、ビルドの後に cem analyze を実行してください。ext の拡張子リマップについてはAPIリファレンスを参照してください。マニフェストを公開しない場合(例えば、自分自身のソースに対するローカルのStorybookやエディタのセットアップなど)は、これらは一切不要です。