@arclux/prism

February 14, 2026 · View on GitHub

CI npm license node

Auto-generate framework wrappers and HTML/CSS examples from Lit web components.

Write your component once as a Lit custom element. Prism reads the source and generates idiomatic wrappers for React, Vue, Svelte, Angular, Solid, and Preact — plus standalone HTML/CSS snippets with optional design-token resolution. No AST libraries, no build step, just regex-based parsing that ships as plain ESM.

What it does

Given a Lit component like this:

class ArcButton extends LitElement {
  static properties = {
    variant: { type: String, reflect: true },
    disabled: { type: Boolean, reflect: true },
  };

  constructor() {
    super();
    this.variant = 'primary';
    this.disabled = false;
  }

  static styles = css`
    :host { display: inline-flex; }
    :host([variant="primary"]) { background: var(--arc-color-primary); }
    :host([variant="secondary"]) { background: var(--arc-color-secondary); }
  `;

  render() {
    return html`<button class="btn"><slot></slot></button>`;
  }
}
customElements.define('arc-button', ArcButton);

Prism generates:

OutputWhat you get
ReactTypeScript wrapper using @lit/react createComponent with a typed ButtonProps interface and 'primary' | 'secondary' enum for variant
Vue 3.vue SFC with defineProps generics and withDefaults
Svelte 5.svelte component using $props() runes
AngularStandalone component with @Input() decorators and CUSTOM_ELEMENTS_SCHEMA
Solid.tsx component using splitProps() for reactivity-safe forwarding
Preact.tsx component with native custom element support
HTMLStatic snippet wrapped in a <span> or <div> (based on host display), slots replaced with placeholder text
Inline HTMLSame snippet with all var() tokens resolved to literal values and styles inlined
CSSShadow DOM CSS transformed to light DOM (:host.arc-button, scoped inner selectors)
CSS bundleAll components combined into a single arc-ui.css with design tokens

Enum values are auto-detected from :host([variant="value"]) patterns in the CSS. Props, defaults, types, events, and interactivity level are all extracted automatically.

Installation

npm i -D @arclux/prism

Requires Node.js 18+. No peer dependencies — the only runtime dependency is chokidar for watch mode.

Usage

# Generate all components defined in prism.config.js
npx prism

# Watch mode — regenerate when source files change
npx prism --watch

# Process a single component file
npx prism path/to/button.js

# Use a custom config path
npx prism --config ./custom.config.js

All flags also have short forms: -w for --watch, -c for --config.

Configuration

Create a prism.config.js in your project root. Every section except components and tiers is optional — include only the outputs you need:

export default {
  // ── Source ────────────────────────────────────────
  prefix: 'arc',
  components: 'packages/web-components/src',
  tiers: ['content', 'reactive', 'application'],
  ignore: ['**/index.js', '**/shared-styles.js', '**/icons/**'],

  // ── Framework wrappers (all optional) ─────────────
  react: {
    outDir: 'packages/react/src',
    wcPackage: '@arclux/arc-ui',
    barrels: true,
  },

  vue: {
    outDir: 'packages/vue/src',
    wcPackage: '@arclux/arc-ui',
    barrels: true,
  },

  svelte: {
    outDir: 'packages/svelte/src',
    wcPackage: '@arclux/arc-ui',
    barrels: true,
  },

  angular: {
    outDir: 'packages/angular/src',
    wcPackage: '@arclux/arc-ui',
    barrels: true,
  },

  solid: {
    outDir: 'packages/solid/src',
    wcPackage: '@arclux/arc-ui',
    barrels: true,
  },

  preact: {
    outDir: 'packages/preact/src',
    wcPackage: '@arclux/arc-ui',
    barrels: true,
  },

  // ── HTML/CSS outputs (optional) ───────────────────
  html: {
    outDir: 'packages/html/examples',
    baseCSS: 'shared/tokens.css',
    inlineVariant: true,
  },

  css: {
    outDir: 'packages/html/css',
    baseCSS: 'shared/tokens.css',
  },
};

Source options

