@sprlab/wccompiler 0.13.0 → 0.15.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 +2078 -2074
- package/lib/compiler-browser.js +545 -545
- package/lib/compiler.js +483 -479
- 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 -114
- package/lib/tree-walker.js +1013 -1013
- package/lib/types.js +262 -262
- 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/lib/compiler.js
CHANGED
|
@@ -1,479 +1,483 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compiler — orchestrates the full compilation pipeline for wcCompiler v2.
|
|
3
|
-
*
|
|
4
|
-
* Pipeline: parse SFC → jsdom template → tree-walk → codegen
|
|
5
|
-
*
|
|
6
|
-
* Takes a .wcc file path and produces a self-contained JavaScript web component string.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { parseHTML } from 'linkedom';
|
|
10
|
-
import { readFileSync } from 'node:fs';
|
|
11
|
-
import { basename } from 'node:path';
|
|
12
|
-
import { walkTree, processIfChains, processForBlocks, processDynamicComponents, recomputeAnchorPath, detectRefs } from './tree-walker.js';
|
|
13
|
-
import { generateComponent } from './codegen.js';
|
|
14
|
-
import { parseSFC } from './sfc-parser.js';
|
|
15
|
-
import {
|
|
16
|
-
stripMacroImport,
|
|
17
|
-
toClassName,
|
|
18
|
-
camelToKebab,
|
|
19
|
-
extractPropsGeneric,
|
|
20
|
-
extractPropsArray,
|
|
21
|
-
extractPropsDefaults,
|
|
22
|
-
extractPropsObjectName,
|
|
23
|
-
extractEmitsFromCallSignatures,
|
|
24
|
-
extractEmits,
|
|
25
|
-
extractEmitsObjectName,
|
|
26
|
-
extractEmitsObjectNameFromGeneric,
|
|
27
|
-
extractSignals,
|
|
28
|
-
extractComputeds,
|
|
29
|
-
extractEffects,
|
|
30
|
-
extractWatchers,
|
|
31
|
-
extractFunctions,
|
|
32
|
-
extractLifecycleHooks,
|
|
33
|
-
extractRefs,
|
|
34
|
-
extractConstants,
|
|
35
|
-
extractExpose,
|
|
36
|
-
extractModels,
|
|
37
|
-
validatePropsAssignment,
|
|
38
|
-
validateDuplicateProps,
|
|
39
|
-
validatePropsConflicts,
|
|
40
|
-
validateEmitsAssignment,
|
|
41
|
-
validateDuplicateEmits,
|
|
42
|
-
validateEmitsConflicts,
|
|
43
|
-
validateUndeclaredEmits,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* @param {
|
|
58
|
-
* @
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
let
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (ch === '
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (ch === '
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
rootEl
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
parseResult.
|
|
414
|
-
parseResult.
|
|
415
|
-
parseResult.
|
|
416
|
-
parseResult.
|
|
417
|
-
parseResult.
|
|
418
|
-
parseResult.
|
|
419
|
-
parseResult.
|
|
420
|
-
parseResult.
|
|
421
|
-
|
|
422
|
-
parseResult.
|
|
423
|
-
parseResult.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
*
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Compiler — orchestrates the full compilation pipeline for wcCompiler v2.
|
|
3
|
+
*
|
|
4
|
+
* Pipeline: parse SFC → jsdom template → tree-walk → codegen
|
|
5
|
+
*
|
|
6
|
+
* Takes a .wcc file path and produces a self-contained JavaScript web component string.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { parseHTML } from 'linkedom';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
import { basename } from 'node:path';
|
|
12
|
+
import { walkTree, processIfChains, processForBlocks, processDynamicComponents, recomputeAnchorPath, detectRefs } from './tree-walker.js';
|
|
13
|
+
import { generateComponent } from './codegen.js';
|
|
14
|
+
import { parseSFC } from './sfc-parser.js';
|
|
15
|
+
import {
|
|
16
|
+
stripMacroImport,
|
|
17
|
+
toClassName,
|
|
18
|
+
camelToKebab,
|
|
19
|
+
extractPropsGeneric,
|
|
20
|
+
extractPropsArray,
|
|
21
|
+
extractPropsDefaults,
|
|
22
|
+
extractPropsObjectName,
|
|
23
|
+
extractEmitsFromCallSignatures,
|
|
24
|
+
extractEmits,
|
|
25
|
+
extractEmitsObjectName,
|
|
26
|
+
extractEmitsObjectNameFromGeneric,
|
|
27
|
+
extractSignals,
|
|
28
|
+
extractComputeds,
|
|
29
|
+
extractEffects,
|
|
30
|
+
extractWatchers,
|
|
31
|
+
extractFunctions,
|
|
32
|
+
extractLifecycleHooks,
|
|
33
|
+
extractRefs,
|
|
34
|
+
extractConstants,
|
|
35
|
+
extractExpose,
|
|
36
|
+
extractModels,
|
|
37
|
+
validatePropsAssignment,
|
|
38
|
+
validateDuplicateProps,
|
|
39
|
+
validatePropsConflicts,
|
|
40
|
+
validateEmitsAssignment,
|
|
41
|
+
validateDuplicateEmits,
|
|
42
|
+
validateEmitsConflicts,
|
|
43
|
+
validateUndeclaredEmits,
|
|
44
|
+
validateNameCollisions,
|
|
45
|
+
} from './parser-extractors.js';
|
|
46
|
+
import { stripTypes } from './parser.js';
|
|
47
|
+
import { normalizeTemplate, pascalToKebab } from './template-normalizer.js';
|
|
48
|
+
import { extractWccImports } from './import-resolver.js';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Compile a single .wcc SFC file into a self-contained JS component.
|
|
52
|
+
*
|
|
53
|
+
* Reads the file, parses the SFC blocks, extracts reactive declarations
|
|
54
|
+
* from the script block using parser-extractors.js, and processes template
|
|
55
|
+
* and style through the existing pipeline (jsdom → tree-walker → codegen).
|
|
56
|
+
*
|
|
57
|
+
* @param {string} filePath — Absolute or relative path to the .wcc file
|
|
58
|
+
* @param {object} [config] — Optional config (reserved for future options)
|
|
59
|
+
* @returns {Promise<string>} The generated JavaScript component code
|
|
60
|
+
*/
|
|
61
|
+
async function compileSFC(filePath, config) {
|
|
62
|
+
// 1. Read and parse the SFC file
|
|
63
|
+
const rawSource = readFileSync(filePath, 'utf-8');
|
|
64
|
+
const fileName = basename(filePath);
|
|
65
|
+
const descriptor = parseSFC(rawSource, fileName);
|
|
66
|
+
|
|
67
|
+
// 2. Process script block — mirrors parser.js logic
|
|
68
|
+
let source = stripMacroImport(descriptor.script);
|
|
69
|
+
|
|
70
|
+
// 2b. Extract .wcc imports using the import resolver
|
|
71
|
+
const wccImports = extractWccImports(source, fileName);
|
|
72
|
+
|
|
73
|
+
// Build importMap: PascalCase identifier → kebab-case tag
|
|
74
|
+
/** @type {Map<string, string>} */
|
|
75
|
+
const importMap = new Map();
|
|
76
|
+
for (const imp of wccImports.named) {
|
|
77
|
+
importMap.set(imp.identifier, pascalToKebab(imp.identifier));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Build childImports from extracted imports
|
|
81
|
+
/** @type {import('./types.js').ChildComponentImport[]} */
|
|
82
|
+
const childImports = [];
|
|
83
|
+
for (const imp of wccImports.named) {
|
|
84
|
+
childImports.push({
|
|
85
|
+
tag: pascalToKebab(imp.identifier),
|
|
86
|
+
identifier: imp.identifier,
|
|
87
|
+
importPath: imp.compiledPath,
|
|
88
|
+
sideEffect: false,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
for (const imp of wccImports.sideEffect) {
|
|
92
|
+
childImports.push({
|
|
93
|
+
tag: '',
|
|
94
|
+
identifier: '',
|
|
95
|
+
importPath: imp.compiledPath,
|
|
96
|
+
sideEffect: true,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Use strippedSource (with .wcc imports removed) for subsequent extraction steps
|
|
101
|
+
source = wccImports.strippedSource;
|
|
102
|
+
|
|
103
|
+
// 3. Extract props/emits from generic forms BEFORE type stripping
|
|
104
|
+
const propsFromGeneric = extractPropsGeneric(source);
|
|
105
|
+
const propsObjectNameFromGeneric = extractPropsObjectName(source);
|
|
106
|
+
const emitsFromCallSignatures = extractEmitsFromCallSignatures(source);
|
|
107
|
+
const emitsObjectNameFromGeneric = extractEmitsObjectNameFromGeneric(source);
|
|
108
|
+
|
|
109
|
+
// 4. Validate props/emits assignment (before type strip)
|
|
110
|
+
validatePropsAssignment(source, filePath);
|
|
111
|
+
validateEmitsAssignment(source, filePath);
|
|
112
|
+
|
|
113
|
+
// 5. Strip TypeScript types if lang === 'ts'
|
|
114
|
+
if (descriptor.lang === 'ts') {
|
|
115
|
+
source = await stripTypes(source);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 6. Extract component metadata
|
|
119
|
+
const tagName = descriptor.tag;
|
|
120
|
+
const className = toClassName(tagName);
|
|
121
|
+
const template = descriptor.template;
|
|
122
|
+
const style = descriptor.style;
|
|
123
|
+
|
|
124
|
+
// 7. Extract lifecycle hooks (before other extractions)
|
|
125
|
+
const { onMountHooks, onDestroyHooks, onAdoptHooks } = extractLifecycleHooks(source);
|
|
126
|
+
|
|
127
|
+
// 7b. Strip lifecycle/watcher blocks from source for extraction
|
|
128
|
+
let sourceForExtraction = source;
|
|
129
|
+
const hookLinePattern = /\bonMount\s*\(|\bonDestroy\s*\(|\bonAdopt\s*\(|\bwatch\s*\(/;
|
|
130
|
+
const sourceLines = sourceForExtraction.split('\n');
|
|
131
|
+
const filteredLines = [];
|
|
132
|
+
let skipDepth = 0;
|
|
133
|
+
let skipping = false;
|
|
134
|
+
for (const line of sourceLines) {
|
|
135
|
+
if (!skipping && hookLinePattern.test(line)) {
|
|
136
|
+
skipping = true;
|
|
137
|
+
skipDepth = 0;
|
|
138
|
+
for (const ch of line) {
|
|
139
|
+
if (ch === '{') skipDepth++;
|
|
140
|
+
if (ch === '}') skipDepth--;
|
|
141
|
+
}
|
|
142
|
+
if (skipDepth <= 0) skipping = false;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (skipping) {
|
|
146
|
+
for (const ch of line) {
|
|
147
|
+
if (ch === '{') skipDepth++;
|
|
148
|
+
if (ch === '}') skipDepth--;
|
|
149
|
+
}
|
|
150
|
+
if (skipDepth <= 0) skipping = false;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
filteredLines.push(line);
|
|
154
|
+
}
|
|
155
|
+
sourceForExtraction = filteredLines.join('\n');
|
|
156
|
+
|
|
157
|
+
// 8. Extract reactive declarations and functions
|
|
158
|
+
const signals = extractSignals(sourceForExtraction);
|
|
159
|
+
const computeds = extractComputeds(sourceForExtraction);
|
|
160
|
+
const effects = extractEffects(sourceForExtraction);
|
|
161
|
+
const watchers = extractWatchers(source);
|
|
162
|
+
const methods = extractFunctions(sourceForExtraction);
|
|
163
|
+
const refs = extractRefs(sourceForExtraction);
|
|
164
|
+
const constantVars = extractConstants(sourceForExtraction);
|
|
165
|
+
const exposeNames = extractExpose(source);
|
|
166
|
+
const modelDefs = extractModels(sourceForExtraction);
|
|
167
|
+
|
|
168
|
+
// 9. Extract props (array form — after type strip, if generic didn't find any)
|
|
169
|
+
const propsFromArray = propsFromGeneric.length > 0 ? [] : extractPropsArray(source);
|
|
170
|
+
let propNames = propsFromGeneric.length > 0 ? propsFromGeneric : propsFromArray;
|
|
171
|
+
|
|
172
|
+
// 10. Extract props defaults
|
|
173
|
+
const propsDefaults = extractPropsDefaults(source);
|
|
174
|
+
if (propNames.length === 0 && Object.keys(propsDefaults).length > 0) {
|
|
175
|
+
propNames = Object.keys(propsDefaults);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 11. Extract propsObjectName
|
|
179
|
+
const propsObjectName = propsObjectNameFromGeneric ?? extractPropsObjectName(source);
|
|
180
|
+
|
|
181
|
+
// 12. Validate props
|
|
182
|
+
validateDuplicateProps(propNames, filePath);
|
|
183
|
+
const signalNameSet = new Set(signals.map(s => s.name));
|
|
184
|
+
const computedNameSet = new Set(computeds.map(c => c.name));
|
|
185
|
+
const constantNameSet = new Set(constantVars.map(v => v.name));
|
|
186
|
+
validatePropsConflicts(propsObjectName, signalNameSet, computedNameSet, constantNameSet, filePath);
|
|
187
|
+
|
|
188
|
+
/** @type {import('./types.js').PropDef[]} */
|
|
189
|
+
const propDefs = propNames.map(name => ({
|
|
190
|
+
name,
|
|
191
|
+
default: propsDefaults[name] ?? 'undefined',
|
|
192
|
+
attrName: camelToKebab(name),
|
|
193
|
+
}));
|
|
194
|
+
|
|
195
|
+
// 13. Extract emits
|
|
196
|
+
const emitsFromArray = emitsFromCallSignatures.length > 0 ? [] : extractEmits(source);
|
|
197
|
+
const emitNames = emitsFromCallSignatures.length > 0 ? emitsFromCallSignatures : emitsFromArray;
|
|
198
|
+
const emitsObjectName = emitsObjectNameFromGeneric ?? extractEmitsObjectName(source);
|
|
199
|
+
|
|
200
|
+
// 14. Validate emits
|
|
201
|
+
validateDuplicateEmits(emitNames, filePath);
|
|
202
|
+
const propNameSet = new Set(propNames);
|
|
203
|
+
validateEmitsConflicts(emitsObjectName, signalNameSet, computedNameSet, constantNameSet, propNameSet, propsObjectName, filePath);
|
|
204
|
+
validateUndeclaredEmits(source, emitsObjectName, emitNames, filePath);
|
|
205
|
+
|
|
206
|
+
// 14b. Validate name collisions between signals/computeds/props and methods
|
|
207
|
+
validateNameCollisions(signalNameSet, computedNameSet, propNameSet, methods, filePath);
|
|
208
|
+
|
|
209
|
+
// 14c. Validate defineModel declarations
|
|
210
|
+
// MODEL_NO_ASSIGNMENT: detect bare defineModel() calls not assigned to a variable
|
|
211
|
+
const bareModelRe = /\bdefineModel\s*\(/g;
|
|
212
|
+
const assignedModelRe = /(?:const|let|var)\s+\w+\s*=\s*defineModel\s*\(/g;
|
|
213
|
+
const bareModelCount = (sourceForExtraction.match(bareModelRe) || []).length;
|
|
214
|
+
const assignedModelCount = (sourceForExtraction.match(assignedModelRe) || []).length;
|
|
215
|
+
if (bareModelCount > assignedModelCount) {
|
|
216
|
+
const error = new Error(`defineModel() must be assigned to a variable`);
|
|
217
|
+
/** @ts-expect-error — custom error code */
|
|
218
|
+
error.code = 'MODEL_NO_ASSIGNMENT';
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// MODEL_MISSING_NAME: check each extracted model has a name property
|
|
223
|
+
for (const md of modelDefs) {
|
|
224
|
+
if (!md.name) {
|
|
225
|
+
const error = new Error(`defineModel() requires a 'name' property in the options object`);
|
|
226
|
+
/** @ts-expect-error — custom error code */
|
|
227
|
+
error.code = 'MODEL_MISSING_NAME';
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// MODEL_NAME_CONFLICT: check model prop names against signals, computeds, constants, and props
|
|
233
|
+
for (const md of modelDefs) {
|
|
234
|
+
if (!md.name) continue;
|
|
235
|
+
if (signalNameSet.has(md.name)) {
|
|
236
|
+
const error = new Error(`defineModel prop '${md.name}' conflicts with existing signal '${md.name}'`);
|
|
237
|
+
/** @ts-expect-error — custom error code */
|
|
238
|
+
error.code = 'MODEL_NAME_CONFLICT';
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
if (computedNameSet.has(md.name)) {
|
|
242
|
+
const error = new Error(`defineModel prop '${md.name}' conflicts with existing computed '${md.name}'`);
|
|
243
|
+
/** @ts-expect-error — custom error code */
|
|
244
|
+
error.code = 'MODEL_NAME_CONFLICT';
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
if (constantNameSet.has(md.name)) {
|
|
248
|
+
const error = new Error(`defineModel prop '${md.name}' conflicts with existing constant '${md.name}'`);
|
|
249
|
+
/** @ts-expect-error — custom error code */
|
|
250
|
+
error.code = 'MODEL_NAME_CONFLICT';
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
if (propNameSet.has(md.name)) {
|
|
254
|
+
const error = new Error(`defineModel prop '${md.name}' conflicts with existing prop '${md.name}'`);
|
|
255
|
+
/** @ts-expect-error — custom error code */
|
|
256
|
+
error.code = 'MODEL_NAME_CONFLICT';
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 15. Build initial ParseResult
|
|
262
|
+
/** @type {import('./types.js').ParseResult} */
|
|
263
|
+
const parseResult = {
|
|
264
|
+
tagName,
|
|
265
|
+
className,
|
|
266
|
+
template,
|
|
267
|
+
style,
|
|
268
|
+
signals,
|
|
269
|
+
computeds,
|
|
270
|
+
effects,
|
|
271
|
+
constantVars,
|
|
272
|
+
watchers,
|
|
273
|
+
methods,
|
|
274
|
+
propDefs,
|
|
275
|
+
propsObjectName: propsObjectName ?? null,
|
|
276
|
+
emits: emitNames,
|
|
277
|
+
emitsObjectName: emitsObjectName ?? null,
|
|
278
|
+
bindings: [],
|
|
279
|
+
events: [],
|
|
280
|
+
processedTemplate: null,
|
|
281
|
+
ifBlocks: [],
|
|
282
|
+
showBindings: [],
|
|
283
|
+
forBlocks: [],
|
|
284
|
+
onMountHooks,
|
|
285
|
+
onDestroyHooks,
|
|
286
|
+
onAdoptHooks,
|
|
287
|
+
modelBindings: [],
|
|
288
|
+
modelPropBindings: [],
|
|
289
|
+
attrBindings: [],
|
|
290
|
+
slots: [],
|
|
291
|
+
refs,
|
|
292
|
+
refBindings: [],
|
|
293
|
+
childComponents: [],
|
|
294
|
+
childImports: [],
|
|
295
|
+
exposeNames,
|
|
296
|
+
modelDefs,
|
|
297
|
+
dynamicComponents: [],
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// 16. Process template through linkedom → tree-walker → codegen
|
|
301
|
+
const normalizedTemplate = normalizeTemplate(template, { importMap, fileName });
|
|
302
|
+
const { document } = parseHTML(`<div id="__root">${normalizedTemplate}</div>`);
|
|
303
|
+
const rootEl = document.getElementById('__root');
|
|
304
|
+
|
|
305
|
+
const signalNames = new Set(signals.map(s => s.name));
|
|
306
|
+
// Add model var names so they are recognized as writable signals in tree-walker
|
|
307
|
+
for (const md of modelDefs) {
|
|
308
|
+
signalNames.add(md.varName);
|
|
309
|
+
}
|
|
310
|
+
const computedNames = new Set(computeds.map(c => c.name));
|
|
311
|
+
const propNamesSet = new Set(propDefs.map(p => p.name));
|
|
312
|
+
|
|
313
|
+
const forBlocks = processForBlocks(rootEl, [], signalNames, computedNames, propNamesSet);
|
|
314
|
+
const ifBlocks = processIfChains(rootEl, [], signalNames, computedNames, propNamesSet);
|
|
315
|
+
const dynamicComponents = processDynamicComponents(rootEl, []);
|
|
316
|
+
|
|
317
|
+
rootEl.normalize();
|
|
318
|
+
|
|
319
|
+
for (const fb of forBlocks) {
|
|
320
|
+
fb.anchorPath = recomputeAnchorPath(rootEl, fb._anchorNode);
|
|
321
|
+
}
|
|
322
|
+
for (const ib of ifBlocks) {
|
|
323
|
+
ib.anchorPath = recomputeAnchorPath(rootEl, ib._anchorNode);
|
|
324
|
+
}
|
|
325
|
+
for (const dc of dynamicComponents) {
|
|
326
|
+
dc.anchorPath = recomputeAnchorPath(rootEl, dc._anchorNode);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const { bindings, events, showBindings, modelBindings, modelPropBindings, attrBindings, slots, childComponents } = walkTree(rootEl, signalNames, computedNames, propNamesSet);
|
|
330
|
+
|
|
331
|
+
const refBindings = detectRefs(rootEl);
|
|
332
|
+
|
|
333
|
+
// 17. Validate refs
|
|
334
|
+
for (const decl of refs) {
|
|
335
|
+
if (!refBindings.find(b => b.refName === decl.refName)) {
|
|
336
|
+
const error = new Error(`templateRef('${decl.refName}') has no matching ref="${decl.refName}" in template`);
|
|
337
|
+
/** @ts-expect-error — custom error code */
|
|
338
|
+
error.code = 'REF_NOT_FOUND';
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
for (const binding of refBindings) {
|
|
343
|
+
if (!refs.find(d => d.refName === binding.refName)) {
|
|
344
|
+
console.warn(`Warning: ref="${binding.refName}" in template has no matching templateRef('${binding.refName}') in script`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// 17b. Validate model bindings
|
|
349
|
+
const constantNamesForModel = new Set(constantVars.map(v => v.name));
|
|
350
|
+
for (const mb of modelBindings) {
|
|
351
|
+
if (propNamesSet.has(mb.signal)) {
|
|
352
|
+
const error = new Error(`model cannot bind to prop '${mb.signal}' (read-only)`);
|
|
353
|
+
/** @ts-expect-error — custom error code */
|
|
354
|
+
error.code = 'MODEL_READONLY';
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
if (computedNames.has(mb.signal)) {
|
|
358
|
+
const error = new Error(`model cannot bind to computed '${mb.signal}' (read-only)`);
|
|
359
|
+
/** @ts-expect-error — custom error code */
|
|
360
|
+
error.code = 'MODEL_READONLY';
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
if (constantNamesForModel.has(mb.signal)) {
|
|
364
|
+
const error = new Error(`model cannot bind to constant '${mb.signal}' (read-only)`);
|
|
365
|
+
/** @ts-expect-error — custom error code */
|
|
366
|
+
error.code = 'MODEL_READONLY';
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
if (!signalNames.has(mb.signal)) {
|
|
370
|
+
const error = new Error(`model references undeclared variable '${mb.signal}'`);
|
|
371
|
+
/** @ts-expect-error — custom error code */
|
|
372
|
+
error.code = 'MODEL_UNKNOWN_VAR';
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 17c. Validate model:propName bindings
|
|
378
|
+
for (const mpb of modelPropBindings) {
|
|
379
|
+
const name = mpb.signal;
|
|
380
|
+
// Check if the referenced variable exists at all
|
|
381
|
+
const isKnown = signalNames.has(name) || computedNames.has(name) || propNamesSet.has(name) || constantNamesForModel.has(name);
|
|
382
|
+
if (!isKnown) {
|
|
383
|
+
const error = new Error(`model:propName references undeclared variable '${name}'`);
|
|
384
|
+
/** @ts-expect-error — custom error code */
|
|
385
|
+
error.code = 'MODEL_PROP_UNKNOWN_VAR';
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
// Check if the referenced variable is read-only
|
|
389
|
+
if (propNamesSet.has(name)) {
|
|
390
|
+
const error = new Error(`model:propName cannot bind to prop '${name}' (read-only)`);
|
|
391
|
+
/** @ts-expect-error — custom error code */
|
|
392
|
+
error.code = 'MODEL_PROP_READONLY';
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
if (computedNames.has(name)) {
|
|
396
|
+
const error = new Error(`model:propName cannot bind to computed '${name}' (read-only)`);
|
|
397
|
+
/** @ts-expect-error — custom error code */
|
|
398
|
+
error.code = 'MODEL_PROP_READONLY';
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
if (constantNamesForModel.has(name)) {
|
|
402
|
+
const error = new Error(`model:propName cannot bind to constant '${name}' (read-only)`);
|
|
403
|
+
/** @ts-expect-error — custom error code */
|
|
404
|
+
error.code = 'MODEL_PROP_READONLY';
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// 18. Child imports already built from extractWccImports (step 2b)
|
|
410
|
+
// No filesystem scanning needed — imports are explicit
|
|
411
|
+
|
|
412
|
+
// 19. Merge tree-walker results into ParseResult
|
|
413
|
+
parseResult.bindings = bindings;
|
|
414
|
+
parseResult.events = events;
|
|
415
|
+
parseResult.showBindings = showBindings;
|
|
416
|
+
parseResult.modelBindings = modelBindings;
|
|
417
|
+
parseResult.modelPropBindings = modelPropBindings;
|
|
418
|
+
parseResult.attrBindings = attrBindings;
|
|
419
|
+
parseResult.ifBlocks = ifBlocks;
|
|
420
|
+
parseResult.forBlocks = forBlocks;
|
|
421
|
+
parseResult.slots = slots;
|
|
422
|
+
parseResult.refBindings = refBindings;
|
|
423
|
+
parseResult.childComponents = childComponents;
|
|
424
|
+
parseResult.dynamicComponents = dynamicComponents;
|
|
425
|
+
|
|
426
|
+
parseResult.childImports = childImports;
|
|
427
|
+
parseResult.processedTemplate = rootEl.innerHTML;
|
|
428
|
+
|
|
429
|
+
// 20. Resolve standalone and generate component
|
|
430
|
+
const standaloneResolved = resolveStandalone(descriptor.standalone, config?.standalone ?? false);
|
|
431
|
+
const genOptions = { ...config, sourceFile: fileName };
|
|
432
|
+
|
|
433
|
+
if (standaloneResolved) {
|
|
434
|
+
// Force inline runtime — ignore any runtimeImportPath
|
|
435
|
+
genOptions.runtimeImportPath = undefined;
|
|
436
|
+
}
|
|
437
|
+
// If standaloneResolved is false, keep config.runtimeImportPath as-is (CLI provides it)
|
|
438
|
+
|
|
439
|
+
const code = generateComponent(parseResult, genOptions);
|
|
440
|
+
const usesSharedRuntime = !standaloneResolved && !!genOptions.runtimeImportPath;
|
|
441
|
+
return { code, usesSharedRuntime };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Resolve the final standalone value.
|
|
446
|
+
* Component-level has priority over global.
|
|
447
|
+
*
|
|
448
|
+
* @param {boolean | undefined} componentValue — standalone from defineComponent (true, false, or undefined)
|
|
449
|
+
* @param {boolean} globalValue — standalone from config (true or false)
|
|
450
|
+
* @returns {boolean}
|
|
451
|
+
*/
|
|
452
|
+
export function resolveStandalone(componentValue, globalValue) {
|
|
453
|
+
if (componentValue === true || componentValue === false) return componentValue;
|
|
454
|
+
return globalValue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Compile a single .wcc SFC file into a self-contained JS component.
|
|
459
|
+
*
|
|
460
|
+
* @param {string} filePath — Absolute or relative path to the .wcc file
|
|
461
|
+
* @param {object} [config] — Optional config (reserved for future options)
|
|
462
|
+
* @returns {Promise<{code: string, usesSharedRuntime: boolean}>} The generated JavaScript component code and metadata
|
|
463
|
+
*/
|
|
464
|
+
export async function compile(filePath, config) {
|
|
465
|
+
const result = await compileSFC(filePath, config);
|
|
466
|
+
|
|
467
|
+
if (config?.minify) {
|
|
468
|
+
const { transform } = await import('esbuild');
|
|
469
|
+
try {
|
|
470
|
+
const minified = await transform(result.code, {
|
|
471
|
+
minify: true,
|
|
472
|
+
loader: 'js',
|
|
473
|
+
target: 'esnext',
|
|
474
|
+
});
|
|
475
|
+
result.code = minified.code;
|
|
476
|
+
} catch {
|
|
477
|
+
// If minification fails (e.g., edge-case syntax), return unminified code
|
|
478
|
+
// This is a graceful fallback — the code still works at runtime
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return result;
|
|
483
|
+
}
|