@reckona/mreact-compiler 0.0.66 → 0.0.67

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.
@@ -0,0 +1,613 @@
1
+ import type { ClientReferenceIr, JsxNodeIr } from "./ir.js";
2
+ import { readArray, readObject } from "./oxc-node-utils.js";
3
+
4
+ const routerEntryCompatRuntimeExports = new Set(["Link"]);
5
+ const routerLinkCompatRuntimeExports = new Set(["Link"]);
6
+
7
+ interface ClientReferenceAliasState {
8
+ references: Map<string, ClientReferenceIr>;
9
+ stringConstants: Map<string, string>;
10
+ objectMembers: Map<string, Map<string, ClientReferenceIr>>;
11
+ }
12
+
13
+ export function collectOxcClientBoundaryImportComponents(
14
+ program: unknown,
15
+ inferredBoundaryImports: ReadonlySet<string>,
16
+ ): Map<string, ClientReferenceIr> {
17
+ const names = new Map<string, ClientReferenceIr>();
18
+
19
+ for (const statement of readArray(readObject(program).body)) {
20
+ const object = readObject(statement);
21
+
22
+ if (
23
+ object.type !== "ImportDeclaration" ||
24
+ !isOxcClientBoundaryImport(object, inferredBoundaryImports)
25
+ ) {
26
+ continue;
27
+ }
28
+
29
+ const moduleId = String(readObject(object.source).value ?? "");
30
+
31
+ for (const specifier of readArray(object.specifiers)) {
32
+ const specifierObject = readObject(specifier);
33
+ const local = readObject(specifierObject.local);
34
+ const localName = typeof local.name === "string" ? local.name : undefined;
35
+
36
+ if (localName === undefined || !/^[A-Z]/.test(localName)) {
37
+ continue;
38
+ }
39
+
40
+ if (specifierObject.type === "ImportDefaultSpecifier") {
41
+ names.set(localName, { moduleId, exportName: "default" });
42
+ continue;
43
+ }
44
+
45
+ if (specifierObject.type === "ImportNamespaceSpecifier") {
46
+ names.set(localName, { moduleId, exportName: "*" });
47
+ continue;
48
+ }
49
+
50
+ if (specifierObject.type === "ImportSpecifier" && specifierObject.importKind !== "type") {
51
+ const imported = readObject(specifierObject.imported);
52
+ names.set(localName, {
53
+ moduleId,
54
+ exportName: String(imported.name ?? localName),
55
+ });
56
+ }
57
+ }
58
+ }
59
+
60
+ collectOxcClientReferenceAliases(program, names);
61
+ return names;
62
+ }
63
+
64
+ export function collectOxcCompatRuntimeImportComponents(
65
+ program: unknown,
66
+ ): Map<string, ClientReferenceIr> {
67
+ const names = new Map<string, ClientReferenceIr>();
68
+
69
+ for (const statement of readArray(readObject(program).body)) {
70
+ const object = readObject(statement);
71
+
72
+ if (object.type !== "ImportDeclaration") {
73
+ continue;
74
+ }
75
+
76
+ const moduleId = String(readObject(object.source).value ?? "");
77
+ const runtimeExports = compatRuntimeExports(moduleId);
78
+ const compatComponentImport = runtimeExports !== undefined || isMdxModuleId(moduleId);
79
+
80
+ if (!compatComponentImport) {
81
+ continue;
82
+ }
83
+
84
+ for (const specifier of readArray(object.specifiers)) {
85
+ const specifierObject = readObject(specifier);
86
+ const local = readObject(specifierObject.local);
87
+ const localName = typeof local.name === "string" ? local.name : undefined;
88
+
89
+ if (localName === undefined || !/^[A-Z]/.test(localName)) {
90
+ continue;
91
+ }
92
+
93
+ if (
94
+ specifierObject.type === "ImportDefaultSpecifier" &&
95
+ (runtimeExports?.has("default") === true || isMdxModuleId(moduleId))
96
+ ) {
97
+ names.set(localName, { moduleId, exportName: "default" });
98
+ continue;
99
+ }
100
+
101
+ if (specifierObject.type === "ImportNamespaceSpecifier") {
102
+ names.set(localName, { moduleId, exportName: "*" });
103
+ continue;
104
+ }
105
+
106
+ if (specifierObject.type === "ImportSpecifier" && specifierObject.importKind !== "type") {
107
+ const imported = readObject(specifierObject.imported);
108
+ const importedName = String(imported.name ?? localName);
109
+
110
+ if (runtimeExports?.has(importedName) === true || isMdxModuleId(moduleId)) {
111
+ names.set(localName, {
112
+ moduleId,
113
+ exportName: importedName,
114
+ });
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ return names;
121
+ }
122
+
123
+ export function markOxcAsyncComponentReferences(
124
+ node: JsxNodeIr,
125
+ asyncComponentNames: Set<string>,
126
+ ): void {
127
+ visitOxcNode(node, (child) => {
128
+ if (child.kind === "component" && asyncComponentNames.has(child.name)) {
129
+ child.async = true;
130
+ }
131
+ });
132
+ }
133
+
134
+ export function markOxcClientReferences(
135
+ node: JsxNodeIr,
136
+ clientReferences: Map<string, ClientReferenceIr>,
137
+ ): void {
138
+ visitOxcNode(node, (child) => {
139
+ if (child.kind !== "component") {
140
+ return;
141
+ }
142
+
143
+ const clientReference = findOxcClientReference(child.name, clientReferences);
144
+
145
+ if (clientReference !== undefined) {
146
+ child.runtime = "compat";
147
+ child.clientReference = clientReference;
148
+ }
149
+ });
150
+ }
151
+
152
+ export function markOxcCompatRuntimeReferences(
153
+ node: JsxNodeIr,
154
+ runtimeReferences: ReadonlyMap<string, ClientReferenceIr>,
155
+ ): void {
156
+ visitOxcNode(node, (child) => {
157
+ if (child.kind !== "component") {
158
+ return;
159
+ }
160
+
161
+ const runtimeReference = findOxcCompatRuntimeReference(child.name, runtimeReferences);
162
+
163
+ if (runtimeReference !== undefined) {
164
+ child.runtime = "compat";
165
+ }
166
+ });
167
+ }
168
+
169
+ function isOxcClientBoundaryImport(
170
+ statement: Record<string, unknown>,
171
+ inferredBoundaryImports: ReadonlySet<string>,
172
+ ): boolean {
173
+ const moduleId = String(readObject(statement.source).value ?? "");
174
+ return inferredBoundaryImports.has(moduleId) || /\.(?:client|compat)\.[cm]?[jt]sx?$/.test(moduleId);
175
+ }
176
+
177
+ function findOxcClientReference(
178
+ name: string,
179
+ clientReferences: Map<string, ClientReferenceIr>,
180
+ ): ClientReferenceIr | undefined {
181
+ const direct = clientReferences.get(name);
182
+
183
+ if (direct !== undefined) {
184
+ return direct;
185
+ }
186
+
187
+ const [rootName, ...memberNames] = name.split(".");
188
+ const rootReference = rootName === undefined ? undefined : clientReferences.get(rootName);
189
+
190
+ if (rootReference === undefined || rootReference.exportName !== "*" || memberNames.length === 0) {
191
+ return rootReference;
192
+ }
193
+
194
+ return {
195
+ moduleId: rootReference.moduleId,
196
+ exportName: memberNames.join("."),
197
+ };
198
+ }
199
+
200
+ function collectOxcClientReferenceAliases(
201
+ node: unknown,
202
+ references: Map<string, ClientReferenceIr>,
203
+ ): void {
204
+ const state: ClientReferenceAliasState = {
205
+ references,
206
+ stringConstants: new Map(),
207
+ objectMembers: new Map(),
208
+ };
209
+
210
+ collectOxcClientReferenceAliasesFromNode(node, state);
211
+ }
212
+
213
+ function collectOxcClientReferenceAliasesFromNode(
214
+ node: unknown,
215
+ state: ClientReferenceAliasState,
216
+ ): void {
217
+ if (Array.isArray(node)) {
218
+ for (const child of node) {
219
+ collectOxcClientReferenceAliasesFromNode(child, state);
220
+ }
221
+ return;
222
+ }
223
+
224
+ const object = readOptionalObject(node);
225
+ if (object === undefined) {
226
+ return;
227
+ }
228
+
229
+ if (typeof object.type === "string" && object.type.startsWith("TS")) {
230
+ return;
231
+ }
232
+
233
+ if (object.type === "VariableDeclaration") {
234
+ const constant = object.kind === "const";
235
+ for (const declaration of readArray(object.declarations)) {
236
+ collectOxcVariableClientReferenceAlias(readOptionalObject(declaration), state, constant);
237
+ }
238
+ return;
239
+ }
240
+
241
+ if (object.type === "VariableDeclarator") {
242
+ collectOxcVariableClientReferenceAlias(object, state, false);
243
+ }
244
+
245
+ if (object.type === "AssignmentExpression") {
246
+ collectOxcAssignmentClientReferenceAlias(object, state);
247
+ }
248
+
249
+ if (object.type === "CallExpression") {
250
+ collectOxcObjectAssignClientReferenceAliases(object, state);
251
+ }
252
+
253
+ for (const [key, value] of Object.entries(object)) {
254
+ if (key === "type" || key === "start" || key === "end" || key === "loc") {
255
+ continue;
256
+ }
257
+
258
+ collectOxcClientReferenceAliasesFromNode(value, state);
259
+ }
260
+ }
261
+
262
+ function collectOxcVariableClientReferenceAlias(
263
+ node: Record<string, unknown> | undefined,
264
+ state: ClientReferenceAliasState,
265
+ constant: boolean,
266
+ ): void {
267
+ if (node?.type !== "VariableDeclarator") {
268
+ return;
269
+ }
270
+
271
+ const id = readOptionalObject(node.id);
272
+ const init = readOptionalObject(node.init);
273
+ const aliasName = typeof id?.name === "string" ? id.name : undefined;
274
+
275
+ if (aliasName === undefined) {
276
+ return;
277
+ }
278
+
279
+ if (constant) {
280
+ const stringValue = stringExpressionValue(init, state);
281
+ if (stringValue !== undefined) {
282
+ state.stringConstants.set(aliasName, stringValue);
283
+ }
284
+ }
285
+
286
+ collectOxcObjectLiteralClientReferenceAliases(aliasName, init, state);
287
+
288
+ const reference = expressionClientReference(init, state);
289
+ if (reference !== undefined) {
290
+ state.references.set(aliasName, reference);
291
+ }
292
+ }
293
+
294
+ function collectOxcObjectLiteralClientReferenceAliases(
295
+ objectName: string | undefined,
296
+ init: Record<string, unknown> | undefined,
297
+ state: ClientReferenceAliasState,
298
+ ): void {
299
+ if (objectName === undefined || init?.type !== "ObjectExpression") {
300
+ return;
301
+ }
302
+
303
+ for (const propertyValue of readArray(init.properties)) {
304
+ const property = readOptionalObject(propertyValue);
305
+ if (property?.type !== "Property") {
306
+ continue;
307
+ }
308
+
309
+ const keyName = propertyName(readOptionalObject(property.key), property.computed === true, state);
310
+ const reference = expressionClientReference(readOptionalObject(property.value), state);
311
+ if (keyName !== undefined && reference !== undefined) {
312
+ setObjectMemberReference(state, objectName, keyName, reference);
313
+ }
314
+ }
315
+ }
316
+
317
+ function collectOxcAssignmentClientReferenceAlias(
318
+ node: Record<string, unknown>,
319
+ state: ClientReferenceAliasState,
320
+ ): void {
321
+ if (node.operator !== "=") {
322
+ return;
323
+ }
324
+
325
+ const left = readOptionalObject(node.left);
326
+ if (left?.type !== "MemberExpression") {
327
+ return;
328
+ }
329
+
330
+ const objectName = expressionObjectName(readOptionalObject(left.object));
331
+ const memberName = propertyName(readOptionalObject(left.property), left.computed === true, state);
332
+ const reference = expressionClientReference(readOptionalObject(node.right), state);
333
+ if (objectName !== undefined && memberName !== undefined && reference !== undefined) {
334
+ setObjectMemberReference(state, objectName, memberName, reference);
335
+ }
336
+ }
337
+
338
+ function collectOxcObjectAssignClientReferenceAliases(
339
+ node: Record<string, unknown>,
340
+ state: ClientReferenceAliasState,
341
+ ): void {
342
+ if (!isObjectAssignCall(node)) {
343
+ return;
344
+ }
345
+
346
+ const args = readArray(node.arguments).map(readOptionalObject);
347
+ const target = expressionObjectName(args[0]);
348
+ if (target === undefined) {
349
+ return;
350
+ }
351
+
352
+ for (const source of args.slice(1)) {
353
+ collectOxcObjectLiteralClientReferenceAliases(target, source, state);
354
+ }
355
+ }
356
+
357
+ function expressionClientReference(
358
+ node: Record<string, unknown> | undefined,
359
+ state: ClientReferenceAliasState,
360
+ ): ClientReferenceIr | undefined {
361
+ if (node === undefined) {
362
+ return undefined;
363
+ }
364
+
365
+ if (node.type === "Identifier" && typeof node.name === "string") {
366
+ return state.references.get(node.name);
367
+ }
368
+
369
+ if (node.type === "MemberExpression") {
370
+ const objectName = expressionObjectName(readOptionalObject(node.object));
371
+ const memberName = propertyName(readOptionalObject(node.property), node.computed === true, state);
372
+ const objectReference =
373
+ objectName === undefined ? undefined : state.references.get(objectName);
374
+
375
+ if (objectName !== undefined && memberName !== undefined) {
376
+ const objectMember = state.objectMembers.get(objectName)?.get(memberName);
377
+ if (objectMember !== undefined) {
378
+ return objectMember;
379
+ }
380
+ }
381
+
382
+ if (objectName !== undefined && memberName === undefined) {
383
+ return uniqueClientReference(Array.from(state.objectMembers.get(objectName)?.values() ?? []));
384
+ }
385
+
386
+ if (objectReference?.exportName === "*" && memberName !== undefined) {
387
+ return {
388
+ moduleId: objectReference.moduleId,
389
+ exportName: memberName,
390
+ };
391
+ }
392
+ }
393
+
394
+ if (node.type === "ConditionalExpression") {
395
+ return uniqueClientReference([
396
+ expressionClientReference(readOptionalObject(node.consequent), state),
397
+ expressionClientReference(readOptionalObject(node.alternate), state),
398
+ ]);
399
+ }
400
+
401
+ if (
402
+ node.type === "ChainExpression" ||
403
+ node.type === "TSAsExpression" ||
404
+ node.type === "TSSatisfiesExpression" ||
405
+ node.type === "TSNonNullExpression" ||
406
+ node.type === "ParenthesizedExpression"
407
+ ) {
408
+ return expressionClientReference(readOptionalObject(node.expression), state);
409
+ }
410
+
411
+ return undefined;
412
+ }
413
+
414
+ function expressionObjectName(node: Record<string, unknown> | undefined): string | undefined {
415
+ return node?.type === "Identifier" && typeof node.name === "string" ? node.name : undefined;
416
+ }
417
+
418
+ function propertyName(
419
+ node: Record<string, unknown> | undefined,
420
+ computed: boolean,
421
+ state: ClientReferenceAliasState,
422
+ ): string | undefined {
423
+ if (!computed && node?.type === "Identifier" && typeof node.name === "string") {
424
+ return node.name;
425
+ }
426
+
427
+ if (computed && node?.type === "Identifier" && typeof node.name === "string") {
428
+ return state.stringConstants.get(node.name);
429
+ }
430
+
431
+ return stringExpressionValue(node, state);
432
+ }
433
+
434
+ function stringExpressionValue(
435
+ node: Record<string, unknown> | undefined,
436
+ state: ClientReferenceAliasState,
437
+ ): string | undefined {
438
+ if (
439
+ (node?.type === "StringLiteral" || node?.type === "Literal") &&
440
+ typeof node.value === "string"
441
+ ) {
442
+ return node.value;
443
+ }
444
+
445
+ if (node?.type === "ConditionalExpression") {
446
+ return uniqueString([
447
+ stringExpressionValue(readOptionalObject(node.consequent), state),
448
+ stringExpressionValue(readOptionalObject(node.alternate), state),
449
+ ]);
450
+ }
451
+
452
+ if (node?.type === "Identifier" && typeof node.name === "string") {
453
+ return state.stringConstants.get(node.name);
454
+ }
455
+
456
+ if (
457
+ node?.type === "ChainExpression" ||
458
+ node?.type === "TSAsExpression" ||
459
+ node?.type === "TSSatisfiesExpression" ||
460
+ node?.type === "TSNonNullExpression" ||
461
+ node?.type === "ParenthesizedExpression"
462
+ ) {
463
+ return stringExpressionValue(readOptionalObject(node.expression), state);
464
+ }
465
+
466
+ return undefined;
467
+ }
468
+
469
+ function setObjectMemberReference(
470
+ state: ClientReferenceAliasState,
471
+ objectName: string,
472
+ memberName: string,
473
+ reference: ClientReferenceIr,
474
+ ): void {
475
+ const members = state.objectMembers.get(objectName) ?? new Map<string, ClientReferenceIr>();
476
+ members.set(memberName, reference);
477
+ state.objectMembers.set(objectName, members);
478
+ }
479
+
480
+ function isObjectAssignCall(node: Record<string, unknown>): boolean {
481
+ const callee = readOptionalObject(node.callee);
482
+ if (callee?.type !== "MemberExpression" || callee.computed === true) {
483
+ return false;
484
+ }
485
+
486
+ const object = readOptionalObject(callee.object);
487
+ const property = readOptionalObject(callee.property);
488
+ return object?.type === "Identifier" && object.name === "Object" &&
489
+ property?.type === "Identifier" && property.name === "assign";
490
+ }
491
+
492
+ function uniqueClientReference(
493
+ values: readonly (ClientReferenceIr | undefined)[],
494
+ ): ClientReferenceIr | undefined {
495
+ const refs = values.filter((value): value is ClientReferenceIr => value !== undefined);
496
+ if (refs.length === 0) {
497
+ return undefined;
498
+ }
499
+
500
+ const first = refs[0];
501
+ if (first === undefined) {
502
+ return undefined;
503
+ }
504
+
505
+ return refs.every(
506
+ (reference) =>
507
+ reference.moduleId === first.moduleId && reference.exportName === first.exportName,
508
+ )
509
+ ? first
510
+ : undefined;
511
+ }
512
+
513
+ function uniqueString(values: readonly (string | undefined)[]): string | undefined {
514
+ const unique = new Set(values.filter((value): value is string => value !== undefined));
515
+ return unique.size === 1 ? Array.from(unique)[0] : undefined;
516
+ }
517
+
518
+ function readOptionalObject(value: unknown): Record<string, unknown> | undefined {
519
+ return typeof value === "object" && value !== null
520
+ ? (value as Record<string, unknown>)
521
+ : undefined;
522
+ }
523
+
524
+ function findOxcCompatRuntimeReference(
525
+ name: string,
526
+ runtimeReferences: ReadonlyMap<string, ClientReferenceIr>,
527
+ ): ClientReferenceIr | undefined {
528
+ const direct = runtimeReferences.get(name);
529
+
530
+ if (direct !== undefined) {
531
+ return direct;
532
+ }
533
+
534
+ const [rootName, ...memberNames] = name.split(".");
535
+ const rootReference = rootName === undefined ? undefined : runtimeReferences.get(rootName);
536
+
537
+ if (rootReference === undefined || rootReference.exportName !== "*" || memberNames.length === 0) {
538
+ return undefined;
539
+ }
540
+
541
+ const exportName = memberNames.join(".");
542
+ return compatRuntimeExports(rootReference.moduleId)?.has(exportName) === true
543
+ ? {
544
+ moduleId: rootReference.moduleId,
545
+ exportName,
546
+ }
547
+ : undefined;
548
+ }
549
+
550
+ function compatRuntimeExports(moduleId: string): ReadonlySet<string> | undefined {
551
+ if (moduleId === "@reckona/mreact-router") {
552
+ return routerEntryCompatRuntimeExports;
553
+ }
554
+
555
+ if (moduleId === "@reckona/mreact-router/link") {
556
+ return routerLinkCompatRuntimeExports;
557
+ }
558
+
559
+ return undefined;
560
+ }
561
+
562
+ function isMdxModuleId(moduleId: string): boolean {
563
+ return /\.mdx(?:[?#].*)?$/.test(moduleId);
564
+ }
565
+
566
+ function visitOxcNode(node: JsxNodeIr, visitor: (node: JsxNodeIr) => void): void {
567
+ visitor(node);
568
+
569
+ if (node.kind === "component") {
570
+ for (const prop of node.props) {
571
+ if (prop.kind === "render-prop") {
572
+ for (const child of prop.children) {
573
+ visitOxcNode(child, visitor);
574
+ }
575
+ }
576
+ }
577
+ for (const child of node.children) {
578
+ visitOxcNode(child, visitor);
579
+ }
580
+ return;
581
+ }
582
+
583
+ if (node.kind === "conditional") {
584
+ for (const child of [...node.whenTrue, ...node.whenFalse]) {
585
+ visitOxcNode(child, visitor);
586
+ }
587
+ return;
588
+ }
589
+
590
+ if (node.kind === "list") {
591
+ for (const child of node.children) {
592
+ visitOxcNode(child, visitor);
593
+ }
594
+ return;
595
+ }
596
+
597
+ if (node.kind === "async-boundary") {
598
+ for (const child of [
599
+ ...node.children,
600
+ ...(node.placeholderChildren ?? []),
601
+ ...(node.catchChildren ?? []),
602
+ ]) {
603
+ visitOxcNode(child, visitor);
604
+ }
605
+ return;
606
+ }
607
+
608
+ if (node.kind === "element" || node.kind === "fragment") {
609
+ for (const child of node.children) {
610
+ visitOxcNode(child, visitor);
611
+ }
612
+ }
613
+ }