@sigil-dev/compiler 0.8.6 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/babel/handlers/dom/globals.ts +3 -0
- package/src/babel/handlers/dom/state.ts +46 -20
- package/src/babel/index.ts +547 -435
- package/src/bun-plugin.ts +5 -3
- package/test/async-components.test.ts +59 -0
- package/test/helpers/transform.ts +22 -0
package/package.json
CHANGED
|
@@ -48,6 +48,7 @@ export function handleGlobalComponent(
|
|
|
48
48
|
t.ifStatement(
|
|
49
49
|
t.binaryExpression(
|
|
50
50
|
"===",
|
|
51
|
+
//@ts-expect-error i dont think it can be undefined here
|
|
51
52
|
t.unaryExpression("typeof", targetExpr),
|
|
52
53
|
t.stringLiteral("undefined"),
|
|
53
54
|
),
|
|
@@ -56,6 +57,7 @@ export function handleGlobalComponent(
|
|
|
56
57
|
t.expressionStatement(
|
|
57
58
|
t.callExpression(
|
|
58
59
|
t.memberExpression(
|
|
60
|
+
//@ts-expect-error i dont think it can be undefined here
|
|
59
61
|
targetExpr,
|
|
60
62
|
t.identifier("addEventListener"),
|
|
61
63
|
),
|
|
@@ -67,6 +69,7 @@ export function handleGlobalComponent(
|
|
|
67
69
|
[],
|
|
68
70
|
t.callExpression(
|
|
69
71
|
t.memberExpression(
|
|
72
|
+
//@ts-expect-error i dont think it can be undefined here
|
|
70
73
|
targetExpr,
|
|
71
74
|
t.identifier("removeEventListener"),
|
|
72
75
|
),
|
|
@@ -1,38 +1,64 @@
|
|
|
1
1
|
import type { NodePath } from "@babel/core";
|
|
2
2
|
import { types as t } from "@babel/core";
|
|
3
3
|
import { MAGIC } from "../../util/magic.ts";
|
|
4
|
+
|
|
4
5
|
export function handleState(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
declaration: NodePath<t.VariableDeclaration>,
|
|
7
|
+
declarator: NodePath<t.VariableDeclarator>,
|
|
8
|
+
init: NodePath<t.CallExpression>,
|
|
9
|
+
signals: Set<string>,
|
|
10
|
+
dev?: boolean,
|
|
11
|
+
fileKey?: string, // e.g. "src/routes/counter.tsx"
|
|
9
12
|
): void {
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
const { id } = declarator.node;
|
|
14
|
+
if (!t.isIdentifier(id)) throw new Error("Destructured $state not supported");
|
|
15
|
+
const name = id.name;
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
// check for // @sigil:reset escape comment
|
|
18
|
+
const hasReset = declaration.node.leadingComments?.some(
|
|
19
|
+
c => c.value.trim() === "@sigil:reset"
|
|
20
|
+
);
|
|
16
21
|
|
|
17
|
-
const setCall = (
|
|
18
|
-
value: t.Expression, // <-- now captures "count"
|
|
19
|
-
) =>
|
|
22
|
+
const setCall = (value: t.Expression) =>
|
|
20
23
|
t.callExpression(
|
|
21
24
|
t.memberExpression(t.identifier(name), t.identifier("set")),
|
|
22
25
|
[value],
|
|
23
26
|
);
|
|
24
|
-
|
|
25
27
|
const binding = declaration.scope.getBinding(name);
|
|
26
28
|
if (!binding) return;
|
|
27
|
-
const refs
|
|
28
|
-
const violations = [...binding.constantViolations];
|
|
29
|
+
const refs = [...binding.referencePaths];
|
|
30
|
+
const violations = [...binding.constantViolations];
|
|
31
|
+
|
|
32
|
+
const originalArgs = init.node.arguments;
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
if (dev && fileKey && !hasReset) {
|
|
35
|
+
const key = `${fileKey}#${name}`;
|
|
36
|
+
// createSignal(__hmrAdopt__("file#count", original))
|
|
37
|
+
init.replaceWith(
|
|
38
|
+
t.callExpression(t.identifier(MAGIC.createSignal), [
|
|
39
|
+
t.callExpression(t.identifier("__hmrAdopt__"), [
|
|
40
|
+
t.stringLiteral(key),
|
|
41
|
+
...(originalArgs as t.Expression[]),
|
|
42
|
+
]),
|
|
43
|
+
])
|
|
44
|
+
);
|
|
45
|
+
// __hmrRegister__("file#count", count) — inserted after declaration
|
|
46
|
+
declaration.insertAfter(
|
|
47
|
+
t.expressionStatement(
|
|
48
|
+
t.callExpression(t.identifier("__hmrRegister__"), [
|
|
49
|
+
t.stringLiteral(key),
|
|
50
|
+
t.identifier(name),
|
|
51
|
+
])
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
init.replaceWith(
|
|
56
|
+
t.callExpression(t.identifier(MAGIC.createSignal), originalArgs as t.Expression[])
|
|
57
|
+
);
|
|
58
|
+
}
|
|
35
59
|
|
|
60
|
+
declaration.node.kind = "const";
|
|
61
|
+
// ... rest of rewrites unchanged
|
|
36
62
|
// rewrite writes
|
|
37
63
|
// violations go before refs becase UpdateExpression
|
|
38
64
|
// is both a read and a write and this prevents count()++
|
package/src/babel/index.ts
CHANGED
|
@@ -33,445 +33,557 @@ const SSR_HELPERS = template.statements.ast(`
|
|
|
33
33
|
* Needed for SSR output so hydration can map items by key.
|
|
34
34
|
*/
|
|
35
35
|
function convertKeyToDataKey(node: t.JSXElement): void {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
36
|
+
for (let i = 0; i < node.openingElement.attributes.length; i++) {
|
|
37
|
+
const attr = node.openingElement.attributes[i];
|
|
38
|
+
if (
|
|
39
|
+
t.isJSXAttribute(attr) &&
|
|
40
|
+
t.isJSXIdentifier(attr.name) &&
|
|
41
|
+
attr.name.name === "key"
|
|
42
|
+
) {
|
|
43
|
+
node.openingElement.attributes[i] = t.jsxAttribute(
|
|
44
|
+
t.jsxIdentifier("data-key"),
|
|
45
|
+
attr.value,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const child of node.children) {
|
|
50
|
+
if (t.isJSXElement(child)) {
|
|
51
|
+
convertKeyToDataKey(child);
|
|
52
|
+
} else if (
|
|
53
|
+
t.isJSXExpressionContainer(child) &&
|
|
54
|
+
t.isJSXElement(child.expression)
|
|
55
|
+
) {
|
|
56
|
+
convertKeyToDataKey(child.expression);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
interface SigilOptions {
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
hash?: string;
|
|
63
|
+
mode?: "dom" | "ssr" | "hydrate";
|
|
64
|
+
dev?: boolean
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export default function sigilPlugin(): PluginObject {
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
68
|
+
const signals = new Set<string>();
|
|
69
|
+
const storeSignals = new Set<string>();
|
|
70
|
+
const asyncFunctions = new Map<string, any>();
|
|
71
|
+
|
|
72
|
+
let scopedHash: string | undefined;
|
|
73
|
+
let isSSR = false;
|
|
74
|
+
let isHydrate = false;
|
|
75
|
+
let didTransform = false;
|
|
76
|
+
|
|
77
|
+
let isDev = false;
|
|
78
|
+
let filename = "";
|
|
79
|
+
|
|
80
|
+
let asyncDefaultPath: any = null;
|
|
81
|
+
let asyncDefaultRef: string | null = null;
|
|
82
|
+
let hasLoadingExport = false;
|
|
83
|
+
let hasErrorExport = false;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
name: "sigil",
|
|
87
|
+
visitor: {
|
|
88
|
+
Program: {
|
|
89
|
+
enter(path, state) {
|
|
90
|
+
signals.clear();
|
|
91
|
+
storeSignals.clear();
|
|
92
|
+
const opts = (state.opts || {}) as SigilOptions;
|
|
93
|
+
scopedHash = opts.hash;
|
|
94
|
+
didTransform = false;
|
|
95
|
+
isSSR = opts.mode === "ssr";
|
|
96
|
+
isHydrate = opts.mode === "hydrate";
|
|
97
|
+
isDev = opts.dev ?? false;
|
|
98
|
+
filename = state.filename ?? "";
|
|
99
|
+
asyncDefaultPath = null;
|
|
100
|
+
hasLoadingExport = false;
|
|
101
|
+
hasErrorExport = false;
|
|
102
|
+
asyncDefaultRef = null;
|
|
103
|
+
asyncFunctions.clear();
|
|
104
|
+
|
|
105
|
+
if (isSSR) {
|
|
106
|
+
path.unshiftContainer("body", SSR_HELPERS);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
exit(path, state) {
|
|
111
|
+
warnDeadReactivity(path, signals, storeSignals, state.filename);
|
|
112
|
+
|
|
113
|
+
if (asyncDefaultPath && !isSSR) {
|
|
114
|
+
let fnNode: t.FunctionDeclaration;
|
|
115
|
+
|
|
116
|
+
if (asyncDefaultPath.isExportDefaultDeclaration()) {
|
|
117
|
+
// export default async function Page() {}
|
|
118
|
+
fnNode = asyncDefaultPath.get("declaration").node as t.FunctionDeclaration;
|
|
119
|
+
} else {
|
|
120
|
+
// async function Page() {} + export default Page
|
|
121
|
+
fnNode = asyncDefaultPath.node as t.FunctionDeclaration;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const originalName = fnNode.id?.name ?? "Component";
|
|
125
|
+
const privateName = `__${originalName}`;
|
|
126
|
+
const propsId = t.identifier("__props");
|
|
127
|
+
|
|
128
|
+
const privateFn = t.functionDeclaration(
|
|
129
|
+
t.identifier(privateName),
|
|
130
|
+
fnNode.params,
|
|
131
|
+
fnNode.body,
|
|
132
|
+
false,
|
|
133
|
+
true,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const wrapperExport = t.exportDefaultDeclaration(
|
|
137
|
+
t.functionDeclaration(
|
|
138
|
+
t.identifier(originalName),
|
|
139
|
+
[propsId],
|
|
140
|
+
t.blockStatement([
|
|
141
|
+
t.returnStatement(
|
|
142
|
+
t.callExpression(t.identifier("__asyncBoundary"), [
|
|
143
|
+
t.arrowFunctionExpression(
|
|
144
|
+
[],
|
|
145
|
+
t.callExpression(t.identifier(privateName), [propsId]),
|
|
146
|
+
),
|
|
147
|
+
hasLoadingExport ? t.identifier("loading") : t.identifier("undefined"),
|
|
148
|
+
hasErrorExport ? t.identifier("error") : t.identifier("undefined"),
|
|
149
|
+
]),
|
|
150
|
+
),
|
|
151
|
+
]),
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
asyncDefaultPath.replaceWithMultiple([privateFn, wrapperExport]);
|
|
156
|
+
didTransform = true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Only inject runtime import if this file actually used sigil transforms
|
|
160
|
+
if (!didTransform || isSSR) return;
|
|
161
|
+
|
|
162
|
+
const requiredSpecifiers = [
|
|
163
|
+
"createSignal",
|
|
164
|
+
"createRawSignal",
|
|
165
|
+
"createEffect",
|
|
166
|
+
"createMemo",
|
|
167
|
+
"reconcile",
|
|
168
|
+
"claim",
|
|
169
|
+
"claimText",
|
|
170
|
+
"claimComment",
|
|
171
|
+
"hydrateKeyedList",
|
|
172
|
+
"insert",
|
|
173
|
+
"getHydrationNodes",
|
|
174
|
+
"pushHydrationNodes",
|
|
175
|
+
"popHydrationNodes",
|
|
176
|
+
"snapshot",
|
|
177
|
+
"tracking",
|
|
178
|
+
"createEffectPre",
|
|
179
|
+
"createInspectEffect",
|
|
180
|
+
"withEffectScope",
|
|
181
|
+
...(asyncDefaultPath ? ["__asyncBoundary"] : []),
|
|
182
|
+
];
|
|
183
|
+
const runtimeImport = path.node.body.find(
|
|
184
|
+
(n): n is t.ImportDeclaration =>
|
|
185
|
+
t.isImportDeclaration(n) &&
|
|
186
|
+
n.source.value === "@sigil-dev/runtime",
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (runtimeImport) {
|
|
190
|
+
// console.log(
|
|
191
|
+
// "[sigil exit] found existing import, specifier count:",
|
|
192
|
+
// runtimeImport.specifiers.length,
|
|
193
|
+
// );
|
|
194
|
+
const existingNames = new Set(
|
|
195
|
+
runtimeImport.specifiers
|
|
196
|
+
.filter(t.isImportSpecifier)
|
|
197
|
+
.map((s) =>
|
|
198
|
+
t.isIdentifier(s.imported)
|
|
199
|
+
? s.imported.name
|
|
200
|
+
: (s.imported as t.StringLiteral).value,
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
for (const name of requiredSpecifiers) {
|
|
204
|
+
if (!existingNames.has(name)) {
|
|
205
|
+
runtimeImport.specifiers.push(
|
|
206
|
+
t.importSpecifier(t.identifier(name), t.identifier(name)),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
path.unshiftContainer(
|
|
212
|
+
"body",
|
|
213
|
+
t.importDeclaration(
|
|
214
|
+
requiredSpecifiers.map((name) =>
|
|
215
|
+
t.importSpecifier(t.identifier(name), t.identifier(name)),
|
|
216
|
+
),
|
|
217
|
+
t.stringLiteral("@sigil-dev/runtime"),
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
VariableDeclaration(path) {
|
|
224
|
+
for (const declarator of path.get("declarations")) {
|
|
225
|
+
const macro = getMacro(declarator);
|
|
226
|
+
if (!macro) continue;
|
|
227
|
+
didTransform = true;
|
|
228
|
+
const { name, init } = macro;
|
|
229
|
+
const varName = t.isIdentifier(declarator.node.id)
|
|
230
|
+
? declarator.node.id.name
|
|
231
|
+
: null;
|
|
232
|
+
|
|
233
|
+
if (name === "$state" || name === "$store" || name === "$state.raw") {
|
|
234
|
+
if (name === "$state.raw") {
|
|
235
|
+
if (!isSSR) {
|
|
236
|
+
const args = init.node.arguments;
|
|
237
|
+
init.replaceWith(
|
|
238
|
+
t.callExpression(t.identifier(MAGIC.createRawSignal), args),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
isSSR
|
|
244
|
+
? handleStateSSR(path, declarator, init, signals)
|
|
245
|
+
: handleState(path, declarator, init, signals, isDev, filename); // ← add
|
|
246
|
+
if (name === "$store" && varName) {
|
|
247
|
+
storeSignals.add(varName);
|
|
248
|
+
}
|
|
249
|
+
} else if (name === "$derived") {
|
|
250
|
+
isSSR
|
|
251
|
+
? handleDerivedSSR(path, declarator, init, signals)
|
|
252
|
+
: handleDerived(path, declarator, init, signals);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
ExpressionStatement(path) {
|
|
257
|
+
if (isSSR) {
|
|
258
|
+
// drop $effect, $inspect, and $debug entirely in SSR
|
|
259
|
+
const expr = path.get("expression");
|
|
260
|
+
if (expr.isCallExpression()) {
|
|
261
|
+
const callee = expr.get("callee");
|
|
262
|
+
if (
|
|
263
|
+
callee.isIdentifier() &&
|
|
264
|
+
(callee.node.name === "$effect" ||
|
|
265
|
+
callee.node.name === "$inspect" ||
|
|
266
|
+
callee.node.name === "$debug")
|
|
267
|
+
) {
|
|
268
|
+
didTransform = true;
|
|
269
|
+
path.remove();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
handleEffect(path);
|
|
275
|
+
// Handle $debug(expr) expression statements
|
|
276
|
+
const expr = path.get("expression");
|
|
277
|
+
if (expr.isCallExpression()) {
|
|
278
|
+
const callee = expr.get("callee");
|
|
279
|
+
if (callee.isIdentifier({ name: "$debug" })) {
|
|
280
|
+
didTransform = true;
|
|
281
|
+
path.replaceWith(
|
|
282
|
+
t.ifStatement(
|
|
283
|
+
t.memberExpression(
|
|
284
|
+
t.memberExpression(
|
|
285
|
+
t.memberExpression(
|
|
286
|
+
t.identifier("import"),
|
|
287
|
+
t.identifier("meta"),
|
|
288
|
+
),
|
|
289
|
+
t.identifier("env"),
|
|
290
|
+
),
|
|
291
|
+
t.identifier("DEV"),
|
|
292
|
+
),
|
|
293
|
+
t.blockStatement([
|
|
294
|
+
t.debuggerStatement(),
|
|
295
|
+
t.expressionStatement(
|
|
296
|
+
t.callExpression(
|
|
297
|
+
t.memberExpression(
|
|
298
|
+
t.identifier("console"),
|
|
299
|
+
t.identifier("log"),
|
|
300
|
+
),
|
|
301
|
+
expr.node.arguments,
|
|
302
|
+
),
|
|
303
|
+
),
|
|
304
|
+
]),
|
|
305
|
+
),
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
CallExpression(path) {
|
|
311
|
+
if (isSSR) return;
|
|
312
|
+
const callee = path.get("callee");
|
|
313
|
+
|
|
314
|
+
// Helper: wrap signal identifiers in call expressions
|
|
315
|
+
const wrapSignal = (expr: t.Expression): t.Expression => {
|
|
316
|
+
if (t.isIdentifier(expr) && signals.has(expr.name)) {
|
|
317
|
+
return t.callExpression(expr, []);
|
|
318
|
+
}
|
|
319
|
+
return expr;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Handle member expression callees: $state.snapshot(x), $effect.tracking(), $effect.pre(fn)
|
|
323
|
+
if (callee.isMemberExpression()) {
|
|
324
|
+
const object = callee.get("object");
|
|
325
|
+
const property = callee.get("property");
|
|
326
|
+
if (object.isIdentifier() && property.isIdentifier()) {
|
|
327
|
+
const obj = object.node.name;
|
|
328
|
+
const prop = property.node.name;
|
|
329
|
+
|
|
330
|
+
if (obj === "$state" && prop === "snapshot") {
|
|
331
|
+
didTransform = true;
|
|
332
|
+
path.get("callee").replaceWith(t.identifier(MAGIC.snapshot));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (obj === "$effect" && prop === "tracking") {
|
|
337
|
+
didTransform = true;
|
|
338
|
+
path.get("callee").replaceWith(t.identifier(MAGIC.tracking));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (obj === "$effect" && prop === "pre") {
|
|
343
|
+
didTransform = true;
|
|
344
|
+
path
|
|
345
|
+
.get("callee")
|
|
346
|
+
.replaceWith(t.identifier(MAGIC.createEffectPre));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Handle $inspect(count).with(cb) chain
|
|
353
|
+
if (callee.isMemberExpression()) {
|
|
354
|
+
const property = callee.get("property");
|
|
355
|
+
if (property.isIdentifier({ name: "with" })) {
|
|
356
|
+
const object = callee.get("object");
|
|
357
|
+
if (object.isCallExpression()) {
|
|
358
|
+
const innerCallee = object.get("callee");
|
|
359
|
+
if (innerCallee.isIdentifier({ name: "$inspect" })) {
|
|
360
|
+
didTransform = true;
|
|
361
|
+
//@ts-expect-error Bizzare but it works if you read LSP
|
|
362
|
+
const args = object.node.arguments.map(wrapSignal);
|
|
363
|
+
const cb = path.node.arguments[0];
|
|
364
|
+
path.replaceWith(
|
|
365
|
+
t.callExpression(t.identifier(MAGIC.createInspectEffect), [
|
|
366
|
+
t.arrowFunctionExpression([], t.arrayExpression(args)),
|
|
367
|
+
//@ts-expect-error Bizzare but it works if you read LSP
|
|
368
|
+
cb,
|
|
369
|
+
]),
|
|
370
|
+
);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Handle standalone $debug(...) — not as expression statement
|
|
378
|
+
if (callee.isIdentifier({ name: "$debug" })) {
|
|
379
|
+
didTransform = true;
|
|
380
|
+
path.replaceWith(
|
|
381
|
+
t.ifStatement(
|
|
382
|
+
t.memberExpression(
|
|
383
|
+
t.memberExpression(
|
|
384
|
+
t.memberExpression(
|
|
385
|
+
t.identifier("import"),
|
|
386
|
+
t.identifier("meta"),
|
|
387
|
+
),
|
|
388
|
+
t.identifier("env"),
|
|
389
|
+
),
|
|
390
|
+
t.identifier("DEV"),
|
|
391
|
+
),
|
|
392
|
+
t.blockStatement([
|
|
393
|
+
t.debuggerStatement(),
|
|
394
|
+
t.expressionStatement(
|
|
395
|
+
t.callExpression(
|
|
396
|
+
t.memberExpression(
|
|
397
|
+
t.identifier("console"),
|
|
398
|
+
t.identifier("log"),
|
|
399
|
+
),
|
|
400
|
+
path.node.arguments,
|
|
401
|
+
),
|
|
402
|
+
),
|
|
403
|
+
]),
|
|
404
|
+
),
|
|
405
|
+
);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Handle standalone $inspect(...)
|
|
410
|
+
if (callee.isIdentifier({ name: "$inspect" })) {
|
|
411
|
+
didTransform = true;
|
|
412
|
+
//@ts-expect-error Bizzare but it works if you read LSP
|
|
413
|
+
const args = path.node.arguments.map(wrapSignal);
|
|
414
|
+
path.replaceWith(
|
|
415
|
+
t.callExpression(t.identifier(MAGIC.createInspectEffect), [
|
|
416
|
+
t.arrowFunctionExpression([], t.arrayExpression(args)),
|
|
417
|
+
]),
|
|
418
|
+
);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
JSXElement(path) {
|
|
423
|
+
didTransform = true;
|
|
424
|
+
// console.log(
|
|
425
|
+
// "[sigil] JSXElement visitor fired, parent:",
|
|
426
|
+
// path.parentPath?.type,
|
|
427
|
+
// "isSSR:", isSSR
|
|
428
|
+
// );
|
|
429
|
+
if (path.parentPath?.isJSXElement() || path.parentPath?.isJSXFragment())
|
|
430
|
+
return;
|
|
431
|
+
if (isSSR) {
|
|
432
|
+
try {
|
|
433
|
+
convertKeyToDataKey(path.node);
|
|
434
|
+
path.replaceWith(
|
|
435
|
+
t.callExpression(t.identifier("__h"), [
|
|
436
|
+
processElementSSR(path.node, signals),
|
|
437
|
+
]),
|
|
438
|
+
);
|
|
439
|
+
} catch (e) {
|
|
440
|
+
console.error("[sigil] processElementSSR threw:", path.toString());
|
|
441
|
+
console.error(e);
|
|
442
|
+
throw e;
|
|
443
|
+
}
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const statements: t.Statement[] = [];
|
|
448
|
+
const genId = (() => {
|
|
449
|
+
let i = 0;
|
|
450
|
+
return () => `_el${i++}`;
|
|
451
|
+
})();
|
|
452
|
+
|
|
453
|
+
const root = processElement(
|
|
454
|
+
path.node,
|
|
455
|
+
statements,
|
|
456
|
+
genId,
|
|
457
|
+
signals,
|
|
458
|
+
scopedHash,
|
|
459
|
+
isHydrate,
|
|
460
|
+
isHydrate ? "__nodes" : undefined,
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
const body: t.Statement[] = [];
|
|
464
|
+
|
|
465
|
+
if (isHydrate) {
|
|
466
|
+
// DECLARE __nodes locally — every IIFE owns its own context
|
|
467
|
+
body.push(
|
|
468
|
+
t.variableDeclaration("const", [
|
|
469
|
+
t.variableDeclarator(
|
|
470
|
+
t.identifier("__nodes"),
|
|
471
|
+
t.callExpression(t.identifier("getHydrationNodes"), []),
|
|
472
|
+
),
|
|
473
|
+
]),
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
body.push(...statements);
|
|
478
|
+
body.push(t.returnStatement(t.identifier(root)));
|
|
479
|
+
|
|
480
|
+
path.replaceWith(
|
|
481
|
+
t.callExpression(
|
|
482
|
+
t.arrowFunctionExpression([], t.blockStatement(body)),
|
|
483
|
+
[],
|
|
484
|
+
),
|
|
485
|
+
);
|
|
486
|
+
},
|
|
487
|
+
JSXFragment(path) {
|
|
488
|
+
didTransform = true;
|
|
489
|
+
if (path.parentPath?.isJSXElement() || path.parentPath?.isJSXFragment())
|
|
490
|
+
return;
|
|
491
|
+
if (isSSR) {
|
|
492
|
+
path.replaceWith(
|
|
493
|
+
t.callExpression(t.identifier("__h"), [
|
|
494
|
+
processFragmentSSR(path.node, signals),
|
|
495
|
+
]),
|
|
496
|
+
);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const statements: t.Statement[] = [];
|
|
501
|
+
const genId = (() => {
|
|
502
|
+
let i = 0;
|
|
503
|
+
return () => `_el${i++}`;
|
|
504
|
+
})();
|
|
505
|
+
|
|
506
|
+
const root = processFragment(
|
|
507
|
+
path.node,
|
|
508
|
+
statements,
|
|
509
|
+
genId,
|
|
510
|
+
signals,
|
|
511
|
+
scopedHash,
|
|
512
|
+
isHydrate,
|
|
513
|
+
isHydrate ? "__nodes" : undefined,
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
const body: t.Statement[] = [];
|
|
517
|
+
|
|
518
|
+
if (isHydrate) {
|
|
519
|
+
// DECLARE __nodes locally — every IIFE owns its own context
|
|
520
|
+
body.push(
|
|
521
|
+
t.variableDeclaration("const", [
|
|
522
|
+
t.variableDeclarator(
|
|
523
|
+
t.identifier("__nodes"),
|
|
524
|
+
t.callExpression(t.identifier("getHydrationNodes"), []),
|
|
525
|
+
),
|
|
526
|
+
]),
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
body.push(...statements);
|
|
531
|
+
body.push(t.returnStatement(t.identifier(root)));
|
|
532
|
+
|
|
533
|
+
path.replaceWith(
|
|
534
|
+
t.callExpression(
|
|
535
|
+
t.arrowFunctionExpression([], t.blockStatement(body)),
|
|
536
|
+
[],
|
|
537
|
+
),
|
|
538
|
+
);
|
|
539
|
+
},
|
|
540
|
+
FunctionDeclaration(path) {
|
|
541
|
+
if (isSSR) return;
|
|
542
|
+
const node = path.node;
|
|
543
|
+
if (node.async && node.id) {
|
|
544
|
+
asyncFunctions.set(node.id.name, path);
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
|
|
548
|
+
ExportDefaultDeclaration(path) {
|
|
549
|
+
const decl = path.get("declaration");
|
|
550
|
+
|
|
551
|
+
// export default async function Page() {}
|
|
552
|
+
if (
|
|
553
|
+
(decl.isFunctionDeclaration() || decl.isFunctionExpression()) &&
|
|
554
|
+
(decl.node as any).async &&
|
|
555
|
+
!isSSR
|
|
556
|
+
) {
|
|
557
|
+
asyncDefaultPath = path;
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// export default Page
|
|
562
|
+
if (decl.isIdentifier() && !isSSR) {
|
|
563
|
+
const name = (decl.node as t.Identifier).name;
|
|
564
|
+
if (asyncFunctions.has(name)) {
|
|
565
|
+
asyncDefaultPath = asyncFunctions.get(name);
|
|
566
|
+
asyncDefaultRef = name;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
|
|
571
|
+
ExportNamedDeclaration(path) {
|
|
572
|
+
const decl = path.get("declaration");
|
|
573
|
+
if (decl.isFunctionDeclaration()) {
|
|
574
|
+
const name = (decl.node as t.FunctionDeclaration).id?.name;
|
|
575
|
+
if (name === "loading") hasLoadingExport = true;
|
|
576
|
+
if (name === "error") hasErrorExport = true;
|
|
577
|
+
}
|
|
578
|
+
if (decl.isVariableDeclaration()) {
|
|
579
|
+
for (const d of decl.node.declarations) {
|
|
580
|
+
if (t.isIdentifier(d.id)) {
|
|
581
|
+
if (d.id.name === "loading") hasLoadingExport = true;
|
|
582
|
+
if (d.id.name === "error") hasErrorExport = true;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
};
|
|
477
589
|
}
|
package/src/bun-plugin.ts
CHANGED
|
@@ -35,6 +35,7 @@ function shouldTransformSource(path: string, code: string): boolean {
|
|
|
35
35
|
|
|
36
36
|
export function sigil(options?: {
|
|
37
37
|
mode?: "dom" | "ssr" | "hydrate";
|
|
38
|
+
dev?: boolean;
|
|
38
39
|
}): BunPlugin {
|
|
39
40
|
return {
|
|
40
41
|
name: "sigil",
|
|
@@ -42,8 +43,8 @@ export function sigil(options?: {
|
|
|
42
43
|
const transpiler = new Bun.Transpiler({ loader: "tsx" });
|
|
43
44
|
|
|
44
45
|
// This is required for both linking errors,
|
|
45
|
-
// and for a bug in the bundler, which was reported in https://github.com/oven-sh/bun/issues/13897
|
|
46
|
-
// and apparently fixed in https://github.com/oven-sh/bun/pull/15119. It wasnt
|
|
46
|
+
// and for a bug in the bundler, which was reported in https://github.com/oven-sh/bun/issues/13897
|
|
47
|
+
// 2 YEARS AGO and apparently fixed in https://github.com/oven-sh/bun/pull/15119. It wasnt
|
|
47
48
|
build.onResolve({ filter: /^@sigil-dev\/runtime($|\/)/ }, (args) => {
|
|
48
49
|
try {
|
|
49
50
|
const resolved = Bun.resolveSync(args.path, process.cwd());
|
|
@@ -66,9 +67,10 @@ export function sigil(options?: {
|
|
|
66
67
|
});
|
|
67
68
|
const res = transformSync(code, {
|
|
68
69
|
parserOpts: {
|
|
70
|
+
//@ts-expect-error holy shut up
|
|
69
71
|
plugins: [["typescript", { isTSX: true }], "jsx"],
|
|
70
72
|
},
|
|
71
|
-
plugins: [[sigilPlugin, { hash, mode: options?.mode }]],
|
|
73
|
+
plugins: [[sigilPlugin, { hash, mode: options?.mode, dev: options?.dev }]],
|
|
72
74
|
filename: path,
|
|
73
75
|
});
|
|
74
76
|
let out = transpiler.transformSync(res?.code ?? "");
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { transformAsync, transformSSR } from "./helpers/transform.ts";
|
|
3
|
+
|
|
4
|
+
describe("Async Components", () => {
|
|
5
|
+
test("async default export wraps in __asyncBoundary in DOM mode", () => {
|
|
6
|
+
const out = transformAsync(`
|
|
7
|
+
export async function Page({ data }) {
|
|
8
|
+
return <div>{data}</div>;
|
|
9
|
+
}
|
|
10
|
+
export default Page;
|
|
11
|
+
`);
|
|
12
|
+
expect(out).toContain("__asyncBoundary");
|
|
13
|
+
expect(out).toContain("__Page");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("loading export passed to __asyncBoundary", () => {
|
|
17
|
+
const out = transformAsync(`
|
|
18
|
+
export async function Page({ data }) {
|
|
19
|
+
return <div>{data}</div>;
|
|
20
|
+
}
|
|
21
|
+
export default Page;
|
|
22
|
+
export function loading() {
|
|
23
|
+
return <div />;
|
|
24
|
+
}
|
|
25
|
+
`);
|
|
26
|
+
expect(out).toContain("loading");
|
|
27
|
+
expect(out).not.toContain("undefined, undefined");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("no loading or error yields undefined args", () => {
|
|
31
|
+
const out = transformAsync(`
|
|
32
|
+
export async function Page({ data }) {
|
|
33
|
+
return <div>{data}</div>;
|
|
34
|
+
}
|
|
35
|
+
export default Page;
|
|
36
|
+
`);
|
|
37
|
+
expect(out).toContain("undefined, undefined");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("SSR mode leaves async function untouched", () => {
|
|
41
|
+
const out = transformSSR(`
|
|
42
|
+
export async function Page({ data }) {
|
|
43
|
+
return <div>{data}</div>;
|
|
44
|
+
}
|
|
45
|
+
export default Page;
|
|
46
|
+
`);
|
|
47
|
+
expect(out).not.toContain("__asyncBoundary");
|
|
48
|
+
expect(out).not.toContain("__Page");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("non-async default is unchanged", () => {
|
|
52
|
+
const out = transformAsync(`
|
|
53
|
+
export default function Page({ data }) {
|
|
54
|
+
return <div>{data}</div>;
|
|
55
|
+
}
|
|
56
|
+
`);
|
|
57
|
+
expect(out).not.toContain("__asyncBoundary");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -24,3 +24,25 @@ export function transformSSR(code: string) {
|
|
|
24
24
|
})?.code ?? ""
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
|
+
export function transformDOM(code: string, hash?: string) {
|
|
28
|
+
return (
|
|
29
|
+
transformSync(code, {
|
|
30
|
+
plugins: [
|
|
31
|
+
"@babel/plugin-syntax-jsx",
|
|
32
|
+
[sigilPlugin, { mode: "dom", hash }],
|
|
33
|
+
],
|
|
34
|
+
filename: "test.tsx",
|
|
35
|
+
})?.code ?? ""
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
export function transformAsync(code: string) {
|
|
39
|
+
return (
|
|
40
|
+
transformSync(code, {
|
|
41
|
+
plugins: [
|
|
42
|
+
"@babel/plugin-syntax-jsx",
|
|
43
|
+
[sigilPlugin, { mode: "dom" }],
|
|
44
|
+
],
|
|
45
|
+
filename: "test.tsx",
|
|
46
|
+
})?.code ?? ""
|
|
47
|
+
);
|
|
48
|
+
}
|