@reckona/mreact-router 0.0.96 → 0.0.98
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/README.md +4 -0
- package/dist/actions.d.ts +7 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +26 -2
- package/dist/actions.js.map +1 -1
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +10 -7
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/static.d.ts.map +1 -1
- package/dist/adapters/static.js +13 -0
- package/dist/adapters/static.js.map +1 -1
- package/dist/build.d.ts +38 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +1093 -307
- package/dist/build.js.map +1 -1
- package/dist/bundle-pipeline.d.ts +7 -0
- package/dist/bundle-pipeline.d.ts.map +1 -1
- package/dist/bundle-pipeline.js +121 -69
- package/dist/bundle-pipeline.js.map +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +10 -8
- package/dist/cache.js.map +1 -1
- package/dist/cli-options.d.ts +1 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +16 -0
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +6 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +120 -9
- package/dist/client.js.map +1 -1
- package/dist/csrf.d.ts.map +1 -1
- package/dist/csrf.js +7 -3
- package/dist/csrf.js.map +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +18 -1
- package/dist/http.js.map +1 -1
- package/dist/import-policy.d.ts.map +1 -1
- package/dist/import-policy.js +7 -3
- package/dist/import-policy.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/link.d.ts +5 -3
- package/dist/link.d.ts.map +1 -1
- package/dist/link.js +92 -3
- package/dist/link.js.map +1 -1
- package/dist/logger.d.ts +7 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/module-runner.d.ts +2 -0
- package/dist/module-runner.d.ts.map +1 -1
- package/dist/module-runner.js +1 -0
- package/dist/module-runner.js.map +1 -1
- package/dist/native-route-matcher.d.ts +2 -1
- package/dist/native-route-matcher.d.ts.map +1 -1
- package/dist/native-route-matcher.js +13 -2
- package/dist/native-route-matcher.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +121 -6
- package/dist/render.js.map +1 -1
- package/dist/route-source.d.ts +11 -11
- package/dist/route-source.d.ts.map +1 -1
- package/dist/route-source.js +32 -25
- package/dist/route-source.js.map +1 -1
- package/dist/route-styles.d.ts +6 -0
- package/dist/route-styles.d.ts.map +1 -1
- package/dist/route-styles.js +10 -1
- package/dist/route-styles.js.map +1 -1
- package/dist/serve.d.ts +5 -0
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +7 -4
- package/dist/serve.js.map +1 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +32 -5
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +42 -2
- package/src/adapters/aws-lambda.ts +33 -16
- package/src/adapters/static.ts +25 -0
- package/src/build.ts +1605 -421
- package/src/bundle-pipeline.ts +136 -70
- package/src/cache.ts +13 -8
- package/src/cli-options.ts +20 -0
- package/src/cli.ts +1 -0
- package/src/client.ts +152 -15
- package/src/csrf.ts +8 -3
- package/src/http.ts +21 -1
- package/src/import-policy.ts +8 -3
- package/src/index.ts +5 -0
- package/src/link.ts +135 -8
- package/src/logger.ts +9 -1
- package/src/module-runner.ts +4 -0
- package/src/native-route-matcher.ts +13 -2
- package/src/render.ts +181 -6
- package/src/route-source.ts +33 -20
- package/src/route-styles.ts +17 -1
- package/src/serve.ts +11 -6
- package/src/vite.ts +41 -5
package/src/client.ts
CHANGED
|
@@ -67,6 +67,7 @@ export interface ClientRouteManifestEntry {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export interface BuildClientRouteOutputOptions {
|
|
70
|
+
cacheDir?: string | undefined;
|
|
70
71
|
code: string;
|
|
71
72
|
clientBoundaryImports?: readonly string[] | undefined;
|
|
72
73
|
clientReferenceImports?: readonly ClientReferenceImport[] | undefined;
|
|
@@ -145,16 +146,20 @@ export interface ClientRouteReferenceResult extends ClientRouteInferenceResult {
|
|
|
145
146
|
export interface ClientRouteInferenceDiagnostic {
|
|
146
147
|
code:
|
|
147
148
|
| typeof clientBoundaryInferenceServerOnlyReferenceCode
|
|
149
|
+
| typeof clientBoundaryInferenceFunctionCallInteractiveCode
|
|
148
150
|
| typeof clientBoundaryInferenceUnsupportedReferenceCode;
|
|
149
151
|
filename: string;
|
|
150
152
|
level: "warn";
|
|
151
153
|
localNames: string[];
|
|
152
154
|
message: string;
|
|
155
|
+
routePath?: string | undefined;
|
|
153
156
|
source: string;
|
|
154
157
|
}
|
|
155
158
|
|
|
156
159
|
const clientBoundaryInferenceServerOnlyReferenceCode =
|
|
157
160
|
"MR_CLIENT_BOUNDARY_INFERENCE_SERVER_ONLY_REFERENCE";
|
|
161
|
+
const clientBoundaryInferenceFunctionCallInteractiveCode =
|
|
162
|
+
"MR_CLIENT_BOUNDARY_INFERENCE_FUNCTION_CALL_INTERACTIVE";
|
|
158
163
|
const clientBoundaryInferenceUnsupportedReferenceCode =
|
|
159
164
|
"MR_CLIENT_BOUNDARY_INFERENCE_UNSUPPORTED_REFERENCE";
|
|
160
165
|
|
|
@@ -244,7 +249,7 @@ export async function inferClientRouteModule(options: {
|
|
|
244
249
|
: routeInference;
|
|
245
250
|
|
|
246
251
|
if (options.appDir === undefined) {
|
|
247
|
-
return mergedRouteInference;
|
|
252
|
+
return withClientRouteDiagnosticPath(mergedRouteInference, options.routePath);
|
|
248
253
|
}
|
|
249
254
|
|
|
250
255
|
const shellInferences = await inferClientRouteShellModules({
|
|
@@ -254,14 +259,18 @@ export async function inferClientRouteModule(options: {
|
|
|
254
259
|
sourceTransform,
|
|
255
260
|
});
|
|
256
261
|
|
|
257
|
-
return
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
return withClientRouteDiagnosticPath(
|
|
263
|
+
{
|
|
264
|
+
client:
|
|
265
|
+
mergedRouteInference.client || shellInferences.some((inference) => inference.client),
|
|
266
|
+
clientBoundaryImports: mergedRouteInference.clientBoundaryImports,
|
|
267
|
+
diagnostics: [
|
|
268
|
+
...mergedRouteInference.diagnostics,
|
|
269
|
+
...shellInferences.flatMap((inference) => inference.diagnostics),
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
options.routePath,
|
|
273
|
+
);
|
|
265
274
|
} catch (error) {
|
|
266
275
|
throw new Error(
|
|
267
276
|
`Failed to infer client route for ${options.routePath ?? "<unknown>"} (${options.filename}).\n${errorMessage(error)}`,
|
|
@@ -337,6 +346,7 @@ export async function collectClientRouteReferences(options: {
|
|
|
337
346
|
cache?: ClientRouteInferenceCache | undefined;
|
|
338
347
|
code: string;
|
|
339
348
|
filename: string;
|
|
349
|
+
routePath?: string | undefined;
|
|
340
350
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
341
351
|
}): Promise<ClientRouteReferenceResult> {
|
|
342
352
|
const cache = options.cache ?? createClientRouteInferenceCache();
|
|
@@ -502,7 +512,9 @@ export async function collectClientRouteReferences(options: {
|
|
|
502
512
|
clientBoundaryImports: routeInference.clientBoundaryImports,
|
|
503
513
|
clientReferenceImports,
|
|
504
514
|
clientReferenceManifest,
|
|
505
|
-
diagnostics: sources
|
|
515
|
+
diagnostics: sources
|
|
516
|
+
.flatMap((source) => source.inference.diagnostics)
|
|
517
|
+
.map((diagnostic) => withClientRouteDiagnosticPath(diagnostic, options.routePath)),
|
|
506
518
|
usesNavigationLink:
|
|
507
519
|
routeInference.usesNavigationLink ||
|
|
508
520
|
sources.some(
|
|
@@ -520,11 +532,17 @@ export async function resolveNavigationRuntime(options: {
|
|
|
520
532
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
521
533
|
}): Promise<boolean> {
|
|
522
534
|
const cache = options.cache ?? createClientRouteInferenceCache();
|
|
535
|
+
const sourceTransform = clientRouteSourceTransformForVitePlugins(options.vitePlugins);
|
|
536
|
+
const code = await transformClientRouteSource({
|
|
537
|
+
code: options.code,
|
|
538
|
+
filename: options.filename,
|
|
539
|
+
sourceTransform,
|
|
540
|
+
});
|
|
523
541
|
// Read the override from the cached module context so the dev server does not
|
|
524
542
|
// re-parse every page route on each request.
|
|
525
543
|
const moduleContext = await compilerModuleContextForSource({
|
|
526
544
|
cache,
|
|
527
|
-
code
|
|
545
|
+
code,
|
|
528
546
|
filename: options.filename,
|
|
529
547
|
});
|
|
530
548
|
const override = readTopLevelBooleanExportFromContext(moduleContext, "navigationRuntime");
|
|
@@ -807,6 +825,12 @@ async function inferClientRouteModuleSource(options: {
|
|
|
807
825
|
}
|
|
808
826
|
|
|
809
827
|
if (renderedByCall && !renderedByJsx) {
|
|
828
|
+
diagnostics.push(
|
|
829
|
+
functionCallInteractiveImportDiagnostic({
|
|
830
|
+
filename: options.filename,
|
|
831
|
+
reference,
|
|
832
|
+
}),
|
|
833
|
+
);
|
|
810
834
|
continue;
|
|
811
835
|
}
|
|
812
836
|
|
|
@@ -1139,6 +1163,28 @@ function serverOnlyClientImportReferenceDiagnostic(options: {
|
|
|
1139
1163
|
};
|
|
1140
1164
|
}
|
|
1141
1165
|
|
|
1166
|
+
function functionCallInteractiveImportDiagnostic(options: {
|
|
1167
|
+
filename: string;
|
|
1168
|
+
reference: StaticImportReference;
|
|
1169
|
+
}): ClientRouteInferenceDiagnostic {
|
|
1170
|
+
const localNames = options.reference.localNames.filter(startsUppercase);
|
|
1171
|
+
const component = localNames[0] ?? options.reference.localNames[0] ?? options.reference.source;
|
|
1172
|
+
const componentUsage = startsUppercase(component) ? `<${component} />` : "JSX";
|
|
1173
|
+
|
|
1174
|
+
return {
|
|
1175
|
+
code: clientBoundaryInferenceFunctionCallInteractiveCode,
|
|
1176
|
+
filename: options.filename,
|
|
1177
|
+
level: "warn",
|
|
1178
|
+
localNames: localNames.length === 0 ? options.reference.localNames : localNames,
|
|
1179
|
+
message:
|
|
1180
|
+
`${options.filename}: interactive component import ${JSON.stringify(options.reference.source)} ` +
|
|
1181
|
+
`is rendered through a function call as ${component}(), so its event handlers or cell() state ` +
|
|
1182
|
+
"will not hydrate as a client boundary. Render it through JSX such as " +
|
|
1183
|
+
`${componentUsage}, or move the call behind an explicit client boundary.`,
|
|
1184
|
+
source: options.reference.source,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1142
1188
|
function unsupportedClientImportReferenceDiagnostic(options: {
|
|
1143
1189
|
filename: string;
|
|
1144
1190
|
identifierReferences: ReadonlySet<string>;
|
|
@@ -1180,7 +1226,31 @@ function startsUppercase(value: string): boolean {
|
|
|
1180
1226
|
export function formatClientRouteInferenceDiagnostic(
|
|
1181
1227
|
diagnostic: ClientRouteInferenceDiagnostic,
|
|
1182
1228
|
): string {
|
|
1183
|
-
|
|
1229
|
+
const route = diagnostic.routePath === undefined ? "" : ` on route ${JSON.stringify(diagnostic.routePath)}`;
|
|
1230
|
+
return `${diagnostic.code}: ${diagnostic.message}${route}`;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function withClientRouteDiagnosticPath<T extends ClientRouteInferenceDiagnostic | ClientRouteInferenceResult>(
|
|
1234
|
+
value: T,
|
|
1235
|
+
routePath: string | undefined,
|
|
1236
|
+
): T {
|
|
1237
|
+
if (routePath === undefined) {
|
|
1238
|
+
return value;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
if ("diagnostics" in value) {
|
|
1242
|
+
return {
|
|
1243
|
+
...value,
|
|
1244
|
+
diagnostics: value.diagnostics.map((diagnostic) =>
|
|
1245
|
+
withClientRouteDiagnosticPath(diagnostic, routePath),
|
|
1246
|
+
),
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
return {
|
|
1251
|
+
...value,
|
|
1252
|
+
routePath: value.routePath ?? routePath,
|
|
1253
|
+
};
|
|
1184
1254
|
}
|
|
1185
1255
|
|
|
1186
1256
|
async function resolveAppLocalModule(options: {
|
|
@@ -1573,6 +1643,7 @@ export async function buildClientRouteOutput(
|
|
|
1573
1643
|
options.dropConsoleFunctions ?? resolveClientConsolePureFunctions(options.dropClientConsole);
|
|
1574
1644
|
const bundled = await bundleRouterModule({
|
|
1575
1645
|
code: entry.code,
|
|
1646
|
+
cacheDir: options.cacheDir,
|
|
1576
1647
|
define: {
|
|
1577
1648
|
__MREACT_CLIENT_DEVTOOLS__: "false",
|
|
1578
1649
|
},
|
|
@@ -1594,6 +1665,7 @@ export async function buildClientRouteOutput(
|
|
|
1594
1665
|
|
|
1595
1666
|
export async function buildClientRouteBatchOutput(options: {
|
|
1596
1667
|
assetBaseUrl?: string | undefined;
|
|
1668
|
+
cacheDir?: string | undefined;
|
|
1597
1669
|
dropConsoleFunctions?: readonly string[] | undefined;
|
|
1598
1670
|
minify?: boolean;
|
|
1599
1671
|
projectRoot?: string | undefined;
|
|
@@ -1616,6 +1688,7 @@ export async function buildClientRouteBatchOutput(options: {
|
|
|
1616
1688
|
);
|
|
1617
1689
|
const bundled = await bundleRouterModules({
|
|
1618
1690
|
base: options.assetBaseUrl ?? "/_mreact/client/",
|
|
1691
|
+
cacheDir: options.cacheDir,
|
|
1619
1692
|
define: {
|
|
1620
1693
|
__MREACT_CLIENT_DEVTOOLS__: "false",
|
|
1621
1694
|
},
|
|
@@ -2085,6 +2158,10 @@ export async function __mreactPrefetch(url) {
|
|
|
2085
2158
|
return false;
|
|
2086
2159
|
}
|
|
2087
2160
|
|
|
2161
|
+
if (__mreactIsCurrentLocationNavigationHref(href)) {
|
|
2162
|
+
return false;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2088
2165
|
const script = __mreactRouteScriptForNavigationUrl(href);
|
|
2089
2166
|
|
|
2090
2167
|
if (script === undefined) {
|
|
@@ -2104,11 +2181,43 @@ function __mreactPrefetchNavigationHtml(href) {
|
|
|
2104
2181
|
return false;
|
|
2105
2182
|
}
|
|
2106
2183
|
|
|
2107
|
-
|
|
2184
|
+
__mreactRememberNavigationHtml(href, html);
|
|
2108
2185
|
return true;
|
|
2109
2186
|
}).catch(() => false);
|
|
2110
2187
|
}
|
|
2111
2188
|
|
|
2189
|
+
const __mreactNavigationHtmlCacheMaxEntries = 64;
|
|
2190
|
+
|
|
2191
|
+
function __mreactCachedNavigationHtml(href) {
|
|
2192
|
+
const html = __mreactNavigationState.cache.get(href);
|
|
2193
|
+
|
|
2194
|
+
if (html === undefined) {
|
|
2195
|
+
return undefined;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
__mreactNavigationState.cache.delete(href);
|
|
2199
|
+
__mreactNavigationState.cache.set(href, html);
|
|
2200
|
+
return html;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
function __mreactRememberNavigationHtml(href, html) {
|
|
2204
|
+
if (__mreactNavigationState.cache.has(href)) {
|
|
2205
|
+
__mreactNavigationState.cache.delete(href);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
__mreactNavigationState.cache.set(href, html);
|
|
2209
|
+
|
|
2210
|
+
while (__mreactNavigationState.cache.size > __mreactNavigationHtmlCacheMaxEntries) {
|
|
2211
|
+
const oldestHref = __mreactNavigationState.cache.keys().next().value;
|
|
2212
|
+
|
|
2213
|
+
if (oldestHref === undefined) {
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
__mreactNavigationState.cache.delete(oldestHref);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2112
2221
|
function __mreactPrefetchRouteScript(script) {
|
|
2113
2222
|
if (typeof document === "undefined") {
|
|
2114
2223
|
return false;
|
|
@@ -2149,14 +2258,14 @@ export async function __mreactNavigate(url, options = {}) {
|
|
|
2149
2258
|
__mreactSetNavigationState(__mreactPendingNavigationState(href, options.type ?? "push"));
|
|
2150
2259
|
|
|
2151
2260
|
try {
|
|
2152
|
-
const cachedHtml =
|
|
2261
|
+
const cachedHtml = __mreactCachedNavigationHtml(href);
|
|
2153
2262
|
const html = cachedHtml ?? await __mreactFetchNavigationHtml(href);
|
|
2154
2263
|
|
|
2155
2264
|
if (typeof html !== "string") {
|
|
2156
2265
|
return false;
|
|
2157
2266
|
}
|
|
2158
2267
|
|
|
2159
|
-
|
|
2268
|
+
__mreactRememberNavigationHtml(href, html);
|
|
2160
2269
|
return await __mreactApplyNavigationHtmlWithOptionalTransition(html, href, options);
|
|
2161
2270
|
} finally {
|
|
2162
2271
|
__mreactSetNavigationState(__mreactIdleNavigationState());
|
|
@@ -2822,6 +2931,24 @@ function __mreactIsHashOnlyNavigation(nextUrl) {
|
|
|
2822
2931
|
nextUrl.hash !== location.hash;
|
|
2823
2932
|
}
|
|
2824
2933
|
|
|
2934
|
+
function __mreactIsCurrentLocationNavigationHref(href) {
|
|
2935
|
+
if (typeof location === "undefined") {
|
|
2936
|
+
return false;
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
return __mreactIsCurrentLocationNavigation(new URL(href, location.href));
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
function __mreactIsCurrentLocationNavigation(nextUrl) {
|
|
2943
|
+
if (typeof location === "undefined") {
|
|
2944
|
+
return false;
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
return nextUrl.origin === location.origin &&
|
|
2948
|
+
nextUrl.pathname === location.pathname &&
|
|
2949
|
+
nextUrl.search === location.search;
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2825
2952
|
function __mreactInstallNavigation() {
|
|
2826
2953
|
if (__mreactNavigationState.installed || typeof document === "undefined") {
|
|
2827
2954
|
return;
|
|
@@ -2884,6 +3011,16 @@ function __mreactInstallNavigation() {
|
|
|
2884
3011
|
return;
|
|
2885
3012
|
}
|
|
2886
3013
|
|
|
3014
|
+
if (__mreactIsCurrentLocationNavigation(nextUrl)) {
|
|
3015
|
+
event.preventDefault();
|
|
3016
|
+
|
|
3017
|
+
if (__mreactAnchorScrollMode(anchor) !== false && nextUrl.hash === "") {
|
|
3018
|
+
__mreactScrollTo(0, 0);
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
|
|
2887
3024
|
event.preventDefault();
|
|
2888
3025
|
void __mreactNavigate(nextUrl.href, {
|
|
2889
3026
|
scroll: __mreactAnchorScrollMode(anchor),
|
package/src/csrf.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const csrfCookieNameProduction = "__Host-mreact.csrf";
|
|
2
2
|
const csrfCookieNameDevelopment = "mreact.csrf";
|
|
3
|
-
const csrfCookieNamesRead = [csrfCookieNameProduction, csrfCookieNameDevelopment];
|
|
4
3
|
const formFieldCsrf = "__mreact_csrf";
|
|
5
4
|
|
|
6
5
|
export const formCsrfFieldName = formFieldCsrf;
|
|
@@ -32,7 +31,7 @@ export function formCsrfCookie(csrfToken: string): string {
|
|
|
32
31
|
export function validateFormCsrf(request: Request, formData: FormData): Response | undefined {
|
|
33
32
|
const formToken = stringFormValue(formData.get(formFieldCsrf));
|
|
34
33
|
const cookieHeader = request.headers.get("cookie");
|
|
35
|
-
const cookieToken = csrfCookieNamesRead
|
|
34
|
+
const cookieToken = csrfCookieNamesRead()
|
|
36
35
|
.map((name) => readCookie(cookieHeader, name))
|
|
37
36
|
.find((token) => token !== undefined);
|
|
38
37
|
|
|
@@ -48,7 +47,7 @@ export function validateFormCsrf(request: Request, formData: FormData): Response
|
|
|
48
47
|
export function readExistingFormCsrfToken(request: Request | undefined): string | undefined {
|
|
49
48
|
const cookieHeader = request?.headers.get("cookie") ?? null;
|
|
50
49
|
|
|
51
|
-
for (const name of csrfCookieNamesRead) {
|
|
50
|
+
for (const name of csrfCookieNamesRead()) {
|
|
52
51
|
const token = readCookie(cookieHeader, name);
|
|
53
52
|
|
|
54
53
|
if (token !== undefined && isCsrfTokenShape(token)) {
|
|
@@ -90,6 +89,12 @@ function currentCsrfCookieName(): string {
|
|
|
90
89
|
return isProductionEnvironment() ? csrfCookieNameProduction : csrfCookieNameDevelopment;
|
|
91
90
|
}
|
|
92
91
|
|
|
92
|
+
function csrfCookieNamesRead(): readonly string[] {
|
|
93
|
+
return isProductionEnvironment()
|
|
94
|
+
? [csrfCookieNameProduction]
|
|
95
|
+
: [csrfCookieNameProduction, csrfCookieNameDevelopment];
|
|
96
|
+
}
|
|
97
|
+
|
|
93
98
|
function timingSafeStringEqual(left: string, right: string): boolean {
|
|
94
99
|
let diff = left.length ^ right.length;
|
|
95
100
|
const length = Math.max(left.length, right.length);
|
package/src/http.ts
CHANGED
|
@@ -88,7 +88,16 @@ export async function sendResponse(
|
|
|
88
88
|
response: Response,
|
|
89
89
|
): Promise<void> {
|
|
90
90
|
outgoing.statusCode = response.status;
|
|
91
|
-
|
|
91
|
+
const setCookieHeaders = responseSetCookieHeaders(response.headers);
|
|
92
|
+
response.headers.forEach((value, key) => {
|
|
93
|
+
if (key.toLowerCase() === "set-cookie" && setCookieHeaders.length > 0) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
outgoing.setHeader(key, value);
|
|
97
|
+
});
|
|
98
|
+
if (setCookieHeaders.length > 0) {
|
|
99
|
+
outgoing.setHeader("set-cookie", setCookieHeaders);
|
|
100
|
+
}
|
|
92
101
|
|
|
93
102
|
if (response.body === null) {
|
|
94
103
|
outgoing.end();
|
|
@@ -126,3 +135,14 @@ export async function sendResponse(
|
|
|
126
135
|
reader.releaseLock();
|
|
127
136
|
}
|
|
128
137
|
}
|
|
138
|
+
|
|
139
|
+
function responseSetCookieHeaders(headers: Headers): string[] {
|
|
140
|
+
const getSetCookie = (headers as Headers & { getSetCookie?: () => string[] }).getSetCookie;
|
|
141
|
+
|
|
142
|
+
if (typeof getSetCookie === "function") {
|
|
143
|
+
return getSetCookie.call(headers);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const value = headers.get("set-cookie");
|
|
147
|
+
return value === null ? [] : [value];
|
|
148
|
+
}
|
package/src/import-policy.ts
CHANGED
|
@@ -106,9 +106,14 @@ export function createAppRouterImportPolicyPlugin(options: AppRouterImportPolicy
|
|
|
106
106
|
|
|
107
107
|
const resolvedPackage = resolvePackageSpecifier(args.path, args.resolveDir, options.appDir);
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
if (resolvedPackage === undefined) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
external: true,
|
|
115
|
+
path: pathToFileURL(resolvedPackage).href,
|
|
116
|
+
};
|
|
112
117
|
});
|
|
113
118
|
},
|
|
114
119
|
};
|
package/src/index.ts
CHANGED
|
@@ -71,6 +71,8 @@ export const getSession = getSessionInternal;
|
|
|
71
71
|
export const rotateSession = rotateSessionInternal;
|
|
72
72
|
export type {
|
|
73
73
|
AwsLambdaArtifactManifest,
|
|
74
|
+
BuildAppPhase,
|
|
75
|
+
BuildAppPhaseTiming,
|
|
74
76
|
BuildAppOptions,
|
|
75
77
|
BuildAppResult,
|
|
76
78
|
BuiltImportPolicyArtifact,
|
|
@@ -78,6 +80,7 @@ export type {
|
|
|
78
80
|
PackageAwsLambdaArtifactOptions,
|
|
79
81
|
PackageCloudflarePagesArtifactOptions,
|
|
80
82
|
} from "./build.js";
|
|
83
|
+
export type { ServerActionContext } from "./actions.js";
|
|
81
84
|
export type {
|
|
82
85
|
InferLoaderData,
|
|
83
86
|
LayoutProps,
|
|
@@ -126,6 +129,7 @@ export type {
|
|
|
126
129
|
export type { CookieOptions } from "./cookies.js";
|
|
127
130
|
export type { AppRouterImportPolicy } from "./import-policy.js";
|
|
128
131
|
export type {
|
|
132
|
+
LinkChild,
|
|
129
133
|
LinkOptions,
|
|
130
134
|
LinkPrefetch,
|
|
131
135
|
LinkProps,
|
|
@@ -140,6 +144,7 @@ export type {
|
|
|
140
144
|
export type { RouterRuntimeCacheStat } from "./runtime-cache.js";
|
|
141
145
|
export type { MemorySessionStoreOptionsInternal as MemorySessionStoreOptions };
|
|
142
146
|
export type {
|
|
147
|
+
AppRouterCspInlineNonceWarningLogEvent,
|
|
143
148
|
AppRouterLogError,
|
|
144
149
|
AppRouterLogEvent,
|
|
145
150
|
AppRouterLogger,
|
package/src/link.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type ReactCompatNode,
|
|
5
|
-
} from "@reckona/mreact-compat";
|
|
1
|
+
import { escapeHtmlAttribute } from "@reckona/mreact-shared/html-escape";
|
|
2
|
+
import type { HtmlSink } from "@reckona/mreact-shared/compiler-contract";
|
|
3
|
+
import { safeUrlAttributeValue } from "@reckona/mreact-shared/url-safety";
|
|
6
4
|
|
|
7
5
|
export type LinkPrefetch = "intent" | "viewport" | "none" | false;
|
|
8
6
|
export type LinkScroll = "top" | "preserve";
|
|
9
7
|
export type LinkTransition = "auto" | "none" | false;
|
|
8
|
+
export type LinkChild =
|
|
9
|
+
| string
|
|
10
|
+
| number
|
|
11
|
+
| boolean
|
|
12
|
+
| null
|
|
13
|
+
| undefined
|
|
14
|
+
| Node
|
|
15
|
+
| readonly LinkChild[];
|
|
10
16
|
|
|
11
17
|
export interface LinkOptions {
|
|
12
18
|
href: string;
|
|
@@ -17,7 +23,7 @@ export interface LinkOptions {
|
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
export interface LinkProps extends LinkOptions {
|
|
20
|
-
children?:
|
|
26
|
+
children?: LinkChild;
|
|
21
27
|
[attribute: string]: unknown;
|
|
22
28
|
}
|
|
23
29
|
|
|
@@ -37,11 +43,132 @@ export function linkProps(options: LinkOptions): Record<string, string> {
|
|
|
37
43
|
};
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
export function Link(props: LinkProps):
|
|
46
|
+
export function Link(props: LinkProps): string | HTMLAnchorElement;
|
|
47
|
+
export function Link(sink: HtmlSink, props: LinkProps): void;
|
|
48
|
+
export function Link(
|
|
49
|
+
sinkOrProps: HtmlSink | LinkProps,
|
|
50
|
+
maybeProps?: LinkProps,
|
|
51
|
+
): string | HTMLAnchorElement | void {
|
|
52
|
+
if (maybeProps !== undefined) {
|
|
53
|
+
(sinkOrProps as HtmlSink).append(renderLinkString(maybeProps));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return renderLink(sinkOrProps as LinkProps);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function renderLink(props: LinkProps): string | HTMLAnchorElement {
|
|
61
|
+
const { href, prefetch, reload, scroll, transition, ...rest } = props;
|
|
62
|
+
const propsWithLinkAttrs = {
|
|
63
|
+
...rest,
|
|
64
|
+
...linkProps({ href, prefetch, reload, scroll, transition }),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (typeof document !== "undefined" && typeof document.createElement === "function") {
|
|
68
|
+
return createAnchorElement(propsWithLinkAttrs);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return renderAnchorString(propsWithLinkAttrs);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function renderLinkString(props: LinkProps): string {
|
|
41
75
|
const { href, prefetch, reload, scroll, transition, ...rest } = props;
|
|
42
76
|
|
|
43
|
-
return
|
|
77
|
+
return renderAnchorString({
|
|
44
78
|
...rest,
|
|
45
79
|
...linkProps({ href, prefetch, reload, scroll, transition }),
|
|
46
80
|
});
|
|
47
81
|
}
|
|
82
|
+
|
|
83
|
+
function createAnchorElement(props: Record<string, unknown>): HTMLAnchorElement {
|
|
84
|
+
const anchor = document.createElement("a");
|
|
85
|
+
|
|
86
|
+
for (const [name, value] of Object.entries(props)) {
|
|
87
|
+
if (name === "children") {
|
|
88
|
+
appendLinkChild(anchor, value as LinkChild);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!shouldSetAttribute(name, value)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
anchor.setAttribute(attributeName(name), String(value));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return anchor;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function appendLinkChild(parent: Node, child: LinkChild): void {
|
|
103
|
+
if (child === null || child === undefined || typeof child === "boolean") {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (Array.isArray(child)) {
|
|
108
|
+
for (const item of child) {
|
|
109
|
+
appendLinkChild(parent, item);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (child instanceof Node) {
|
|
115
|
+
parent.appendChild(child);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
parent.appendChild(document.createTextNode(String(child)));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function renderAnchorString(props: Record<string, unknown>): string {
|
|
123
|
+
return `<a${renderAnchorAttributes(props)}>${renderChildren(props.children as LinkChild)}</a>`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function renderAnchorAttributes(props: Record<string, unknown>): string {
|
|
127
|
+
const attrs: string[] = [];
|
|
128
|
+
|
|
129
|
+
for (const [name, value] of Object.entries(props)) {
|
|
130
|
+
if (name === "children" || !shouldSetAttribute(name, value)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const attrName = attributeName(name);
|
|
135
|
+
const attrValue = String(value);
|
|
136
|
+
const safeValue = safeUrlAttributeValue(attrName, attrValue);
|
|
137
|
+
|
|
138
|
+
if (safeValue === undefined) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
attrs.push(`${escapeHtmlAttribute(attrName)}="${escapeHtmlAttribute(safeValue)}"`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return attrs.length === 0 ? "" : ` ${attrs.join(" ")}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function shouldSetAttribute(name: string, value: unknown): boolean {
|
|
149
|
+
return (
|
|
150
|
+
name !== "key" &&
|
|
151
|
+
name !== "ref" &&
|
|
152
|
+
value !== null &&
|
|
153
|
+
value !== undefined &&
|
|
154
|
+
value !== false &&
|
|
155
|
+
typeof value !== "function" &&
|
|
156
|
+
typeof value !== "symbol"
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function attributeName(name: string): string {
|
|
161
|
+
return name === "className" ? "class" : name;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function renderChildren(child: LinkChild): string {
|
|
165
|
+
if (child === null || child === undefined || typeof child === "boolean") {
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (Array.isArray(child)) {
|
|
170
|
+
return child.map(renderChildren).join("");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return String(child);
|
|
174
|
+
}
|
package/src/logger.ts
CHANGED
|
@@ -14,7 +14,8 @@ export type AppRouterLogEvent =
|
|
|
14
14
|
| AppRouterRequestEndLogEvent
|
|
15
15
|
| AppRouterRequestErrorLogEvent
|
|
16
16
|
| AppRouterRequestTimingLogEvent
|
|
17
|
-
| AppRouterRenderTimingLogEvent
|
|
17
|
+
| AppRouterRenderTimingLogEvent
|
|
18
|
+
| AppRouterCspInlineNonceWarningLogEvent;
|
|
18
19
|
|
|
19
20
|
export interface AppRouterRequestStartLogEvent {
|
|
20
21
|
method: string;
|
|
@@ -59,6 +60,13 @@ export interface AppRouterRenderTimingLogEvent {
|
|
|
59
60
|
type: "router:render:timing";
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
export interface AppRouterCspInlineNonceWarningLogEvent {
|
|
64
|
+
directive: "script-src" | "style-src";
|
|
65
|
+
path: string;
|
|
66
|
+
tag: "script" | "style";
|
|
67
|
+
type: "router:csp:inline-nonce-warning";
|
|
68
|
+
}
|
|
69
|
+
|
|
62
70
|
export interface AppRouterLogError {
|
|
63
71
|
message: string;
|
|
64
72
|
name: string;
|
package/src/module-runner.ts
CHANGED
|
@@ -85,6 +85,7 @@ export async function importAppRouterSourceModule<T>(options: {
|
|
|
85
85
|
label: string;
|
|
86
86
|
plugins?: readonly RouterCompatPlugin[] | undefined;
|
|
87
87
|
resolveDir?: string | undefined;
|
|
88
|
+
root?: string | undefined;
|
|
88
89
|
serverSourceTransform?: ServerSourceTransformOptions | undefined;
|
|
89
90
|
sourcefile?: string | undefined;
|
|
90
91
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
@@ -125,6 +126,7 @@ async function importAppRouterSourceModuleWithoutCache<T>(options: {
|
|
|
125
126
|
label: string;
|
|
126
127
|
plugins?: readonly RouterCompatPlugin[] | undefined;
|
|
127
128
|
resolveDir?: string | undefined;
|
|
129
|
+
root?: string | undefined;
|
|
128
130
|
serverSourceTransform?: ServerSourceTransformOptions | undefined;
|
|
129
131
|
sourcefile?: string | undefined;
|
|
130
132
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
@@ -275,6 +277,7 @@ export async function bundleAppRouterSourceModule(options: {
|
|
|
275
277
|
label: string;
|
|
276
278
|
plugins?: readonly RouterCompatPlugin[] | undefined;
|
|
277
279
|
resolveDir?: string | undefined;
|
|
280
|
+
root?: string | undefined;
|
|
278
281
|
serverSourceTransform?: ServerSourceTransformOptions | undefined;
|
|
279
282
|
sourcefile?: string | undefined;
|
|
280
283
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
@@ -284,6 +287,7 @@ export async function bundleAppRouterSourceModule(options: {
|
|
|
284
287
|
externalizeAppSourceModuleDirs: options.externalizeAppSourceModuleDirs,
|
|
285
288
|
filename: options.sourcefile ?? join(options.resolveDir ?? process.cwd(), "module.js"),
|
|
286
289
|
platform: "node",
|
|
290
|
+
root: options.root,
|
|
287
291
|
vitePlugins: options.vitePlugins,
|
|
288
292
|
plugins: [
|
|
289
293
|
workspacePackageResolutionPlugin(),
|
|
@@ -70,7 +70,7 @@ export function createNativeRouteMatcher(
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function normalizeNativeParams(
|
|
73
|
+
export function normalizeNativeParams(
|
|
74
74
|
route: AppRoute,
|
|
75
75
|
params: Record<string, string>,
|
|
76
76
|
): MatchedRoute["params"] {
|
|
@@ -82,13 +82,24 @@ function normalizeNativeParams(
|
|
|
82
82
|
}
|
|
83
83
|
const value = normalized[segment.name];
|
|
84
84
|
if (typeof value === "string") {
|
|
85
|
-
normalized[segment.name] = value
|
|
85
|
+
normalized[segment.name] = value
|
|
86
|
+
.split("/")
|
|
87
|
+
.filter((part: string) => part !== "")
|
|
88
|
+
.map((part: string) => safeDecodeURIComponent(part) ?? part);
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
return normalized;
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
function safeDecodeURIComponent(value: string): string | undefined {
|
|
96
|
+
try {
|
|
97
|
+
return decodeURIComponent(value);
|
|
98
|
+
} catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
92
103
|
export function shouldUseNativeRouteMatcher(
|
|
93
104
|
routeCount: number,
|
|
94
105
|
mode: string | undefined,
|