OptionTypeDefaultDescription
prefixstring'arc'Component tag prefix. Controls tag stripping (arc-buttonButton), CSS bundle filename (arc-ui.css), and custom event detection. Change this to match your own design system prefix.
componentsstringrequiredRoot directory containing Lit component source files
tiersstring[]requiredSubdirectories within components to scan (e.g. ['content', 'reactive'])
ignorestring[][]Patterns to skip — bare filenames (index.js), prefixed (**/index.js), or directory globs (**/icons/**)

Framework options

Each framework section (react, vue, svelte, angular, solid, preact) accepts:

OptionTypeDefaultDescription
outDirstringrequiredOutput directory for generated wrappers
wcPackagestring'@{prefix}/{prefix}-ui'Package name used in import statements for the web component
barrelsbooleanfalseAppend exports to tier-level and root-level barrel (index) files

HTML options

OptionTypeDefaultDescription
html.outDirstringrequiredOutput directory for HTML snippet files
html.baseCSSstringPath to design tokens CSS (used by inline variant to resolve var() references)
html.inlineVariantbooleanfalseAlso generate .inline.html files with all tokens resolved and styles inlined

CSS options

OptionTypeDefaultDescription
css.outDirstringrequiredOutput directory for per-component CSS files and {prefix}-ui.css bundle
css.baseCSSstringPath to design tokens CSS (included as :root block in the bundle)

How parsing works

Prism uses regex-based parsing (no AST library) to extract metadata from Lit source files:

  1. Tag + class name from customElements.define('arc-button', ArcButton)
  2. Properties from static properties = { ... } — extracts name, type, and reflect
  3. Defaults from constructor() { this.variant = 'primary'; }
  4. CSS from css`...` template literals
  5. Enum values from :host([prop="value"]) patterns in the CSS
  6. Template from render() { return html...; } — supports variable inlining when templates are built from multiple html`` blocks
  7. Events from dispatchEvent(new CustomEvent('name')) calls
  8. Host display from :host { display: ... } — determines whether HTML output uses <div> or <span> wrapper
  9. Interactivity level — see below

Interactivity detection

Prism classifies each component to determine whether it can be represented as static HTML/CSS or requires JavaScript:

LevelMeaningHTML/CSS output?
staticPure display, no JS neededYes
hybridVisual works without JS, JS adds featuresYes
interactiveRequires JS to functionNo

All components get framework wrappers regardless of interactivity level. The classification only affects HTML/CSS output.

Auto-detection

Prism looks for these signals in the source:

  • @click=, @input=, @change=, etc. in template → interactive
  • dispatchEvent(new CustomEvent(...))interactive
  • this.shadowRoot.querySelectorinteractive
  • :host { display: none }interactive
  • None of the above → static

Auto-detection is binary (static or interactive). The hybrid level requires a manual override.

Manual overrides

Add an @arc-prism JSDoc tag to the class comment:

/**
 * Code block with copy-to-clipboard button.
 * @arc-prism hybrid — renders without JS; copy button requires JS
 */
class ArcCodeBlock extends LitElement { ... }

Valid values: static, hybrid, interactive. The override is checked before auto-detection, so it always wins.

CSS transformation

The css and html outputs convert shadow DOM CSS to light DOM equivalents:

Shadow DOMLight DOM
:host.arc-button
:host([variant="primary"]).arc-button[data-variant="primary"]
:host([disabled]).arc-button[disabled]
:host(:hover).arc-button:hover
:host(::before).arc-button::before
:host(:not([variant="primary"])).arc-button:not([data-variant="primary"])
.btn (inner selector).arc-button .btn

The inline HTML variant further resolves all var(--token) references using your design tokens CSS, and inlines the computed styles directly onto elements. Pseudo-state rules (:hover, :focus, etc.) that can't be inlined are placed in a <style> block.

Safety guarantees

  • Header check — every generated file starts with // Auto-generated by @arclux/prism — do not edit manually (or the comment equivalent for HTML/CSS). If a file exists without this header, Prism assumes it was manually written and never overwrites it.
  • Append-only barrels — barrel file updates only append new export lines. Existing exports are never removed, reordered, or modified.

Programmatic API

The parser, CSS transform, and token resolver are available as package exports:

import { parseComponent } from '@arclux/prism/parser';
import { shadowToLight } from '@arclux/prism/css-transform';
import { loadTokenMap, resolveTokens } from '@arclux/prism/resolve-tokens';

const meta = parseComponent(source, filePath, 'arc');
const lightCSS = shadowToLight(meta.css, meta.tag);

Contributing

See CONTRIBUTING.md for development setup, code style, and PR guidelines.

License

MIT © Arclight Digital, LLC