wcCompiler

Write single-file components. Get zero-dependency custom elements.

No framework lock-in. No virtual DOM. No runtime. Just vanilla JavaScript web components with signals-based reactivity — compiled away at build time.

Get Started GitHub npm
npm install -D @sprlab/wccompiler

How It Works

Write
src/counter.wcc
┌─────────────────┐
│ <script>        │
│   signal, effect │
│ <template>      │
│   {{count()}}   │
│ <style>         │
│   .counter {}   │
└─────────────────┘
Build
$ npx wcc build

✓ Parsed SFC blocks
✓ Analyzed reactivity
✓ Walked template DOM
✓ Generated output
Ship
dist/counter.js
┌─────────────────────┐
│ class WccCounter     │
│   extends HTMLElement│
│ customElements       │
│   .define(...)       │
└─────────────────────┘
Zero dependencies ✓

Features

⚡ Signals Reactivity

Fine-grained reactivity with automatic dependency tracking.

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

📝 Template Directives

Declarative template syntax — no v- prefix needed.

<div if="active()">...</div>
<li each="item in list()">
<input model="name">

🔌 Scoped Slots

Named, default, and scoped slots with reactive prop passing.

<slot name="header" :count="count">
<template #header="{ count }">
  {{count}} items
</template>

🎨 CSS Scoping

Automatic tag-name prefixing. No Shadow DOM — composable styles.

/* input */  .btn { color: blue }
/* output */ wcc-app .btn { color: blue }

🔷 TypeScript

Full TS support via esbuild. Generics for props and emits.

defineProps<{ count: number }>()
defineEmits<{ (e: 'change'): void }>()

📦 Standalone Mode

Inline the runtime for zero-dependency distributable components. One file, no imports, works anywhere.

defineComponent({
  tag: 'my-widget',
  standalone: true
})

🧩 Nested Components

Auto-import child components. Reactive prop binding with :prop.

<wcc-child :count="count()"
            @change="handler">
</wcc-child>

🔄 Dev Server

SSE live-reload, file watching, instant recompilation.

$ npx wcc dev
Watching src/ for changes...
Compiled: wcc-counter.wcc

🛠️ VS Code Extension

Syntax highlighting, completions, and diagnostics for .wcc files.

// Install from marketplace:
// "wcCompiler (.wcc) Language Support"

Why wcCompiler?

wcCompiler Lit Stencil Svelte
Runtime 0 KB (compiled away) ~5 KB ~15 KB ~2 KB
Output Vanilla Custom Element Lit CE Stencil CE Framework component
Shadow DOM No (scoped CSS) Yes (required) Optional No
SFC format .wcc No No .svelte
Signals Built-in No No Runes
Two-way binding model directive Manual Manual bind:
Scoped slots Yes No No Yes (let:)
Cross-framework v-model defineModel Manual Manual No
Framework zero-config Vue/React/Angular Vue/React/Angular React/Angular No (Svelte only)

Quick Start

1. Install

npm install -D @sprlab/wccompiler

2. 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>

3. Build

npx wcc build

4. Use

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

That's it. The compiled output is a single .js file — works in any browser that supports custom elements.

API Reference

Script API

FunctionDescription
signal(value)Create a reactive variable. Read: x(), Write: x.set(val). Note: x(val) also works (used internally by compiled output) but .set() is the recommended API.
computed(() => expr)Derived value with caching and auto-invalidation
effect(() => { ... })Side effect that re-runs when dependencies change. Supports cleanup via return function.
batch(() => { ... })Group multiple signal writes — effects flush once at the end
watch(signal, (n, o) => {})Observe a signal or getter with old/new values
defineProps({ key: default })Declare external props with defaults
defineEmits(['event'])Declare custom events (validated at compile time)
defineModel({ name, default })Two-way binding prop — emits change events cross-framework
templateRef('name')Get a DOM element reference from the template
onMount(() => {})Lifecycle: connectedCallback (supports async)
onDestroy(() => {})Lifecycle: disconnectedCallback
defineExpose({ ... })Expose methods/properties for external access via ref

Template Directives

DirectiveExampleDescription
{{expr()}}<span>{{count()}}</span>Reactive text interpolation
@event@click="handler"DOM event listener
if / else-if / elseif="active()"Conditional rendering
eacheach="item in items()"List rendering (keyed with :key)
showshow="visible()"CSS visibility toggle
modelmodel="name"Two-way binding
:attr:href="url()"Attribute / class / style binding
refref="canvas"Template element reference

CLI

CommandDescription
wcc buildCompile all .wcc files to output directory
wcc build --bundleCompile + produce a single bundle.js (IIFE, works from file://)
wcc build --minifyCompile with minification
wcc build --bundle --minifyProduction bundle (smallest output, no server needed)
wcc devBuild + watch + SSE live-reload dev server

Bundle Mode

The --bundle flag produces a single dist/bundle.js that includes all components in one file. It uses no ES module imports — just a plain <script> tag. Works by opening the HTML file directly from disk (no server required).

<!-- Works from file:// — no server needed -->
<script src="dist/bundle.js"></script>
<wcc-my-app></wcc-my-app>

Configuration (wcc.config.js)

OptionDefaultDescription
port4100Dev server port
input'src'Source directory
output'dist'Output directory
standalonefalseInline runtime per component (zero-dep output)

Full documentation on npm and GitHub.

Ecosystem

🛠️ VS Code Extension

Syntax highlighting, completions, diagnostics, and typed templateRef support for .wcc files.

Install from Marketplace →

📦 Standalone Mode

Inline the reactive runtime for distributable components. Perfect for npm packages, CDN widgets, and micro-frontends.

standalone: true in config or per-component.

🔄 Dev Server

Built-in SSE live-reload server. File watching with instant recompilation. Error overlay in browser.

npx wcc dev

🔗 Framework Integrations

Native in any framework. Props, events, named slots, and two-way binding work out of the box. Optional plugins for scoped slots and idiomatic DX.

// Vue
<wcc-counter v-model:count="ref">
  <template #item="{ name }">{{name}}</template>
</wcc-counter>

// React
<WccCard>
  <WccCard.Header>Title</WccCard.Header>
</WccCard>

// Angular
<wcc-counter [(count)]="signal">
  <ng-template slot="item" let-name>{{name}}</ng-template>
</wcc-counter>