sinwan-compiler 0.2.1 → 0.2.3
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/dist/auto-cc.d.ts +34 -0
- package/dist/{chunk-tvnem5mj.js → chunk-w580fdrf.js} +598 -85
- package/dist/cli.js +1 -1
- package/dist/exports.d.ts +27 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +142 -32
- package/dist/reactive-wrap.d.ts +16 -3
- package/dist/transform.d.ts +22 -0
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SinwanJS Compiler — Auto `cc` wrapping
|
|
3
|
+
*
|
|
4
|
+
* Detects top-level exported functions that look like components (uppercase
|
|
5
|
+
* name, 0–1 params, return JSX) and wraps them with `cc(...)` so the reactive
|
|
6
|
+
* analyzer treats them as components and the runtime gives them a component
|
|
7
|
+
* instance + displayName.
|
|
8
|
+
*
|
|
9
|
+
* This lets plain `export function App()` / `export const App = () => <div/>`
|
|
10
|
+
* work as Sinwan components without an explicit `cc(...)` call, matching the
|
|
11
|
+
* ergonomics of React function components while preserving Sinwan's explicit
|
|
12
|
+
* reactivity model (the compiler still only auto-wraps JSX expressions that
|
|
13
|
+
* read tracked reactive sources).
|
|
14
|
+
*
|
|
15
|
+
* Detection is conservative on purpose:
|
|
16
|
+
* - Only exported bindings are considered (named or default export).
|
|
17
|
+
* - The binding name must start with an uppercase letter (component naming
|
|
18
|
+
* convention). Anonymous default-exported function declarations are also
|
|
19
|
+
* accepted since they are conventionally components.
|
|
20
|
+
* - The function must have 0 or 1 parameters and must lexically return JSX
|
|
21
|
+
* (a JSXElement/JSXFragment in a return statement or arrow body).
|
|
22
|
+
* - Functions already wrapped in `cc(...)` (or an alias) are skipped to keep
|
|
23
|
+
* the transform idempotent.
|
|
24
|
+
*
|
|
25
|
+
* If any wrapping is performed and `cc` is not already imported from
|
|
26
|
+
* `sinwan` / `sinwan/component`, an `import { cc } from "sinwan/component"` is
|
|
27
|
+
* prepended so the subsequent reactive-wrap pass detects the components.
|
|
28
|
+
*/
|
|
29
|
+
import * as t from "@babel/types";
|
|
30
|
+
/**
|
|
31
|
+
* Mutates the AST, wrapping exported component-like functions with `cc(...)`.
|
|
32
|
+
* Returns `true` if any wrapping was performed.
|
|
33
|
+
*/
|
|
34
|
+
export declare function autoWrapComponents(ast: t.Node, filename?: string): boolean;
|
|
@@ -4,8 +4,8 @@ import * as path3 from "path";
|
|
|
4
4
|
|
|
5
5
|
// src/analyze.ts
|
|
6
6
|
import { parse } from "@babel/parser";
|
|
7
|
-
import * as
|
|
8
|
-
import
|
|
7
|
+
import * as t3 from "@babel/types";
|
|
8
|
+
import _traverse3 from "@babel/traverse";
|
|
9
9
|
import * as fs from "fs";
|
|
10
10
|
import * as path2 from "path";
|
|
11
11
|
|
|
@@ -18,7 +18,7 @@ var REACTIVE_SOURCE_MODULES = {
|
|
|
18
18
|
"sinwan/store": new Set(["createMutable", "createStore"]),
|
|
19
19
|
"sinwan/reactivity": new Set(["signal", "computed"]),
|
|
20
20
|
"sinwan/react": new Set(["useState"]),
|
|
21
|
-
"sinwan/
|
|
21
|
+
"sinwan/hook": new Set(["useFetch"])
|
|
22
22
|
};
|
|
23
23
|
var COMPONENT_FACTORY_MODULES = {
|
|
24
24
|
sinwan: new Set(["cc"]),
|
|
@@ -43,6 +43,9 @@ function isBuiltinReactiveProp(componentName, attrName) {
|
|
|
43
43
|
return false;
|
|
44
44
|
return entry.reactiveProps.has(attrName);
|
|
45
45
|
}
|
|
46
|
+
function isBuiltinControlFlowComponent(componentName) {
|
|
47
|
+
return !!componentName && BUILTIN_REACTIVE_PROPS.has(componentName);
|
|
48
|
+
}
|
|
46
49
|
function trackReactiveImports(ast) {
|
|
47
50
|
const names = {
|
|
48
51
|
createMutable: new Set,
|
|
@@ -50,6 +53,7 @@ function trackReactiveImports(ast) {
|
|
|
50
53
|
signal: new Set,
|
|
51
54
|
computed: new Set,
|
|
52
55
|
useState: new Set,
|
|
56
|
+
useFetch: new Set,
|
|
53
57
|
cc: new Set
|
|
54
58
|
};
|
|
55
59
|
traverse(ast, {
|
|
@@ -97,6 +101,32 @@ function isReactiveSourceCall(expr, names) {
|
|
|
97
101
|
}
|
|
98
102
|
return null;
|
|
99
103
|
}
|
|
104
|
+
var USEFETCH_CHAIN_METHODS = new Set([
|
|
105
|
+
"json",
|
|
106
|
+
"text",
|
|
107
|
+
"blob",
|
|
108
|
+
"arrayBuffer",
|
|
109
|
+
"formData",
|
|
110
|
+
"get",
|
|
111
|
+
"put",
|
|
112
|
+
"post",
|
|
113
|
+
"delete",
|
|
114
|
+
"patch",
|
|
115
|
+
"head",
|
|
116
|
+
"options"
|
|
117
|
+
]);
|
|
118
|
+
function isUseFetchCall(expr, names) {
|
|
119
|
+
if (!t.isCallExpression(expr))
|
|
120
|
+
return false;
|
|
121
|
+
const callee = expr.callee;
|
|
122
|
+
if (t.isIdentifier(callee)) {
|
|
123
|
+
return names.useFetch.has(callee.name);
|
|
124
|
+
}
|
|
125
|
+
if (t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.property) && USEFETCH_CHAIN_METHODS.has(callee.property.name)) {
|
|
126
|
+
return isUseFetchCall(callee.object, names);
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
100
130
|
function trackLocalScopeBindings(path2, names, scope) {
|
|
101
131
|
path2.traverse({
|
|
102
132
|
VariableDeclarator(p) {
|
|
@@ -105,27 +135,42 @@ function trackLocalScopeBindings(path2, names, scope) {
|
|
|
105
135
|
if (!init)
|
|
106
136
|
return;
|
|
107
137
|
const source = isReactiveSourceCall(init, names);
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
138
|
+
if (source) {
|
|
139
|
+
if (source.isArray) {
|
|
140
|
+
if (t.isArrayPattern(id) && id.elements[0]) {
|
|
141
|
+
const first = id.elements[0];
|
|
142
|
+
if (t.isIdentifier(first)) {
|
|
143
|
+
scope.bindings.set(first.name, { kind: source.kind });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (t.isIdentifier(id)) {
|
|
149
|
+
if (source.kind === "mutable") {
|
|
150
|
+
scope.bindings.set(id.name, {
|
|
151
|
+
kind: "mutable",
|
|
152
|
+
root: id.name,
|
|
153
|
+
path: []
|
|
154
|
+
});
|
|
155
|
+
} else {
|
|
156
|
+
scope.bindings.set(id.name, { kind: source.kind });
|
|
115
157
|
}
|
|
116
158
|
}
|
|
117
159
|
return;
|
|
118
160
|
}
|
|
119
|
-
if (
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
161
|
+
if (isUseFetchCall(init, names)) {
|
|
162
|
+
if (t.isObjectPattern(id)) {
|
|
163
|
+
for (const prop of id.properties) {
|
|
164
|
+
if (t.isObjectProperty(prop) && !prop.computed && t.isIdentifier(prop.value)) {
|
|
165
|
+
scope.bindings.set(prop.value.name, {
|
|
166
|
+
kind: "signal"
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} else if (t.isIdentifier(id)) {
|
|
171
|
+
scope.bindings.set(id.name, { kind: "signalObject" });
|
|
128
172
|
}
|
|
173
|
+
return;
|
|
129
174
|
}
|
|
130
175
|
}
|
|
131
176
|
});
|
|
@@ -242,16 +287,27 @@ function analyzeObjectLiteralSpread(spread, fullScope, calleeReactive) {
|
|
|
242
287
|
}
|
|
243
288
|
}
|
|
244
289
|
}
|
|
290
|
+
function isMemberLike(node) {
|
|
291
|
+
return t.isMemberExpression(node) || t.isOptionalMemberExpression(node);
|
|
292
|
+
}
|
|
245
293
|
function getMemberExpressionRootAndPath(node) {
|
|
246
294
|
const path2 = [];
|
|
247
295
|
let current = node;
|
|
248
|
-
while (
|
|
249
|
-
if (current
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
296
|
+
while (true) {
|
|
297
|
+
if (t.isTSNonNullExpression(current)) {
|
|
298
|
+
current = current.expression;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (isMemberLike(current)) {
|
|
302
|
+
if (current.computed)
|
|
303
|
+
return null;
|
|
304
|
+
if (!t.isIdentifier(current.property))
|
|
305
|
+
return null;
|
|
306
|
+
path2.push(current.property.name);
|
|
307
|
+
current = current.object;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
break;
|
|
255
311
|
}
|
|
256
312
|
if (t.isIdentifier(current)) {
|
|
257
313
|
return { root: current.name, path: path2.reverse() };
|
|
@@ -259,6 +315,9 @@ function getMemberExpressionRootAndPath(node) {
|
|
|
259
315
|
return null;
|
|
260
316
|
}
|
|
261
317
|
function isReactiveRead(node, scope) {
|
|
318
|
+
if (t.isTSNonNullExpression(node)) {
|
|
319
|
+
return isReactiveRead(node.expression, scope);
|
|
320
|
+
}
|
|
262
321
|
if (t.isIdentifier(node)) {
|
|
263
322
|
const binding2 = scope.bindings.get(node.name);
|
|
264
323
|
if (!binding2)
|
|
@@ -271,6 +330,8 @@ function isReactiveRead(node, scope) {
|
|
|
271
330
|
}
|
|
272
331
|
return false;
|
|
273
332
|
}
|
|
333
|
+
if (!isMemberLike(node))
|
|
334
|
+
return false;
|
|
274
335
|
const rootPath = getMemberExpressionRootAndPath(node);
|
|
275
336
|
if (!rootPath)
|
|
276
337
|
return false;
|
|
@@ -286,6 +347,9 @@ function isReactiveRead(node, scope) {
|
|
|
286
347
|
if (binding.kind === "signal" || binding.kind === "computed") {
|
|
287
348
|
return rootPath.path[0] === "value";
|
|
288
349
|
}
|
|
350
|
+
if (binding.kind === "signalObject") {
|
|
351
|
+
return rootPath.path.length >= 2 && rootPath.path[1] === "value";
|
|
352
|
+
}
|
|
289
353
|
return false;
|
|
290
354
|
}
|
|
291
355
|
function isGetterCall(node, scope) {
|
|
@@ -296,12 +360,17 @@ function isGetterCall(node, scope) {
|
|
|
296
360
|
return binding?.kind === "getter";
|
|
297
361
|
}
|
|
298
362
|
function isReactiveValue(node, scope) {
|
|
363
|
+
if (t.isTSNonNullExpression(node)) {
|
|
364
|
+
return isReactiveValue(node.expression, scope);
|
|
365
|
+
}
|
|
299
366
|
if (t.isIdentifier(node)) {
|
|
300
367
|
const binding2 = scope.bindings.get(node.name);
|
|
301
368
|
if (!binding2)
|
|
302
369
|
return false;
|
|
303
370
|
return binding2.kind === "prop" || binding2.kind === "signal" || binding2.kind === "computed" || binding2.kind === "mutable";
|
|
304
371
|
}
|
|
372
|
+
if (!isMemberLike(node))
|
|
373
|
+
return false;
|
|
305
374
|
const rootPath = getMemberExpressionRootAndPath(node);
|
|
306
375
|
if (!rootPath)
|
|
307
376
|
return false;
|
|
@@ -314,6 +383,9 @@ function isReactiveValue(node, scope) {
|
|
|
314
383
|
if (binding.kind === "signal" || binding.kind === "computed") {
|
|
315
384
|
return rootPath.path[0] === "value";
|
|
316
385
|
}
|
|
386
|
+
if (binding.kind === "signalObject") {
|
|
387
|
+
return rootPath.path.length >= 2 && rootPath.path[1] === "value";
|
|
388
|
+
}
|
|
317
389
|
return false;
|
|
318
390
|
}
|
|
319
391
|
function containsReactiveValue(expr, scope) {
|
|
@@ -323,7 +395,7 @@ function containsReactiveValue(expr, scope) {
|
|
|
323
395
|
return;
|
|
324
396
|
if (!node || typeof node !== "object")
|
|
325
397
|
return;
|
|
326
|
-
if (t.
|
|
398
|
+
if (isMemberLike(node) || t.isTSNonNullExpression(node) || t.isIdentifier(node)) {
|
|
327
399
|
if (isReactiveValue(node, scope)) {
|
|
328
400
|
found = true;
|
|
329
401
|
return;
|
|
@@ -360,7 +432,7 @@ function containsReactiveRead(expr, scope) {
|
|
|
360
432
|
return;
|
|
361
433
|
if (!node || typeof node !== "object")
|
|
362
434
|
return;
|
|
363
|
-
if (t.
|
|
435
|
+
if (isMemberLike(node) || t.isTSNonNullExpression(node) || t.isIdentifier(node)) {
|
|
364
436
|
if (isReactiveRead(node, scope)) {
|
|
365
437
|
found = true;
|
|
366
438
|
return;
|
|
@@ -431,7 +503,7 @@ function getComponentExpressionInfo(exprPath) {
|
|
|
431
503
|
}
|
|
432
504
|
return { isComponent: true, componentName, attributeName };
|
|
433
505
|
}
|
|
434
|
-
function isReactiveComponentProp(info, componentNames, reactiveProps) {
|
|
506
|
+
function isReactiveComponentProp(info, componentNames, reactiveProps, importedReactiveProps) {
|
|
435
507
|
if (!info.isComponent || !info.componentName || !info.attributeName) {
|
|
436
508
|
return false;
|
|
437
509
|
}
|
|
@@ -439,12 +511,16 @@ function isReactiveComponentProp(info, componentNames, reactiveProps) {
|
|
|
439
511
|
return true;
|
|
440
512
|
}
|
|
441
513
|
const componentFn = componentNames.get(info.componentName);
|
|
442
|
-
if (
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
514
|
+
if (componentFn) {
|
|
515
|
+
const props = reactiveProps.get(componentFn);
|
|
516
|
+
if (!props)
|
|
517
|
+
return false;
|
|
518
|
+
return props.has(info.attributeName);
|
|
519
|
+
}
|
|
520
|
+
const importedProps = importedReactiveProps.get(info.componentName);
|
|
521
|
+
if (!importedProps)
|
|
446
522
|
return false;
|
|
447
|
-
return
|
|
523
|
+
return importedProps.has(info.attributeName);
|
|
448
524
|
}
|
|
449
525
|
function shouldWrap(expr, scope) {
|
|
450
526
|
if (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr))
|
|
@@ -453,6 +529,65 @@ function shouldWrap(expr, scope) {
|
|
|
453
529
|
return false;
|
|
454
530
|
return containsReactiveRead(expr, scope);
|
|
455
531
|
}
|
|
532
|
+
var RESOLVE_HELPER = "_$unwrap";
|
|
533
|
+
function transformPropMemberAccess(expr, scope) {
|
|
534
|
+
let changed = false;
|
|
535
|
+
function resolveRoot(rootName) {
|
|
536
|
+
return t.callExpression(t.identifier(RESOLVE_HELPER), [
|
|
537
|
+
t.identifier(rootName)
|
|
538
|
+
]);
|
|
539
|
+
}
|
|
540
|
+
function replaceMemberRoot(node, rootName) {
|
|
541
|
+
let current = node;
|
|
542
|
+
let parent = null;
|
|
543
|
+
while (true) {
|
|
544
|
+
if (t.isTSNonNullExpression(current)) {
|
|
545
|
+
current = current.expression;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (!isMemberLike(current))
|
|
549
|
+
break;
|
|
550
|
+
parent = current;
|
|
551
|
+
current = current.object;
|
|
552
|
+
}
|
|
553
|
+
if (parent && t.isIdentifier(current) && current.name === rootName) {
|
|
554
|
+
parent.object = resolveRoot(rootName);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function visit(node) {
|
|
558
|
+
if (!node || typeof node !== "object")
|
|
559
|
+
return;
|
|
560
|
+
if (t.isArrowFunctionExpression(node) || t.isFunctionExpression(node))
|
|
561
|
+
return;
|
|
562
|
+
if (isMemberLike(node)) {
|
|
563
|
+
const rootPath = getMemberExpressionRootAndPath(node);
|
|
564
|
+
if (rootPath) {
|
|
565
|
+
const binding = scope.bindings.get(rootPath.root);
|
|
566
|
+
if (binding && binding.kind === "prop") {
|
|
567
|
+
replaceMemberRoot(node, rootPath.root);
|
|
568
|
+
changed = true;
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
for (const key of Object.keys(node)) {
|
|
575
|
+
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments")
|
|
576
|
+
continue;
|
|
577
|
+
const value = node[key];
|
|
578
|
+
if (Array.isArray(value)) {
|
|
579
|
+
for (const item of value) {
|
|
580
|
+
if (item && typeof item === "object")
|
|
581
|
+
visit(item);
|
|
582
|
+
}
|
|
583
|
+
} else if (value && typeof value === "object") {
|
|
584
|
+
visit(value);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
visit(expr);
|
|
589
|
+
return changed;
|
|
590
|
+
}
|
|
456
591
|
function wrapExpression(expr) {
|
|
457
592
|
return t.arrowFunctionExpression([], expr);
|
|
458
593
|
}
|
|
@@ -682,53 +817,219 @@ function propagateReactiveProps(localScopes, callGraph, componentFunctions) {
|
|
|
682
817
|
return reactiveProps;
|
|
683
818
|
}
|
|
684
819
|
function wrapReactiveExpressions(ast, options = {}) {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
} = collectComponentFunctions(ast, names.cc);
|
|
694
|
-
const localScopes = computeLocalScopes(ast, names);
|
|
695
|
-
const callGraph = collectComponentCallGraph(ast, componentNames);
|
|
696
|
-
const reactiveProps = propagateReactiveProps(localScopes, callGraph, componentFunctions);
|
|
697
|
-
const exportNameByFn = new Map;
|
|
698
|
-
for (const [localName, fn] of componentNames) {
|
|
699
|
-
if (exported.has(fn)) {
|
|
700
|
-
exportNameByFn.set(fn, localName);
|
|
820
|
+
let metadata = null;
|
|
821
|
+
if (options.analyzeMetadata) {
|
|
822
|
+
metadata = options.analyzeMetadata;
|
|
823
|
+
} else if (options.analyze && options.filename) {
|
|
824
|
+
try {
|
|
825
|
+
metadata = loadMetadata(options.analyze);
|
|
826
|
+
} catch {
|
|
827
|
+
metadata = null;
|
|
701
828
|
}
|
|
702
829
|
}
|
|
830
|
+
const absoluteFilename = options.filename ? path.resolve(options.filename) : null;
|
|
831
|
+
const resolveImport = options.resolveImport;
|
|
832
|
+
const names = {
|
|
833
|
+
createMutable: new Set,
|
|
834
|
+
createStore: new Set,
|
|
835
|
+
signal: new Set,
|
|
836
|
+
computed: new Set,
|
|
837
|
+
useState: new Set,
|
|
838
|
+
useFetch: new Set,
|
|
839
|
+
cc: new Set
|
|
840
|
+
};
|
|
841
|
+
const componentFunctions = new Set;
|
|
842
|
+
const componentNames = new Map;
|
|
843
|
+
const exported = new Set;
|
|
844
|
+
const localScopes = new Map;
|
|
845
|
+
const callGraph = new Map;
|
|
846
|
+
const rawCallGraph = new Map;
|
|
847
|
+
const importedReactiveProps = new Map;
|
|
848
|
+
const exportNameByFn = new Map;
|
|
849
|
+
let hasUnwrapImport = false;
|
|
703
850
|
traverse(ast, {
|
|
704
|
-
|
|
705
|
-
const
|
|
851
|
+
ImportDeclaration(p) {
|
|
852
|
+
const source = p.node.source.value;
|
|
853
|
+
const reactiveAllowed = REACTIVE_SOURCE_MODULES[source];
|
|
854
|
+
const componentAllowed = COMPONENT_FACTORY_MODULES[source];
|
|
855
|
+
if (reactiveAllowed || componentAllowed) {
|
|
856
|
+
for (const spec of p.node.specifiers) {
|
|
857
|
+
if (!t.isImportSpecifier(spec))
|
|
858
|
+
continue;
|
|
859
|
+
const imported = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
|
|
860
|
+
if (reactiveAllowed?.has(imported)) {
|
|
861
|
+
names[imported].add(spec.local.name);
|
|
862
|
+
} else if (componentAllowed?.has(imported)) {
|
|
863
|
+
names.cc.add(spec.local.name);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (source === "sinwan/reactivity") {
|
|
868
|
+
for (const spec of p.node.specifiers) {
|
|
869
|
+
if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported) && spec.imported.name === "unwrap") {
|
|
870
|
+
hasUnwrapImport = true;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (metadata && absoluteFilename && resolveImport && source.startsWith(".")) {
|
|
875
|
+
const resolved = resolveImport(source, absoluteFilename);
|
|
876
|
+
if (resolved) {
|
|
877
|
+
const fileProps = metadata.get(resolved);
|
|
878
|
+
if (fileProps) {
|
|
879
|
+
for (const spec of p.node.specifiers) {
|
|
880
|
+
if (!t.isImportSpecifier(spec))
|
|
881
|
+
continue;
|
|
882
|
+
const imported = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
|
|
883
|
+
if (!imported || !/^[A-Z]/.test(imported))
|
|
884
|
+
continue;
|
|
885
|
+
const props = fileProps.get(imported);
|
|
886
|
+
if (props && props.size > 0) {
|
|
887
|
+
importedReactiveProps.set(spec.local.name, new Set(props));
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
},
|
|
894
|
+
CallExpression(p) {
|
|
895
|
+
const callee = p.node.callee;
|
|
896
|
+
if (!t.isIdentifier(callee) || !names.cc.has(callee.name))
|
|
897
|
+
return;
|
|
898
|
+
const firstArg = p.node.arguments[0];
|
|
899
|
+
if (!firstArg || !t.isFunction(firstArg))
|
|
900
|
+
return;
|
|
901
|
+
const componentFn = firstArg;
|
|
902
|
+
componentFunctions.add(componentFn);
|
|
903
|
+
const parent = p.parentPath;
|
|
904
|
+
if (parent && parent.isVariableDeclarator && parent.isVariableDeclarator()) {
|
|
905
|
+
const id = parent.node.id;
|
|
906
|
+
if (t.isIdentifier(id)) {
|
|
907
|
+
componentNames.set(id.name, componentFn);
|
|
908
|
+
}
|
|
909
|
+
} else if (parent && parent.isExportNamedDeclaration && parent.isExportNamedDeclaration()) {
|
|
910
|
+
const declaration = parent.node.declaration;
|
|
911
|
+
if (t.isVariableDeclaration(declaration)) {
|
|
912
|
+
for (const decl of declaration.declarations) {
|
|
913
|
+
if (t.isIdentifier(decl.id)) {
|
|
914
|
+
componentNames.set(decl.id.name, componentFn);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
ExportNamedDeclaration(p) {
|
|
921
|
+
const declaration = p.node.declaration;
|
|
922
|
+
if (!t.isVariableDeclaration(declaration))
|
|
923
|
+
return;
|
|
924
|
+
for (const decl of declaration.declarations) {
|
|
925
|
+
const init = decl.init;
|
|
926
|
+
if (!init || !t.isCallExpression(init))
|
|
927
|
+
continue;
|
|
928
|
+
const callee = init.callee;
|
|
929
|
+
if (!t.isIdentifier(callee) || !names.cc.has(callee.name))
|
|
930
|
+
continue;
|
|
931
|
+
const firstArg = init.arguments[0];
|
|
932
|
+
if (!firstArg || !t.isFunction(firstArg))
|
|
933
|
+
continue;
|
|
934
|
+
exported.add(firstArg);
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
ExportDefaultDeclaration(p) {
|
|
938
|
+
const decl = p.node.declaration;
|
|
706
939
|
if (t.isIdentifier(decl)) {
|
|
707
940
|
const fn = componentNames.get(decl.name);
|
|
708
|
-
if (fn)
|
|
941
|
+
if (fn) {
|
|
942
|
+
exported.add(fn);
|
|
709
943
|
exportNameByFn.set(fn, "default");
|
|
944
|
+
}
|
|
710
945
|
} else if (t.isCallExpression(decl)) {
|
|
711
946
|
const callee = decl.callee;
|
|
712
947
|
if (t.isIdentifier(callee) && names.cc.has(callee.name)) {
|
|
713
948
|
const firstArg = decl.arguments[0];
|
|
714
949
|
if (firstArg && t.isFunction(firstArg)) {
|
|
950
|
+
exported.add(firstArg);
|
|
715
951
|
exportNameByFn.set(firstArg, "default");
|
|
716
952
|
}
|
|
717
953
|
}
|
|
718
954
|
}
|
|
955
|
+
},
|
|
956
|
+
Function(p) {
|
|
957
|
+
const scope = { bindings: new Map };
|
|
958
|
+
trackLocalScopeBindings(p, names, scope);
|
|
959
|
+
localScopes.set(p.node, scope);
|
|
960
|
+
},
|
|
961
|
+
JSXElement(p) {
|
|
962
|
+
const name = p.node.openingElement.name;
|
|
963
|
+
if (!t.isJSXIdentifier(name))
|
|
964
|
+
return;
|
|
965
|
+
const callerPath = p.findParent((pp) => pp.isFunction());
|
|
966
|
+
const callerFn = callerPath ? callerPath.node : null;
|
|
967
|
+
if (!callerFn)
|
|
968
|
+
return;
|
|
969
|
+
const props = [];
|
|
970
|
+
const spreads = [];
|
|
971
|
+
for (const attr of p.node.openingElement.attributes) {
|
|
972
|
+
if (t.isJSXSpreadAttribute(attr)) {
|
|
973
|
+
if (attr.argument) {
|
|
974
|
+
spreads.push(attr.argument);
|
|
975
|
+
}
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (t.isJSXAttribute(attr)) {
|
|
979
|
+
const attrName = attr.name.name;
|
|
980
|
+
if (!attrName)
|
|
981
|
+
continue;
|
|
982
|
+
if (t.isJSXExpressionContainer(attr.value)) {
|
|
983
|
+
const expr = attr.value.expression;
|
|
984
|
+
if (!expr || t.isJSXEmptyExpression(expr))
|
|
985
|
+
continue;
|
|
986
|
+
props.push({ name: attrName, value: expr });
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
const childExprs = [];
|
|
991
|
+
for (const child of p.node.children) {
|
|
992
|
+
if (t.isJSXExpressionContainer(child)) {
|
|
993
|
+
const expr = child.expression;
|
|
994
|
+
if (!expr || t.isJSXEmptyExpression(expr))
|
|
995
|
+
continue;
|
|
996
|
+
childExprs.push(expr);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (childExprs.length === 1) {
|
|
1000
|
+
props.push({ name: "children", value: childExprs[0] });
|
|
1001
|
+
} else if (childExprs.length > 1) {
|
|
1002
|
+
props.push({ name: "children", value: t.arrayExpression(childExprs) });
|
|
1003
|
+
}
|
|
1004
|
+
const sites = rawCallGraph.get(callerFn) ?? [];
|
|
1005
|
+
sites.push({ calleeName: name.name, props, spreads });
|
|
1006
|
+
rawCallGraph.set(callerFn, sites);
|
|
719
1007
|
}
|
|
720
1008
|
});
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1009
|
+
for (const [callerFn, sites] of rawCallGraph) {
|
|
1010
|
+
const resolved = [];
|
|
1011
|
+
for (const site of sites) {
|
|
1012
|
+
const callee = componentNames.get(site.calleeName);
|
|
1013
|
+
if (callee) {
|
|
1014
|
+
resolved.push({ callee, props: site.props, spreads: site.spreads });
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
if (resolved.length > 0) {
|
|
1018
|
+
callGraph.set(callerFn, resolved);
|
|
729
1019
|
}
|
|
730
1020
|
}
|
|
731
|
-
const
|
|
1021
|
+
for (const [localName, fn] of componentNames) {
|
|
1022
|
+
if (exported.has(fn)) {
|
|
1023
|
+
if (!exportNameByFn.has(fn)) {
|
|
1024
|
+
exportNameByFn.set(fn, localName);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
const hasAnyImport = Object.values(names).some((s) => s.size > 0);
|
|
1029
|
+
if (!hasAnyImport)
|
|
1030
|
+
return;
|
|
1031
|
+
const reactiveProps = propagateReactiveProps(localScopes, callGraph, componentFunctions);
|
|
1032
|
+
let needsResolve = false;
|
|
732
1033
|
traverse(ast, {
|
|
733
1034
|
Function(path2) {
|
|
734
1035
|
const scope = localScopes.get(path2.node);
|
|
@@ -767,21 +1068,232 @@ function wrapReactiveExpressions(ast, options = {}) {
|
|
|
767
1068
|
const expr = exprPath.node.expression;
|
|
768
1069
|
const compInfo = getComponentExpressionInfo(exprPath);
|
|
769
1070
|
if (compInfo.isComponent) {
|
|
770
|
-
if (
|
|
771
|
-
|
|
1071
|
+
if (compInfo.attributeName !== null) {
|
|
1072
|
+
if (!isReactiveComponentProp(compInfo, componentNames, reactiveProps, importedReactiveProps)) {
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
} else {
|
|
1076
|
+
if (!isBuiltinControlFlowComponent(compInfo.componentName)) {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
772
1079
|
}
|
|
773
1080
|
}
|
|
774
1081
|
if (shouldWrap(expr, scope)) {
|
|
1082
|
+
if (transformPropMemberAccess(expr, scope)) {
|
|
1083
|
+
needsResolve = true;
|
|
1084
|
+
}
|
|
775
1085
|
exprPath.replaceWith(t.jsxExpressionContainer(createBindingDescriptor(expr, exprPath, options)));
|
|
776
1086
|
}
|
|
777
1087
|
}
|
|
778
1088
|
});
|
|
779
1089
|
}
|
|
780
1090
|
});
|
|
1091
|
+
if (needsResolve && !hasUnwrapImport) {
|
|
1092
|
+
ast.program.body.unshift(t.importDeclaration([
|
|
1093
|
+
t.importSpecifier(t.identifier(RESOLVE_HELPER), t.identifier("unwrap"))
|
|
1094
|
+
], t.stringLiteral("sinwan/reactivity")));
|
|
1095
|
+
}
|
|
781
1096
|
}
|
|
782
1097
|
|
|
783
|
-
// src/
|
|
1098
|
+
// src/auto-cc.ts
|
|
1099
|
+
import * as t2 from "@babel/types";
|
|
1100
|
+
import _traverse2 from "@babel/traverse";
|
|
784
1101
|
var traverse2 = typeof _traverse2 === "function" ? _traverse2 : _traverse2.default ?? _traverse2;
|
|
1102
|
+
var CC_SOURCE_MODULES = new Set(["sinwan", "sinwan/component"]);
|
|
1103
|
+
var EXCLUDE_KEYS = new Set([
|
|
1104
|
+
"loc",
|
|
1105
|
+
"start",
|
|
1106
|
+
"end",
|
|
1107
|
+
"type",
|
|
1108
|
+
"leadingComments",
|
|
1109
|
+
"trailingComments",
|
|
1110
|
+
"innerComments",
|
|
1111
|
+
"extra"
|
|
1112
|
+
]);
|
|
1113
|
+
function isComponentName(name) {
|
|
1114
|
+
return !!name && /^[A-Z]/.test(name);
|
|
1115
|
+
}
|
|
1116
|
+
function containsJsx(node, enterFunctions) {
|
|
1117
|
+
if (!node || typeof node !== "object")
|
|
1118
|
+
return false;
|
|
1119
|
+
if (t2.isJSXElement(node) || t2.isJSXFragment(node))
|
|
1120
|
+
return true;
|
|
1121
|
+
if (!enterFunctions && (t2.isFunctionExpression(node) || t2.isArrowFunctionExpression(node) || t2.isFunctionDeclaration(node))) {
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
for (const key of Object.keys(node)) {
|
|
1125
|
+
if (EXCLUDE_KEYS.has(key))
|
|
1126
|
+
continue;
|
|
1127
|
+
const val = node[key];
|
|
1128
|
+
if (Array.isArray(val)) {
|
|
1129
|
+
for (const item of val) {
|
|
1130
|
+
if (item && typeof item === "object" && containsJsx(item, enterFunctions)) {
|
|
1131
|
+
return true;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
} else if (val && typeof val === "object" && containsJsx(val, enterFunctions)) {
|
|
1135
|
+
return true;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return false;
|
|
1139
|
+
}
|
|
1140
|
+
function functionReturnsJsx(fnPath) {
|
|
1141
|
+
const fn = fnPath.node;
|
|
1142
|
+
if (t2.isArrowFunctionExpression(fn) && !t2.isBlockStatement(fn.body)) {
|
|
1143
|
+
return containsJsx(fn.body, false);
|
|
1144
|
+
}
|
|
1145
|
+
let found = false;
|
|
1146
|
+
fnPath.traverse({
|
|
1147
|
+
ReturnStatement(p) {
|
|
1148
|
+
if (p.node.argument && containsJsx(p.node.argument, false)) {
|
|
1149
|
+
found = true;
|
|
1150
|
+
p.stop();
|
|
1151
|
+
}
|
|
1152
|
+
},
|
|
1153
|
+
Function(p) {
|
|
1154
|
+
p.skip();
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
return found;
|
|
1158
|
+
}
|
|
1159
|
+
function isComponentArity(fn) {
|
|
1160
|
+
if (t2.isFunctionDeclaration(fn) && fn.generator)
|
|
1161
|
+
return false;
|
|
1162
|
+
if ((t2.isFunctionExpression(fn) || t2.isArrowFunctionExpression(fn)) && fn.generator) {
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
return fn.params.length <= 1;
|
|
1166
|
+
}
|
|
1167
|
+
function collectCcAliases(ast) {
|
|
1168
|
+
const aliases = new Set;
|
|
1169
|
+
traverse2(ast, {
|
|
1170
|
+
ImportDeclaration(path2) {
|
|
1171
|
+
const source = path2.node.source.value;
|
|
1172
|
+
if (!CC_SOURCE_MODULES.has(source))
|
|
1173
|
+
return;
|
|
1174
|
+
for (const spec of path2.node.specifiers) {
|
|
1175
|
+
if (!t2.isImportSpecifier(spec))
|
|
1176
|
+
continue;
|
|
1177
|
+
const imported = t2.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
|
|
1178
|
+
if (imported === "cc") {
|
|
1179
|
+
aliases.add(spec.local.name);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
return { aliases, alreadyImported: aliases.size > 0 };
|
|
1185
|
+
}
|
|
1186
|
+
function ccCall(arg, ccName) {
|
|
1187
|
+
return t2.callExpression(t2.identifier(ccName), [arg]);
|
|
1188
|
+
}
|
|
1189
|
+
function autoWrapComponents(ast, filename) {
|
|
1190
|
+
const { aliases, alreadyImported } = collectCcAliases(ast);
|
|
1191
|
+
const ccName = aliases.values().next().value;
|
|
1192
|
+
let changed = false;
|
|
1193
|
+
const wrapIfCandidate = (fnPath, name) => {
|
|
1194
|
+
if (name !== null && !isComponentName(name))
|
|
1195
|
+
return "not-candidate";
|
|
1196
|
+
const fn = fnPath.node;
|
|
1197
|
+
if (!isComponentArity(fn))
|
|
1198
|
+
return "not-candidate";
|
|
1199
|
+
if (!functionReturnsJsx(fnPath))
|
|
1200
|
+
return "not-candidate";
|
|
1201
|
+
return "wrapped";
|
|
1202
|
+
};
|
|
1203
|
+
traverse2(ast, {
|
|
1204
|
+
ExportNamedDeclaration(path2) {
|
|
1205
|
+
const decl = path2.node.declaration;
|
|
1206
|
+
if (!decl)
|
|
1207
|
+
return;
|
|
1208
|
+
if (t2.isFunctionDeclaration(decl) && decl.id) {
|
|
1209
|
+
const name = decl.id.name;
|
|
1210
|
+
if (!isComponentName(name))
|
|
1211
|
+
return;
|
|
1212
|
+
const fnPath = path2.get("declaration");
|
|
1213
|
+
if (!isComponentArity(decl))
|
|
1214
|
+
return;
|
|
1215
|
+
if (!functionReturnsJsx(fnPath))
|
|
1216
|
+
return;
|
|
1217
|
+
const fnExpr = t2.functionExpression(decl.id, decl.params, decl.body, decl.generator, decl.async);
|
|
1218
|
+
const varDecl = t2.variableDeclaration("const", [
|
|
1219
|
+
t2.variableDeclarator(t2.identifier(name), ccCall(fnExpr, ccName ?? "cc"))
|
|
1220
|
+
]);
|
|
1221
|
+
path2.replaceWith(t2.exportNamedDeclaration(varDecl, []));
|
|
1222
|
+
changed = true;
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
if (t2.isVariableDeclaration(decl)) {
|
|
1226
|
+
for (const dPath of path2.get("declaration.declarations")) {
|
|
1227
|
+
const id = dPath.node.id;
|
|
1228
|
+
if (!t2.isIdentifier(id) || !isComponentName(id.name))
|
|
1229
|
+
continue;
|
|
1230
|
+
const init = dPath.node.init;
|
|
1231
|
+
if (!init)
|
|
1232
|
+
continue;
|
|
1233
|
+
if (t2.isCallExpression(init) && t2.isIdentifier(init.callee) && aliases.has(init.callee.name)) {
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
if (!t2.isFunction(init))
|
|
1237
|
+
continue;
|
|
1238
|
+
const initPath = dPath.get("init");
|
|
1239
|
+
if (!isComponentArity(init))
|
|
1240
|
+
continue;
|
|
1241
|
+
if (!functionReturnsJsx(initPath))
|
|
1242
|
+
continue;
|
|
1243
|
+
dPath.node.init = ccCall(init, ccName ?? "cc");
|
|
1244
|
+
changed = true;
|
|
1245
|
+
}
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
},
|
|
1249
|
+
ExportDefaultDeclaration(path2) {
|
|
1250
|
+
const decl = path2.node.declaration;
|
|
1251
|
+
if (t2.isFunctionDeclaration(decl) && decl.id) {
|
|
1252
|
+
const name = decl.id.name;
|
|
1253
|
+
if (!isComponentName(name))
|
|
1254
|
+
return;
|
|
1255
|
+
const fnPath = path2.get("declaration");
|
|
1256
|
+
if (!isComponentArity(decl))
|
|
1257
|
+
return;
|
|
1258
|
+
if (!functionReturnsJsx(fnPath))
|
|
1259
|
+
return;
|
|
1260
|
+
const fnDecl = t2.functionDeclaration(decl.id, decl.params, decl.body, decl.generator, decl.async);
|
|
1261
|
+
const exportDefault = t2.exportDefaultDeclaration(ccCall(t2.identifier(name), ccName ?? "cc"));
|
|
1262
|
+
path2.replaceWithMultiple([fnDecl, exportDefault]);
|
|
1263
|
+
changed = true;
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
if (t2.isFunctionDeclaration(decl) && !decl.id) {
|
|
1267
|
+
const fnPath = path2.get("declaration");
|
|
1268
|
+
if (!isComponentArity(decl))
|
|
1269
|
+
return;
|
|
1270
|
+
if (!functionReturnsJsx(fnPath))
|
|
1271
|
+
return;
|
|
1272
|
+
const fnExpr = t2.functionExpression(null, decl.params, decl.body, decl.generator, decl.async);
|
|
1273
|
+
path2.node.declaration = ccCall(fnExpr, ccName ?? "cc");
|
|
1274
|
+
changed = true;
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (t2.isArrowFunctionExpression(decl) || t2.isFunctionExpression(decl)) {
|
|
1278
|
+
const declPath = path2.get("declaration");
|
|
1279
|
+
if (!isComponentArity(decl))
|
|
1280
|
+
return;
|
|
1281
|
+
if (!functionReturnsJsx(declPath))
|
|
1282
|
+
return;
|
|
1283
|
+
path2.node.declaration = ccCall(decl, ccName ?? "cc");
|
|
1284
|
+
changed = true;
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
if (changed && !alreadyImported) {
|
|
1290
|
+
ast.program.body.unshift(t2.importDeclaration([t2.importSpecifier(t2.identifier("cc"), t2.identifier("cc"))], t2.stringLiteral("sinwan/component")));
|
|
1291
|
+
}
|
|
1292
|
+
return changed;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// src/analyze.ts
|
|
1296
|
+
var traverse3 = typeof _traverse3 === "function" ? _traverse3 : _traverse3.default ?? _traverse3;
|
|
785
1297
|
function functionRef(fn) {
|
|
786
1298
|
return { start: fn.start ?? -1, end: fn.end ?? -1 };
|
|
787
1299
|
}
|
|
@@ -790,7 +1302,7 @@ function refKey(ref) {
|
|
|
790
1302
|
}
|
|
791
1303
|
function findFunctionByRef(ast, ref) {
|
|
792
1304
|
let found = null;
|
|
793
|
-
|
|
1305
|
+
traverse3(ast, {
|
|
794
1306
|
Function(path3) {
|
|
795
1307
|
const node = path3.node;
|
|
796
1308
|
if (node.start === ref.start && node.end === ref.end) {
|
|
@@ -946,15 +1458,15 @@ function parseRefKey(key) {
|
|
|
946
1458
|
}
|
|
947
1459
|
function collectImports(ast) {
|
|
948
1460
|
const imports = new Map;
|
|
949
|
-
|
|
1461
|
+
traverse3(ast, {
|
|
950
1462
|
ImportDeclaration(path3) {
|
|
951
1463
|
const source = path3.node.source.value;
|
|
952
1464
|
for (const spec of path3.node.specifiers) {
|
|
953
|
-
if (
|
|
1465
|
+
if (t3.isImportSpecifier(spec)) {
|
|
954
1466
|
const localName = spec.local.name;
|
|
955
|
-
const importedName =
|
|
1467
|
+
const importedName = t3.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
|
|
956
1468
|
imports.set(localName, { source, importedName });
|
|
957
|
-
} else if (
|
|
1469
|
+
} else if (t3.isImportDefaultSpecifier(spec)) {
|
|
958
1470
|
imports.set(spec.local.name, { source, importedName: "default" });
|
|
959
1471
|
}
|
|
960
1472
|
}
|
|
@@ -964,10 +1476,10 @@ function collectImports(ast) {
|
|
|
964
1476
|
}
|
|
965
1477
|
function collectImportedCallSites(ast, componentNames, imports) {
|
|
966
1478
|
const sites = [];
|
|
967
|
-
|
|
1479
|
+
traverse3(ast, {
|
|
968
1480
|
JSXElement(path3) {
|
|
969
1481
|
const name = path3.node.openingElement.name;
|
|
970
|
-
if (!
|
|
1482
|
+
if (!t3.isJSXIdentifier(name))
|
|
971
1483
|
return;
|
|
972
1484
|
const tagName = name.name;
|
|
973
1485
|
if (!/^[A-Z]/.test(tagName))
|
|
@@ -984,19 +1496,19 @@ function collectImportedCallSites(ast, componentNames, imports) {
|
|
|
984
1496
|
const props = [];
|
|
985
1497
|
const spreads = [];
|
|
986
1498
|
for (const attr of path3.node.openingElement.attributes) {
|
|
987
|
-
if (
|
|
1499
|
+
if (t3.isJSXSpreadAttribute(attr)) {
|
|
988
1500
|
if (attr.argument) {
|
|
989
1501
|
spreads.push(attr.argument);
|
|
990
1502
|
}
|
|
991
1503
|
continue;
|
|
992
1504
|
}
|
|
993
|
-
if (
|
|
1505
|
+
if (t3.isJSXAttribute(attr)) {
|
|
994
1506
|
const attrName = attr.name.name;
|
|
995
1507
|
if (!attrName)
|
|
996
1508
|
continue;
|
|
997
|
-
if (
|
|
1509
|
+
if (t3.isJSXExpressionContainer(attr.value)) {
|
|
998
1510
|
const expr = attr.value.expression;
|
|
999
|
-
if (!expr ||
|
|
1511
|
+
if (!expr || t3.isJSXEmptyExpression(expr))
|
|
1000
1512
|
continue;
|
|
1001
1513
|
props.push({ name: attrName, value: expr });
|
|
1002
1514
|
}
|
|
@@ -1004,9 +1516,9 @@ function collectImportedCallSites(ast, componentNames, imports) {
|
|
|
1004
1516
|
}
|
|
1005
1517
|
const childExprs = [];
|
|
1006
1518
|
for (const child of path3.node.children) {
|
|
1007
|
-
if (
|
|
1519
|
+
if (t3.isJSXExpressionContainer(child)) {
|
|
1008
1520
|
const expr = child.expression;
|
|
1009
|
-
if (!expr ||
|
|
1521
|
+
if (!expr || t3.isJSXEmptyExpression(expr))
|
|
1010
1522
|
continue;
|
|
1011
1523
|
childExprs.push(expr);
|
|
1012
1524
|
}
|
|
@@ -1014,7 +1526,7 @@ function collectImportedCallSites(ast, componentNames, imports) {
|
|
|
1014
1526
|
if (childExprs.length === 1) {
|
|
1015
1527
|
props.push({ name: "children", value: childExprs[0] });
|
|
1016
1528
|
} else if (childExprs.length > 1) {
|
|
1017
|
-
props.push({ name: "children", value:
|
|
1529
|
+
props.push({ name: "children", value: t3.arrayExpression(childExprs) });
|
|
1018
1530
|
}
|
|
1019
1531
|
sites.push({
|
|
1020
1532
|
caller: callerFn,
|
|
@@ -1032,6 +1544,7 @@ function analyzeModule(code, filePath) {
|
|
|
1032
1544
|
sourceType: "module",
|
|
1033
1545
|
plugins: ["jsx", "typescript"]
|
|
1034
1546
|
});
|
|
1547
|
+
autoWrapComponents(ast, filePath);
|
|
1035
1548
|
const names = trackReactiveImports(ast);
|
|
1036
1549
|
const {
|
|
1037
1550
|
functions,
|
|
@@ -1049,18 +1562,18 @@ function analyzeModule(code, filePath) {
|
|
|
1049
1562
|
exports.set(localName, fn);
|
|
1050
1563
|
}
|
|
1051
1564
|
}
|
|
1052
|
-
|
|
1565
|
+
traverse3(ast, {
|
|
1053
1566
|
ExportDefaultDeclaration(path3) {
|
|
1054
1567
|
const decl = path3.node.declaration;
|
|
1055
|
-
if (
|
|
1568
|
+
if (t3.isIdentifier(decl)) {
|
|
1056
1569
|
const fn = componentNames.get(decl.name);
|
|
1057
1570
|
if (fn)
|
|
1058
1571
|
defaultExport = fn;
|
|
1059
|
-
} else if (
|
|
1572
|
+
} else if (t3.isCallExpression(decl)) {
|
|
1060
1573
|
const callee = decl.callee;
|
|
1061
|
-
if (
|
|
1574
|
+
if (t3.isIdentifier(callee) && names.cc.has(callee.name)) {
|
|
1062
1575
|
const firstArg = decl.arguments[0];
|
|
1063
|
-
if (firstArg &&
|
|
1576
|
+
if (firstArg && t3.isFunction(firstArg)) {
|
|
1064
1577
|
defaultExport = firstArg;
|
|
1065
1578
|
}
|
|
1066
1579
|
}
|
|
@@ -1846,4 +2359,4 @@ if (import.meta.main) {
|
|
|
1846
2359
|
runAnalyzeCli();
|
|
1847
2360
|
}
|
|
1848
2361
|
|
|
1849
|
-
export { analyzeModule, analyzeProject, AnalyzerCache, analyze, loadMetadata, trackReactiveImports, getAllPropNames, isReactiveValue, containsReactiveValue, collectComponentFunctions, computeLocalScopes, collectComponentCallGraph, buildFullScope, propagateReactiveProps, wrapReactiveExpressions, runAnalyzeCli };
|
|
2362
|
+
export { autoWrapComponents, analyzeModule, analyzeProject, AnalyzerCache, analyze, loadMetadata, trackReactiveImports, getAllPropNames, isReactiveValue, containsReactiveValue, collectComponentFunctions, computeLocalScopes, collectComponentCallGraph, buildFullScope, propagateReactiveProps, wrapReactiveExpressions, runAnalyzeCli };
|
package/dist/cli.js
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SinwanJS Compiler — Exported component detection.
|
|
3
|
+
*
|
|
4
|
+
* Shared utility for detecting top-level exported component bindings in a
|
|
5
|
+
* module. Used by the Vite and Bun plugins for HMR boundary injection.
|
|
6
|
+
*
|
|
7
|
+
* Detection is intentionally conservative: only top-level exports whose name
|
|
8
|
+
* starts with an uppercase letter (the component naming convention) are
|
|
9
|
+
* returned. The runtime safely ignores anything that is not used as a
|
|
10
|
+
* component, so over-matching is harmless.
|
|
11
|
+
*/
|
|
12
|
+
export interface ExportedComponent {
|
|
13
|
+
/** Local binding name in the module (referenced in the accept callback). */
|
|
14
|
+
local: string;
|
|
15
|
+
/** Export key on the module namespace ("default" for the default export). */
|
|
16
|
+
key: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Collect top-level exported component bindings from a module source string.
|
|
20
|
+
*
|
|
21
|
+
* Parses the source with Babel, then walks top-level export declarations to
|
|
22
|
+
* find functions and variables whose name starts with an uppercase letter.
|
|
23
|
+
*
|
|
24
|
+
* Returns an array of `{ local, key }` pairs. Returns an empty array if the
|
|
25
|
+
* source cannot be parsed.
|
|
26
|
+
*/
|
|
27
|
+
export declare function collectExportedComponents(code: string, filename: string): ExportedComponent[];
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* used by the Bun and Vite plugins.
|
|
6
6
|
*/
|
|
7
7
|
export { transformJSX, type TransformOptions } from "./transform";
|
|
8
|
+
export { COMPILER_TEMPLATE_SLOT_PROTOCOL } from "./transform";
|
|
9
|
+
export { autoWrapComponents } from "./auto-cc";
|
|
8
10
|
export { analyze, analyzeProject, analyzeModule, loadMetadata, AnalyzerCache, type AnalyzeOptions, type ModuleAnalysis, type ProjectAnalysis, type ImportedCallSite, type ImportInfo, type WorkspacesConfig, } from "./analyze";
|
|
11
|
+
export { collectExportedComponents, type ExportedComponent } from "./exports";
|
|
9
12
|
export { runAnalyzeCli } from "./cli";
|
|
10
13
|
export { wrapReactiveExpressions, trackReactiveImports, collectComponentFunctions, computeLocalScopes, collectComponentCallGraph, propagateReactiveProps, buildFullScope, containsReactiveValue, getAllPropNames, isReactiveValue, type ReactiveScope, type Binding, type CallSite, type ImportNames, type ComponentExpressionInfo, } from "./reactive-wrap";
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
analyze,
|
|
5
5
|
analyzeModule,
|
|
6
6
|
analyzeProject,
|
|
7
|
+
autoWrapComponents,
|
|
7
8
|
buildFullScope,
|
|
8
9
|
collectComponentCallGraph,
|
|
9
10
|
collectComponentFunctions,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
runAnalyzeCli,
|
|
17
18
|
trackReactiveImports,
|
|
18
19
|
wrapReactiveExpressions
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-w580fdrf.js";
|
|
20
21
|
|
|
21
22
|
// src/transform.ts
|
|
22
23
|
import { parse } from "@babel/parser";
|
|
@@ -25,10 +26,21 @@ import * as t from "@babel/types";
|
|
|
25
26
|
import _traverse from "@babel/traverse";
|
|
26
27
|
var generate = typeof _generate === "function" ? _generate : _generate.default ?? _generate;
|
|
27
28
|
var traverse = typeof _traverse === "function" ? _traverse : _traverse.default ?? _traverse;
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
var COMPILER_TEMPLATE_SLOT_PROTOCOL = {
|
|
30
|
+
slotPrefix: "s",
|
|
31
|
+
encodeSlot(index) {
|
|
32
|
+
return `s:${index}`;
|
|
33
|
+
},
|
|
34
|
+
decodeSlot(data) {
|
|
35
|
+
if (data.startsWith("s:")) {
|
|
36
|
+
const idx = parseInt(data.slice(2), 10);
|
|
37
|
+
return Number.isNaN(idx) ? null : idx;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function nextSlotId(counter) {
|
|
43
|
+
return `<!--${COMPILER_TEMPLATE_SLOT_PROTOCOL.encodeSlot(counter.value++)}-->`;
|
|
32
44
|
}
|
|
33
45
|
var VOID_ELEMENTS = new Set([
|
|
34
46
|
"area",
|
|
@@ -81,9 +93,9 @@ function cleanJSXText(value) {
|
|
|
81
93
|
return str;
|
|
82
94
|
}
|
|
83
95
|
function extractTemplate(node, filename) {
|
|
84
|
-
|
|
96
|
+
const counter = { value: 0 };
|
|
85
97
|
const slots = [];
|
|
86
|
-
const html = elementToHtml(node, slots, [], filename);
|
|
98
|
+
const html = elementToHtml(node, slots, [], filename, counter);
|
|
87
99
|
return { html, slots };
|
|
88
100
|
}
|
|
89
101
|
function warnSuspectedStyleInterpolation(attrValue, filename, line) {
|
|
@@ -153,7 +165,7 @@ function serializeStyleValue(expr) {
|
|
|
153
165
|
}
|
|
154
166
|
return parts.join(";");
|
|
155
167
|
}
|
|
156
|
-
function elementToHtml(node, slots, path, filename) {
|
|
168
|
+
function elementToHtml(node, slots, path, filename, counter) {
|
|
157
169
|
const tagName = jsxNameToString(node.openingElement.name);
|
|
158
170
|
if (tagName === "")
|
|
159
171
|
return "";
|
|
@@ -204,8 +216,15 @@ function elementToHtml(node, slots, path, filename) {
|
|
|
204
216
|
html += isVoid ? " />" : ">";
|
|
205
217
|
if (isVoid)
|
|
206
218
|
return html;
|
|
207
|
-
|
|
208
|
-
|
|
219
|
+
const childResult = childrenToHtml(node.children, slots, path, filename, 0, counter);
|
|
220
|
+
html += childResult.html;
|
|
221
|
+
html += `</${tagName}>`;
|
|
222
|
+
return html;
|
|
223
|
+
}
|
|
224
|
+
function childrenToHtml(children, slots, path, filename, startIndex, counter) {
|
|
225
|
+
let html = "";
|
|
226
|
+
let childIndex = startIndex;
|
|
227
|
+
for (const child of children) {
|
|
209
228
|
if (child.type === "JSXText") {
|
|
210
229
|
const text = cleanJSXText(child.value ?? "");
|
|
211
230
|
if (text) {
|
|
@@ -215,7 +234,7 @@ function elementToHtml(node, slots, path, filename) {
|
|
|
215
234
|
} else if (child.type === "JSXExpressionContainer") {
|
|
216
235
|
if (child.expression.type === "JSXEmptyExpression")
|
|
217
236
|
continue;
|
|
218
|
-
const slot = nextSlotId();
|
|
237
|
+
const slot = nextSlotId(counter);
|
|
219
238
|
slots.push({
|
|
220
239
|
path: [...path, childIndex],
|
|
221
240
|
type: "child",
|
|
@@ -224,12 +243,29 @@ function elementToHtml(node, slots, path, filename) {
|
|
|
224
243
|
html += slot;
|
|
225
244
|
childIndex++;
|
|
226
245
|
} else if (child.type === "JSXElement") {
|
|
227
|
-
|
|
228
|
-
|
|
246
|
+
const childName = child.openingElement.name;
|
|
247
|
+
const childTag = childName.type === "JSXIdentifier" ? childName.name : "";
|
|
248
|
+
const isFirstCharUpper = childTag && childTag.charAt(0) === childTag.charAt(0).toUpperCase();
|
|
249
|
+
if (isFirstCharUpper) {
|
|
250
|
+
const slot = nextSlotId(counter);
|
|
251
|
+
slots.push({
|
|
252
|
+
path: [...path, childIndex],
|
|
253
|
+
type: "child",
|
|
254
|
+
expr: child
|
|
255
|
+
});
|
|
256
|
+
html += slot;
|
|
257
|
+
childIndex++;
|
|
258
|
+
} else {
|
|
259
|
+
html += elementToHtml(child, slots, [...path, childIndex], filename, counter);
|
|
260
|
+
childIndex++;
|
|
261
|
+
}
|
|
262
|
+
} else if (child.type === "JSXFragment") {
|
|
263
|
+
const fragResult = childrenToHtml(child.children, slots, path, filename, childIndex, counter);
|
|
264
|
+
html += fragResult.html;
|
|
265
|
+
childIndex = fragResult.childIndex;
|
|
229
266
|
}
|
|
230
267
|
}
|
|
231
|
-
html
|
|
232
|
-
return html;
|
|
268
|
+
return { html, childIndex };
|
|
233
269
|
}
|
|
234
270
|
function jsxNameToString(name) {
|
|
235
271
|
if (name.type === "JSXIdentifier")
|
|
@@ -245,10 +281,12 @@ function transformJSX(code, filename, options = {}) {
|
|
|
245
281
|
plugins: ["jsx", "typescript"],
|
|
246
282
|
sourceFilename: filename
|
|
247
283
|
});
|
|
284
|
+
autoWrapComponents(ast, filename);
|
|
248
285
|
wrapReactiveExpressions(ast, {
|
|
249
286
|
explicitBindings: options.explicitBindings,
|
|
250
287
|
analyze: options.analyze,
|
|
251
288
|
analyzeMetadata: options.analyzeMetadata,
|
|
289
|
+
resolveImport: options.resolveImport,
|
|
252
290
|
filename
|
|
253
291
|
});
|
|
254
292
|
const hoist = options.hoist !== false;
|
|
@@ -275,10 +313,23 @@ function transformJSX(code, filename, options = {}) {
|
|
|
275
313
|
const templateExpr = t.callExpression(t.identifier("_$createTemplate"), [t.identifier(tmplId), t.arrayExpression(dynamicExprs)]);
|
|
276
314
|
const parentIsJSX = path.parentPath?.isJSXElement() || path.parentPath?.isJSXFragment();
|
|
277
315
|
path.replaceWith(parentIsJSX ? t.jsxExpressionContainer(templateExpr) : templateExpr);
|
|
278
|
-
} catch {
|
|
316
|
+
} catch (err) {
|
|
317
|
+
if (options.dev) {
|
|
318
|
+
console.warn(`[Sinwan] template hoisting skipped in ${filename}: ${err.message}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
279
321
|
}
|
|
280
322
|
});
|
|
281
323
|
if (templates.length === 0) {
|
|
324
|
+
if (options.explicitBindings) {
|
|
325
|
+
const bindingImport = t.importDeclaration([
|
|
326
|
+
t.importSpecifier(t.identifier("_$bindText"), t.identifier("_$bindText")),
|
|
327
|
+
t.importSpecifier(t.identifier("_$bindAttr"), t.identifier("_$bindAttr")),
|
|
328
|
+
t.importSpecifier(t.identifier("_$bindStyle"), t.identifier("_$bindStyle")),
|
|
329
|
+
t.importSpecifier(t.identifier("_$bindClass"), t.identifier("_$bindClass"))
|
|
330
|
+
], t.stringLiteral("sinwan/renderer"));
|
|
331
|
+
ast.program.body.unshift(bindingImport);
|
|
332
|
+
}
|
|
282
333
|
const result2 = generate(ast, {
|
|
283
334
|
sourceMaps: true,
|
|
284
335
|
sourceFileName: filename
|
|
@@ -308,22 +359,81 @@ function transformJSX(code, filename, options = {}) {
|
|
|
308
359
|
const result = generate(ast, { sourceMaps: true, sourceFileName: filename });
|
|
309
360
|
return { code: result.code, map: result.map };
|
|
310
361
|
}
|
|
362
|
+
// src/exports.ts
|
|
363
|
+
import { parse as parse2 } from "@babel/parser";
|
|
364
|
+
function isComponentName(name) {
|
|
365
|
+
return !!name && name[0] === name[0].toUpperCase() && /^[A-Z]/.test(name);
|
|
366
|
+
}
|
|
367
|
+
function collectExportedComponents(code, filename) {
|
|
368
|
+
let ast;
|
|
369
|
+
try {
|
|
370
|
+
ast = parse2(code, {
|
|
371
|
+
sourceType: "module",
|
|
372
|
+
plugins: ["jsx", "typescript"],
|
|
373
|
+
sourceFilename: filename
|
|
374
|
+
});
|
|
375
|
+
} catch {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
const found = [];
|
|
379
|
+
const seen = new Set;
|
|
380
|
+
const add = (local, key) => {
|
|
381
|
+
const sig = `${local}::${key}`;
|
|
382
|
+
if (seen.has(sig))
|
|
383
|
+
return;
|
|
384
|
+
seen.add(sig);
|
|
385
|
+
found.push({ local, key });
|
|
386
|
+
};
|
|
387
|
+
for (const node of ast.program.body) {
|
|
388
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
389
|
+
if (node.declaration) {
|
|
390
|
+
const decl = node.declaration;
|
|
391
|
+
if (decl.type === "FunctionDeclaration" && decl.id && isComponentName(decl.id.name)) {
|
|
392
|
+
add(decl.id.name, decl.id.name);
|
|
393
|
+
} else if (decl.type === "VariableDeclaration") {
|
|
394
|
+
for (const d of decl.declarations) {
|
|
395
|
+
if (d.id?.type === "Identifier" && isComponentName(d.id.name)) {
|
|
396
|
+
add(d.id.name, d.id.name);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
} else if (node.specifiers && !node.source) {
|
|
401
|
+
for (const spec of node.specifiers) {
|
|
402
|
+
if (spec.type === "ExportSpecifier" && spec.local?.type === "Identifier" && spec.exported?.type === "Identifier" && isComponentName(spec.exported.name)) {
|
|
403
|
+
add(spec.local.name, spec.exported.name);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
} else if (node.type === "ExportDefaultDeclaration") {
|
|
408
|
+
const decl = node.declaration;
|
|
409
|
+
if (decl.type === "Identifier") {
|
|
410
|
+
add(decl.name, "default");
|
|
411
|
+
} else if (decl.type === "FunctionDeclaration" && decl.id) {
|
|
412
|
+
add(decl.id.name, "default");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return found;
|
|
417
|
+
}
|
|
311
418
|
export {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
trackReactiveImports,
|
|
315
|
-
runAnalyzeCli,
|
|
316
|
-
propagateReactiveProps,
|
|
317
|
-
loadMetadata,
|
|
318
|
-
isReactiveValue,
|
|
319
|
-
getAllPropNames,
|
|
320
|
-
containsReactiveValue,
|
|
321
|
-
computeLocalScopes,
|
|
322
|
-
collectComponentFunctions,
|
|
323
|
-
collectComponentCallGraph,
|
|
324
|
-
buildFullScope,
|
|
325
|
-
analyzeProject,
|
|
326
|
-
analyzeModule,
|
|
419
|
+
AnalyzerCache,
|
|
420
|
+
COMPILER_TEMPLATE_SLOT_PROTOCOL,
|
|
327
421
|
analyze,
|
|
328
|
-
|
|
422
|
+
analyzeModule,
|
|
423
|
+
analyzeProject,
|
|
424
|
+
autoWrapComponents,
|
|
425
|
+
buildFullScope,
|
|
426
|
+
collectComponentCallGraph,
|
|
427
|
+
collectComponentFunctions,
|
|
428
|
+
collectExportedComponents,
|
|
429
|
+
computeLocalScopes,
|
|
430
|
+
containsReactiveValue,
|
|
431
|
+
getAllPropNames,
|
|
432
|
+
isReactiveValue,
|
|
433
|
+
loadMetadata,
|
|
434
|
+
propagateReactiveProps,
|
|
435
|
+
runAnalyzeCli,
|
|
436
|
+
trackReactiveImports,
|
|
437
|
+
transformJSX,
|
|
438
|
+
wrapReactiveExpressions
|
|
329
439
|
};
|
package/dist/reactive-wrap.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Supported reactive sources:
|
|
9
9
|
* - `createMutable` / `createStore` from `sinwan/store`
|
|
10
10
|
* - `signal` / `computed` from `sinwan/reactivity`
|
|
11
|
-
* - `useState` from `sinwan/react
|
|
11
|
+
* - `useState` from `sinwan/react`
|
|
12
12
|
*
|
|
13
13
|
* Example:
|
|
14
14
|
* <p>{state.name}</p> → <p>{() => state.name}</p>
|
|
@@ -22,6 +22,7 @@ export interface ImportNames {
|
|
|
22
22
|
signal: Set<string>;
|
|
23
23
|
computed: Set<string>;
|
|
24
24
|
useState: Set<string>;
|
|
25
|
+
useFetch: Set<string>;
|
|
25
26
|
cc: Set<string>;
|
|
26
27
|
}
|
|
27
28
|
export declare function trackReactiveImports(ast: t.Node): ImportNames;
|
|
@@ -39,15 +40,26 @@ export interface ComputedBinding {
|
|
|
39
40
|
export interface GetterBinding {
|
|
40
41
|
kind: "getter";
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Binding for a variable holding the object returned by `useFetch()` /
|
|
45
|
+
* `createFetch()(...)`. Every property on that object is a `Signal` or
|
|
46
|
+
* `Computed`, so a read like `f.data.value` (path `["data", "value"]`) is
|
|
47
|
+
* reactive. The shell object itself (`f`) and a bare property (`f.data`, the
|
|
48
|
+
* signal object) are NOT reactive reads — the runtime `resolve()` unwraps them
|
|
49
|
+
* inside effects when forwarded to control-flow props such as `<Show when>`.
|
|
50
|
+
*/
|
|
51
|
+
export interface SignalObjectBinding {
|
|
52
|
+
kind: "signalObject";
|
|
53
|
+
}
|
|
42
54
|
export interface PropBinding {
|
|
43
55
|
kind: "prop";
|
|
44
56
|
}
|
|
45
|
-
export type Binding = MutableBinding | SignalBinding | ComputedBinding | GetterBinding | PropBinding;
|
|
57
|
+
export type Binding = MutableBinding | SignalBinding | ComputedBinding | GetterBinding | SignalObjectBinding | PropBinding;
|
|
46
58
|
export interface ReactiveScope {
|
|
47
59
|
bindings: Map<string, Binding>;
|
|
48
60
|
}
|
|
49
61
|
export declare function getAllPropNames(params: t.Node | undefined): Set<string>;
|
|
50
|
-
export declare function isReactiveValue(node: t.
|
|
62
|
+
export declare function isReactiveValue(node: t.Node, scope: ReactiveScope): boolean;
|
|
51
63
|
export declare function containsReactiveValue(expr: t.Expression, scope: ReactiveScope): boolean;
|
|
52
64
|
export interface ComponentExpressionInfo {
|
|
53
65
|
isComponent: boolean;
|
|
@@ -76,5 +88,6 @@ export declare function wrapReactiveExpressions(ast: t.Node, options?: {
|
|
|
76
88
|
explicitBindings?: boolean;
|
|
77
89
|
analyze?: string;
|
|
78
90
|
analyzeMetadata?: Map<string, Map<string, Set<string>>>;
|
|
91
|
+
resolveImport?: (source: string, fromFile: string) => string | null;
|
|
79
92
|
filename?: string;
|
|
80
93
|
}): void;
|
package/dist/transform.d.ts
CHANGED
|
@@ -16,7 +16,29 @@ export interface TransformOptions {
|
|
|
16
16
|
analyze?: string;
|
|
17
17
|
/** In-memory reactive-props metadata (used by plugin-level caches). */
|
|
18
18
|
analyzeMetadata?: Map<string, Map<string, Set<string>>>;
|
|
19
|
+
/**
|
|
20
|
+
* Resolves an import source (e.g. "./Child") to an absolute file path,
|
|
21
|
+
* relative to the file being transformed. Used to look up reactive-prop
|
|
22
|
+
* metadata for components imported from other modules so reactive values
|
|
23
|
+
* passed to them are wrapped at the call site. When omitted, cross-module
|
|
24
|
+
* component call sites fall back to the conservative (non-wrapping) behavior.
|
|
25
|
+
*/
|
|
26
|
+
resolveImport?: (source: string, fromFile: string) => string | null;
|
|
19
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Template slot marker encoding.
|
|
30
|
+
*
|
|
31
|
+
* This mirrors the runtime's `DEFAULT_TEMPLATE_SLOT_PROTOCOL` in
|
|
32
|
+
* `sinwan/src/renderer/template-protocol.ts`. The compiler cannot import the
|
|
33
|
+
* runtime (separate packages), so the format is duplicated here. A drift test
|
|
34
|
+
* (`sinwan/__tests__/compiler-protocol-drift.test.ts`) asserts the two stay
|
|
35
|
+
* in sync.
|
|
36
|
+
*/
|
|
37
|
+
export declare const COMPILER_TEMPLATE_SLOT_PROTOCOL: {
|
|
38
|
+
readonly slotPrefix: "s";
|
|
39
|
+
readonly encodeSlot: (index: number) => string;
|
|
40
|
+
readonly decodeSlot: (data: string) => number | null;
|
|
41
|
+
};
|
|
20
42
|
export declare function transformJSX(code: string, filename: string, options?: TransformOptions): {
|
|
21
43
|
code: string;
|
|
22
44
|
map?: any;
|