Web Component Analyzer CLI
raw JSON →`web-component-analyzer` is a command-line interface (CLI) tool designed to analyze web component source code, including both JavaScript and TypeScript, to extract structured metadata. It parses code and JSDoc comments to identify and document `properties`, `attributes`, `methods`, `events`, `slots`, `CSS shadow parts`, and `CSS custom properties`. The tool supports vanilla web components and provides specialized analysis for components built with frameworks like LitElement, Polymer, Stencil (partial), and LWC. The current stable version is 2.0.0. While there isn't a strict regular release cadence, updates are made to enhance parsing logic, fix bugs, and add support for new Web Component features or framework nuances. Its primary differentiation lies in its comprehensive extraction capabilities across multiple web component ecosystems, outputting to formats like Markdown, JSON, and VS Code-specific schemas, making it useful for documentation generation and IDE tooling workflows. It explicitly handles complexities like mixins and JSDoc type definitions, improving accuracy over simple regex-based parsers, and ships with TypeScript types for programmatic integration.
Common errors
error Error: Could not find any custom elements in the specified files. ↓
customElements.define('my-element', MyElement);) and that the analyzer's glob pattern or input text covers the definition. error TypeError: Cannot read properties of undefined (reading 'map') ↓
?.) for safer property access. error Error: Unknown option `--out-file` ↓
--outFile, --outDir, --format). Most options use camelCase. error Error: Parsing failed for file <filename> ↓
Warnings
breaking The jump to version 2.0.0, despite not having explicit breaking change notes in the provided changelog, typically indicates potential breaking changes, especially for the programmatic API or the structure of the JSON output, which was previously experimental. Users upgrading from 1.x should test thoroughly. ↓
gotcha The JSON output format was considered 'experimental' in earlier versions (e.g., prior to v1.1.0), meaning its structure could change without being explicitly flagged as a breaking change in minor versions. Relying on a fixed JSON schema in older versions might lead to unexpected parsing errors. ↓
gotcha Older versions (prior to v1.0.2) had known issues with CLI globbing on Windows, which could lead to files not being analyzed correctly or analysis failures. ↓
gotcha JSDoc parsing for specific tags like `@fires` with type information (e.g., `@fires my-event {MouseEvent}`) was improved in v1.1.0. Older versions might not correctly parse such detailed JSDoc entries, leading to incomplete metadata. ↓
Install
npm install web-component-analyzer yarn add web-component-analyzer pnpm add web-component-analyzer Imports
- analyzeText wrong
const analyzeText = require('web-component-analyzer')correctimport { analyzeText } from 'web-component-analyzer' - analyzeGlobs wrong
import analyzeGlobs from 'web-component-analyzer'correctimport { analyzeGlobs } from 'web-component-analyzer' - ConsoleLogger
import { ConsoleLogger } from 'web-component-analyzer'
Quickstart
import { analyzeText } from 'web-component-analyzer';
const componentCode = `
/**
* A simple counter element.
* @slot default - The content rendered inside the counter.
* @csspart button - The button part.
* @prop {number} count - The current count.
* @attr {string} label - A label for the counter.
* @fires count-changed - Emitted when the count changes.
*/
class MyCounter extends HTMLElement {
static get observedAttributes() {
return ['label'];
}
constructor() {
super();
this.count = 0;
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
:host { display: block; padding: 16px; border: 1px solid #ccc; }
.wrapper { display: flex; align-items: center; gap: 8px; }
button { padding: 8px 16px; cursor: pointer; }
</style>
<div class="wrapper">
<span part="button">${this.label || 'Counter'}: ${this.count}</span>
<button id="increment">Increment</button>
</div>
`;
this.shadowRoot.querySelector('#increment')?.addEventListener('click', this._onIncrement.bind(this));
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'label') {
this.label = newValue;
if (this.shadowRoot) {
this.shadowRoot.querySelector('span').textContent = `${this.label}: ${this.count}`;
}
}
}
_onIncrement() {
this.count++;
if (this.shadowRoot) {
this.shadowRoot.querySelector('span').textContent = `${this.label || 'Counter'}: ${this.count}`;
}
this.dispatchEvent(new CustomEvent('count-changed', { detail: this.count }));
}
}
customElements.define('my-counter', MyCounter);
`;
async function analyzeMyComponent() {
try {
const result = await analyzeText(componentCode, {
fileName: 'my-counter.js',
// 'defaultPackage' can be used to set properties for the default package in the manifest
defaultPackage: { name: 'my-components', version: '1.0.0' }
});
if (result && result.length > 0) {
console.log("Analysis Result for 'my-counter':");
console.log(JSON.stringify(result[0], null, 2));
console.log("\nProperties found:", result[0].declarations?.[0]?.members?.filter(m => m.kind === 'field' || m.kind === 'property').map(p => p.name));
} else {
console.log("No components found in the provided code.");
}
} catch (error) {
console.error("Error analyzing component:", error);
}
}
analyzeMyComponent();