Web Component Analyzer CLI

raw JSON →
2.0.0 verified Thu Apr 23 auth: no javascript

`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.

error Error: Could not find any custom elements in the specified files.
cause No `customElements.define` call or other recognized web component definition pattern was found in the analyzed source code.
fix
Ensure your web component is correctly defined and registered (e.g., 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')
cause Attempting to access properties on an `undefined` analysis result, often because `analyzeText` or `analyzeGlobs` returned an empty array or an unexpected structure.
fix
Always check if the analysis result is defined and not empty before attempting to access its properties. Use optional chaining (?.) for safer property access.
error Error: Unknown option `--out-file`
cause Incorrect casing or spelling for CLI options. The correct option is `--outFile` (camelCase).
fix
Verify CLI option spellings and casing against the documentation (--outFile, --outDir, --format). Most options use camelCase.
error Error: Parsing failed for file <filename>
cause Syntax errors in the source file, unsupported JavaScript/TypeScript features in the configured TypeScript parser, or issues with file access.
fix
Check the specified file for syntax errors. Ensure your TypeScript configuration (if programmatic) or environment supports the language features used. Verify file paths and permissions.
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.
fix Consult the official GitHub repository for detailed 2.0.0 release notes, if available, or review the source code for changes to the analysis API and output format. Adapt programmatic integrations accordingly.
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.
fix Always use the latest stable version of `web-component-analyzer` to ensure the most consistent JSON output, and robustify your parsing logic to handle potential schema evolution.
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.
fix Update to `web-component-analyzer` version 1.0.2 or newer to resolve known globbing issues on Windows. If problems persist, consider providing explicit file paths instead of globs.
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.
fix Ensure you are on `web-component-analyzer` v1.1.0 or newer for improved JSDoc parsing accuracy. Review your JSDoc comments to match supported formats.
npm install web-component-analyzer
yarn add web-component-analyzer
pnpm add web-component-analyzer

Demonstrates programmatic analysis of a single web component string using `analyzeText`, extracting its metadata including properties, attributes, events, slots, and CSS parts, then logging the structured output.

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();