npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sprlab/wccompiler

v0.16.7

Published

Zero-runtime compiler that transforms .wcc single-file components into native web components with signals-based reactivity

Readme

wcCompiler

Zero-runtime compiler that transforms .wcc single-file components into native web components. No framework, no virtual DOM, no runtime — just vanilla JavaScript custom elements with signals-based reactivity.

Install

npm install -D @sprlab/wccompiler

Quick Start

1. Create a component

<!-- src/wcc-counter.wcc -->
<script>
import { defineComponent, signal } from 'wcc'

export default defineComponent({
  tag: 'wcc-counter',
})

const count = signal(0)

function increment() {
  count.set(count() + 1)
}
</script>

<template>
<div class="counter">
  <span>{{count()}}</span>
  <button @click="increment">+</button>
</div>
</template>

<style>
.counter { display: flex; gap: 8px; align-items: center; }
</style>

2. Build

npx wcc build

3. Use

<script type="module" src="dist/wcc-counter.js"></script>
<wcc-counter></wcc-counter>

The compiled output is a single .js file with zero dependencies — works in any browser that supports custom elements.

How It Works

  src/wcc-counter.wcc          dist/wcc-counter.js
  ┌──────────────────┐         ┌──────────────────────────┐
  │ <script>         │         │ // Reactive runtime       │
  │   signal, effect │  ───►   │ // (inline or imported)   │
  │ <template>       │  build  │ class WccCounter extends  │
  │   {{count()}}    │         │   HTMLElement { ... }     │
  │ <style>          │         │ customElements.define(...) │
  └──────────────────┘         └──────────────────────────┘
                                         +
                               dist/__wcc-signals.js (shared mode)

The compiler reads your .wcc source, extracts script/template/style blocks, analyzes reactive declarations, walks the template DOM for bindings and directives, and generates a self-contained custom element class. CSS is automatically scoped by tag name.

Single File Component (.wcc)

wcCompiler uses a single-file component format with the .wcc extension. Each file contains three blocks:

  • <script> — Component logic (signals, props, events, lifecycle)
  • <template> — HTML template with directives
  • <style> — Scoped CSS
<script>
import { defineComponent, signal } from 'wcc'

export default defineComponent({
  tag: 'wcc-my-component',
})

const message = signal('Hello')
</script>

<template>
<p>{{message()}}</p>
</template>

<style>
p { color: steelblue; }
</style>

Use <script lang="ts"> for TypeScript support. The CLI discovers and compiles all .wcc files in your source directory.

Coming from Vue?

If you're familiar with Vue, here's how wcCompiler maps:

| Vue | wcCompiler | |-----|------------| | ref(0) | signal(0) | | computed(() => ...) | computed(() => ...) | | watch(source, cb) | watch(source, cb) | | v-if | if | | v-else-if | else-if | | v-else | else | | v-for="item in items" | each="item in items()" | | v-show | show | | v-model | model | | @click | @click | | :prop | :prop | | defineProps() | defineProps() | | defineEmits() | defineEmits() | | onMounted() | onMount() | | onUnmounted() | onDestroy() | | <slot> | <slot> |

Key differences: signals use .set() to write and () to read. Template directives have no v- prefix. Output is vanilla JS with no runtime framework.

Reactivity

Signals

const count = signal(0)       // create
count()                        // read → 0
count.set(5)                   // write → 5

Note: .set() is the public API for writing signals. The compiled output uses direct invocation (count(5)) as an internal optimization — both forms are equivalent, but .set() is the recommended way to write signals in your source code.

Computed

const doubled = computed(() => count() * 2)
doubled()  // auto-updates when count changes

Effects

effect(() => {
  console.log('Count is:', count())  // re-runs on change
})

Effects support cleanup — return a function to run before re-execution:

effect(() => {
  const id = setInterval(() => tick.set(tick() + 1), 1000)
  return () => clearInterval(id)  // called before re-run
})

Batch

Group multiple signal writes into a single update pass:

import { batch } from 'wcc'

batch(() => {
  firstName.set('John')
  lastName.set('Doe')
  age.set(30)
})
// Effects run once after all three writes, not three times

Nested batches are supported — effects flush only when the outermost batch completes.

Watch

