How It Works
src/counter.wcc
┌─────────────────┐
│ <script> │
│ signal, effect │
│ <template> │
│ {{count()}} │
│ <style> │
│ .counter {} │
└─────────────────┘
$ npx wcc build
✓ Parsed SFC blocks
✓ Analyzed reactivity
✓ Walked template DOM
✓ Generated output
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
<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
| Function | Description |
|---|---|
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
| Directive | Example | Description |
|---|---|---|
{{expr()}} | <span>{{count()}}</span> | Reactive text interpolation |
@event | @click="handler" | DOM event listener |
if / else-if / else | if="active()" | Conditional rendering |
each | each="item in items()" | List rendering (keyed with :key) |
show | show="visible()" | CSS visibility toggle |
model | model="name" | Two-way binding |
:attr | :href="url()" | Attribute / class / style binding |
ref | ref="canvas" | Template element reference |
CLI
| Command | Description |
|---|---|
wcc build | Compile all .wcc files to output directory |
wcc build --bundle | Compile + produce a single bundle.js (IIFE, works from file://) |
wcc build --minify | Compile with minification |
wcc build --bundle --minify | Production bundle (smallest output, no server needed) |
wcc dev | Build + 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)
| Option | Default | Description |
|---|---|---|
port | 4100 | Dev server port |
input | 'src' | Source directory |
output | 'dist' | Output directory |
standalone | false | Inline runtime per component (zero-dep output) |
Ecosystem
🛠️ VS Code Extension
Syntax highlighting, completions, diagnostics, and typed templateRef support for .wcc files.
📦 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>