Skip to content

Latest commit

 

History

History
740 lines (615 loc) · 18.9 KB

File metadata and controls

740 lines (615 loc) · 18.9 KB

Web Component Standardization Guide

This document provides instructions for standardizing ProfPowell web component repositories. Use this guide when refactoring any component to match the standard.

Quick Reference

Repositories in this suite:

Component npm GitHub Pages
code-block @profpowell/code-block Demo
browser-window @profpowell/browser-window Demo
terminal-window @profpowell/terminal-window Demo
browser-console @profpowell/browser-console Demo
http-component @profpowell/http-component Demo

Work Tracking with beads

Use bd (beads) for all work tracking:

bd ready                              # Find available work
bd show <id>                          # View issue details
bd update <id> --status in_progress   # Claim work
bd close <id>                         # Complete work
bd sync                               # Sync with git

Before starting any refactor work, check for existing issues with bd ready.


Standard Directory Structure

repo/
├── src/
│   └── component-name.js        # Main source with JSDoc comments
├── dist/
│   └── component-name.js        # Built ES module (committed)
├── docs/
│   ├── index.html               # Home page → GitHub Pages
│   ├── demos.html               # Interactive demos
│   ├── api.html                 # API documentation
│   └── styles.css               # Shared styles
├── test/
│   └── component-name.spec.js   # Playwright E2E tests
├── component-name.d.ts          # Manual TypeScript definitions
├── custom-elements.json         # Generated by CEM analyzer
├── package.json
├── vite.config.js
├── eslint.config.js
├── .prettierrc
├── playwright.config.js
├── README.md
├── LICENSE
├── CHANGELOG.md
└── CLAUDE.md

Required devDependencies

Exactly 5 devDependencies. Unlikely more and not less.

{
  "devDependencies": {
    "@custom-elements-manifest/analyzer": "^0.11.0",
    "@playwright/test": "^1.57.0",
    "eslint": "^9.0.0",
    "prettier": "^3.0.0",
    "vite": "^6.0.0"
  }
}

Remove any of these if present:

  • Storybook packages (@storybook/*)
  • Alternative test runners (vitest, jest, mocha, @web/test-runner, @open-wc/testing) but make sure to port to new test replacement
  • Alternative bundlers (esbuild, rollup, webpack, terser)
  • TypeScript compiler (typescript, tsc) - before removal prompt user and discuss use
  • jsdom, happy-dom

Configuration Files

vite.config.js

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/COMPONENT_NAME.js',
      formats: ['es'],
      fileName: () => 'COMPONENT_NAME.js'
    },
    rollupOptions: {
      // Externalize runtime deps if any (e.g., highlight.js for code-block)
      external: [],
      output: {
        globals: {}
      }
    }
  },
  server: {
    open: '/docs/index.html'
  }
})

Replace COMPONENT_NAME with the actual component name (e.g., code-block, browser-window).

eslint.config.js

import js from '@eslint/js'

export default [
  js.configs.recommended,
  {
    languageOptions: {
      ecmaVersion: 2022,
      sourceType: 'module',
      globals: {
        window: 'readonly',
        document: 'readonly',
        customElements: 'readonly',
        HTMLElement: 'readonly',
        CustomEvent: 'readonly',
        MutationObserver: 'readonly',
        IntersectionObserver: 'readonly',
        ResizeObserver: 'readonly',
        navigator: 'readonly',
        console: 'readonly',
        setTimeout: 'readonly',
        clearTimeout: 'readonly',
        setInterval: 'readonly',
        clearInterval: 'readonly',
        requestAnimationFrame: 'readonly',
        cancelAnimationFrame: 'readonly',
        fetch: 'readonly',
        URL: 'readonly',
        URLSearchParams: 'readonly',
        Blob: 'readonly',
        File: 'readonly',
        FileReader: 'readonly',
        FormData: 'readonly',
        Headers: 'readonly',
        Request: 'readonly',
        Response: 'readonly',
        Event: 'readonly',
        KeyboardEvent: 'readonly',
        MouseEvent: 'readonly',
        ClipboardEvent: 'readonly',
        DOMParser: 'readonly',
        Node: 'readonly',
        NodeList: 'readonly',
        Element: 'readonly',
        DocumentFragment: 'readonly',
        CSSStyleSheet: 'readonly',
        ShadowRoot: 'readonly'
      }
    },
    rules: {
      'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
      'no-console': 'off'
    }
  },
  {
    ignores: ['dist/', 'node_modules/', 'docs/', 'test/']
  }
]

.prettierrc

{
  "semi": false,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "none",
  "printWidth": 100
}

playwright.config.js

import { defineConfig } from '@playwright/test'

export default defineConfig({
  testDir: './test',
  fullyParallel: false,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: 1,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:5174',
    trace: 'on-first-retry'
  },
  webServer: {
    command: 'npx vite --port 5174',
    url: 'http://localhost:5174/test/test-page.html',
    reuseExistingServer: !process.env.CI,
    timeout: 30000
  }
})

custom-elements-manifest.config.mjs

export default {
  globs: ['src/**/*.js'],
  exclude: ['dist', 'node_modules'],
  outdir: './',
  litelement: true
}