// Watch a signal directly
watch(count, (newVal, oldVal) => {
  console.log(`Changed from ${oldVal} to ${newVal}`)
})

// Watch a getter function (useful for props or derived values)
watch(() => props.count, (newVal, oldVal) => {
  console.log(`Prop changed: ${oldVal} → ${newVal}`)
})

// Watch a derived expression
watch(() => count() * 2, (newVal, oldVal) => {
  console.log(`Doubled changed: ${oldVal} → ${newVal}`)
})

watch observes a specific signal or getter and provides both old and new values. The callback does not run on initial mount — only on subsequent changes.

Constants

const TAX_RATE = 0.21  // non-reactive, no signal() wrapper

Props

const props = defineProps({ label: 'Click', count: 0 })
<wcc-counter label="Clicks:" count="5"></wcc-counter>

You can also call defineProps without assignment — the props are available by name in the template:

defineProps({ label: 'Click' })
<span>{{label}}</span>

TypeScript generics:

const props = defineProps<{ label: string, count: number }>({ label: 'Click', count: 0 })

Props are reactive — they update when attributes change. Supports boolean and number coercion.

Custom Events

const emit = defineEmits(['change', 'reset'])

function handleClick() {
  emit('change', count())
}

TypeScript call signatures:

const emit = defineEmits<{ (e: 'change', value: number): void }>()

The compiler validates emit calls against declared events at compile time.

defineModel (Two-Way Binding)

defineModel declares a prop that supports two-way binding across frameworks:

import { defineModel } from 'wcc'

const count = defineModel({ name: 'count', default: 0 })
const title = defineModel({ name: 'title', default: 'untitled' })

Read and write like a signal:

count()          // read current value
count.set(5)     // write — updates internal state + emits events

Events emitted on write:

| Event | Purpose | |-------|---------| | count-changed | Kebab-case — for Vue plugin, addEventListener | | countChanged | camelCase — for Angular, direct binding | | countChange | Angular [(count)] banana-box syntax | | wcc:model | Generic — for vanilla JS and WCC-to-WCC |

Usage per framework:

<!-- Vue (with plugin) -->
<wcc-counter v-model:count="ref"></wcc-counter>

<!-- Vue (without plugin) -->
<wcc-counter :count="ref" @count-changed="ref = $event.detail"></wcc-counter>
// React (with wrapper)
<WccCounter count={count} onCountChange={(value) => setCount(value)} />

// React (native CE)
<wcc-counter count={count} oncountchanged={(e) => setCount(e.detail)} />
<!-- Angular (zero-config two-way) -->
<wcc-counter [(count)]="signal"></wcc-counter>

<!-- Angular (manual) -->
<wcc-counter [count]="signal()" (countChange)="signal.set($event.detail)"></wcc-counter>

Template Directives

Text Interpolation

Signals and computeds require () to read their value in templates:

<span>{{count()}}</span>
<p>You have {{items().length}} items.</p>
<span>{{doubled()}}</span>

Props accessed without assignment use their name directly (no parentheses):

<span>{{label}}</span>
<p>Hello, {{name}}!</p>

Event Binding

<button @click="increment">+</button>
<input @input="handleInput">

Event handlers support expressions and inline arguments:

<button @click="removeItem(item)">×</button>
<button @click="() => doSomething()">Do it</button>

Conditional Rendering

<div if="status() === 'active'">Active</div>
<div else-if="status() === 'pending'">Pending</div>
<div else>Inactive</div>

List Rendering

<li each="item in items()">{{item.name}}</li>
<li each="(item, index) in items()">{{index}}: {{item.name}}</li>

The source expression calls the signal (items()) to read the current array. Supports keyed rendering with :key:

<li each="item in items()" :key="item.id">{{item.name}}</li>

Numeric ranges are also supported:

<li each="n in 5">Item {{n}}</li>

Nested Directives in each

Directives work inside each blocks — including conditionals and nested loops:

<div each="user in users()">
  <span>{{user.name}}</span>
  <span if="user.active" class="badge">Active</span>
  <span else class="badge muted">Inactive</span>
  <ul>
    <li each="role in user.roles">{{role}}</li>
  </ul>
</div>

Visibility Toggle

<div show="isVisible()">Shown or hidden via CSS display</div>

Two-Way Binding

