@reckona/mreact-router 0.0.101 → 0.0.103
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/build.d.ts.map +1 -1
- package/dist/build.js +217 -5
- package/dist/build.js.map +1 -1
- package/package.json +11 -11
- package/src/build.ts +289 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reckona/mreact-router",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.103",
|
|
4
4
|
"description": "File-system app router, SSR, actions, and deployment adapters for mreact.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jsx",
|
|
@@ -105,20 +105,20 @@
|
|
|
105
105
|
},
|
|
106
106
|
"dependencies": {
|
|
107
107
|
"typescript": "^6.0.3",
|
|
108
|
-
"@reckona/mreact": "0.0.
|
|
109
|
-
"@reckona/mreact-
|
|
110
|
-
"@reckona/mreact-
|
|
111
|
-
"@reckona/mreact-
|
|
112
|
-
"@reckona/mreact-
|
|
113
|
-
"@reckona/mreact-
|
|
114
|
-
"@reckona/mreact-reactive-dom": "0.0.
|
|
115
|
-
"@reckona/mreact-server": "0.0.
|
|
116
|
-
"@reckona/mreact-shared": "0.0.
|
|
108
|
+
"@reckona/mreact": "0.0.103",
|
|
109
|
+
"@reckona/mreact-compat": "0.0.103",
|
|
110
|
+
"@reckona/mreact-compiler": "0.0.103",
|
|
111
|
+
"@reckona/mreact-devtools": "0.0.103",
|
|
112
|
+
"@reckona/mreact-query": "0.0.103",
|
|
113
|
+
"@reckona/mreact-reactive-core": "0.0.103",
|
|
114
|
+
"@reckona/mreact-reactive-dom": "0.0.103",
|
|
115
|
+
"@reckona/mreact-server": "0.0.103",
|
|
116
|
+
"@reckona/mreact-shared": "0.0.103"
|
|
117
117
|
},
|
|
118
118
|
"peerDependencies": {
|
|
119
119
|
"vite": ">=8 <9"
|
|
120
120
|
},
|
|
121
121
|
"optionalDependencies": {
|
|
122
|
-
"@reckona/mreact-router-native": "0.0.
|
|
122
|
+
"@reckona/mreact-router-native": "0.0.103"
|
|
123
123
|
}
|
|
124
124
|
}
|
package/src/build.ts
CHANGED
|
@@ -2385,6 +2385,12 @@ interface CloudflareBatchedComponentRoute {
|
|
|
2385
2385
|
routeId: string;
|
|
2386
2386
|
}
|
|
2387
2387
|
|
|
2388
|
+
interface CloudflareBatchedStringRoute {
|
|
2389
|
+
filename: string;
|
|
2390
|
+
routeId: string;
|
|
2391
|
+
shellFiles: CloudflareShellFile[];
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2388
2394
|
interface RouteRequestModuleBatchEntry {
|
|
2389
2395
|
code: string;
|
|
2390
2396
|
filename: string;
|
|
@@ -2448,7 +2454,16 @@ async function writeCloudflareRouteModules(options: {
|
|
|
2448
2454
|
serverModules: options.serverModules,
|
|
2449
2455
|
sourceAnalysis: options.sourceAnalysis,
|
|
2450
2456
|
});
|
|
2457
|
+
const stringShellComponentRoutes = await collectCloudflareStringShellComponentRoutes({
|
|
2458
|
+
requiredRoutes,
|
|
2459
|
+
routesDir: options.routesDir,
|
|
2460
|
+
serverModules: options.serverModules,
|
|
2461
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
2462
|
+
});
|
|
2451
2463
|
const directComponentRouteIds = new Set(directComponentRoutes.map((route) => route.routeId));
|
|
2464
|
+
const stringShellComponentRouteIds = new Set(
|
|
2465
|
+
stringShellComponentRoutes.map((route) => route.routeId),
|
|
2466
|
+
);
|
|
2452
2467
|
const directComponentModules = await buildCloudflareServerComponentModuleBatch({
|
|
2453
2468
|
cacheDir: options.cacheDir,
|
|
2454
2469
|
projectRoot: options.projectRoot,
|
|
@@ -2457,11 +2472,20 @@ async function writeCloudflareRouteModules(options: {
|
|
|
2457
2472
|
sourceAnalysis: options.sourceAnalysis,
|
|
2458
2473
|
vitePlugins: options.vitePlugins,
|
|
2459
2474
|
});
|
|
2475
|
+
const stringShellComponentModules = await buildCloudflareStringRouteComponentModuleBatch({
|
|
2476
|
+
cacheDir: options.cacheDir,
|
|
2477
|
+
projectRoot: options.projectRoot,
|
|
2478
|
+
routes: stringShellComponentRoutes,
|
|
2479
|
+
serverModules: options.serverModules,
|
|
2480
|
+
sourceAnalysis: options.sourceAnalysis,
|
|
2481
|
+
vitePlugins: options.vitePlugins,
|
|
2482
|
+
});
|
|
2460
2483
|
|
|
2461
2484
|
await Promise.all([
|
|
2462
2485
|
writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, serverRouteModules),
|
|
2463
2486
|
writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, loaderRouteModules),
|
|
2464
2487
|
writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, directComponentModules),
|
|
2488
|
+
writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, stringShellComponentModules),
|
|
2465
2489
|
]);
|
|
2466
2490
|
|
|
2467
2491
|
const registryEntries = await mapWithBuildConcurrency(requiredRoutes, async ({ route, routeFile, routeId }) => {
|
|
@@ -2502,7 +2526,11 @@ async function writeCloudflareRouteModules(options: {
|
|
|
2502
2526
|
|
|
2503
2527
|
try {
|
|
2504
2528
|
const batchedComponent =
|
|
2505
|
-
|
|
2529
|
+
stringShellComponentRouteIds.has(routeId)
|
|
2530
|
+
? stringShellComponentModules.entries.get(routeId)
|
|
2531
|
+
: directComponentRouteIds.has(routeId)
|
|
2532
|
+
? directComponentModules.entries.get(routeId)
|
|
2533
|
+
: undefined;
|
|
2506
2534
|
let componentFile = batchedComponent?.fileName;
|
|
2507
2535
|
|
|
2508
2536
|
if (batchedComponent === undefined) {
|
|
@@ -2618,6 +2646,45 @@ async function collectCloudflareDirectComponentRoutes(options: {
|
|
|
2618
2646
|
return routes.filter((route): route is CloudflareBatchedComponentRoute => route !== undefined);
|
|
2619
2647
|
}
|
|
2620
2648
|
|
|
2649
|
+
async function collectCloudflareStringShellComponentRoutes(options: {
|
|
2650
|
+
requiredRoutes: readonly CloudflareRequiredRoute[];
|
|
2651
|
+
routesDir: string;
|
|
2652
|
+
serverModules: Record<string, BuiltServerModuleArtifact>;
|
|
2653
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
2654
|
+
}): Promise<CloudflareBatchedStringRoute[]> {
|
|
2655
|
+
const routes = await Promise.all(
|
|
2656
|
+
options.requiredRoutes.map(async ({ route, routeFile, routeId }) => {
|
|
2657
|
+
if (route.kind !== "page") {
|
|
2658
|
+
return undefined;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
const serverOutput =
|
|
2662
|
+
options.serverModules[routeFile]?.analysis?.streamRoute === true ||
|
|
2663
|
+
options.sourceAnalysis.byRouteFile.get(routeFile)?.streamRoute === true
|
|
2664
|
+
? "stream"
|
|
2665
|
+
: "string";
|
|
2666
|
+
|
|
2667
|
+
if (serverOutput !== "string") {
|
|
2668
|
+
return undefined;
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
const shellFiles = await cloudflareShellFilesForPage(options.routesDir, route.file);
|
|
2672
|
+
|
|
2673
|
+
if (shellFiles.length === 0) {
|
|
2674
|
+
return undefined;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
return {
|
|
2678
|
+
filename: route.file,
|
|
2679
|
+
routeId,
|
|
2680
|
+
shellFiles,
|
|
2681
|
+
};
|
|
2682
|
+
}),
|
|
2683
|
+
);
|
|
2684
|
+
|
|
2685
|
+
return routes.filter((route): route is CloudflareBatchedStringRoute => route !== undefined);
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2621
2688
|
interface CloudflareShellFile {
|
|
2622
2689
|
file: string;
|
|
2623
2690
|
id: string;
|
|
@@ -2714,6 +2781,216 @@ async function buildCloudflareServerComponentModuleBatch(options: {
|
|
|
2714
2781
|
return { chunks: output.chunks, entries };
|
|
2715
2782
|
}
|
|
2716
2783
|
|
|
2784
|
+
async function buildCloudflareStringRouteComponentModuleBatch(options: {
|
|
2785
|
+
cacheDir?: string | undefined;
|
|
2786
|
+
projectRoot: string;
|
|
2787
|
+
routes: readonly CloudflareBatchedStringRoute[];
|
|
2788
|
+
serverModules: Record<string, BuiltServerModuleArtifact>;
|
|
2789
|
+
sourceAnalysis: BuildSourceAnalysisScope;
|
|
2790
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2791
|
+
}): Promise<CloudflareBatchedRouteModules> {
|
|
2792
|
+
if (options.routes.length === 0) {
|
|
2793
|
+
return { chunks: [], entries: new Map() };
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
const virtualModules = new Map<string, CloudflareVirtualModule>();
|
|
2797
|
+
const bundleEntries = await Promise.all(
|
|
2798
|
+
options.routes.map(async (route) => {
|
|
2799
|
+
const metadataImports: string[] = [];
|
|
2800
|
+
const pageMetadataId = `mreact:metadata:${route.routeId}:page`;
|
|
2801
|
+
const pageMetadataModule = await buildCloudflareRouteMetadataExportModule({
|
|
2802
|
+
cacheDir: options.cacheDir,
|
|
2803
|
+
filename: route.filename,
|
|
2804
|
+
hasMetadata: buildSourceAnalysisForFile(
|
|
2805
|
+
options.sourceAnalysis,
|
|
2806
|
+
options.projectRoot,
|
|
2807
|
+
route.filename,
|
|
2808
|
+
)?.hasMetadata,
|
|
2809
|
+
root: options.projectRoot,
|
|
2810
|
+
vitePlugins: options.vitePlugins,
|
|
2811
|
+
});
|
|
2812
|
+
|
|
2813
|
+
if (pageMetadataModule === undefined) {
|
|
2814
|
+
metadataImports.push("const pageMetadataModule = {};");
|
|
2815
|
+
} else {
|
|
2816
|
+
virtualModules.set(pageMetadataId, {
|
|
2817
|
+
contents: pageMetadataModule,
|
|
2818
|
+
resolveDir: dirname(route.filename),
|
|
2819
|
+
});
|
|
2820
|
+
metadataImports.push(`import * as pageMetadataModule from ${JSON.stringify(pageMetadataId)};`);
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
const shellMetadataImports = await Promise.all(
|
|
2824
|
+
route.shellFiles.map(async (shell, index) => {
|
|
2825
|
+
const shellMetadataId = `mreact:metadata:${route.routeId}:shell:${index}`;
|
|
2826
|
+
const shellMetadataModule = await buildCloudflareRouteMetadataExportModule({
|
|
2827
|
+
cacheDir: options.cacheDir,
|
|
2828
|
+
filename: shell.file,
|
|
2829
|
+
hasMetadata: buildSourceAnalysisForFile(
|
|
2830
|
+
options.sourceAnalysis,
|
|
2831
|
+
options.projectRoot,
|
|
2832
|
+
shell.file,
|
|
2833
|
+
)?.hasMetadata,
|
|
2834
|
+
root: options.projectRoot,
|
|
2835
|
+
vitePlugins: options.vitePlugins,
|
|
2836
|
+
});
|
|
2837
|
+
|
|
2838
|
+
if (shellMetadataModule === undefined) {
|
|
2839
|
+
return `const shellMetadataModule${index} = {};`;
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
virtualModules.set(shellMetadataId, {
|
|
2843
|
+
contents: shellMetadataModule,
|
|
2844
|
+
resolveDir: dirname(shell.file),
|
|
2845
|
+
});
|
|
2846
|
+
return `import * as shellMetadataModule${index} from ${JSON.stringify(shellMetadataId)};`;
|
|
2847
|
+
}),
|
|
2848
|
+
);
|
|
2849
|
+
metadataImports.push(...shellMetadataImports);
|
|
2850
|
+
|
|
2851
|
+
return {
|
|
2852
|
+
code: cloudflareStringRouteComponentModuleEntry({
|
|
2853
|
+
filename: route.filename,
|
|
2854
|
+
metadataImports,
|
|
2855
|
+
pageMetadataModuleName: "pageMetadataModule",
|
|
2856
|
+
shellFiles: route.shellFiles,
|
|
2857
|
+
shellMetadataModuleName: (index) => `shellMetadataModule${index}`,
|
|
2858
|
+
}),
|
|
2859
|
+
filename: `${route.filename}.mreact-cloudflare-string-route.js`,
|
|
2860
|
+
name: `${route.routeId}.string`,
|
|
2861
|
+
routeId: route.routeId,
|
|
2862
|
+
};
|
|
2863
|
+
}),
|
|
2864
|
+
);
|
|
2865
|
+
const output = await bundleRouterModules({
|
|
2866
|
+
cacheDir: options.cacheDir,
|
|
2867
|
+
chunkFileNames: "routes/chunks/[name].[hash].mjs",
|
|
2868
|
+
entries: bundleEntries,
|
|
2869
|
+
entryFileNames: "routes/[name].[hash].mjs",
|
|
2870
|
+
minify: true,
|
|
2871
|
+
platform: "node",
|
|
2872
|
+
plugins: [
|
|
2873
|
+
cloudflareVirtualModulesPlugin(virtualModules),
|
|
2874
|
+
cloudflareServerSourceTransformPlugin({
|
|
2875
|
+
projectRoot: options.projectRoot,
|
|
2876
|
+
serverOutput: "string",
|
|
2877
|
+
serverModules: options.serverModules,
|
|
2878
|
+
vitePlugins: options.vitePlugins,
|
|
2879
|
+
}),
|
|
2880
|
+
cloudflareWorkspaceRuntimePlugin(),
|
|
2881
|
+
],
|
|
2882
|
+
root: options.projectRoot,
|
|
2883
|
+
target: "es2022",
|
|
2884
|
+
vitePlugins: options.vitePlugins,
|
|
2885
|
+
});
|
|
2886
|
+
const entriesByName = new Map(bundleEntries.map((entry) => [entry.name, entry]));
|
|
2887
|
+
const entries = new Map<string, CloudflareBatchedRouteModule>();
|
|
2888
|
+
|
|
2889
|
+
for (const chunk of output.chunks) {
|
|
2890
|
+
if (!chunk.isEntry) {
|
|
2891
|
+
continue;
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
const entry = entriesByName.get(chunk.name);
|
|
2895
|
+
|
|
2896
|
+
if (entry === undefined) {
|
|
2897
|
+
continue;
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
entries.set(entry.routeId, {
|
|
2901
|
+
code: chunk.code,
|
|
2902
|
+
fileName: chunk.fileName,
|
|
2903
|
+
});
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
return { chunks: output.chunks, entries };
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
function cloudflareStringRouteComponentModuleEntry(options: {
|
|
2910
|
+
filename: string;
|
|
2911
|
+
metadataImports: readonly string[];
|
|
2912
|
+
pageMetadataModuleName: string;
|
|
2913
|
+
shellFiles: readonly CloudflareShellFile[];
|
|
2914
|
+
shellMetadataModuleName: (index: number) => string;
|
|
2915
|
+
}): string {
|
|
2916
|
+
const shellImports = options.shellFiles.map(
|
|
2917
|
+
(shell, index) => `import * as shellRouteModule${index} from ${JSON.stringify(shell.file)};`,
|
|
2918
|
+
);
|
|
2919
|
+
const shellDefinitions = options.shellFiles.map(
|
|
2920
|
+
(shell, index) =>
|
|
2921
|
+
`{ component: selectComponent(shell${index}, ${JSON.stringify(shell.file)}), id: ${JSON.stringify(shell.id)}, kind: ${JSON.stringify(shell.kind)}, module: shell${index} }`,
|
|
2922
|
+
);
|
|
2923
|
+
const shellModules = options.shellFiles.map(
|
|
2924
|
+
(_, index) =>
|
|
2925
|
+
`const shell${index} = routeComponentModule(shellRouteModule${index}, ${options.shellMetadataModuleName(index)});`,
|
|
2926
|
+
);
|
|
2927
|
+
|
|
2928
|
+
return `import * as pageRouteModule from ${JSON.stringify(options.filename)};
|
|
2929
|
+
${shellImports.join("\n")}
|
|
2930
|
+
${options.metadataImports.join("\n")}
|
|
2931
|
+
|
|
2932
|
+
const pageModule = routeComponentModule(pageRouteModule, ${options.pageMetadataModuleName});
|
|
2933
|
+
${shellModules.join("\n")}
|
|
2934
|
+
const pageComponent = selectComponent(pageModule, ${JSON.stringify(options.filename)});
|
|
2935
|
+
const shells = [${shellDefinitions.join(", ")}];
|
|
2936
|
+
export const slots = pageModule.slots;
|
|
2937
|
+
export const App = renderCloudflareStringRoute;
|
|
2938
|
+
export default renderCloudflareStringRoute;
|
|
2939
|
+
|
|
2940
|
+
async function renderCloudflareStringRoute(props) {
|
|
2941
|
+
const slotHtml = await renderRouteSlots(pageModule.slots, props);
|
|
2942
|
+
const layoutShells = await renderLayoutShells(shells, props, slotHtml);
|
|
2943
|
+
const metadata = await resolveRouteMetadata([...shells.map((shell) => shell.module), pageModule], props);
|
|
2944
|
+
let html = "<!DOCTYPE html>";
|
|
2945
|
+
for (const shell of layoutShells) {
|
|
2946
|
+
html += shell.prefix;
|
|
2947
|
+
}
|
|
2948
|
+
html += String(await pageComponent(props) ?? "");
|
|
2949
|
+
for (const shell of [...layoutShells].reverse()) {
|
|
2950
|
+
html += shell.suffix;
|
|
2951
|
+
}
|
|
2952
|
+
html = injectCloudflareHead(html, metadata, cloudflareRouteHeadTags(props.clientManifest, props.route.path));
|
|
2953
|
+
return new Response(html, {
|
|
2954
|
+
headers: {
|
|
2955
|
+
"content-type": "text/html; charset=utf-8"
|
|
2956
|
+
}
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
function routeComponentModule(routeModule, metadataModule) {
|
|
2961
|
+
const component = routeModule.default ?? routeModule.App ?? Object.values(routeModule).find((value) => typeof value === "function");
|
|
2962
|
+
return {
|
|
2963
|
+
...routeModule,
|
|
2964
|
+
App: component,
|
|
2965
|
+
default: component,
|
|
2966
|
+
generateMetadata: metadataModule.generateMetadata,
|
|
2967
|
+
metadata: metadataModule.metadata,
|
|
2968
|
+
slots: routeModule.slots,
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
function selectComponent(module, label) {
|
|
2973
|
+
const component = module.default ?? module.App ?? Object.values(module).find((value) => typeof value === "function");
|
|
2974
|
+
if (typeof component !== "function") {
|
|
2975
|
+
throw new Error(\`No Cloudflare component export was found for \${label}.\`);
|
|
2976
|
+
}
|
|
2977
|
+
return component;
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
async function renderRouteSlots(slots, props) {
|
|
2981
|
+
if (slots === undefined) {
|
|
2982
|
+
return {};
|
|
2983
|
+
}
|
|
2984
|
+
const rendered = {};
|
|
2985
|
+
for (const [name, value] of Object.entries(slots)) {
|
|
2986
|
+
rendered[name] = typeof value === "function" ? String(await value(props) ?? "") : String(value ?? "");
|
|
2987
|
+
}
|
|
2988
|
+
return rendered;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
${cloudflareShellRuntimeSource()}`;
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2717
2994
|
async function buildCloudflareServerComponentModule(options: {
|
|
2718
2995
|
cacheDir?: string | undefined;
|
|
2719
2996
|
filename: string;
|
|
@@ -4557,13 +4834,15 @@ async function collectBuildFiles(
|
|
|
4557
4834
|
|
|
4558
4835
|
for (const directory of allowedSourceDirs) {
|
|
4559
4836
|
const sourceFiles = (await collectFiles(directory)).flatMap((file) => {
|
|
4560
|
-
if (isAppFileConventionAsset(file, appDir)) {
|
|
4561
|
-
return [];
|
|
4562
|
-
}
|
|
4563
|
-
|
|
4564
4837
|
const relativeFile = relative(projectRoot, file);
|
|
4565
4838
|
|
|
4566
|
-
if (
|
|
4839
|
+
if (
|
|
4840
|
+
relativeFile === "" ||
|
|
4841
|
+
relativeFile.startsWith("..") ||
|
|
4842
|
+
relativeFile.startsWith(sep) ||
|
|
4843
|
+
isAppFileConventionAsset(file, appDir) ||
|
|
4844
|
+
isProductionBuildIgnoredSourceFile(relativeFile)
|
|
4845
|
+
) {
|
|
4567
4846
|
return [];
|
|
4568
4847
|
}
|
|
4569
4848
|
|
|
@@ -4582,6 +4861,10 @@ async function collectBuildFiles(
|
|
|
4582
4861
|
return files;
|
|
4583
4862
|
}
|
|
4584
4863
|
|
|
4864
|
+
function isProductionBuildIgnoredSourceFile(relativeFile: string): boolean {
|
|
4865
|
+
return /(?:^|[/\\])[^/\\]+\.(?:spec|test)\.[cm]?[jt]sx?$/u.test(relativeFile);
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4585
4868
|
function isAppFileConventionAsset(file: string, appDir: string): boolean {
|
|
4586
4869
|
const relativeFile = relative(appDir, file);
|
|
4587
4870
|
if (relativeFile === "" || relativeFile.startsWith("..") || relativeFile.includes(sep)) {
|