Home / roadmap / COMPONENT-REGISTRY-PLAN

Component Registry Plan

Overview

Create a comprehensive JSON registry of all LARC components with metadata extracted from JSDoc comments. This registry will power:

  • ๐ŸŽจ Playground component palette
  • ๐Ÿ› ๏ธ Visual editor toolbox
  • ๐Ÿ“– Auto-generated documentation
  • ๐Ÿ” Component search/discovery
  • ๐Ÿ’ก IntelliSense and tooltips

JSON Structure

{
  "version": "1.0.0",
  "generated": "2025-11-12T21:45:00Z",
  "components": [
    {
      "name": "pan-router",
      "displayName": "Router",
      "description": "Client-side routing with navigation and history management",
      "category": "routing",
      "path": "./components/pan-router.mjs",
      "icon": "๐Ÿงญ",
      "tags": ["routing", "navigation", "spa"],
      "status": "stable",
      "since": "1.0.0",

      "attributes": [
        {
          "name": "base-path",
          "type": "string",
          "default": "/",
          "description": "Base path for all routes",
          "required": false
        }
      ],

      "properties": [
        {
          "name": "routes",
          "type": "Array<Route>",
          "description": "Registered routes",
          "readonly": true
        }
      ],

      "methods": [
        {
          "name": "navigate",
          "description": "Navigate to a path",
          "parameters": [
            {
              "name": "path",
              "type": "string",
              "description": "Path to navigate to"
            },
            {
              "name": "options",
              "type": "NavigateOptions",
              "optional": true,
              "description": "Navigation options"
            }
          ],
          "returns": {
            "type": "void"
          }
        },
        {
          "name": "getCurrentRoute",
          "description": "Get current route information",
          "parameters": [],
          "returns": {
            "type": "{ path: string, params: Record<string, string> }"
          }
        }
      ],

      "events": [
        {
          "name": "route-changed",
          "description": "Fired when route changes",
          "detail": {
            "type": "{ path: string, params: Record<string, string> }"
          }
        }
      ],

      "slots": [
        {
          "name": "default",
          "description": "Router outlet content"
        }
      ],

      "examples": [
        {
          "title": "Basic routing",
          "code": "<pan-router>\n  <pan-route path=\"/home\" component=\"home-page\"></pan-route>\n  <pan-route path=\"/about\" component=\"about-page\"></pan-route>\n</pan-router>"
        }
      ],

      "dependencies": ["@larcjs/core"],
      "related": ["pan-link", "pan-route"]
    }
  ],

  "categories": [
    {
      "id": "routing",
      "name": "Routing & Navigation",
      "icon": "๐Ÿงญ",
      "description": "Components for client-side routing"
    },
    {
      "id": "state",
      "name": "State Management",
      "icon": "๐Ÿ’พ",
      "description": "State management and persistence"
    },
    {
      "id": "forms",
      "name": "Forms & Input",
      "icon": "๐Ÿ“",
      "description": "Form handling and user input"
    },
    {
      "id": "data",
      "name": "Data & Connectivity",
      "icon": "๐Ÿ”Œ",
      "description": "Data fetching and API integration"
    },
    {
      "id": "ui",
      "name": "UI Components",
      "icon": "๐ŸŽจ",
      "description": "User interface building blocks"
    },
    {
      "id": "content",
      "name": "Content & Media",
      "icon": "๐Ÿ“„",
      "description": "Content display and editing"
    },
    {
      "id": "auth",
      "name": "Authentication",
      "icon": "๐Ÿ”",
      "description": "Authentication and security"
    },
    {
      "id": "theme",
      "name": "Theming",
      "icon": "๐ŸŽญ",
      "description": "Theme and styling management"
    },
    {
      "id": "devtools",
      "name": "Developer Tools",
      "icon": "๐Ÿ”ง",
      "description": "Debugging and development utilities"
    },
    {
      "id": "advanced",
      "name": "Advanced",
      "icon": "โš™๏ธ",
      "description": "Advanced functionality"
    }
  ]
}

Generator Script

Approach 1: JSDoc Parser (Recommended)

Use existing JSDoc parsing tools to extract metadata:

// scripts/generate-registry.js
import { parse } from 'jsdoc-parse';
import { glob } from 'glob';
import fs from 'fs/promises';

async function generateRegistry() {
  const files = await glob('./components/**/*.mjs');
  const components = [];

  for (const file of files) {
    const content = await fs.readFile(file, 'utf-8');
    const docs = await parse(content);

    // Extract component metadata
    const component = {
      name: extractComponentName(docs),
      displayName: extractDisplayName(docs),
      description: extractDescription(docs),
      category: categorizeComponent(docs),
      path: file,
      icon: guessIcon(docs),
      attributes: extractAttributes(docs),
      properties: extractProperties(docs),
      methods: extractMethods(docs),
      events: extractEvents(docs),
      slots: extractSlots(docs),
      examples: extractExamples(docs)
    };

    components.push(component);
  }

  const registry = {
    version: '1.0.0',
    generated: new Date().toISOString(),
    components,
    categories: getCategories()
  };

  await fs.writeFile(
    './component-registry.json',
    JSON.stringify(registry, null, 2)
  );
}

Approach 2: Custom AST Parser

Parse JavaScript with @babel/parser or acorn:

import { parse } from '@babel/parser';
import traverse from '@babel/traverse';