<input type="text" model="name">
<input type="number" model="age">
<input type="checkbox" model="agree">
<input type="radio" name="color" value="red" model="color">
<select model="country">...</select>
<textarea model="bio"></textarea>

Attribute Binding

<a :href="url()">Link</a>
<button :disabled="isLoading()">Submit</button>
<div :class="{ active: isActive(), error: hasError() }">...</div>
<div :style="{ color: textColor() }">...</div>

Template Refs

const canvas = templateRef('myCanvas')

onMount(() => {
  const ctx = canvas.value.getContext('2d')
})
<canvas ref="myCanvas"></canvas>

Slots

Named Slots

Component template:

<div class="card">
  <slot name="header">Default Header</slot>
  <slot>Default Body</slot>
  <slot name="footer">Default Footer</slot>
</div>

Consumer:

<wcc-card>
  <template #header><strong>Custom Header</strong></template>
  <p>Custom body content</p>
  <template #footer>Custom footer</template>
</wcc-card>

Scoped Slots

Component template (passes reactive data to consumer):

<slot name="stats" :likes="likes">Likes: {{likes}}</slot>

Consumer (receives data via template props):

<wcc-card>
  <template #stats="{ likes }">🔥 {{likes}} likes!</template>
</wcc-card>

Nested Components

Components can import and use other components in their templates using PascalCase tags:

<!-- src/nested/wcc-profile.wcc -->
<script>
import { defineComponent, signal } from 'wcc'
import WccBadge from './wcc-badge.wcc'

export default defineComponent({ tag: 'wcc-profile' })

const count = signal(0)

function increment() {
  count.set(count() + 1)
}
</script>

<template>
<div class="profile">
  <WccBadge :count="count()" @click="increment"></WccBadge>
</div>
</template>
  • Named import: import WccBadge from './wcc-badge.wcc' — the PascalCase identifier becomes the tag alias in the template
  • Side-effect import: import './wcc-child.wcc' — registers the child without using it in the template (for programmatic creation)
  • Reactive props: Use :prop="expr" to pass reactive data down — updates automatically when the expression changes
  • Event listening: Use @event="handler" to listen to custom events emitted by the child
  • Compile-time validation: Using a PascalCase tag without a matching import throws an error at build time
  • Hyphenated tags: Tags like <my-element> without a corresponding import are treated as plain custom elements (no import generated)

Lifecycle Hooks

onMount(() => {
  console.log('Component connected to DOM')
})

onMount(async () => {
  const data = await fetch('/api/items').then(r => r.json())
  items.set(data)
})

onDestroy(() => {
  console.log('Component removed from DOM')
})

Async callbacks are wrapped in an IIFE — connectedCallback itself stays synchronous.

Details:

  • Multiple onMount / onDestroy calls are supported — they all run in declaration order
  • connectedCallback is idempotent — re-mounting a component (e.g., moving it in the DOM) re-attaches listeners and effects cleanly
  • All effects and event listeners are automatically cleaned up in disconnectedCallback via AbortController

CSS Scoping

Styles are automatically scoped to the component using tag-name prefixing:

/* Input */
.counter { display: flex; }

/* Output */
wcc-counter .counter { display: flex; }

@media rules are recursively scoped. @keyframes are preserved without prefixing.

TypeScript

Use <script lang="ts"> in your .wcc file for full type support:

<script lang="ts">
import { defineComponent, defineProps, defineEmits, signal, computed, watch, defineExpose } from 'wcc'

export default defineComponent({
  tag: 'wcc-typescript',
})

const props = defineProps<{ title: string, count: number }>({ title: 'Demo', count: 0 })
const emit = defineEmits<{ (e: 'update', value: number): void }>()

const doubled = computed<number>(() => props.count * 2)
const watchLog = signal<string>('(no changes yet)')

watch(() => props.count, (newVal, oldVal) => {
  watchLog.set(`count changed: ${oldVal} → ${newVal}`)
})

function handleUpdate(): void {
  emit('update', doubled())
}

defineExpose({ doubled, handleUpdate, watchLog })
</script>

<template>
<div class="demo">
  <span>{{title}}: {{count}}</span>
  <span>Doubled: {{doubled()}}</span>
  <span>Watch: {{watchLog()}}</span>
  <button @click="handleUpdate">Update</button>