package.json Template

{
  "name": "@profpowell/COMPONENT_NAME",
  "version": "X.X.X",
  "description": "DESCRIPTION",
  "type": "module",
  "main": "dist/COMPONENT_NAME.js",
  "module": "dist/COMPONENT_NAME.js",
  "types": "COMPONENT_NAME.d.ts",
  "exports": {
    ".": {
      "types": "./COMPONENT_NAME.d.ts",
      "import": "./dist/COMPONENT_NAME.js",
      "default": "./dist/COMPONENT_NAME.js"
    }
  },
  "files": [
    "dist",
    "COMPONENT_NAME.d.ts",
    "custom-elements.json",
    "README.md",
    "LICENSE"
  ],
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test": "playwright test",
    "test:ui": "playwright test --ui",
    "lint": "eslint src/",
    "lint:fix": "eslint src/ --fix",
    "format": "prettier --write \"src/**/*.js\"",
    "format:check": "prettier --check \"src/**/*.js\"",
    "analyze": "cem analyze --litelement",
    "prepublishOnly": "npm run build && npm run analyze"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ProfPowell/COMPONENT_NAME.git"
  },
  "keywords": ["web-components", "custom-elements", "vanilla-js"],
  "author": "ProfPowell",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/ProfPowell/COMPONENT_NAME/issues"
  },
  "homepage": "https://profpowell.github.io/COMPONENT_NAME/",
  "devDependencies": {
    "@custom-elements-manifest/analyzer": "^0.11.0",
    "@playwright/test": "^1.57.0",
    "eslint": "^9.0.0",
    "prettier": "^3.0.0",
    "vite": "^6.0.0"
  }
}

JSDoc Standards

All public classes, methods, properties, and events must have JSDoc comments.

Class Documentation

/**
 * A web component that displays syntax-highlighted code blocks.
 *
 * @element code-block
 * @fires copy - Fired when code is copied to clipboard
 * @fires collapse - Fired when block is collapsed/expanded
 *
 * @csspart container - The main container element
 * @csspart code - The code display area
 *
 * @cssprop [--code-block-bg=#1e1e1e] - Background color
 * @cssprop [--code-block-text=#d4d4d4] - Text color
 *
 * @slot - Default slot for code content
 */
class CodeBlock extends HTMLElement {

Property Documentation

/**
 * The programming language for syntax highlighting.
 * @type {string}
 * @attr language
 */
get language() {
  return this.getAttribute('language') || 'text'
}

Method Documentation

/**
 * Copies the code content to the clipboard.
 * @returns {Promise<void>}
 * @fires copy
 */
async copyCode() {

Event Documentation

/**
 * @event copy
 * @type {CustomEvent}
 * @property {string} detail.code - The copied code
 */
this.dispatchEvent(new CustomEvent('copy', {
  detail: { code: this.getCode() }
}))

TypeScript Definitions (.d.ts)

Create manual .d.ts files at repo root.

/**
 * A web component that displays syntax-highlighted code blocks.
 */
export class CodeBlock extends HTMLElement {
  /** The programming language for syntax highlighting */
  language: string;

  /** Whether to show line numbers */
  showLines: boolean;

  /** Copy the code to clipboard */
  copyCode(): Promise<void>;

  /** Get the current code content */
  getCode(): string;

  /** Set new code content */
  setCode(code: string): void;
}

declare global {
  interface HTMLElementTagNameMap {
    'code-block': CodeBlock;
  }
}

Playwright Test Template

Create test/COMPONENT_NAME.spec.js and test/test-page.html:

test/test-page.html (loads local source for testing):

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>COMPONENT_NAME Test Page</title>
  <script type="module" src="../src/COMPONENT_NAME.js"></script>
</head>
<body>
  <COMPONENT_NAME>Test content</COMPONENT_NAME>
</body>
</html>

test/COMPONENT_NAME.spec.js:

import { test, expect } from '@playwright/test'

test.describe('COMPONENT_NAME', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/test/test-page.html')
  })