function extractComponentMetadata(code) {
  const ast = parse(code, {
    sourceType: 'module',
    plugins: ['classProperties']
  });

  let metadata = {};

  traverse(ast, {
    ClassDeclaration(path) {
      if (path.node.superClass?.name === 'HTMLElement') {
        metadata.name = path.node.id.name;
        metadata.methods = extractMethods(path);
        metadata.properties = extractProperties(path);
      }
    },

    CallExpression(path) {
      if (path.node.callee.object?.name === 'customElements' &&
          path.node.callee.property?.name === 'define') {
        metadata.tagName = path.node.arguments[0].value;
      }
    }
  });

  return metadata;
}

Approach 3: Hybrid (Best)

Combine JSDoc for documentation + AST for code structure:

async function analyzeComponent(file) {
  const content = await fs.readFile(file, 'utf-8');

  // Parse JSDoc comments
  const jsdocInfo = parseJSDoc(content);

  // Parse code structure
  const codeInfo = parseAST(content);

  // Merge both
  return {
    ...jsdocInfo,
    ...codeInfo,
    path: file
  };
}

JSDoc Convention

Standardize JSDoc comments in components:

/**
 * @component pan-router
 * @displayName Router
 * @category routing
 * @icon ๐Ÿงญ
 * @status stable
 * @since 1.0.0
 * @description Client-side routing with navigation and history management
 *
 * @attribute {string} base-path="/" - Base path for all routes
 * @attribute {boolean} hash-mode=false - Use hash-based routing
 *
 * @property {Array<Route>} routes - Registered routes (readonly)
 *
 * @fires route-changed - Fired when route changes
 * @fires navigation-error - Fired when navigation fails
 *
 * @method navigate
 * @param {string} path - Path to navigate to
 * @param {NavigateOptions} [options] - Navigation options
 * @returns {void}
 *
 * @method getCurrentRoute
 * @returns {{ path: string, params: Record<string, string> }}
 *
 * @slot default - Router outlet content
 *
 * @example
 * <pan-router base-path="/app">
 *   <pan-route path="/home" component="home-page"></pan-route>
 * </pan-router>
 *
 * @related pan-link
 * @related pan-route
 * @dependency @larcjs/core
 */
class PanRouter extends HTMLElement {
  // implementation
}

Implementation Steps

  • Define JSON Schema โœ… (above)
  • Choose parser approach (Hybrid recommended)
  • Create generator script (scripts/generate-registry.js)
  • Add JSDoc conventions to component files
  • Generate initial registry
  • Add npm script (npm run registry:generate)
  • Integrate with build process
  • Create TypeScript types for registry
  • Add validation (JSON schema validation)
  • Documentation on how to use registry
  • Usage Examples

    In Playground

    import registry from './component-registry.json';
    
    // Render component palette
    function renderPalette() {
      const categories = registry.categories;
    
      return categories.map(cat => {
        const components = registry.components
          .filter(c => c.category === cat.id);
    
        return `
          <div class="category">
            <h3>${cat.icon} ${cat.name}</h3>
            ${components.map(c => `
              <button class="component-item"
                      data-component="${c.name}"
                      title="${c.description}">
                ${c.icon} ${c.displayName}
              </button>
            `).join('')}
          </div>
        `;
      }).join('');
    }
    
    // Get component details for property panel
    function getComponentDetails(name) {
      return registry.components.find(c => c.name === name);
    }

    In Visual Editor

    // Drag and drop from palette
    function onDragStart(e, componentName) {
      const component = registry.components.find(c => c.name === componentName);
      e.dataTransfer.setData('component', JSON.stringify(component));
    }
    
    // Show properties panel
    function showProperties(componentName) {
      const component = registry.components.find(c => c.name === componentName);
    
      return component.attributes.map(attr => `
        <label>
          ${attr.name}
          <input type="${getInputType(attr.type)}"
                 value="${attr.default}"
                 placeholder="${attr.description}">
        </label>
      `).join('');
    }

    In Documentation Generator

    // Generate API docs
    function generateDocs() {
      return registry.components.map(component => {
        return `
    # ${component.displayName}
    
    ${component.description}
    
    ## Attributes
    
    ${component.attributes.map(attr =>
      `- **${attr.name}** (${attr.type}): ${attr.description}`
    ).join('\n')}
    
    ## Methods
    
    ${component.methods.map(method =>
      `### ${method.name}(${method.parameters.map(p => p.name).join(', ')})`
    ).join('\n')}
    
    ## Examples
    
    \`\`\`html
    ${component.examples[0].code}
    \`\`\`
        `;
      }).join('\n\n');
    }

    Benefits

  • Single Source of Truth - All component metadata in one place
  • Automatic Documentation - Generate docs from code
  • Playground/Editor Ready - Metadata powers visual tools
  • Searchable - Easy to find components by category, tag, or name
  • Versioned - Track component changes over time
  • Validation - Can validate components implement what they document
  • Type Safety - TypeScript types from registry
  • Extensible - Easy to add new metadata fields
  • Timeline

    Recommended: Add to Phase 4 (Developer Experience) or Phase 5 (before Playground)
    • Week 1: Design JSON schema and JSDoc conventions
    • Week 2: Build generator script
    • Week 3: Add JSDoc to all components
    • Week 4: Generate registry and integrate
    Alternative: Start now as it will benefit Playground development

    Open Questions

  • Should we include default styles/CSS in registry?
  • Should we version the registry separately from components?
  • Should we generate separate registries for categories?
  • Should we include component dependencies graph?
  • Should we track component size/bundle impact?
  • Next Steps

  • Finalize JSON schema
  • Choose parser approach
  • Create generator script
  • Document JSDoc conventions
  • Start adding JSDoc to key components
  • Generate initial registry