</div>
</template>

<style>
.demo { font-family: sans-serif; }
</style>

defineExpose() exposes methods and properties for external access via ref.

// wcc-timer.wcc — exposes start/stop/elapsed
const elapsed = signal(0)
let interval = null

function start() { interval = setInterval(() => elapsed.set(elapsed() + 1), 1000) }
function stop() { clearInterval(interval) }

defineExpose({ elapsed, start, stop })
<!-- Parent component accessing exposed API -->
<script>
import { defineComponent, templateRef, onMount } from 'wcc'
import './wcc-timer.wcc'

export default defineComponent({ tag: 'wcc-app' })

const timer = templateRef('timer')

onMount(() => {
  timer.value.start()       // call exposed method
  console.log(timer.value.elapsed)  // read exposed signal
})
</script>

<template>
<wcc-timer ref="timer"></wcc-timer>
</template>

The language server automatically generates a typed interface (PascalCase of the tag name) that can be imported by consumers:

// In the parent component:
import type { WccTimer } from './wcc-timer.wcc'
const timer = templateRef<WccTimer>('timer')
timer.value!.start() // ✅ typed

CLI

wcc build              # Compile all .wcc files from input/ to output/
wcc build --bundle     # Compile + produce a single bundle.js (works from file://)
wcc build --minify     # Compile with minification
wcc build --bundle --minify  # Production bundle (smallest output)
wcc dev                # Build + watch + live-reload dev server

The CLI discovers all .wcc files in your source directory and compiles each into a standalone .js file.

Bundle Mode

The --bundle flag produces a single bundle.js file that includes all components and their dependencies in one IIFE (Immediately Invoked Function Expression). This file:

  • Works with <script src="bundle.js"> (no type="module" needed)
  • Works from file:// protocol (no server required)
  • Includes all child component imports resolved and inlined
  • Includes the reactive runtime
  • Supports --minify for production
<!-- Works by double-clicking the HTML file — no server needed -->
<!DOCTYPE html>
<html>
<body>
  <wcc-my-app></wcc-my-app>
  <script src="dist/bundle.js"></script>
</body>
</html>

When to use --bundle:

  • Static HTML files opened from disk
  • Electron apps loading local files
  • Offline-first applications
  • Quick prototyping without a dev server
  • Distributing a complete app as HTML + JS

When NOT to use --bundle:

  • Apps served via HTTP (use ES modules for better caching)
  • When you need per-component lazy loading
  • When using a bundler like Vite/Webpack (they handle bundling themselves)

Configuration

Create wcc.config.js in your project root:

export default {
  port: 4100,       // dev server port (default: 4100)
  input: 'src',     // source directory (default: 'src')
  output: 'dist',   // output directory (default: 'dist')
  standalone: false  // inline runtime per component (default: false)
}

All options are optional — defaults shown above.

| Option | Type | Default | Description | |--------|------|---------|-------------| | port | number | 4100 | Dev server port for wcc dev | | input | string | 'src' | Source directory containing .wcc files | | output | string | 'dist' | Output directory for compiled .js files | | standalone | boolean | false | Inline reactive runtime in each component |

Standalone Mode

Controls whether the reactive runtime is inlined in each component or imported from a shared module.

// wcc.config.js
export default {
  standalone: true  // inline runtime in every component (default: false)
}
  • standalone: false (default) — Components import the runtime from a shared __wcc-signals.js file. Smaller per-component size when using multiple components.
  • standalone: true — Each component includes the full reactive runtime inline. Zero external dependencies per component.

Output difference:

Default (false):   component.js → imports __wcc-signals.js
Standalone (true): component.js → runtime inlined, zero imports

When to use standalone:

  • Publishing components as npm packages
  • Embedding widgets in third-party sites
  • CDN distribution (<script src="component.js">)
  • Micro-frontends where you don't control the host

When NOT to use standalone:

  • Apps with multiple components (runtime would be duplicated in each)
  • Internal projects where you control the build

Per-Component Override

Override the global setting for individual components:

<script>
import { defineComponent, signal } from 'wcc'

export default defineComponent({
  tag: 'wcc-widget',
  standalone: true,  // this component is self-contained regardless of global config
})
</script>

