@reckona/mreact-router 0.0.97 → 0.0.99
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/build.d.ts +38 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +1087 -304
- 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 -87
- 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 +113 -8
- 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 -2
- 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 +117 -2
- package/dist/render.js.map +1 -1
- package/dist/route-source.d.ts +4 -4
- package/dist/route-source.d.ts.map +1 -1
- package/dist/route-source.js +8 -8
- 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/package.json +11 -11
- package/src/actions.ts +42 -2
- package/src/adapters/aws-lambda.ts +33 -16
- package/src/build.ts +1578 -397
- package/src/bundle-pipeline.ts +136 -88
- package/src/cache.ts +13 -8
- package/src/cli-options.ts +20 -0
- package/src/cli.ts +1 -0
- package/src/client.ts +145 -14
- 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 +129 -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 +177 -2
- package/src/route-source.ts +8 -8
- package/src/route-styles.ts +17 -1
- package/src/serve.ts +11 -6
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(
|
|
@@ -813,6 +825,12 @@ async function inferClientRouteModuleSource(options: {
|
|
|
813
825
|
}
|
|
814
826
|
|
|
815
827
|
if (renderedByCall && !renderedByJsx) {
|
|
828
|
+
diagnostics.push(
|
|
829
|
+
functionCallInteractiveImportDiagnostic({
|
|
830
|
+
filename: options.filename,
|
|
831
|
+
reference,
|
|
832
|
+
}),
|
|
833
|
+
);
|
|
816
834
|
continue;
|
|
817
835
|
}
|
|
818
836
|
|
|
@@ -1145,6 +1163,28 @@ function serverOnlyClientImportReferenceDiagnostic(options: {
|
|
|
1145
1163
|
};
|
|
1146
1164
|
}
|
|
1147
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
|
+
|
|
1148
1188
|
function unsupportedClientImportReferenceDiagnostic(options: {
|
|
1149
1189
|
filename: string;
|
|
1150
1190
|
identifierReferences: ReadonlySet<string>;
|
|
@@ -1186,7 +1226,31 @@ function startsUppercase(value: string): boolean {
|
|
|
1186
1226
|
export function formatClientRouteInferenceDiagnostic(
|
|
1187
1227
|
diagnostic: ClientRouteInferenceDiagnostic,
|
|
1188
1228
|
): string {
|
|
1189
|
-
|
|
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
|
+
};
|
|
1190
1254
|
}
|
|
1191
1255
|
|
|
1192
1256
|
async function resolveAppLocalModule(options: {
|
|
@@ -1579,6 +1643,7 @@ export async function buildClientRouteOutput(
|
|
|
1579
1643
|
options.dropConsoleFunctions ?? resolveClientConsolePureFunctions(options.dropClientConsole);
|
|
1580
1644
|
const bundled = await bundleRouterModule({
|
|
1581
1645
|
code: entry.code,
|
|
1646
|
+
cacheDir: options.cacheDir,
|
|
1582
1647
|
define: {
|
|
1583
1648
|
__MREACT_CLIENT_DEVTOOLS__: "false",
|
|
1584
1649
|
},
|
|
@@ -1600,6 +1665,7 @@ export async function buildClientRouteOutput(
|
|
|
1600
1665
|
|
|
1601
1666
|
export async function buildClientRouteBatchOutput(options: {
|
|
1602
1667
|
assetBaseUrl?: string | undefined;
|
|
1668
|
+
cacheDir?: string | undefined;
|
|
1603
1669
|
dropConsoleFunctions?: readonly string[] | undefined;
|
|
1604
1670
|
minify?: boolean;
|
|
1605
1671
|
projectRoot?: string | undefined;
|
|
@@ -1622,6 +1688,7 @@ export async function buildClientRouteBatchOutput(options: {
|
|
|
1622
1688
|
);
|
|
1623
1689
|
const bundled = await bundleRouterModules({
|
|
1624
1690
|
base: options.assetBaseUrl ?? "/_mreact/client/",
|
|
1691
|
+
cacheDir: options.cacheDir,
|
|
1625
1692
|
define: {
|
|
1626
1693
|
__MREACT_CLIENT_DEVTOOLS__: "false",
|
|
1627
1694
|
},
|
|
@@ -2091,6 +2158,10 @@ export async function __mreactPrefetch(url) {
|
|
|
2091
2158
|
return false;
|
|
2092
2159
|
}
|
|
2093
2160
|
|
|
2161
|
+
if (__mreactIsCurrentLocationNavigationHref(href)) {
|
|
2162
|
+
return false;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2094
2165
|
const script = __mreactRouteScriptForNavigationUrl(href);
|
|
2095
2166
|
|
|
2096
2167
|
if (script === undefined) {
|
|
@@ -2110,11 +2181,43 @@ function __mreactPrefetchNavigationHtml(href) {
|
|
|
2110
2181
|
return false;
|
|
2111
2182
|
}
|
|
2112
2183
|
|
|
2113
|
-
|
|
2184
|
+
__mreactRememberNavigationHtml(href, html);
|
|
2114
2185
|
return true;
|
|
2115
2186
|
}).catch(() => false);
|
|
2116
2187
|
}
|
|
2117
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
|
+
|
|
2118
2221
|
function __mreactPrefetchRouteScript(script) {
|
|
2119
2222
|
if (typeof document === "undefined") {
|
|
2120
2223
|
return false;
|
|
@@ -2155,14 +2258,14 @@ export async function __mreactNavigate(url, options = {}) {
|
|
|
2155
2258
|
__mreactSetNavigationState(__mreactPendingNavigationState(href, options.type ?? "push"));
|
|
2156
2259
|
|
|
2157
2260
|
try {
|
|
2158
|
-
const cachedHtml =
|
|
2261
|
+
const cachedHtml = __mreactCachedNavigationHtml(href);
|
|
2159
2262
|
const html = cachedHtml ?? await __mreactFetchNavigationHtml(href);
|
|
2160
2263
|
|
|
2161
2264
|
if (typeof html !== "string") {
|
|
2162
2265
|
return false;
|
|
2163
2266
|
}
|
|
2164
2267
|
|
|
2165
|
-
|
|
2268
|
+
__mreactRememberNavigationHtml(href, html);
|
|
2166
2269
|
return await __mreactApplyNavigationHtmlWithOptionalTransition(html, href, options);
|
|
2167
2270
|
} finally {
|
|
2168
2271
|
__mreactSetNavigationState(__mreactIdleNavigationState());
|
|
@@ -2828,6 +2931,24 @@ function __mreactIsHashOnlyNavigation(nextUrl) {
|
|
|
2828
2931
|
nextUrl.hash !== location.hash;
|
|
2829
2932
|
}
|
|
2830
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
|
+
|
|
2831
2952
|
function __mreactInstallNavigation() {
|
|
2832
2953
|
if (__mreactNavigationState.installed || typeof document === "undefined") {
|
|
2833
2954
|
return;
|
|
@@ -2890,6 +3011,16 @@ function __mreactInstallNavigation() {
|
|
|
2890
3011
|
return;
|
|
2891
3012
|
}
|
|
2892
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
|
+
|
|
2893
3024
|
event.preventDefault();
|
|
2894
3025
|
void __mreactNavigate(nextUrl.href, {
|
|
2895
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,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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 type { ReactCompatElement, ReactCompatNode } from "@reckona/mreact-compat";
|
|
4
|
+
import { safeUrlAttributeValue } from "@reckona/mreact-shared/url-safety";
|
|
6
5
|
|
|
7
6
|
export type LinkPrefetch = "intent" | "viewport" | "none" | false;
|
|
8
7
|
export type LinkScroll = "top" | "preserve";
|
|
9
8
|
export type LinkTransition = "auto" | "none" | false;
|
|
9
|
+
export type LinkChild = ReactCompatNode | Node | readonly LinkChild[];
|
|
10
10
|
|
|
11
11
|
export interface LinkOptions {
|
|
12
12
|
href: string;
|
|
@@ -17,7 +17,7 @@ export interface LinkOptions {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export interface LinkProps extends LinkOptions {
|
|
20
|
-
children?:
|
|
20
|
+
children?: LinkChild;
|
|
21
21
|
[attribute: string]: unknown;
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -37,11 +37,132 @@ export function linkProps(options: LinkOptions): Record<string, string> {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export function Link(props: LinkProps): ReactCompatElement
|
|
40
|
+
export function Link(props: LinkProps): ReactCompatElement;
|
|
41
|
+
export function Link(sink: HtmlSink, props: LinkProps): void;
|
|
42
|
+
export function Link(
|
|
43
|
+
sinkOrProps: HtmlSink | LinkProps,
|
|
44
|
+
maybeProps?: LinkProps,
|
|
45
|
+
): ReactCompatElement | string | HTMLAnchorElement | void {
|
|
46
|
+
if (maybeProps !== undefined) {
|
|
47
|
+
(sinkOrProps as HtmlSink).append(renderLinkString(maybeProps));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return renderLink(sinkOrProps as LinkProps);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderLink(props: LinkProps): string | HTMLAnchorElement {
|
|
55
|
+
const { href, prefetch, reload, scroll, transition, ...rest } = props;
|
|
56
|
+
const propsWithLinkAttrs = {
|
|
57
|
+
...rest,
|
|
58
|
+
...linkProps({ href, prefetch, reload, scroll, transition }),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (typeof document !== "undefined" && typeof document.createElement === "function") {
|
|
62
|
+
return createAnchorElement(propsWithLinkAttrs);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return renderAnchorString(propsWithLinkAttrs);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderLinkString(props: LinkProps): string {
|
|
41
69
|
const { href, prefetch, reload, scroll, transition, ...rest } = props;
|
|
42
70
|
|
|
43
|
-
return
|
|
71
|
+
return renderAnchorString({
|
|
44
72
|
...rest,
|
|
45
73
|
...linkProps({ href, prefetch, reload, scroll, transition }),
|
|
46
74
|
});
|
|
47
75
|
}
|
|
76
|
+
|
|
77
|
+
function createAnchorElement(props: Record<string, unknown>): HTMLAnchorElement {
|
|
78
|
+
const anchor = document.createElement("a");
|
|
79
|
+
|
|
80
|
+
for (const [name, value] of Object.entries(props)) {
|
|
81
|
+
if (name === "children") {
|
|
82
|
+
appendLinkChild(anchor, value as LinkChild);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!shouldSetAttribute(name, value)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
anchor.setAttribute(attributeName(name), String(value));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return anchor;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function appendLinkChild(parent: Node, child: LinkChild): void {
|
|
97
|
+
if (child === null || child === undefined || typeof child === "boolean") {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (Array.isArray(child)) {
|
|
102
|
+
for (const item of child) {
|
|
103
|
+
appendLinkChild(parent, item);
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (child instanceof Node) {
|
|
109
|
+
parent.appendChild(child);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
parent.appendChild(document.createTextNode(String(child)));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function renderAnchorString(props: Record<string, unknown>): string {
|
|
117
|
+
return `<a${renderAnchorAttributes(props)}>${renderChildren(props.children as LinkChild)}</a>`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function renderAnchorAttributes(props: Record<string, unknown>): string {
|
|
121
|
+
const attrs: string[] = [];
|
|
122
|
+
|
|
123
|
+
for (const [name, value] of Object.entries(props)) {
|
|
124
|
+
if (name === "children" || !shouldSetAttribute(name, value)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const attrName = attributeName(name);
|
|
129
|
+
const attrValue = String(value);
|
|
130
|
+
const safeValue = safeUrlAttributeValue(attrName, attrValue);
|
|
131
|
+
|
|
132
|
+
if (safeValue === undefined) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
attrs.push(`${escapeHtmlAttribute(attrName)}="${escapeHtmlAttribute(safeValue)}"`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return attrs.length === 0 ? "" : ` ${attrs.join(" ")}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function shouldSetAttribute(name: string, value: unknown): boolean {
|
|
143
|
+
return (
|
|
144
|
+
name !== "key" &&
|
|
145
|
+
name !== "ref" &&
|
|
146
|
+
value !== null &&
|
|
147
|
+
value !== undefined &&
|
|
148
|
+
value !== false &&
|
|
149
|
+
typeof value !== "function" &&
|
|
150
|
+
typeof value !== "symbol"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function attributeName(name: string): string {
|
|
155
|
+
return name === "className" ? "class" : name;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function renderChildren(child: LinkChild): string {
|
|
159
|
+
if (child === null || child === undefined || typeof child === "boolean") {
|
|
160
|
+
return "";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (Array.isArray(child)) {
|
|
164
|
+
return child.map(renderChildren).join("");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return String(child);
|
|
168
|
+
}
|
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,
|