  test('renders correctly', async ({ page }) => {
    const component = page.locator('COMPONENT_NAME')
    await expect(component).toBeVisible()
  })

  test('has shadow DOM', async ({ page }) => {
    const hasShadow = await page.evaluate(() => {
      const el = document.querySelector('COMPONENT_NAME')
      return el?.shadowRoot !== null
    })
    expect(hasShadow).toBe(true)
  })

  // Add component-specific tests
})

Documentation Site Requirements

CRITICAL: Every component MUST have a full documentation site hosted on GitHub Pages, not just a demo page. Reference: https://profpowell.github.io/browser-window/

Required Site Structure

docs/
├── index.html          # Home page with overview, features, quick start
├── demos.html          # Interactive examples and use cases
├── api.html            # Full API documentation
└── styles.css          # Shared styles

IMPORTANT: The docs/ folder uses CDN links (jsdelivr) for production GitHub Pages. The test/test-page.html file uses local ../src/ paths for testing.

Required Pages

1. Home Page (index.html)

Must include:

  • Header navigation: Links to Home, Demos, API, GitHub repo
  • Hero section: Component name, tagline, quick action buttons
  • Features grid: 4-6 key capabilities with icons/descriptions
  • Quick Start: Installation (npm + CDN) and basic usage examples
  • Related components nav: Links to sibling components
  • Footer: License, credits, links

2. Demos Page (demos.html)

Must include:

  • All component variations and configurations
  • Live interactive examples
  • Code snippets for each example
  • Organized by feature/use case

3. API Page (api.html)

Must include:

  • All attributes with types and defaults
  • All methods with signatures and descriptions
  • All events with payload details
  • All CSS custom properties
  • All CSS parts and slots

Site Design Standards

  • Clean, minimal design (similar to browser-window site)
  • Dark/light theme support
  • Mobile responsive
  • Consistent header/footer across all pages
  • GitHub-inspired styling
  • Fast loading (no heavy frameworks)

Documentation Site Template

index.html (Home)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>COMPONENT_NAME - Description</title>
  <!-- Use CDN for production GitHub Pages -->
  <script type="module" src="https://cdn.jsdelivr.net/gh/ProfPowell/COMPONENT_NAME@main/dist/COMPONENT_NAME.js"></script>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <header class="site-header">
    <nav>
      <a href="index.html" class="nav-brand">&lt;COMPONENT_NAME&gt;</a>
      <div class="nav-links">
        <a href="index.html" class="active">Home</a>
        <a href="demos.html">Demos</a>
        <a href="api.html">API</a>
        <a href="https://github.com/ProfPowell/COMPONENT_NAME">GitHub</a>
      </div>
    </nav>
  </header>

  <main>
    <section class="hero">
      <h1>&lt;COMPONENT_NAME&gt;</h1>
      <p class="tagline">SHORT_DESCRIPTION</p>
      <div class="hero-actions">
        <a href="demos.html" class="btn primary">View Demos</a>
        <a href="api.html" class="btn secondary">API Docs</a>
      </div>
    </section>

    <section class="features">
      <h2>Features</h2>
      <div class="feature-grid">
        <!-- 4-6 feature cards -->
        <div class="feature-card">
          <h3>Feature Name</h3>
          <p>Feature description</p>
        </div>
      </div>
    </section>

    <section class="quick-start">
      <h2>Quick Start</h2>

      <h3>Installation</h3>
      <code-block language="bash">npm install @profpowell/COMPONENT_NAME</code-block>

      <p>Or via CDN:</p>
      <code-block language="html">&lt;script type="module" src="https://unpkg.com/@profpowell/COMPONENT_NAME"&gt;&lt;/script&gt;</code-block>

      <h3>Basic Usage</h3>
      <code-block language="html">&lt;COMPONENT_NAME&gt;...&lt;/COMPONENT_NAME&gt;</code-block>
    </section>

    <section class="related">
      <h2>Related Components</h2>
      <div class="related-grid">
        <a href="https://profpowell.github.io/code-block/" class="related-card">
          <strong>code-block</strong>
          <span>Syntax highlighted code blocks</span>
        </a>
        <a href="https://profpowell.github.io/browser-window/" class="related-card">
          <strong>browser-window</strong>
          <span>Safari-style browser frames</span>
        </a>
        <a href="https://profpowell.github.io/terminal-window/" class="related-card">
          <strong>terminal-window</strong>
          <span>Interactive terminal emulator</span>
        </a>
        <a href="https://profpowell.github.io/browser-console/" class="related-card">
          <strong>browser-console</strong>
          <span>Console log display</span>
        </a>
        <a href="https://profpowell.github.io/http-component/" class="related-card">
          <strong>http-component</strong>
          <span>HTTP request/response viewer</span>
        </a>
      </div>
    </section>
  </main>

  <footer class="site-footer">
    <p>MIT License | <a href="https://github.com/ProfPowell/COMPONENT_NAME">View on GitHub</a></p>
  </footer>
