@reckona/mreact-router 0.0.86 → 0.0.88
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/bundle-pipeline.d.ts +1 -0
- package/dist/bundle-pipeline.d.ts.map +1 -1
- package/dist/bundle-pipeline.js +115 -6
- package/dist/bundle-pipeline.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +81 -20
- package/dist/client.js.map +1 -1
- package/dist/module-runner.d.ts +4 -0
- package/dist/module-runner.d.ts.map +1 -1
- package/dist/module-runner.js +448 -14
- package/dist/module-runner.js.map +1 -1
- package/dist/render.d.ts +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +37 -5
- package/dist/render.js.map +1 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +32 -20
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/bundle-pipeline.ts +157 -24
- package/src/client.ts +91 -37
- package/src/module-runner.ts +587 -16
- package/src/render.ts +76 -30
- package/src/vite.ts +47 -37
package/src/module-runner.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join, sep } from "node:path";
|
|
4
|
+
import { dirname, extname, isAbsolute, join, sep } from "node:path";
|
|
4
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
6
|
import { formatDiagnostic } from "@reckona/mreact-compiler";
|
|
6
7
|
import type { ServerOutputMode } from "@reckona/mreact-shared/compiler-contract";
|
|
7
8
|
import { transformCompilerModuleContext } from "@reckona/mreact-compiler/internal";
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
createRunnableDevEnvironment,
|
|
11
|
+
mergeConfig,
|
|
12
|
+
resolveConfig,
|
|
13
|
+
type InlineConfig,
|
|
14
|
+
type PluginOption,
|
|
15
|
+
type RunnableDevEnvironment,
|
|
16
|
+
} from "vite";
|
|
9
17
|
import { resolveWorkspacePackageFile } from "./workspace-packages.js";
|
|
10
18
|
import {
|
|
11
19
|
bundleRouterModule,
|
|
@@ -47,6 +55,10 @@ const maxServerSourceTransformCacheEntries = resolveRouterCacheLimit(
|
|
|
47
55
|
512,
|
|
48
56
|
);
|
|
49
57
|
const serverSourceTransformCacheCounters = createRouterRuntimeCacheCounters();
|
|
58
|
+
const packageTypeCache = new Map<string, string | undefined>();
|
|
59
|
+
const runnerVirtualModulePrefix = "virtual:mreact-router-source/";
|
|
60
|
+
const runnerVirtualModules = new Map<string, string>();
|
|
61
|
+
let sharedRunnerEnvironment: Promise<RunnableDevEnvironment> | undefined;
|
|
50
62
|
let fileImportVersion = 0;
|
|
51
63
|
|
|
52
64
|
export function routerModuleRunnerRuntimeCacheStats(): RouterRuntimeCacheStat[] {
|
|
@@ -69,7 +81,9 @@ export function routerModuleRunnerRuntimeCacheStats(): RouterRuntimeCacheStat[]
|
|
|
69
81
|
export async function importAppRouterSourceModule<T>(options: {
|
|
70
82
|
cacheKey?: string | undefined;
|
|
71
83
|
code: string;
|
|
84
|
+
externalizeAppSourceModuleDirs?: readonly string[] | undefined;
|
|
72
85
|
label: string;
|
|
86
|
+
plugins?: readonly RouterCompatPlugin[] | undefined;
|
|
73
87
|
resolveDir?: string | undefined;
|
|
74
88
|
serverSourceTransform?: ServerSourceTransformOptions | undefined;
|
|
75
89
|
sourcefile?: string | undefined;
|
|
@@ -107,7 +121,9 @@ export async function importAppRouterSourceModule<T>(options: {
|
|
|
107
121
|
|
|
108
122
|
async function importAppRouterSourceModuleWithoutCache<T>(options: {
|
|
109
123
|
code: string;
|
|
124
|
+
externalizeAppSourceModuleDirs?: readonly string[] | undefined;
|
|
110
125
|
label: string;
|
|
126
|
+
plugins?: readonly RouterCompatPlugin[] | undefined;
|
|
111
127
|
resolveDir?: string | undefined;
|
|
112
128
|
serverSourceTransform?: ServerSourceTransformOptions | undefined;
|
|
113
129
|
sourcefile?: string | undefined;
|
|
@@ -115,27 +131,108 @@ async function importAppRouterSourceModuleWithoutCache<T>(options: {
|
|
|
115
131
|
}): Promise<T> {
|
|
116
132
|
const code =
|
|
117
133
|
options.resolveDir === undefined ? options.code : await bundleAppRouterSourceModule(options);
|
|
134
|
+
const sourcefile = options.sourcefile ?? join(options.resolveDir ?? process.cwd(), "module.js");
|
|
118
135
|
const executableCode = withNodeRequireShimForEsmBundle({
|
|
119
|
-
code,
|
|
136
|
+
code: withFileImportMetaUrl(code, sourcefile),
|
|
137
|
+
filename: sourcefile,
|
|
120
138
|
requireBaseDir:
|
|
121
139
|
options.resolveDir ??
|
|
122
140
|
(options.sourcefile === undefined ? undefined : dirname(options.sourcefile)),
|
|
123
141
|
});
|
|
124
142
|
const encodedLabel = encodeURIComponent(options.label.replace(/[^A-Za-z0-9_$.-]/g, "-"));
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
)}#${encodedLabel}-${Date.now()}-${Math.random()}`;
|
|
128
|
-
const result = await runnerImport<T>(url, runnerConfig);
|
|
143
|
+
const publicId = `${runnerVirtualModulePrefix}${encodedLabel}-${Date.now()}-${Math.random()}.mjs`;
|
|
144
|
+
runnerVirtualModules.set(publicId, executableCode);
|
|
129
145
|
|
|
130
|
-
|
|
146
|
+
try {
|
|
147
|
+
return await importWithSharedRunner<T>(publicId, { invalidateEntry: true });
|
|
148
|
+
} finally {
|
|
149
|
+
runnerVirtualModules.delete(publicId);
|
|
150
|
+
}
|
|
131
151
|
}
|
|
132
152
|
|
|
133
153
|
export async function importAppRouterFileModule<T>(file: string): Promise<T> {
|
|
134
154
|
fileImportVersion += 1;
|
|
135
155
|
const url = `${pathToFileURL(file).href}?t=${Date.now()}${fileImportVersion}`;
|
|
136
|
-
const result = await runnerImport<T>(url, runnerConfig);
|
|
137
156
|
|
|
138
|
-
return
|
|
157
|
+
return await importWithSharedRunner<T>(url, { invalidateEntry: true });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function importWithSharedRunner<T>(
|
|
161
|
+
moduleId: string,
|
|
162
|
+
options?: { invalidateEntry?: boolean | undefined },
|
|
163
|
+
): Promise<T> {
|
|
164
|
+
const environment = await getSharedRunnerEnvironment();
|
|
165
|
+
const module = (await environment.runner.import(moduleId)) as T;
|
|
166
|
+
|
|
167
|
+
if (options?.invalidateEntry === true) {
|
|
168
|
+
const evaluatedModule =
|
|
169
|
+
environment.runner.evaluatedModules.getModuleByUrl(moduleId) ??
|
|
170
|
+
environment.runner.evaluatedModules.getModuleById(moduleId) ??
|
|
171
|
+
environment.runner.evaluatedModules.getModuleById(`\0${moduleId}`);
|
|
172
|
+
|
|
173
|
+
if (evaluatedModule !== undefined) {
|
|
174
|
+
environment.runner.evaluatedModules.invalidateModule(evaluatedModule);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return module;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function getSharedRunnerEnvironment(): Promise<RunnableDevEnvironment> {
|
|
182
|
+
if (sharedRunnerEnvironment !== undefined) {
|
|
183
|
+
return await sharedRunnerEnvironment;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
sharedRunnerEnvironment = createSharedRunnerEnvironment().catch((error) => {
|
|
187
|
+
sharedRunnerEnvironment = undefined;
|
|
188
|
+
throw error;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
return await sharedRunnerEnvironment;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function createSharedRunnerEnvironment(): Promise<RunnableDevEnvironment> {
|
|
195
|
+
const config = await resolveConfig(
|
|
196
|
+
mergeConfig(runnerConfig, {
|
|
197
|
+
cacheDir: process.cwd(),
|
|
198
|
+
configFile: false,
|
|
199
|
+
envDir: false,
|
|
200
|
+
environments: {
|
|
201
|
+
mreact_router: {
|
|
202
|
+
consumer: "server",
|
|
203
|
+
dev: { moduleRunnerTransform: true },
|
|
204
|
+
resolve: {
|
|
205
|
+
conditions: ["node"],
|
|
206
|
+
external: true,
|
|
207
|
+
mainFields: [],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
plugins: [
|
|
212
|
+
{
|
|
213
|
+
name: "mreact-router-source-module",
|
|
214
|
+
resolveId(source) {
|
|
215
|
+
return runnerVirtualModules.has(source) ? `\0${source}` : undefined;
|
|
216
|
+
},
|
|
217
|
+
load(id) {
|
|
218
|
+
const source = id.startsWith("\0") ? id.slice(1) : id;
|
|
219
|
+
|
|
220
|
+
return source.startsWith(runnerVirtualModulePrefix)
|
|
221
|
+
? runnerVirtualModules.get(source)
|
|
222
|
+
: undefined;
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
} satisfies InlineConfig),
|
|
227
|
+
"serve",
|
|
228
|
+
);
|
|
229
|
+
const environment = createRunnableDevEnvironment("mreact_router", config, {
|
|
230
|
+
hot: false,
|
|
231
|
+
runnerOptions: { hmr: { logger: false } },
|
|
232
|
+
});
|
|
233
|
+
await environment.init();
|
|
234
|
+
|
|
235
|
+
return environment;
|
|
139
236
|
}
|
|
140
237
|
|
|
141
238
|
export async function importAppRouterBuiltFileModule<T>(options: {
|
|
@@ -174,7 +271,9 @@ export async function importAppRouterBuiltFileModule<T>(options: {
|
|
|
174
271
|
|
|
175
272
|
export async function bundleAppRouterSourceModule(options: {
|
|
176
273
|
code: string;
|
|
274
|
+
externalizeAppSourceModuleDirs?: readonly string[] | undefined;
|
|
177
275
|
label: string;
|
|
276
|
+
plugins?: readonly RouterCompatPlugin[] | undefined;
|
|
178
277
|
resolveDir?: string | undefined;
|
|
179
278
|
serverSourceTransform?: ServerSourceTransformOptions | undefined;
|
|
180
279
|
sourcefile?: string | undefined;
|
|
@@ -182,6 +281,7 @@ export async function bundleAppRouterSourceModule(options: {
|
|
|
182
281
|
}): Promise<string> {
|
|
183
282
|
const output = await bundleRouterModule({
|
|
184
283
|
code: options.code,
|
|
284
|
+
externalizeAppSourceModuleDirs: options.externalizeAppSourceModuleDirs,
|
|
185
285
|
filename: options.sourcefile ?? join(options.resolveDir ?? process.cwd(), "module.js"),
|
|
186
286
|
platform: "node",
|
|
187
287
|
vitePlugins: options.vitePlugins,
|
|
@@ -190,6 +290,7 @@ export async function bundleAppRouterSourceModule(options: {
|
|
|
190
290
|
...(options.serverSourceTransform === undefined
|
|
191
291
|
? []
|
|
192
292
|
: [serverSourceTransformPlugin(options.serverSourceTransform)]),
|
|
293
|
+
...(options.plugins ?? []),
|
|
193
294
|
],
|
|
194
295
|
});
|
|
195
296
|
const code = output.code;
|
|
@@ -360,29 +461,471 @@ function withFileImportMetaUrl(source: string, filename: string): string {
|
|
|
360
461
|
|
|
361
462
|
function withNodeRequireShimForEsmBundle(options: {
|
|
362
463
|
code: string;
|
|
464
|
+
filename: string;
|
|
363
465
|
requireBaseDir?: string | undefined;
|
|
364
466
|
}): string {
|
|
365
467
|
const requireBaseUrl = pathToFileURL(
|
|
366
468
|
join(options.requireBaseDir ?? process.cwd(), "__mreact_require_shim.cjs"),
|
|
367
469
|
).href;
|
|
368
|
-
const
|
|
470
|
+
const rewritten = rewriteNodeModulesExternalImports(options.code);
|
|
471
|
+
const code = rewritten.code.replaceAll(
|
|
369
472
|
"createRequire(import.meta.url)",
|
|
370
473
|
`createRequire(${JSON.stringify(requireBaseUrl)})`,
|
|
371
474
|
);
|
|
475
|
+
const needsFilenameGlobalShim = needsCommonJsFilenameGlobalShim(options.code);
|
|
476
|
+
const needsRequireShim = needsNodeRequireShim(options.code);
|
|
372
477
|
|
|
373
|
-
if (
|
|
478
|
+
if (
|
|
479
|
+
!rewritten.needsNativeImport &&
|
|
480
|
+
!rewritten.needsRequire &&
|
|
481
|
+
!needsFilenameGlobalShim &&
|
|
482
|
+
!needsRequireShim
|
|
483
|
+
) {
|
|
374
484
|
return code;
|
|
375
485
|
}
|
|
376
486
|
|
|
377
|
-
return
|
|
378
|
-
const
|
|
379
|
-
${
|
|
487
|
+
return `${rewritten.needsNativeImport ? 'const __mreactNativeImport = Function("specifier", "return import(specifier)");\n' : ""}${needsFilenameGlobalShim ? `const __filename = ${JSON.stringify(options.filename)};
|
|
488
|
+
const __dirname = ${JSON.stringify(dirname(options.filename))};
|
|
489
|
+
` : ""}${rewritten.needsRequire || needsRequireShim ? `import { createRequire as __mreactCreateRequire } from "node:module";
|
|
490
|
+
const __mreactRequire = __mreactCreateRequire(${JSON.stringify(requireBaseUrl)});
|
|
491
|
+
const require = __mreactRequire;
|
|
492
|
+
` : ""}${code}`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function needsCommonJsFilenameGlobalShim(code: string): boolean {
|
|
496
|
+
return /\b__(?:filename|dirname)\b/.test(code);
|
|
380
497
|
}
|
|
381
498
|
|
|
382
499
|
function needsNodeRequireShim(code: string): boolean {
|
|
383
500
|
return code.includes("Dynamic require of") && /\b__require\s*=/.test(code);
|
|
384
501
|
}
|
|
385
502
|
|
|
503
|
+
function rewriteNodeModulesExternalImports(code: string): {
|
|
504
|
+
code: string;
|
|
505
|
+
needsNativeImport: boolean;
|
|
506
|
+
needsRequire: boolean;
|
|
507
|
+
} {
|
|
508
|
+
const nativeImportBindings = new Map<string, string>();
|
|
509
|
+
let needsNativeImport = false;
|
|
510
|
+
let needsRequire = false;
|
|
511
|
+
let importIndex = 0;
|
|
512
|
+
const importFromPattern = /^import\s+([^;\n]+?)\s+from\s+(["'])([^"']+)\2;?$/gm;
|
|
513
|
+
const sideEffectImportPattern = /^import\s+(["'])([^"']+)\1;?$/gm;
|
|
514
|
+
const withRewrittenImports = code.replace(
|
|
515
|
+
importFromPattern,
|
|
516
|
+
(statement: string, clause: string, _quote: string, specifier: string) => {
|
|
517
|
+
const file = nodeModulesExternalImportPath(specifier);
|
|
518
|
+
|
|
519
|
+
if (file === undefined || !isNodeImportableModuleFile(file)) {
|
|
520
|
+
return statement;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (isNodeCommonJsModuleFile(file)) {
|
|
524
|
+
needsRequire = true;
|
|
525
|
+
const requireExpression = `__mreactRequire(${JSON.stringify(file)})`;
|
|
526
|
+
return commonJsImportClauseToRequireStatements(
|
|
527
|
+
clause.trim(),
|
|
528
|
+
requireExpression,
|
|
529
|
+
importIndex++,
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
needsNativeImport = true;
|
|
534
|
+
const rewritten = esmImportClauseToNativeImportStatements(clause.trim(), specifier, importIndex++);
|
|
535
|
+
for (const [name, replacement] of rewritten.bindings) {
|
|
536
|
+
nativeImportBindings.set(name, replacement);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return rewritten.code;
|
|
540
|
+
},
|
|
541
|
+
);
|
|
542
|
+
const rewrittenCode = withRewrittenImports.replace(
|
|
543
|
+
sideEffectImportPattern,
|
|
544
|
+
(statement: string, _quote: string, specifier: string) => {
|
|
545
|
+
const file = nodeModulesExternalImportPath(specifier);
|
|
546
|
+
|
|
547
|
+
if (file === undefined || !isNodeImportableModuleFile(file)) {
|
|
548
|
+
return statement;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (isNodeCommonJsModuleFile(file)) {
|
|
552
|
+
needsRequire = true;
|
|
553
|
+
return `__mreactRequire(${JSON.stringify(file)});`;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
needsNativeImport = true;
|
|
557
|
+
return `await __mreactNativeImport(${JSON.stringify(specifier)});`;
|
|
558
|
+
},
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
code:
|
|
563
|
+
nativeImportBindings.size === 0
|
|
564
|
+
? rewrittenCode
|
|
565
|
+
: replaceImportedIdentifiers(rewrittenCode, nativeImportBindings),
|
|
566
|
+
needsNativeImport,
|
|
567
|
+
needsRequire,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function nodeModulesExternalImportPath(specifier: string): string | undefined {
|
|
572
|
+
const file = externalImportFilePath(specifier);
|
|
573
|
+
|
|
574
|
+
if (file === undefined || !isNodeModulesPath(file)) {
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return file;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function externalImportFilePath(specifier: string): string | undefined {
|
|
582
|
+
if (specifier.startsWith("file://")) {
|
|
583
|
+
try {
|
|
584
|
+
return fileURLToPath(specifier);
|
|
585
|
+
} catch {
|
|
586
|
+
return undefined;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return isAbsolute(specifier) ? specifier : undefined;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function isNodeModulesPath(file: string): boolean {
|
|
594
|
+
return file.split(sep).includes("node_modules");
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function isNodeImportableModuleFile(file: string): boolean {
|
|
598
|
+
const extension = extname(file);
|
|
599
|
+
|
|
600
|
+
return extension === ".cjs" || extension === ".mjs" || extension === ".js" || extension === ".node";
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function isNodeCommonJsModuleFile(file: string): boolean {
|
|
604
|
+
const extension = extname(file);
|
|
605
|
+
|
|
606
|
+
if (extension === ".cjs" || extension === ".cts" || extension === ".node") {
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (extension === ".mjs" || extension === ".mts" || extension !== ".js") {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return nearestPackageType(file) !== "module";
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function nearestPackageType(file: string): string | undefined {
|
|
618
|
+
let directory = dirname(file);
|
|
619
|
+
|
|
620
|
+
while (true) {
|
|
621
|
+
const packageJson = join(directory, "package.json");
|
|
622
|
+
|
|
623
|
+
if (packageTypeCache.has(packageJson)) {
|
|
624
|
+
return packageTypeCache.get(packageJson);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (existsSync(packageJson)) {
|
|
628
|
+
const type = readPackageType(packageJson);
|
|
629
|
+
packageTypeCache.set(packageJson, type);
|
|
630
|
+
return type;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const parent = dirname(directory);
|
|
634
|
+
|
|
635
|
+
if (parent === directory) {
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
directory = parent;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function readPackageType(packageJson: string): string | undefined {
|
|
644
|
+
try {
|
|
645
|
+
const parsed = JSON.parse(readFileSync(packageJson, "utf8")) as { type?: unknown };
|
|
646
|
+
|
|
647
|
+
return typeof parsed.type === "string" ? parsed.type : undefined;
|
|
648
|
+
} catch {
|
|
649
|
+
return undefined;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function commonJsImportClauseToRequireStatements(
|
|
654
|
+
clause: string,
|
|
655
|
+
requireExpression: string,
|
|
656
|
+
importIndex: number,
|
|
657
|
+
): string {
|
|
658
|
+
if (clause.startsWith("* as ")) {
|
|
659
|
+
return `const ${clause.slice(5).trim()} = ${requireExpression};`;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (clause.startsWith("{")) {
|
|
663
|
+
return namedCommonJsImportsToRequireStatements(clause, requireExpression);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const commaIndex = clause.indexOf(",");
|
|
667
|
+
|
|
668
|
+
if (commaIndex === -1) {
|
|
669
|
+
return `const ${clause} = ${requireExpression};`;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const defaultName = clause.slice(0, commaIndex).trim();
|
|
673
|
+
const namedOrNamespace = clause.slice(commaIndex + 1).trim();
|
|
674
|
+
const temporaryName = `__mreactExternalCommonJs${importIndex}`;
|
|
675
|
+
const statements = [`const ${temporaryName} = ${requireExpression};`, `const ${defaultName} = ${temporaryName};`];
|
|
676
|
+
|
|
677
|
+
if (namedOrNamespace.startsWith("* as ")) {
|
|
678
|
+
statements.push(`const ${namedOrNamespace.slice(5).trim()} = ${temporaryName};`);
|
|
679
|
+
} else if (namedOrNamespace.startsWith("{")) {
|
|
680
|
+
statements.push(namedCommonJsImportsToRequireStatements(namedOrNamespace, temporaryName));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return statements.join("\n");
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function esmImportClauseToNativeImportStatements(
|
|
687
|
+
clause: string,
|
|
688
|
+
specifier: string,
|
|
689
|
+
importIndex: number,
|
|
690
|
+
): { bindings: Map<string, string>; code: string } {
|
|
691
|
+
const bindings = new Map<string, string>();
|
|
692
|
+
const temporaryName = `__mreactExternalModule${importIndex}`;
|
|
693
|
+
const statements = [
|
|
694
|
+
`const ${temporaryName} = await __mreactNativeImport(${JSON.stringify(specifier)});`,
|
|
695
|
+
];
|
|
696
|
+
|
|
697
|
+
if (clause.startsWith("* as ")) {
|
|
698
|
+
statements.push(`const ${clause.slice(5).trim()} = ${temporaryName};`);
|
|
699
|
+
} else if (clause.startsWith("{")) {
|
|
700
|
+
collectNamedEsmImportBindings(clause, temporaryName, bindings);
|
|
701
|
+
} else {
|
|
702
|
+
const commaIndex = clause.indexOf(",");
|
|
703
|
+
|
|
704
|
+
if (commaIndex === -1) {
|
|
705
|
+
bindings.set(clause, `${temporaryName}.default`);
|
|
706
|
+
} else {
|
|
707
|
+
const defaultName = clause.slice(0, commaIndex).trim();
|
|
708
|
+
const namedOrNamespace = clause.slice(commaIndex + 1).trim();
|
|
709
|
+
bindings.set(defaultName, `${temporaryName}.default`);
|
|
710
|
+
|
|
711
|
+
if (namedOrNamespace.startsWith("* as ")) {
|
|
712
|
+
statements.push(`const ${namedOrNamespace.slice(5).trim()} = ${temporaryName};`);
|
|
713
|
+
} else if (namedOrNamespace.startsWith("{")) {
|
|
714
|
+
collectNamedEsmImportBindings(namedOrNamespace, temporaryName, bindings);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return { bindings, code: statements.join("\n") };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function collectNamedEsmImportBindings(
|
|
723
|
+
clause: string,
|
|
724
|
+
moduleName: string,
|
|
725
|
+
bindings: Map<string, string>,
|
|
726
|
+
): void {
|
|
727
|
+
for (const binding of namedImportBindings(clause)) {
|
|
728
|
+
const replacement =
|
|
729
|
+
binding.imported === "default"
|
|
730
|
+
? `${moduleName}.default`
|
|
731
|
+
: `${moduleName}[${JSON.stringify(binding.imported)}]`;
|
|
732
|
+
bindings.set(binding.local, replacement);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function namedCommonJsImportsToRequireStatements(
|
|
737
|
+
clause: string,
|
|
738
|
+
sourceExpression: string,
|
|
739
|
+
): string {
|
|
740
|
+
const objectBindings: string[] = [];
|
|
741
|
+
const statements: string[] = [];
|
|
742
|
+
|
|
743
|
+
for (const binding of namedImportBindings(clause)) {
|
|
744
|
+
if (binding.imported === "default") {
|
|
745
|
+
statements.push(`const ${binding.local} = ${sourceExpression};`);
|
|
746
|
+
} else if (binding.imported === binding.local) {
|
|
747
|
+
objectBindings.push(binding.imported);
|
|
748
|
+
} else {
|
|
749
|
+
objectBindings.push(`${JSON.stringify(binding.imported)}: ${binding.local}`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (objectBindings.length > 0) {
|
|
754
|
+
statements.unshift(`const { ${objectBindings.join(", ")} } = ${sourceExpression};`);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return statements.join("\n");
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function namedImportBindings(clause: string): Array<{ imported: string; local: string }> {
|
|
761
|
+
return clause
|
|
762
|
+
.slice(1, -1)
|
|
763
|
+
.split(",")
|
|
764
|
+
.map((binding) => binding.trim())
|
|
765
|
+
.filter((binding) => binding.length > 0)
|
|
766
|
+
.map((binding) => {
|
|
767
|
+
const alias = binding.match(/^(.+?)\s+as\s+(.+)$/);
|
|
768
|
+
const imported = alias?.[1]?.trim() ?? binding;
|
|
769
|
+
const local = alias?.[2]?.trim() ?? binding;
|
|
770
|
+
|
|
771
|
+
return { imported, local };
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function replaceImportedIdentifiers(code: string, bindings: ReadonlyMap<string, string>): string {
|
|
776
|
+
let rewritten = "";
|
|
777
|
+
let index = 0;
|
|
778
|
+
|
|
779
|
+
while (index < code.length) {
|
|
780
|
+
const char = code[index];
|
|
781
|
+
const next = code[index + 1];
|
|
782
|
+
|
|
783
|
+
if (char === '"' || char === "'") {
|
|
784
|
+
const end = quotedStringEnd(code, index, char);
|
|
785
|
+
rewritten += code.slice(index, end);
|
|
786
|
+
index = end;
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (char === "`") {
|
|
791
|
+
const end = templateLiteralEnd(code, index);
|
|
792
|
+
rewritten += code.slice(index, end);
|
|
793
|
+
index = end;
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (char === "/" && next === "/") {
|
|
798
|
+
const end = lineCommentEnd(code, index);
|
|
799
|
+
rewritten += code.slice(index, end);
|
|
800
|
+
index = end;
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
if (char === "/" && next === "*") {
|
|
805
|
+
const end = blockCommentEnd(code, index);
|
|
806
|
+
rewritten += code.slice(index, end);
|
|
807
|
+
index = end;
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (isIdentifierStart(char)) {
|
|
812
|
+
const end = identifierEnd(code, index);
|
|
813
|
+
const identifier = code.slice(index, end);
|
|
814
|
+
const replacement = bindings.get(identifier);
|
|
815
|
+
|
|
816
|
+
if (replacement !== undefined && shouldReplaceImportedIdentifier(code, index, end)) {
|
|
817
|
+
rewritten += importedIdentifierReplacement(code, index, end, identifier, replacement);
|
|
818
|
+
} else {
|
|
819
|
+
rewritten += identifier;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
index = end;
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
rewritten += char;
|
|
827
|
+
index += 1;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return rewritten;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function shouldReplaceImportedIdentifier(code: string, start: number, end: number): boolean {
|
|
834
|
+
const previous = previousNonWhitespace(code, start);
|
|
835
|
+
const next = nextNonWhitespace(code, end);
|
|
836
|
+
|
|
837
|
+
return previous !== "." && previous !== "?" && next !== ":";
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function importedIdentifierReplacement(
|
|
841
|
+
code: string,
|
|
842
|
+
start: number,
|
|
843
|
+
end: number,
|
|
844
|
+
identifier: string,
|
|
845
|
+
replacement: string,
|
|
846
|
+
): string {
|
|
847
|
+
const previous = previousNonWhitespace(code, start);
|
|
848
|
+
const next = nextNonWhitespace(code, end);
|
|
849
|
+
|
|
850
|
+
return (previous === "{" || previous === ",") && (next === "," || next === "}")
|
|
851
|
+
? `${identifier}: ${replacement}`
|
|
852
|
+
: replacement;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function previousNonWhitespace(code: string, index: number): string | undefined {
|
|
856
|
+
for (let position = index - 1; position >= 0; position -= 1) {
|
|
857
|
+
if (!/\s/u.test(code[position] ?? "")) {
|
|
858
|
+
return code[position];
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
return undefined;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function nextNonWhitespace(code: string, index: number): string | undefined {
|
|
866
|
+
for (let position = index; position < code.length; position += 1) {
|
|
867
|
+
if (!/\s/u.test(code[position] ?? "")) {
|
|
868
|
+
return code[position];
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return undefined;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function quotedStringEnd(code: string, start: number, quote: string): number {
|
|
876
|
+
for (let index = start + 1; index < code.length; index += 1) {
|
|
877
|
+
if (code[index] === "\\") {
|
|
878
|
+
index += 1;
|
|
879
|
+
} else if (code[index] === quote) {
|
|
880
|
+
return index + 1;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return code.length;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function templateLiteralEnd(code: string, start: number): number {
|
|
888
|
+
for (let index = start + 1; index < code.length; index += 1) {
|
|
889
|
+
if (code[index] === "\\") {
|
|
890
|
+
index += 1;
|
|
891
|
+
} else if (code[index] === "`") {
|
|
892
|
+
return index + 1;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return code.length;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function lineCommentEnd(code: string, start: number): number {
|
|
900
|
+
const end = code.indexOf("\n", start + 2);
|
|
901
|
+
|
|
902
|
+
return end === -1 ? code.length : end;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function blockCommentEnd(code: string, start: number): number {
|
|
906
|
+
const end = code.indexOf("*/", start + 2);
|
|
907
|
+
|
|
908
|
+
return end === -1 ? code.length : end + 2;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function identifierEnd(code: string, start: number): number {
|
|
912
|
+
let end = start + 1;
|
|
913
|
+
|
|
914
|
+
while (end < code.length && isIdentifierPart(code[end] ?? "")) {
|
|
915
|
+
end += 1;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
return end;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function isIdentifierStart(char: string | undefined): boolean {
|
|
922
|
+
return char !== undefined && /[A-Za-z_$]/u.test(char);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function isIdentifierPart(char: string): boolean {
|
|
926
|
+
return /[A-Za-z0-9_$]/u.test(char);
|
|
927
|
+
}
|
|
928
|
+
|
|
386
929
|
function workspacePackageResolutionPlugin() {
|
|
387
930
|
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
388
931
|
const packageRoot = dirname(currentDir);
|
|
@@ -419,6 +962,34 @@ function workspacePackageResolutionPlugin() {
|
|
|
419
962
|
specifier,
|
|
420
963
|
});
|
|
421
964
|
const entries = new Map<string, { entry: string; monorepoDir: string; packageName: string }>([
|
|
965
|
+
[
|
|
966
|
+
"react",
|
|
967
|
+
{ entry: "index", monorepoDir: "react-compat", packageName: "@reckona/mreact-compat" },
|
|
968
|
+
],
|
|
969
|
+
[
|
|
970
|
+
"react-dom",
|
|
971
|
+
{ entry: "index", monorepoDir: "react-compat", packageName: "@reckona/mreact-compat" },
|
|
972
|
+
],
|
|
973
|
+
[
|
|
974
|
+
"react-dom/client",
|
|
975
|
+
{ entry: "index", monorepoDir: "react-compat", packageName: "@reckona/mreact-compat" },
|
|
976
|
+
],
|
|
977
|
+
[
|
|
978
|
+
"react-dom/server",
|
|
979
|
+
{ entry: "index", monorepoDir: "react-compat", packageName: "@reckona/mreact-compat" },
|
|
980
|
+
],
|
|
981
|
+
[
|
|
982
|
+
"react/jsx-dev-runtime",
|
|
983
|
+
{
|
|
984
|
+
entry: "jsx-dev-runtime",
|
|
985
|
+
monorepoDir: "react-compat",
|
|
986
|
+
packageName: "@reckona/mreact-compat",
|
|
987
|
+
},
|
|
988
|
+
],
|
|
989
|
+
[
|
|
990
|
+
"react/jsx-runtime",
|
|
991
|
+
{ entry: "jsx-runtime", monorepoDir: "react-compat", packageName: "@reckona/mreact-compat" },
|
|
992
|
+
],
|
|
422
993
|
["@reckona/mreact", { entry: "index", monorepoDir: "react", packageName: "@reckona/mreact" }],
|
|
423
994
|
[
|
|
424
995
|
"@reckona/mreact/jsx-dev-runtime",
|
|
@@ -503,7 +1074,7 @@ function workspacePackageResolutionPlugin() {
|
|
|
503
1074
|
buildApi.onResolve(
|
|
504
1075
|
{
|
|
505
1076
|
filter:
|
|
506
|
-
|
|
1077
|
+
/^(?:react(?:\/jsx-(?:dev-)?runtime)?|react-dom(?:\/(?:client|server))?|@reckona\/(?:mreact(?:\/(?:jsx-dev-runtime|jsx-runtime))?|mreact-(?:auth|query|reactive-core|server|router|compat)(?:\/(?:event-priority|flight|internal|jsx-dev-runtime|jsx-runtime|scheduler|app-router-globals|link|native-escape|navigation-state|session|stream-list|internal\/native-escape|internal\/session))?))$/,
|
|
507
1078
|
},
|
|
508
1079
|
(args) => {
|
|
509
1080
|
const routerPath = routerEntries.get(args.path);
|