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.
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:
| Output | What you get |
|---|---|
| React | TypeScript 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 |
| Angular | Standalone 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 |
| HTML | Static snippet wrapped in a <span> or <div> (based on host display), slots replaced with placeholder text |
| Inline HTML | Same snippet with all var() tokens resolved to literal values and styles inlined |
| CSS | Shadow DOM CSS transformed to light DOM (:host → .arc-button, scoped inner selectors) |
| CSS bundle | All 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.
npm i -D @arclux/prismRequires Node.js 18+. No peer dependencies — the only runtime dependency is chokidar for watch mode.
# 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.jsAll flags also have short forms: -w for --watch, -c for --config.
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',
},
};| Option | Type | Default | Description |
|---|---|---|---|
prefix |
string |
'arc' |
Component tag prefix. Controls tag stripping (arc-button → Button), CSS bundle filename (arc-ui.css), and custom event detection. Change this to match your own design system prefix. |
components |
string |
required | Root directory containing Lit component source files |
tiers |
string[] |
required | Subdirectories within components to scan (e.g. ['content', 'reactive']) |
ignore |
string[] |
[] |
Patterns to skip — bare filenames (index.js), prefixed (**/index.js), or directory globs (**/icons/**) |
Each framework section (react, vue, svelte, angular, solid, preact) accepts:
| Option | Type | Default | Description |
|---|---|---|---|
outDir |
string |
required | Output directory for generated wrappers |
wcPackage |
string |
'@{prefix}/{prefix}-ui' |
Package name used in import statements for the web component |
barrels |
boolean |
false |
Append exports to tier-level and root-level barrel (index) files |
| Option | Type | Default | Description |
|---|---|---|---|
html.outDir |
string |
required | Output directory for HTML snippet files |
html.baseCSS |
string |
— | Path to design tokens CSS (used by inline variant to resolve var() references) |
html.inlineVariant |
boolean |
false |
Also generate .inline.html files with all tokens resolved and styles inlined |
| Option | Type | Default | Description |
|---|---|---|---|
css.outDir |
string |
required | Output directory for per-component CSS files and {prefix}-ui.css bundle |
css.baseCSS |
string |
— | Path to design tokens CSS (included as :root block in the bundle) |
Prism uses regex-based parsing (no AST library) to extract metadata from Lit source files:
- Tag + class name from
customElements.define('arc-button', ArcButton) - Properties from
static properties = { ... }— extracts name, type, and reflect - Defaults from
constructor() { this.variant = 'primary'; } - CSS from
css`...`template literals - Enum values from
:host([prop="value"])patterns in the CSS - Template from
render() { return html...; }— supports variable inlining when templates are built from multiplehtml``blocks - Events from
dispatchEvent(new CustomEvent('name'))calls - Host display from
:host { display: ... }— determines whether HTML output uses<div>or<span>wrapper - Interactivity level — see below
Prism classifies each component to determine whether it can be represented as static HTML/CSS or requires JavaScript:
| Level | Meaning | HTML/CSS output? |
|---|---|---|
| static | Pure display, no JS needed | Yes |
| hybrid | Visual works without JS, JS adds features | Yes |
| interactive | Requires JS to function | No |
All components get framework wrappers regardless of interactivity level. The classification only affects HTML/CSS output.
Prism looks for these signals in the source:
@click=,@input=,@change=, etc. in template → interactivedispatchEvent(new CustomEvent(...))→ interactivethis.shadowRoot.querySelector→ interactive:host { display: none }→ interactive- None of the above → static
Auto-detection is binary (static or interactive). The hybrid level requires a manual override.
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.
The css and html outputs convert shadow DOM CSS to light DOM equivalents:
| Shadow DOM | Light 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.
- 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.
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);See CONTRIBUTING.md for development setup, code style, and PR guidelines.
MIT © Arclight Digital, LLC