</body>
</html>

GitHub Pages Setup

  1. Go to repo SettingsPages
  2. Source: Deploy from a branch
  3. Branch: main
  4. Folder: /docs
  5. Save

Or via CLI:

gh api repos/ProfPowell/COMPONENT_NAME/pages -X POST --input - <<EOF
{
  "source": {
    "branch": "main",
    "path": "/docs"
  }
}
EOF

The site will be available at https://profpowell.github.io/COMPONENT_NAME/


npm Publishing Checklist

Before publishing:

  • Version bumped in package.json
  • CHANGELOG.md updated
  • npm run build succeeds
  • npm run test passes
  • npm run lint passes
  • npm run analyze generates custom-elements.json
  • README.md has accurate documentation
  • Cross-links to sibling components are present

Publish:

npm login                    # If not logged in
npm publish --access public  # First time
npm publish                  # Subsequent times

Refactor Checklist

Use this checklist when standardizing a repository:

Structure

  • Source in src/ directory
  • Docs site in docs/ (index.html, demos.html, api.html, styles.css)
  • Tests in test/ directory (test-page.html + spec files)
  • Built output in dist/ (ES module only)

Configuration

  • vite.config.js present and correct
  • eslint.config.js present
  • .prettierrc present
  • playwright.config.js present
  • custom-elements-manifest.config.mjs present

package.json

  • name is @profpowell/COMPONENT_NAME
  • type is "module"
  • main points to dist/
  • exports configured correctly
  • Only 5 devDependencies
  • All standard scripts present
  • homepage points to GitHub Pages

Documentation

  • JSDoc comments on all public API
  • Manual .d.ts file at repo root
  • README.md with examples
  • CHANGELOG.md maintained
  • Cross-links to sibling components

Documentation Site (GitHub Pages)

  • docs/index.html - Home page with hero, features, quick start
  • docs/demos.html - Interactive examples page
  • docs/api.html - Full API documentation
  • docs/styles.css - Shared styles
  • Uses CDN links for component (not local src)
  • Header navigation on all pages
  • Footer with license and links
  • Dark/light theme support
  • Mobile responsive
  • Related components section

Quality

  • npm run lint passes
  • npm run format:check passes
  • npm run test passes
  • npm run build succeeds

Publishing

  • GitHub Pages enabled
  • Published to npm under @profpowell scope

Common Migration Tasks

Moving source to src/

mkdir -p src
git mv component-name.js src/
# Update vite.config.js entry point

Moving demo to docs/

mkdir -p docs
git mv demo.html docs/index.html
# Or: git mv demos/index.html docs/index.html
# Update vite.config.js server.open to /docs/index.html
# Update HTML to use CDN links for production

Removing unnecessary devDependencies

npm uninstall @storybook/web-components @storybook/addon-essentials \
  vitest jsdom @web/test-runner @open-wc/testing chai \
  esbuild terser typescript

Installing standard devDependencies

npm install -D vite@^6.0.0 eslint@^9.0.0 prettier@^3.0.0 \
  @playwright/test@^1.57.0 @custom-elements-manifest/analyzer@^0.11.0
npx playwright install  # Install browsers

Updating vite.config.js for ES-only output

Change formats: ['es', 'umd'] to formats: ['es'] and remove UMD-related config.


Cross-Linking Guidelines

Every component should reference siblings in:

  1. README.md - "Related Components" section
  2. docs/index.html - "Related Components" section at bottom
  3. package.json - keywords include "profpowell-web-components"

This promotes the vanilla web component ecosystem and helps users discover related tools.