Component-level standalone always takes precedence over the global config. This lets you have a project with shared runtime but mark specific components as fully self-contained for distribution.

Reactive Scope Isolation

Each standalone component has its own isolated reactive runtime. Signals from component A cannot be observed by effects in component B — they are completely independent. This is by design for distribution scenarios where components must be self-contained. If you need cross-component reactivity (e.g., shared state), use the default shared mode (standalone: false).

Framework Integrations

WCC components are native custom elements — they work in any framework. Props, events, and named slots work natively with zero WCC-specific config. Two-way binding is zero-config in Angular; Vue requires a plugin. Scoped slots require a framework plugin or directive for idiomatic syntax.

Feature Support Matrix

| Feature | Vue (plugin) | Angular (directive) | React 19 (plugin) | |---------|--------------|--------------------|--------------------| | Props | ✅ :count="ref" | ✅ [count]="signal()" | ✅ count={state} | | Events | ✅ @count-changed="handler($event.detail)" | ✅ (count-changed)="handler($event.detail)" | ✅ oncountchanged={(e) => handler(e.detail)} | | Two-way binding | ✅ v-model:count="ref" | ✅ [(count)]="signal" | ❌ Not applicable | | Default slot | ✅ children | ✅ children | ✅ children | | Named slots | ✅ <template #name> | ✅ <div slot-name> | ✅ <WccCard.Header> | | Scoped slots | ✅ <template #name="{ prop }"> | ✅ <ng-template slot="name" let-prop> | ✅ <WccList.Item>{(prop) => jsx}</WccList.Item> |

Vue (with wccVuePlugin)

// vite.config.js
import { wccVuePlugin } from '@sprlab/wccompiler/integrations/vue'
export default defineConfig({ plugins: [wccVuePlugin()] })
<script setup>
import { ref } from 'vue'
const count = ref(0)
const text = ref('')
</script>

<template>
  <!-- Props -->
  <wcc-counter :count="count" label="Clicks"></wcc-counter>

  <!-- Events -->
  <wcc-counter @count-changed="count = $event.detail"></wcc-counter>

  <!-- Two-way binding (v-model) -->
  <wcc-counter v-model:count="count"></wcc-counter>
  <wcc-input v-model.trim="text"></wcc-input>

  <!-- Default slot -->
  <wcc-card>
    <p>Body content</p>
  </wcc-card>

  <!-- Named slots -->
  <wcc-card>
    <template #header><strong>Title</strong></template>
    <p>Body</p>
    <template #footer>Footer text</template>
  </wcc-card>

  <!-- Scoped slots -->
  <wcc-list>
    <template #item="{ item, index }">
      <li>{{ index }}: {{ item }}</li>
    </template>
  </wcc-list>
</template>

The plugin provides: isCustomElement config, v-model:prop support, v-model modifiers (.trim, .number), and scoped slot syntax ({{prop}}{%prop%} escape).

Angular (with WccSlotsDirective)

import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { WccSlotsDirective, WccSlotDef } from '@sprlab/wccompiler/adapters/angular'

