@sprlab/wccompiler 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -37
- package/bin/wcc.js +6 -6
- package/bin/wcc.test.js +23 -16
- package/lib/codegen.js +169 -38
- package/lib/compiler-browser.js +21 -0
- package/lib/compiler.js +228 -91
- package/lib/dev-server.js +30 -1
- package/lib/parser-extractors.js +43 -21
- package/lib/sfc-parser.js +262 -0
- package/lib/tree-walker.js +25 -18
- package/lib/types.js +3 -1
- package/package.json +3 -3
- package/types/wcc.d.ts +5 -6
- package/types/wcc.test.js +2 -2
- package/lib/printer.js +0 -118
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# wcCompiler
|
|
2
2
|
|
|
3
|
-
Zero-runtime compiler that transforms `.
|
|
3
|
+
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.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -12,14 +12,13 @@ npm install -D @sprlab/wccompiler
|
|
|
12
12
|
|
|
13
13
|
**1. Create a component**
|
|
14
14
|
|
|
15
|
-
```
|
|
16
|
-
|
|
15
|
+
```html
|
|
16
|
+
<!-- src/wcc-counter.wcc -->
|
|
17
|
+
<script>
|
|
17
18
|
import { defineComponent, signal } from 'wcc'
|
|
18
19
|
|
|
19
20
|
export default defineComponent({
|
|
20
21
|
tag: 'wcc-counter',
|
|
21
|
-
template: './wcc-counter.html',
|
|
22
|
-
styles: './wcc-counter.css',
|
|
23
22
|
})
|
|
24
23
|
|
|
25
24
|
const count = signal(0)
|
|
@@ -27,19 +26,18 @@ const count = signal(0)
|
|
|
27
26
|
function increment() {
|
|
28
27
|
count.set(count() + 1)
|
|
29
28
|
}
|
|
30
|
-
|
|
29
|
+
</script>
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
<!-- src/wcc-counter.html -->
|
|
31
|
+
<template>
|
|
34
32
|
<div class="counter">
|
|
35
|
-
<span>{{count}}</span>
|
|
33
|
+
<span>{{count()}}</span>
|
|
36
34
|
<button @click="increment">+</button>
|
|
37
35
|
</div>
|
|
38
|
-
|
|
36
|
+
</template>
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
/* src/wcc-counter.css */
|
|
38
|
+
<style>
|
|
42
39
|
.counter { display: flex; gap: 8px; align-items: center; }
|
|
40
|
+
</style>
|
|
43
41
|
```
|
|
44
42
|
|
|
45
43
|
**2. Build**
|
|
@@ -57,6 +55,36 @@ npx wcc build
|
|
|
57
55
|
|
|
58
56
|
The compiled output is a single `.js` file with zero dependencies — works in any browser that supports custom elements.
|
|
59
57
|
|
|
58
|
+
## Single File Component (.wcc)
|
|
59
|
+
|
|
60
|
+
wcCompiler uses a single-file component format with the `.wcc` extension. Each file contains three blocks:
|
|
61
|
+
|
|
62
|
+
- `<script>` — Component logic (signals, props, events, lifecycle)
|
|
63
|
+
- `<template>` — HTML template with directives
|
|
64
|
+
- `<style>` — Scoped CSS
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<script>
|
|
68
|
+
import { defineComponent, signal } from 'wcc'
|
|
69
|
+
|
|
70
|
+
export default defineComponent({
|
|
71
|
+
tag: 'wcc-my-component',
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const message = signal('Hello')
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<p>{{message()}}</p>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<style>
|
|
82
|
+
p { color: steelblue; }
|
|
83
|
+
</style>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use `<script lang="ts">` for TypeScript support. The CLI discovers and compiles all `.wcc` files in your source directory.
|
|
87
|
+
|
|
60
88
|
## Reactivity
|
|
61
89
|
|
|
62
90
|
### Signals
|
|
@@ -94,13 +122,18 @@ effect(() => {
|
|
|
94
122
|
### Watch
|
|
95
123
|
|
|
96
124
|
```js
|
|
97
|
-
|
|
125
|
+
// Watch a signal directly
|
|
126
|
+
watch(count, (newVal, oldVal) => {
|
|
98
127
|
console.log(`Changed from ${oldVal} to ${newVal}`)
|
|
99
|
-
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Watch a getter function (useful for props or derived values)
|
|
131
|
+
watch(() => props.count, (newVal, oldVal) => {
|
|
132
|
+
console.log(`Prop changed: ${oldVal} → ${newVal}`)
|
|
100
133
|
})
|
|
101
134
|
```
|
|
102
135
|
|
|
103
|
-
`watch` observes a specific signal
|
|
136
|
+
`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.
|
|
104
137
|
|
|
105
138
|
### Constants
|
|
106
139
|
|
|
@@ -118,6 +151,16 @@ const props = defineProps({ label: 'Click', count: 0 })
|
|
|
118
151
|
<wcc-counter label="Clicks:" count="5"></wcc-counter>
|
|
119
152
|
```
|
|
120
153
|
|
|
154
|
+
You can also call `defineProps` without assignment — the props are available by name in the template:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
defineProps({ label: 'Click' })
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```html
|
|
161
|
+
<span>{{label}}</span>
|
|
162
|
+
```
|
|
163
|
+
|
|
121
164
|
TypeScript generics:
|
|
122
165
|
|
|
123
166
|
```ts
|
|
@@ -148,9 +191,19 @@ The compiler validates emit calls against declared events at compile time.
|
|
|
148
191
|
|
|
149
192
|
### Text Interpolation
|
|
150
193
|
|
|
194
|
+
Signals and computeds require `()` to read their value in templates:
|
|
195
|
+
|
|
196
|
+
```html
|
|
197
|
+
<span>{{count()}}</span>
|
|
198
|
+
<p>You have {{items().length}} items.</p>
|
|
199
|
+
<span>{{doubled()}}</span>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Props accessed without assignment use their name directly (no parentheses):
|
|
203
|
+
|
|
151
204
|
```html
|
|
152
|
-
<span>{{
|
|
153
|
-
<p>Hello, {{name}}
|
|
205
|
+
<span>{{label}}</span>
|
|
206
|
+
<p>Hello, {{name}}!</p>
|
|
154
207
|
```
|
|
155
208
|
|
|
156
209
|
### Event Binding
|
|
@@ -160,25 +213,34 @@ The compiler validates emit calls against declared events at compile time.
|
|
|
160
213
|
<input @input="handleInput">
|
|
161
214
|
```
|
|
162
215
|
|
|
216
|
+
Event handlers support expressions and inline arguments:
|
|
217
|
+
|
|
218
|
+
```html
|
|
219
|
+
<button @click="removeItem(item)">×</button>
|
|
220
|
+
<button @click="() => doSomething()">Do it</button>
|
|
221
|
+
```
|
|
222
|
+
|
|
163
223
|
### Conditional Rendering
|
|
164
224
|
|
|
165
225
|
```html
|
|
166
|
-
<div if="status === 'active'">Active</div>
|
|
167
|
-
<div else-if="status === 'pending'">Pending</div>
|
|
226
|
+
<div if="status() === 'active'">Active</div>
|
|
227
|
+
<div else-if="status() === 'pending'">Pending</div>
|
|
168
228
|
<div else>Inactive</div>
|
|
169
229
|
```
|
|
170
230
|
|
|
171
231
|
### List Rendering
|
|
172
232
|
|
|
173
233
|
```html
|
|
174
|
-
<li each="item in items">{{item.name}}</li>
|
|
175
|
-
<li each="(item, index) in items">{{index}}: {{item.name}}</li>
|
|
234
|
+
<li each="item in items()">{{item.name}}</li>
|
|
235
|
+
<li each="(item, index) in items()">{{index}}: {{item.name}}</li>
|
|
176
236
|
```
|
|
177
237
|
|
|
238
|
+
The source expression calls the signal (`items()`) to read the current array.
|
|
239
|
+
|
|
178
240
|
### Visibility Toggle
|
|
179
241
|
|
|
180
242
|
```html
|
|
181
|
-
<div show="isVisible">Shown or hidden via CSS display</div>
|
|
243
|
+
<div show="isVisible()">Shown or hidden via CSS display</div>
|
|
182
244
|
```
|
|
183
245
|
|
|
184
246
|
### Two-Way Binding
|
|
@@ -195,10 +257,10 @@ The compiler validates emit calls against declared events at compile time.
|
|
|
195
257
|
### Attribute Binding
|
|
196
258
|
|
|
197
259
|
```html
|
|
198
|
-
<a :href="url">Link</a>
|
|
199
|
-
<button :disabled="isLoading">Submit</button>
|
|
200
|
-
<div :class="{ active: isActive, error: hasError }">...</div>
|
|
201
|
-
<div :style="{ color: textColor }">...</div>
|
|
260
|
+
<a :href="url()">Link</a>
|
|
261
|
+
<button :disabled="isLoading()">Submit</button>
|
|
262
|
+
<div :class="{ active: isActive(), error: hasError() }">...</div>
|
|
263
|
+
<div :style="{ color: textColor() }">...</div>
|
|
202
264
|
```
|
|
203
265
|
|
|
204
266
|
### Template Refs
|
|
@@ -286,31 +348,58 @@ wcc-counter .counter { display: flex; }
|
|
|
286
348
|
|
|
287
349
|
## TypeScript
|
|
288
350
|
|
|
289
|
-
Use `.
|
|
351
|
+
Use `<script lang="ts">` in your `.wcc` file for full type support:
|
|
290
352
|
|
|
291
|
-
```
|
|
292
|
-
|
|
353
|
+
```html
|
|
354
|
+
<script lang="ts">
|
|
355
|
+
import { defineComponent, defineProps, defineEmits, signal, computed, watch, defineExpose } from 'wcc'
|
|
293
356
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
357
|
+
export default defineComponent({
|
|
358
|
+
tag: 'wcc-typescript',
|
|
359
|
+
})
|
|
297
360
|
|
|
298
|
-
|
|
299
|
-
|
|
361
|
+
const props = defineProps<{ title: string, count: number }>({ title: 'Demo', count: 0 })
|
|
362
|
+
const emit = defineEmits<{ (e: 'update', value: number): void }>()
|
|
363
|
+
|
|
364
|
+
const doubled = computed<number>(() => props.count * 2)
|
|
365
|
+
const watchLog = signal<string>('(no changes yet)')
|
|
366
|
+
|
|
367
|
+
watch(() => props.count, (newVal, oldVal) => {
|
|
368
|
+
watchLog.set(`count changed: ${oldVal} → ${newVal}`)
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
function handleUpdate(): void {
|
|
372
|
+
emit('update', doubled())
|
|
300
373
|
}
|
|
301
374
|
|
|
302
|
-
|
|
375
|
+
defineExpose({ doubled, handleUpdate, watchLog })
|
|
376
|
+
</script>
|
|
377
|
+
|
|
378
|
+
<template>
|
|
379
|
+
<div class="demo">
|
|
380
|
+
<span>{{title}}: {{count}}</span>
|
|
381
|
+
<span>Doubled: {{doubled()}}</span>
|
|
382
|
+
<span>Watch: {{watchLog()}}</span>
|
|
383
|
+
<button @click="handleUpdate">Update</button>
|
|
384
|
+
</div>
|
|
385
|
+
</template>
|
|
386
|
+
|
|
387
|
+
<style>
|
|
388
|
+
.demo { font-family: sans-serif; }
|
|
389
|
+
</style>
|
|
303
390
|
```
|
|
304
391
|
|
|
305
|
-
`
|
|
392
|
+
`defineExpose()` exposes methods and properties for external access via ref.
|
|
306
393
|
|
|
307
394
|
## CLI
|
|
308
395
|
|
|
309
396
|
```bash
|
|
310
|
-
wcc build # Compile all .
|
|
397
|
+
wcc build # Compile all .wcc files from input/ to output/
|
|
311
398
|
wcc dev # Build + watch + live-reload dev server
|
|
312
399
|
```
|
|
313
400
|
|
|
401
|
+
The CLI discovers all `.wcc` files in your source directory and compiles each into a standalone `.js` file.
|
|
402
|
+
|
|
314
403
|
### Configuration
|
|
315
404
|
|
|
316
405
|
Create `wcc.config.js` in your project root:
|
|
@@ -325,6 +414,10 @@ export default {
|
|
|
325
414
|
|
|
326
415
|
All options are optional — defaults shown above.
|
|
327
416
|
|
|
417
|
+
## Editor Support
|
|
418
|
+
|
|
419
|
+
The **wcCompiler (.wcc) Language Support** extension is available on the VS Code Marketplace. It provides syntax highlighting, completions, and diagnostics for `.wcc` files.
|
|
420
|
+
|
|
328
421
|
## Runtime Helper
|
|
329
422
|
|
|
330
423
|
An optional `wcc-runtime.js` is copied to your output directory for declarative host-page bindings:
|
package/bin/wcc.js
CHANGED
|
@@ -23,7 +23,7 @@ async function build(config, cwd) {
|
|
|
23
23
|
try {
|
|
24
24
|
const output = await compile(file);
|
|
25
25
|
const relPath = relative(inputDir, file);
|
|
26
|
-
const outPath = resolve(outputDir, relPath.replace(/\.
|
|
26
|
+
const outPath = resolve(outputDir, relPath.replace(/\.wcc$/, '.js'));
|
|
27
27
|
const outDir = dirname(outPath);
|
|
28
28
|
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
29
29
|
writeFileSync(outPath, output);
|
|
@@ -49,9 +49,8 @@ function discoverFiles(dir) {
|
|
|
49
49
|
for (const entry of entries) {
|
|
50
50
|
if (!entry.isFile()) continue;
|
|
51
51
|
const ext = extname(entry.name);
|
|
52
|
-
if (ext !== '.
|
|
52
|
+
if (ext !== '.wcc') continue;
|
|
53
53
|
if (entry.name.includes('.test.')) continue;
|
|
54
|
-
if (entry.name.endsWith('.d.ts')) continue;
|
|
55
54
|
const fullPath = resolve(dir, entry.parentPath ? relative(dir, entry.parentPath) : '', entry.name);
|
|
56
55
|
results.push(fullPath);
|
|
57
56
|
}
|
|
@@ -68,24 +67,25 @@ async function main() {
|
|
|
68
67
|
} else if (command === 'dev') {
|
|
69
68
|
await build(config, cwd);
|
|
70
69
|
const outputDir = resolve(cwd, config.output);
|
|
71
|
-
startDevServer({ port: config.port, root: cwd, outputDir });
|
|
70
|
+
const devServer = startDevServer({ port: config.port, root: cwd, outputDir });
|
|
72
71
|
const inputDir = resolve(cwd, config.input);
|
|
73
72
|
console.log(`Watching ${inputDir} for changes...`);
|
|
74
73
|
watch(inputDir, { recursive: true }, async (eventType, filename) => {
|
|
75
74
|
if (!filename) return;
|
|
76
75
|
const ext = extname(filename);
|
|
77
|
-
if (ext !== '.ts' && ext !== '.js') return;
|
|
76
|
+
if (ext !== '.ts' && ext !== '.js' && ext !== '.wcc') return;
|
|
78
77
|
if (filename.includes('.test.')) return;
|
|
79
78
|
const filePath = resolve(inputDir, filename);
|
|
80
79
|
try {
|
|
81
80
|
const output = await compile(filePath);
|
|
82
|
-
const outPath = resolve(outputDir, filename.replace(/\.ts$/, '.js'));
|
|
81
|
+
const outPath = resolve(outputDir, filename.replace(/\.ts$/, '.js').replace(/\.wcc$/, '.js'));
|
|
83
82
|
const outDir = dirname(outPath);
|
|
84
83
|
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
85
84
|
writeFileSync(outPath, output);
|
|
86
85
|
console.log(`Compiled: ${filename}`);
|
|
87
86
|
} catch (err) {
|
|
88
87
|
console.error(`Error compiling ${filename}: ${err.message}`);
|
|
88
|
+
devServer.notifyError(`${filename}\n\n${err.message}`);
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
91
|
} else {
|
package/bin/wcc.test.js
CHANGED
|
@@ -24,37 +24,37 @@ describe('wcc CLI', () => {
|
|
|
24
24
|
const srcDir = join(tmpDir, dir);
|
|
25
25
|
if (!existsSync(srcDir)) mkdirSync(srcDir, { recursive: true });
|
|
26
26
|
|
|
27
|
-
// Write a minimal component
|
|
28
|
-
|
|
27
|
+
// Write a minimal .wcc SFC component
|
|
28
|
+
const sfcContent = `<script>
|
|
29
29
|
import { defineComponent, signal } from 'wcc'
|
|
30
30
|
|
|
31
|
-
export default defineComponent({
|
|
32
|
-
tag: '${name}',
|
|
33
|
-
template: './${name}.html',
|
|
34
|
-
})
|
|
31
|
+
export default defineComponent({ tag: '${name}' })
|
|
35
32
|
|
|
36
33
|
const count = signal(0)
|
|
37
34
|
|
|
38
35
|
function increment() {
|
|
39
36
|
count.set(count() + 1)
|
|
40
37
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<div>{{count()}}</div>
|
|
42
|
+
</template>
|
|
43
|
+
`;
|
|
44
|
+
writeFileSync(join(srcDir, `${name}.wcc`), sfcContent);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
function writeConfig(config) {
|
|
47
48
|
writeFileSync(join(tmpDir, 'wcc.config.js'), `export default ${JSON.stringify(config)};\n`);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
it('discovers .
|
|
51
|
+
it('discovers .wcc files, excludes *.test.* files', () => {
|
|
51
52
|
// Create various files
|
|
52
53
|
writeComponent('wcc-counter');
|
|
53
54
|
writeFileSync(join(tmpDir, 'src', 'helper.test.js'), 'test file');
|
|
54
|
-
writeFileSync(join(tmpDir, 'src', 'types.d.ts'), 'declare module "x" {}');
|
|
55
55
|
writeFileSync(join(tmpDir, 'src', 'readme.md'), '# readme');
|
|
56
56
|
|
|
57
|
-
// Run build — it should only compile wcc-counter.
|
|
57
|
+
// Run build — it should only compile wcc-counter.wcc (not test or md files)
|
|
58
58
|
const result = execFileSync('node', [cliPath, 'build'], {
|
|
59
59
|
cwd: tmpDir,
|
|
60
60
|
encoding: 'utf-8',
|
|
@@ -63,9 +63,8 @@ function increment() {
|
|
|
63
63
|
|
|
64
64
|
// Check that output was created
|
|
65
65
|
expect(existsSync(join(tmpDir, 'dist', 'wcc-counter.js'))).toBe(true);
|
|
66
|
-
// Check that test/
|
|
66
|
+
// Check that test/md files were NOT compiled
|
|
67
67
|
expect(existsSync(join(tmpDir, 'dist', 'helper.test.js'))).toBe(false);
|
|
68
|
-
expect(existsSync(join(tmpDir, 'dist', 'types.d.ts'))).toBe(false);
|
|
69
68
|
expect(existsSync(join(tmpDir, 'dist', 'readme.md'))).toBe(false);
|
|
70
69
|
});
|
|
71
70
|
|
|
@@ -87,8 +86,16 @@ function increment() {
|
|
|
87
86
|
});
|
|
88
87
|
|
|
89
88
|
it('exits with non-zero code on compilation error', () => {
|
|
90
|
-
// Write an invalid component (no defineComponent)
|
|
91
|
-
|
|
89
|
+
// Write an invalid .wcc component (no defineComponent)
|
|
90
|
+
const badSfc = `<script>
|
|
91
|
+
const x = 1;
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<template>
|
|
95
|
+
<div>hello</div>
|
|
96
|
+
</template>
|
|
97
|
+
`;
|
|
98
|
+
writeFileSync(join(tmpDir, 'src', 'bad.wcc'), badSfc);
|
|
92
99
|
|
|
93
100
|
try {
|
|
94
101
|
execFileSync('node', [cliPath, 'build'], {
|