@sprlab/wccompiler 0.12.1 → 0.14.0
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 +998 -998
- package/adapters/angular-compiled/angular.d.ts +197 -197
- package/adapters/angular-compiled/angular.mjs +488 -488
- package/adapters/angular.js +54 -54
- package/adapters/angular.ts +630 -630
- package/adapters/react.js +114 -114
- package/adapters/vue.js +103 -103
- package/bin/wcc.js +412 -412
- package/bin/wcc.test.js +126 -126
- package/integrations/angular.js +73 -73
- package/integrations/react.js +859 -859
- package/integrations/vue.js +253 -253
- package/lib/codegen.js +2074 -2029
- package/lib/compiler-browser.js +545 -545
- package/lib/compiler.js +483 -473
- package/lib/config.js +71 -71
- package/lib/css-scoper.js +180 -180
- package/lib/dev-server.js +193 -193
- package/lib/import-resolver.js +160 -160
- package/lib/parser-extractors.js +1240 -1169
- package/lib/parser.js +273 -269
- package/lib/reactive-runtime.js +143 -143
- package/lib/sfc-parser.js +333 -333
- package/lib/template-normalizer.js +114 -109
- package/lib/tree-walker.js +1013 -923
- package/lib/types.js +262 -240
- package/lib/wcc-runtime.js +68 -68
- package/package.json +85 -85
- package/types/wcc.d.ts +28 -28
- package/types/wcc.test.js +46 -46
package/bin/wcc.test.js
CHANGED
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { execFileSync, execFile } from 'node:child_process';
|
|
3
|
-
import { mkdirSync, writeFileSync, rmSync, existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
4
|
-
import { resolve, join } from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
|
|
7
|
-
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
8
|
-
const cliPath = resolve(__dirname, 'wcc.js');
|
|
9
|
-
|
|
10
|
-
describe('wcc CLI', () => {
|
|
11
|
-
const tmpDir = resolve(__dirname, '__tmp_cli_test__');
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true });
|
|
15
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
16
|
-
mkdirSync(join(tmpDir, 'src'), { recursive: true });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true });
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
function writeComponent(name, dir = 'src') {
|
|
24
|
-
const srcDir = join(tmpDir, dir);
|
|
25
|
-
if (!existsSync(srcDir)) mkdirSync(srcDir, { recursive: true });
|
|
26
|
-
|
|
27
|
-
// Write a minimal .wcc SFC component
|
|
28
|
-
const sfcContent = `<script>
|
|
29
|
-
import { defineComponent, signal } from 'wcc'
|
|
30
|
-
|
|
31
|
-
export default defineComponent({ tag: '${name}' })
|
|
32
|
-
|
|
33
|
-
const count = signal(0)
|
|
34
|
-
|
|
35
|
-
function increment() {
|
|
36
|
-
count.set(count() + 1)
|
|
37
|
-
}
|
|
38
|
-
</script>
|
|
39
|
-
|
|
40
|
-
<template>
|
|
41
|
-
<div>{{count()}}</div>
|
|
42
|
-
</template>
|
|
43
|
-
`;
|
|
44
|
-
writeFileSync(join(srcDir, `${name}.wcc`), sfcContent);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function writeConfig(config) {
|
|
48
|
-
writeFileSync(join(tmpDir, 'wcc.config.js'), `export default ${JSON.stringify(config)};\n`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
it('discovers .wcc files, excludes *.test.* files', () => {
|
|
52
|
-
// Create various files
|
|
53
|
-
writeComponent('wcc-counter');
|
|
54
|
-
writeFileSync(join(tmpDir, 'src', 'helper.test.js'), 'test file');
|
|
55
|
-
writeFileSync(join(tmpDir, 'src', 'readme.md'), '# readme');
|
|
56
|
-
|
|
57
|
-
// Run build — it should only compile wcc-counter.wcc (not test or md files)
|
|
58
|
-
const result = execFileSync('node', [cliPath, 'build'], {
|
|
59
|
-
cwd: tmpDir,
|
|
60
|
-
encoding: 'utf-8',
|
|
61
|
-
timeout: 30000,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Check that output was created
|
|
65
|
-
expect(existsSync(join(tmpDir, 'dist', 'wcc-counter.js'))).toBe(true);
|
|
66
|
-
// Check that test/md files were NOT compiled
|
|
67
|
-
expect(existsSync(join(tmpDir, 'dist', 'helper.test.js'))).toBe(false);
|
|
68
|
-
expect(existsSync(join(tmpDir, 'dist', 'readme.md'))).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('writes compiled output to the configured output directory', () => {
|
|
72
|
-
writeComponent('wcc-app');
|
|
73
|
-
writeConfig({ output: 'out' });
|
|
74
|
-
|
|
75
|
-
execFileSync('node', [cliPath, 'build'], {
|
|
76
|
-
cwd: tmpDir,
|
|
77
|
-
encoding: 'utf-8',
|
|
78
|
-
timeout: 30000,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
expect(existsSync(join(tmpDir, 'out', 'wcc-app.js'))).toBe(true);
|
|
82
|
-
const content = readFileSync(join(tmpDir, 'out', 'wcc-app.js'), 'utf-8');
|
|
83
|
-
// Should contain the compiled component
|
|
84
|
-
expect(content).toContain('customElements.define');
|
|
85
|
-
expect(content).toContain('wcc-app');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('exits with non-zero code on compilation error', () => {
|
|
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);
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
execFileSync('node', [cliPath, 'build'], {
|
|
102
|
-
cwd: tmpDir,
|
|
103
|
-
encoding: 'utf-8',
|
|
104
|
-
timeout: 30000,
|
|
105
|
-
});
|
|
106
|
-
// Should not reach here
|
|
107
|
-
expect.fail('Expected non-zero exit code');
|
|
108
|
-
} catch (err) {
|
|
109
|
-
expect(err.status).not.toBe(0);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('prints usage and exits with non-zero code for unknown command', () => {
|
|
114
|
-
try {
|
|
115
|
-
execFileSync('node', [cliPath, 'unknown'], {
|
|
116
|
-
cwd: tmpDir,
|
|
117
|
-
encoding: 'utf-8',
|
|
118
|
-
timeout: 30000,
|
|
119
|
-
});
|
|
120
|
-
expect.fail('Expected non-zero exit code');
|
|
121
|
-
} catch (err) {
|
|
122
|
-
expect(err.status).not.toBe(0);
|
|
123
|
-
expect(err.stderr).toContain('Usage');
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
});
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { execFileSync, execFile } from 'node:child_process';
|
|
3
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
4
|
+
import { resolve, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
8
|
+
const cliPath = resolve(__dirname, 'wcc.js');
|
|
9
|
+
|
|
10
|
+
describe('wcc CLI', () => {
|
|
11
|
+
const tmpDir = resolve(__dirname, '__tmp_cli_test__');
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true });
|
|
15
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
16
|
+
mkdirSync(join(tmpDir, 'src'), { recursive: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function writeComponent(name, dir = 'src') {
|
|
24
|
+
const srcDir = join(tmpDir, dir);
|
|
25
|
+
if (!existsSync(srcDir)) mkdirSync(srcDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
// Write a minimal .wcc SFC component
|
|
28
|
+
const sfcContent = `<script>
|
|
29
|
+
import { defineComponent, signal } from 'wcc'
|
|
30
|
+
|
|
31
|
+
export default defineComponent({ tag: '${name}' })
|
|
32
|
+
|
|
33
|
+
const count = signal(0)
|
|
34
|
+
|
|
35
|
+
function increment() {
|
|
36
|
+
count.set(count() + 1)
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<div>{{count()}}</div>
|
|
42
|
+
</template>
|
|
43
|
+
`;
|
|
44
|
+
writeFileSync(join(srcDir, `${name}.wcc`), sfcContent);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function writeConfig(config) {
|
|
48
|
+
writeFileSync(join(tmpDir, 'wcc.config.js'), `export default ${JSON.stringify(config)};\n`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
it('discovers .wcc files, excludes *.test.* files', () => {
|
|
52
|
+
// Create various files
|
|
53
|
+
writeComponent('wcc-counter');
|
|
54
|
+
writeFileSync(join(tmpDir, 'src', 'helper.test.js'), 'test file');
|
|
55
|
+
writeFileSync(join(tmpDir, 'src', 'readme.md'), '# readme');
|
|
56
|
+
|
|
57
|
+
// Run build — it should only compile wcc-counter.wcc (not test or md files)
|
|
58
|
+
const result = execFileSync('node', [cliPath, 'build'], {
|
|
59
|
+
cwd: tmpDir,
|
|
60
|
+
encoding: 'utf-8',
|
|
61
|
+
timeout: 30000,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Check that output was created
|
|
65
|
+
expect(existsSync(join(tmpDir, 'dist', 'wcc-counter.js'))).toBe(true);
|
|
66
|
+
// Check that test/md files were NOT compiled
|
|
67
|
+
expect(existsSync(join(tmpDir, 'dist', 'helper.test.js'))).toBe(false);
|
|
68
|
+
expect(existsSync(join(tmpDir, 'dist', 'readme.md'))).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('writes compiled output to the configured output directory', () => {
|
|
72
|
+
writeComponent('wcc-app');
|
|
73
|
+
writeConfig({ output: 'out' });
|
|
74
|
+
|
|
75
|
+
execFileSync('node', [cliPath, 'build'], {
|
|
76
|
+
cwd: tmpDir,
|
|
77
|
+
encoding: 'utf-8',
|
|
78
|
+
timeout: 30000,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(existsSync(join(tmpDir, 'out', 'wcc-app.js'))).toBe(true);
|
|
82
|
+
const content = readFileSync(join(tmpDir, 'out', 'wcc-app.js'), 'utf-8');
|
|
83
|
+
// Should contain the compiled component
|
|
84
|
+
expect(content).toContain('customElements.define');
|
|
85
|
+
expect(content).toContain('wcc-app');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('exits with non-zero code on compilation error', () => {
|
|
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);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
execFileSync('node', [cliPath, 'build'], {
|
|
102
|
+
cwd: tmpDir,
|
|
103
|
+
encoding: 'utf-8',
|
|
104
|
+
timeout: 30000,
|
|
105
|
+
});
|
|
106
|
+
// Should not reach here
|
|
107
|
+
expect.fail('Expected non-zero exit code');
|
|
108
|
+
} catch (err) {
|
|
109
|
+
expect(err.status).not.toBe(0);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('prints usage and exits with non-zero code for unknown command', () => {
|
|
114
|
+
try {
|
|
115
|
+
execFileSync('node', [cliPath, 'unknown'], {
|
|
116
|
+
cwd: tmpDir,
|
|
117
|
+
encoding: 'utf-8',
|
|
118
|
+
timeout: 30000,
|
|
119
|
+
});
|
|
120
|
+
expect.fail('Expected non-zero exit code');
|
|
121
|
+
} catch (err) {
|
|
122
|
+
expect(err.status).not.toBe(0);
|
|
123
|
+
expect(err.stderr).toContain('Usage');
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
package/integrations/angular.js
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Angular integration guide for WCC custom elements.
|
|
3
|
-
*
|
|
4
|
-
* @module @sprlab/wccompiler/integrations/angular
|
|
5
|
-
*
|
|
6
|
-
* Setup requires two steps:
|
|
7
|
-
*
|
|
8
|
-
* 1. Import the adapter in main.ts (enables [(prop)] two-way binding):
|
|
9
|
-
* ```ts
|
|
10
|
-
* import '@sprlab/wccompiler/adapters/angular'
|
|
11
|
-
* ```
|
|
12
|
-
*
|
|
13
|
-
* 2. Add CUSTOM_ELEMENTS_SCHEMA to your component/module:
|
|
14
|
-
*
|
|
15
|
-
* @example Standalone component (Angular 17+)
|
|
16
|
-
* ```ts
|
|
17
|
-
* import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
18
|
-
*
|
|
19
|
-
* @Component({
|
|
20
|
-
* selector: 'app-root',
|
|
21
|
-
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
22
|
-
* template: `
|
|
23
|
-
* <!-- Simple one-way binding -->
|
|
24
|
-
* <wcc-counter [count]="myCount"></wcc-counter>
|
|
25
|
-
*
|
|
26
|
-
* <!-- Two-way binding with [(prop)] -->
|
|
27
|
-
* <wcc-input [(value)]="text"></wcc-input>
|
|
28
|
-
* `
|
|
29
|
-
* })
|
|
30
|
-
* export class AppComponent {
|
|
31
|
-
* text = '';
|
|
32
|
-
* myCount = 0;
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
35
|
-
*
|
|
36
|
-
* @example NgModule approach
|
|
37
|
-
* ```ts
|
|
38
|
-
* import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
39
|
-
*
|
|
40
|
-
* @NgModule({
|
|
41
|
-
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
42
|
-
* })
|
|
43
|
-
* export class AppModule {}
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* @example Two-way binding with [(prop)]
|
|
47
|
-
* The WccModel directive translates wcc:model events to Angular's propChange convention.
|
|
48
|
-
* Angular's banana-in-a-box [(prop)] expands to:
|
|
49
|
-
* [prop]="value" (propChange)="value = $event.detail"
|
|
50
|
-
*
|
|
51
|
-
* So when the WCC component emits wcc:model with { prop: 'value', value: 'new' },
|
|
52
|
-
* the WccModel directive re-dispatches as 'valueChange' CustomEvent, which Angular picks up.
|
|
53
|
-
*
|
|
54
|
-
* @example ngModel support (requires ControlValueAccessor)
|
|
55
|
-
* For ngModel/ReactiveForms, see the WccValueAccessor guide in:
|
|
56
|
-
* @sprlab/wccompiler/adapters/angular
|
|
57
|
-
*
|
|
58
|
-
* That file contains a copy-paste TypeScript directive implementation.
|
|
59
|
-
*/
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Configuration instructions for Angular projects using WCC components.
|
|
63
|
-
* This is a documentation-only export — Angular's AOT compiler requires
|
|
64
|
-
* CUSTOM_ELEMENTS_SCHEMA to be imported directly from @angular/core.
|
|
65
|
-
*
|
|
66
|
-
* @type {{ schema: string, standalone: string, ngModule: string, adapter: string }}
|
|
67
|
-
*/
|
|
68
|
-
export const WCC_ANGULAR_CONFIG = {
|
|
69
|
-
schema: "import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'",
|
|
70
|
-
standalone: "@Component({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })",
|
|
71
|
-
ngModule: "@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })",
|
|
72
|
-
adapter: "import '@sprlab/wccompiler/adapters/angular' // in main.ts",
|
|
73
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Angular integration guide for WCC custom elements.
|
|
3
|
+
*
|
|
4
|
+
* @module @sprlab/wccompiler/integrations/angular
|
|
5
|
+
*
|
|
6
|
+
* Setup requires two steps:
|
|
7
|
+
*
|
|
8
|
+
* 1. Import the adapter in main.ts (enables [(prop)] two-way binding):
|
|
9
|
+
* ```ts
|
|
10
|
+
* import '@sprlab/wccompiler/adapters/angular'
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* 2. Add CUSTOM_ELEMENTS_SCHEMA to your component/module:
|
|
14
|
+
*
|
|
15
|
+
* @example Standalone component (Angular 17+)
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
18
|
+
*
|
|
19
|
+
* @Component({
|
|
20
|
+
* selector: 'app-root',
|
|
21
|
+
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
22
|
+
* template: `
|
|
23
|
+
* <!-- Simple one-way binding -->
|
|
24
|
+
* <wcc-counter [count]="myCount"></wcc-counter>
|
|
25
|
+
*
|
|
26
|
+
* <!-- Two-way binding with [(prop)] -->
|
|
27
|
+
* <wcc-input [(value)]="text"></wcc-input>
|
|
28
|
+
* `
|
|
29
|
+
* })
|
|
30
|
+
* export class AppComponent {
|
|
31
|
+
* text = '';
|
|
32
|
+
* myCount = 0;
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example NgModule approach
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
39
|
+
*
|
|
40
|
+
* @NgModule({
|
|
41
|
+
* schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
42
|
+
* })
|
|
43
|
+
* export class AppModule {}
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example Two-way binding with [(prop)]
|
|
47
|
+
* The WccModel directive translates wcc:model events to Angular's propChange convention.
|
|
48
|
+
* Angular's banana-in-a-box [(prop)] expands to:
|
|
49
|
+
* [prop]="value" (propChange)="value = $event.detail"
|
|
50
|
+
*
|
|
51
|
+
* So when the WCC component emits wcc:model with { prop: 'value', value: 'new' },
|
|
52
|
+
* the WccModel directive re-dispatches as 'valueChange' CustomEvent, which Angular picks up.
|
|
53
|
+
*
|
|
54
|
+
* @example ngModel support (requires ControlValueAccessor)
|
|
55
|
+
* For ngModel/ReactiveForms, see the WccValueAccessor guide in:
|
|
56
|
+
* @sprlab/wccompiler/adapters/angular
|
|
57
|
+
*
|
|
58
|
+
* That file contains a copy-paste TypeScript directive implementation.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Configuration instructions for Angular projects using WCC components.
|
|
63
|
+
* This is a documentation-only export — Angular's AOT compiler requires
|
|
64
|
+
* CUSTOM_ELEMENTS_SCHEMA to be imported directly from @angular/core.
|
|
65
|
+
*
|
|
66
|
+
* @type {{ schema: string, standalone: string, ngModule: string, adapter: string }}
|
|
67
|
+
*/
|
|
68
|
+
export const WCC_ANGULAR_CONFIG = {
|
|
69
|
+
schema: "import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'",
|
|
70
|
+
standalone: "@Component({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })",
|
|
71
|
+
ngModule: "@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })",
|
|
72
|
+
adapter: "import '@sprlab/wccompiler/adapters/angular' // in main.ts",
|
|
73
|
+
}
|