@Component({
  imports: [WccSlotsDirective, WccSlotDef],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <!-- Props -->
    <wcc-counter [count]="count" label="Clicks"></wcc-counter>

    <!-- Events -->
    <wcc-counter (count-changed)="onCount($event.detail)"></wcc-counter>

    <!-- Two-way binding (banana-box) -->
    <wcc-counter [(count)]="count"></wcc-counter>

    <!-- Default slot -->
    <wcc-card>
      <p>Body content</p>
    </wcc-card>

    <!-- Named slots -->
    <wcc-card wccSlots>
      <strong slot-header>Title</strong>
      <p>Body</p>
      <span slot-footer>Footer text</span>
    </wcc-card>

    <!-- Scoped slots -->
    <wcc-list wccSlots>
      <ng-template slot="item" let-item let-index="index">
        <li>{{ index }}: {{ item }}</li>
      </ng-template>
    </wcc-list>
  `
})
export class AppComponent {
  count = 0
  onCount(value: number) { this.count = value }
}

Angular needs no plugin for props, events, or two-way binding — only CUSTOM_ELEMENTS_SCHEMA. The directive is only needed for named slots (with slot-name syntax) and scoped slots.

React 19 (with wccReactPlugin)

// vite.config.js
import { wccReactPlugin } from '@sprlab/wccompiler/integrations/react'
import react from '@vitejs/plugin-react'
export default defineConfig({ plugins: [wccReactPlugin({ prefix: 'wcc-' }), react()] })
import { useState } from 'react'
import { WccCard, WccList } from './dist/wcc-react'

export default function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      {/* Props */}
      <wcc-counter count={count} label="Clicks"></wcc-counter>

      {/* Events */}
      <wcc-counter oncountchanged={(e) => setCount(e.detail)}></wcc-counter>

      {/* Default slot */}
      <WccCard>
        <p>Body content</p>
      </WccCard>

      {/* Named slots (compound pattern) */}
      <WccCard>
        <WccCard.Header><strong>Title</strong></WccCard.Header>
        <p>Body</p>
        <WccCard.Footer>Footer text</WccCard.Footer>
      </WccCard>

      {/* Named slots (props pattern) */}
      <wcc-card header={<strong>Title</strong>} footer="Footer text">
        <p>Body</p>
      </wcc-card>

      {/* Scoped slots (compound pattern) */}
      <WccList>
        <WccList.Item>{(item, index) => <li>{index}: {item}</li>}</WccList.Item>
      </WccList>

      {/* Scoped slots (render prop pattern) */}
      <wcc-list renderItem={(item, index) => <li>{index}: {item}</li>} />
    </>
  )
}

The plugin transforms PascalCase tags, compound components, props-as-slots, and render props at build time. Import stubs from ./dist/wcc-react (auto-generated by wcc build).

Vanilla (no framework)

No configuration needed:

<script type="module" src="dist/wcc-counter.js"></script>
<script type="module" src="dist/wcc-card.js"></script>
<script type="module" src="dist/wcc-list.js"></script>

<!-- Props (attributes) -->
<wcc-counter count="0" label="Clicks"></wcc-counter>

<!-- Events -->
<script>
  document.querySelector('wcc-counter')
    .addEventListener('count-changed', (e) => console.log(e.detail))
</script>

<!-- Default slot -->
<wcc-card>
  <p>Body content</p>
</wcc-card>

<!-- Named slots -->
<wcc-card>
  <strong slot="header">Title</strong>
  <p>Body</p>
  <span slot="footer">Footer text</span>
</wcc-card>

<!-- Scoped slots -->
<wcc-list>
  <template #item="{ item, index }">
    <li>{{index}}: {{item}}</li>
  </template>
</wcc-list>

TypeScript Types for Frameworks

wcc build auto-generates typed stubs for each framework in the dist/ folder:

dist/
├── wcc-vue.d.ts      ← Vue/Volar prop autocompletion
├── wcc-vue.js        ← Vue component stubs
├── wcc-react.d.ts    ← React compound component types
├── wcc-react.js      ← React component stubs
└── ...

Vue (Volar autocompletion)

Add dist/wcc-vue.d.ts to your tsconfig to get prop/event autocompletion in .vue templates:

// tsconfig.json
{
  "include": ["src/**/*", "dist/wcc-vue.d.ts"]
}

After this, Volar provides:

  • Prop autocompletion: <wcc-counter :la| → suggests label
  • Type-checking: <wcc-counter :count="'string'"> → type error (expects number)
  • Event types on hover

React

React 19 treats custom elements (hyphenated tags) as any in JSX — this is by React's design. No additional type setup needed. Compound component stubs (WccCard.Header) are typed via dist/wcc-react.d.ts and work when imported directly.

Angular

Angular's CUSTOM_ELEMENTS_SCHEMA disables all type-checking on custom elements. No additional type setup possible from the library side.

Editor Support

The wcCompiler (.wcc) Language Support extension is available on the VS Code Marketplace. It provides syntax highlighting, completions, and diagnostics for .wcc files.

Runtime Helper

An optional wcc-runtime.js is copied to your output directory for declarative host-page bindings:

<wcc-counter :count="count" @change="handleChange"></wcc-counter>

<script type="module">
  import './dist/wcc-counter.js'
  import { init, on, set, get } from './dist/wcc-runtime.js'

  on('handleChange', (e) => set('count', e.detail))
  init({ count: 0 })
</script>

License

MIT