@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/render.ts
CHANGED
|
@@ -1124,6 +1124,13 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
1124
1124
|
vitePlugins: options.vitePlugins,
|
|
1125
1125
|
});
|
|
1126
1126
|
html = injectHeadMetadata(html, metadata);
|
|
1127
|
+
warnIfCspNonceWouldBlockInlineTags({
|
|
1128
|
+
html,
|
|
1129
|
+
logger: options.logger,
|
|
1130
|
+
metadata,
|
|
1131
|
+
request: options.request,
|
|
1132
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1133
|
+
});
|
|
1127
1134
|
html = injectAuthSessionClaims(
|
|
1128
1135
|
html,
|
|
1129
1136
|
originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined,
|
|
@@ -1377,6 +1384,13 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
1377
1384
|
finishRenderTimingPhase(timing, phaseStartedAt, "metadataMs");
|
|
1378
1385
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1379
1386
|
html = injectHeadMetadata(html, metadata);
|
|
1387
|
+
warnIfCspNonceWouldBlockInlineTags({
|
|
1388
|
+
html,
|
|
1389
|
+
logger: options.logger,
|
|
1390
|
+
metadata,
|
|
1391
|
+
request: options.request,
|
|
1392
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1393
|
+
});
|
|
1380
1394
|
html = injectAuthSessionClaims(
|
|
1381
1395
|
html,
|
|
1382
1396
|
originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined,
|
|
@@ -2319,6 +2333,7 @@ export async function bundleMiddlewareModuleCode(options: {
|
|
|
2319
2333
|
code: options.code,
|
|
2320
2334
|
filename: options.file,
|
|
2321
2335
|
platform: "node",
|
|
2336
|
+
root: options.importPolicy?.projectRoot,
|
|
2322
2337
|
plugins: [
|
|
2323
2338
|
fileImportMetaUrlPlugin(),
|
|
2324
2339
|
createAppRouterImportPolicyPlugin({
|
|
@@ -2525,7 +2540,7 @@ async function analyzeRouteSourceUncached(options: {
|
|
|
2525
2540
|
serverSourceFiles?: ReadonlyMap<string, string> | undefined;
|
|
2526
2541
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2527
2542
|
}): Promise<RouteSourceAnalysis> {
|
|
2528
|
-
const routeCode = stripRouteModuleExports(options.code);
|
|
2543
|
+
const routeCode = stripRouteModuleExports(options.code, options.filename);
|
|
2529
2544
|
const clientInference = await inferClientRouteModule({
|
|
2530
2545
|
appDir: options.appDir,
|
|
2531
2546
|
cache: options.clientRouteInferenceCache,
|
|
@@ -3464,7 +3479,7 @@ async function renderShellPrefixSuffix(
|
|
|
3464
3479
|
? { client: false, clientBoundaryImports: [], diagnostics: [] }
|
|
3465
3480
|
: await inferClientRouteModule({
|
|
3466
3481
|
cache: clientRouteInferenceCache,
|
|
3467
|
-
code: stripRouteClientOnlyExports(code),
|
|
3482
|
+
code: stripRouteClientOnlyExports(code, shell.file),
|
|
3468
3483
|
filename: shell.file,
|
|
3469
3484
|
vitePlugins,
|
|
3470
3485
|
});
|
|
@@ -3967,7 +3982,7 @@ export async function bundleRouteLoaderModuleCode(options: {
|
|
|
3967
3982
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
3968
3983
|
}): Promise<string> {
|
|
3969
3984
|
const output = await bundleRouterModule({
|
|
3970
|
-
code: stripRouteLoaderOnlyExports(options.code),
|
|
3985
|
+
code: stripRouteLoaderOnlyExports(options.code, options.filename),
|
|
3971
3986
|
externalizeAppSourceModuleDirs: options.externalizeAppSourceModuleDirs,
|
|
3972
3987
|
filename: options.filename,
|
|
3973
3988
|
platform: "node",
|
|
@@ -4111,7 +4126,7 @@ async function bundleRouteMetadataModuleCode(options: {
|
|
|
4111
4126
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
4112
4127
|
}): Promise<string> {
|
|
4113
4128
|
const output = await bundleRouterModule({
|
|
4114
|
-
code: stripRouteMetadataOnlyExports(options.code),
|
|
4129
|
+
code: stripRouteMetadataOnlyExports(options.code, options.filename),
|
|
4115
4130
|
filename: options.filename,
|
|
4116
4131
|
platform: "node",
|
|
4117
4132
|
vitePlugins: options.vitePlugins,
|
|
@@ -4269,6 +4284,166 @@ function usesRuntimeCacheControl(code: string): boolean {
|
|
|
4269
4284
|
return /\bcacheControl\s*\(/.test(code);
|
|
4270
4285
|
}
|
|
4271
4286
|
|
|
4287
|
+
function warnIfCspNonceWouldBlockInlineTags(options: {
|
|
4288
|
+
html: string;
|
|
4289
|
+
logger: AppRouterLogger | undefined;
|
|
4290
|
+
metadata: RouteMetadata | undefined;
|
|
4291
|
+
request: Request;
|
|
4292
|
+
serverModuleCacheVersion: string | undefined;
|
|
4293
|
+
}): void {
|
|
4294
|
+
if (
|
|
4295
|
+
options.serverModuleCacheVersion !== undefined ||
|
|
4296
|
+
process.env.NODE_ENV === "production" ||
|
|
4297
|
+
options.metadata?.csp?.disable === true ||
|
|
4298
|
+
options.metadata?.csp?.directives === undefined ||
|
|
4299
|
+
!/<(?:script|style)\b/i.test(options.html)
|
|
4300
|
+
) {
|
|
4301
|
+
return;
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4304
|
+
const scriptNonces = cspDirectiveNonces(options.metadata.csp, "script-src");
|
|
4305
|
+
const styleNonces = cspDirectiveNonces(options.metadata.csp, "style-src");
|
|
4306
|
+
|
|
4307
|
+
if (scriptNonces.size === 0 && styleNonces.size === 0) {
|
|
4308
|
+
return;
|
|
4309
|
+
}
|
|
4310
|
+
|
|
4311
|
+
for (const tag of inlineCspTags(options.html)) {
|
|
4312
|
+
if (tag.name === "script") {
|
|
4313
|
+
if (
|
|
4314
|
+
scriptNonces.size > 0 &&
|
|
4315
|
+
!scriptHasExternalSourceOrInertType(tag.attributes) &&
|
|
4316
|
+
tag.content.trim() !== "" &&
|
|
4317
|
+
!tagHasMatchingNonce(tag.attributes, scriptNonces)
|
|
4318
|
+
) {
|
|
4319
|
+
warnCspInlineNonceMismatch(options, "script-src", "script");
|
|
4320
|
+
}
|
|
4321
|
+
continue;
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4324
|
+
if (
|
|
4325
|
+
styleNonces.size > 0 &&
|
|
4326
|
+
tag.content.trim() !== "" &&
|
|
4327
|
+
!tagHasMatchingNonce(tag.attributes, styleNonces)
|
|
4328
|
+
) {
|
|
4329
|
+
warnCspInlineNonceMismatch(options, "style-src", "style");
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
function warnCspInlineNonceMismatch(
|
|
4335
|
+
options: {
|
|
4336
|
+
logger: AppRouterLogger | undefined;
|
|
4337
|
+
request: Request;
|
|
4338
|
+
},
|
|
4339
|
+
directive: "script-src" | "style-src",
|
|
4340
|
+
tag: "script" | "style",
|
|
4341
|
+
): void {
|
|
4342
|
+
const message =
|
|
4343
|
+
tag === "script"
|
|
4344
|
+
? "mreact router: CSP script-src uses a nonce, but an inline <script> without a matching nonce will be blocked. Add the script through metadata.head with nonce: true, move it to an external script, or remove script-src for this route."
|
|
4345
|
+
: "mreact router: CSP style-src uses a nonce, but an inline <style> without a matching nonce will be blocked. Add the style through metadata.head with nonce: true, move it to an external stylesheet, or remove style-src for this route.";
|
|
4346
|
+
|
|
4347
|
+
if (options.logger === undefined) {
|
|
4348
|
+
console.warn(message);
|
|
4349
|
+
return;
|
|
4350
|
+
}
|
|
4351
|
+
|
|
4352
|
+
emitRouterLog(options.logger, "warn", {
|
|
4353
|
+
directive,
|
|
4354
|
+
path: new URL(options.request.url).pathname,
|
|
4355
|
+
tag,
|
|
4356
|
+
type: "router:csp:inline-nonce-warning",
|
|
4357
|
+
});
|
|
4358
|
+
}
|
|
4359
|
+
|
|
4360
|
+
function cspDirectiveNonces(
|
|
4361
|
+
csp: NonNullable<RouteMetadata["csp"]>,
|
|
4362
|
+
directive: "script-src" | "style-src",
|
|
4363
|
+
): ReadonlySet<string> {
|
|
4364
|
+
const values = csp.directives?.[directive];
|
|
4365
|
+
const nonces = new Set<string>();
|
|
4366
|
+
|
|
4367
|
+
if (values !== undefined && csp.nonce !== undefined) {
|
|
4368
|
+
nonces.add(csp.nonce);
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
for (const value of Array.isArray(values) ? values : values === undefined ? [] : [values]) {
|
|
4372
|
+
const match = /^'nonce-([^']+)'$/.exec(value);
|
|
4373
|
+
|
|
4374
|
+
if (match?.[1] !== undefined) {
|
|
4375
|
+
nonces.add(match[1]);
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
return nonces;
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
interface InlineCspTag {
|
|
4383
|
+
attributes: ReadonlyMap<string, string>;
|
|
4384
|
+
content: string;
|
|
4385
|
+
name: "script" | "style";
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
function inlineCspTags(html: string): InlineCspTag[] {
|
|
4389
|
+
const tags: InlineCspTag[] = [];
|
|
4390
|
+
const pattern = /<(script|style)\b([^>]*)>([\s\S]*?)<\/\1>/gi;
|
|
4391
|
+
let match: RegExpExecArray | null;
|
|
4392
|
+
|
|
4393
|
+
while ((match = pattern.exec(html)) !== null) {
|
|
4394
|
+
const name = match[1]?.toLowerCase();
|
|
4395
|
+
|
|
4396
|
+
if (name !== "script" && name !== "style") {
|
|
4397
|
+
continue;
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
tags.push({
|
|
4401
|
+
attributes: parseTagAttributes(match[2] ?? ""),
|
|
4402
|
+
content: match[3] ?? "",
|
|
4403
|
+
name,
|
|
4404
|
+
});
|
|
4405
|
+
}
|
|
4406
|
+
|
|
4407
|
+
return tags;
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4410
|
+
function parseTagAttributes(source: string): ReadonlyMap<string, string> {
|
|
4411
|
+
const attributes = new Map<string, string>();
|
|
4412
|
+
const pattern = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
4413
|
+
let match: RegExpExecArray | null;
|
|
4414
|
+
|
|
4415
|
+
while ((match = pattern.exec(source)) !== null) {
|
|
4416
|
+
const name = match[1]?.toLowerCase();
|
|
4417
|
+
|
|
4418
|
+
if (name === undefined) {
|
|
4419
|
+
continue;
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4422
|
+
attributes.set(name, match[2] ?? match[3] ?? match[4] ?? "");
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
return attributes;
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
function scriptHasExternalSourceOrInertType(attributes: ReadonlyMap<string, string>): boolean {
|
|
4429
|
+
if (attributes.has("src")) {
|
|
4430
|
+
return true;
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
const type = attributes.get("type")?.trim().toLowerCase();
|
|
4434
|
+
|
|
4435
|
+
return type === "application/json" || type === "application/ld+json";
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4438
|
+
function tagHasMatchingNonce(
|
|
4439
|
+
attributes: ReadonlyMap<string, string>,
|
|
4440
|
+
expectedNonces: ReadonlySet<string>,
|
|
4441
|
+
): boolean {
|
|
4442
|
+
const nonce = attributes.get("nonce");
|
|
4443
|
+
|
|
4444
|
+
return nonce !== undefined && expectedNonces.has(nonce);
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4272
4447
|
function injectQueryState(html: string, state: DehydratedQueryClient): string {
|
|
4273
4448
|
if (state.queries.length === 0) {
|
|
4274
4449
|
return html;
|
|
@@ -4279,7 +4454,7 @@ function injectQueryState(html: string, state: DehydratedQueryClient): string {
|
|
|
4279
4454
|
)}</script>`;
|
|
4280
4455
|
|
|
4281
4456
|
return /<\/body>/i.test(html)
|
|
4282
|
-
? html.replace(/<\/body>/i, `${script}</body>`)
|
|
4457
|
+
? html.replace(/<\/body>/i, () => `${script}</body>`)
|
|
4283
4458
|
: `${html}${script}`;
|
|
4284
4459
|
}
|
|
4285
4460
|
|
|
@@ -4293,7 +4468,7 @@ function injectAuthSessionClaims(html: string, claims: unknown): string {
|
|
|
4293
4468
|
)}</script>`;
|
|
4294
4469
|
|
|
4295
4470
|
return /<\/body>/i.test(html)
|
|
4296
|
-
? html.replace(/<\/body>/i, `${script}</body>`)
|
|
4471
|
+
? html.replace(/<\/body>/i, () => `${script}</body>`)
|
|
4297
4472
|
: `${html}${script}`;
|
|
4298
4473
|
}
|
|
4299
4474
|
|
package/src/route-source.ts
CHANGED
|
@@ -37,21 +37,26 @@ const routeRequestExportNames = new Set<string>([
|
|
|
37
37
|
const routeLoaderOnlyExportNames = new Set<string>(["loader"]);
|
|
38
38
|
const routeMetadataOnlyExportNames = new Set<string>(["generateMetadata", "metadata"]);
|
|
39
39
|
|
|
40
|
-
export function stripRouteModuleExports(code: string): string {
|
|
40
|
+
export function stripRouteModuleExports(code: string, filename?: string | undefined): string {
|
|
41
41
|
return demoteRouteHelperExports(stripTopLevelExportDeclarations({
|
|
42
42
|
code,
|
|
43
|
+
filename,
|
|
43
44
|
names: routeModuleExportNames,
|
|
44
|
-
}));
|
|
45
|
+
}), routeRenderExportNames, filename);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
export function stripRouteClientOnlyExports(code: string): string {
|
|
48
|
+
export function stripRouteClientOnlyExports(code: string, filename?: string | undefined): string {
|
|
48
49
|
return stripUnusedStaticValueImports({
|
|
49
50
|
code: demoteRouteHelperExports(
|
|
50
51
|
stripTopLevelExportDeclarations({
|
|
51
52
|
code,
|
|
53
|
+
filename,
|
|
52
54
|
names: routeClientOnlyExportNames,
|
|
53
55
|
}),
|
|
56
|
+
routeRenderExportNames,
|
|
57
|
+
filename,
|
|
54
58
|
),
|
|
59
|
+
filename,
|
|
55
60
|
});
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -60,7 +65,7 @@ export function stripRouteClientSource(input: {
|
|
|
60
65
|
filename?: string | undefined;
|
|
61
66
|
}): string {
|
|
62
67
|
return stripRouteClientFormActionExpressions({
|
|
63
|
-
code: stripRouteClientOnlyExports(input.code),
|
|
68
|
+
code: stripRouteClientOnlyExports(input.code, input.filename),
|
|
64
69
|
filename: input.filename,
|
|
65
70
|
});
|
|
66
71
|
}
|
|
@@ -79,49 +84,56 @@ export function stripRouteClientFormActionExpressions(input: {
|
|
|
79
84
|
return code;
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
export function stripRouteBuildExports(code: string): string {
|
|
83
|
-
return stripRouteClientOnlyExports(code);
|
|
87
|
+
export function stripRouteBuildExports(code: string, filename?: string | undefined): string {
|
|
88
|
+
return stripRouteClientOnlyExports(code, filename);
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
export function stripRouteRequestOnlyExports(code: string): string {
|
|
91
|
+
export function stripRouteRequestOnlyExports(code: string, filename?: string | undefined): string {
|
|
87
92
|
return demoteRouteHelperExports(
|
|
88
93
|
stripTopLevelExportDeclarations({
|
|
89
94
|
code,
|
|
95
|
+
filename,
|
|
90
96
|
names: routeRequestRenderExportNames,
|
|
91
97
|
}),
|
|
92
98
|
routeRequestExportNames,
|
|
99
|
+
filename,
|
|
93
100
|
);
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
export function stripRouteLoaderOnlyExports(code: string): string {
|
|
103
|
+
export function stripRouteLoaderOnlyExports(code: string, filename?: string | undefined): string {
|
|
97
104
|
return demoteRouteHelperExports(
|
|
98
105
|
stripTopLevelExportDeclarations({
|
|
99
106
|
code,
|
|
107
|
+
filename,
|
|
100
108
|
names: ["auth", "default", "generateMetadata", "generateStaticParams", "metadata", "middleware", "prerender", "revalidate", "slots", "stream"],
|
|
101
109
|
}),
|
|
102
110
|
routeLoaderOnlyExportNames,
|
|
111
|
+
filename,
|
|
103
112
|
);
|
|
104
113
|
}
|
|
105
114
|
|
|
106
|
-
export function stripRouteMetadataOnlyExports(code: string): string {
|
|
115
|
+
export function stripRouteMetadataOnlyExports(code: string, filename?: string | undefined): string {
|
|
107
116
|
return demoteRouteHelperExports(
|
|
108
117
|
stripTopLevelExportDeclarations({
|
|
109
118
|
code,
|
|
119
|
+
filename,
|
|
110
120
|
names: ["auth", "default", "generateStaticParams", "loader", "middleware", "prerender", "revalidate", "slots", "stream"],
|
|
111
121
|
}),
|
|
112
122
|
routeMetadataOnlyExportNames,
|
|
123
|
+
filename,
|
|
113
124
|
);
|
|
114
125
|
}
|
|
115
126
|
|
|
116
|
-
export function stripRouteConfigExports(code: string): string {
|
|
127
|
+
export function stripRouteConfigExports(code: string, filename?: string | undefined): string {
|
|
117
128
|
return stripTopLevelExportDeclarations({
|
|
118
129
|
code,
|
|
130
|
+
filename,
|
|
119
131
|
names: ["auth", "prerender", "revalidate", "stream"],
|
|
120
132
|
});
|
|
121
133
|
}
|
|
122
134
|
|
|
123
|
-
export function isStreamRouteSource(code: string): boolean {
|
|
124
|
-
return hasTopLevelExportDeclaration({ code, names: ["stream"] });
|
|
135
|
+
export function isStreamRouteSource(code: string, filename?: string | undefined): boolean {
|
|
136
|
+
return hasTopLevelExportDeclaration({ code, filename, names: ["stream"] });
|
|
125
137
|
}
|
|
126
138
|
|
|
127
139
|
export function mayUseAwaitBoundarySource(code: string): boolean {
|
|
@@ -194,28 +206,29 @@ export function routeClosureMayUseAwaitBoundary(options: {
|
|
|
194
206
|
}
|
|
195
207
|
}
|
|
196
208
|
|
|
197
|
-
export function hasPrerenderExport(code: string): boolean {
|
|
198
|
-
return hasTopLevelExportDeclaration({ code, names: ["prerender"] });
|
|
209
|
+
export function hasPrerenderExport(code: string, filename?: string | undefined): boolean {
|
|
210
|
+
return hasTopLevelExportDeclaration({ code, filename, names: ["prerender"] });
|
|
199
211
|
}
|
|
200
212
|
|
|
201
|
-
export function hasGenerateStaticParamsExport(code: string): boolean {
|
|
202
|
-
return hasTopLevelExportDeclaration({ code, names: ["generateStaticParams"] });
|
|
213
|
+
export function hasGenerateStaticParamsExport(code: string, filename?: string | undefined): boolean {
|
|
214
|
+
return hasTopLevelExportDeclaration({ code, filename, names: ["generateStaticParams"] });
|
|
203
215
|
}
|
|
204
216
|
|
|
205
|
-
export function hasLoaderExport(code: string): boolean {
|
|
206
|
-
return hasTopLevelExportDeclaration({ code, names: ["loader"] });
|
|
217
|
+
export function hasLoaderExport(code: string, filename?: string | undefined): boolean {
|
|
218
|
+
return hasTopLevelExportDeclaration({ code, filename, names: ["loader"] });
|
|
207
219
|
}
|
|
208
220
|
|
|
209
221
|
function demoteRouteHelperExports(
|
|
210
222
|
code: string,
|
|
211
223
|
preservedExportNames: ReadonlySet<string> = routeRenderExportNames,
|
|
224
|
+
filename?: string | undefined,
|
|
212
225
|
): string {
|
|
213
|
-
const helperNames = collectTopLevelValueExportNames({ code })
|
|
226
|
+
const helperNames = collectTopLevelValueExportNames({ code, filename })
|
|
214
227
|
.filter((name) => !preservedExportNames.has(name) && startsLowercase(name));
|
|
215
228
|
|
|
216
229
|
return helperNames.length === 0
|
|
217
230
|
? code
|
|
218
|
-
: demoteTopLevelExportDeclarations({ code, names: helperNames });
|
|
231
|
+
: demoteTopLevelExportDeclarations({ code, filename, names: helperNames });
|
|
219
232
|
}
|
|
220
233
|
|
|
221
234
|
function startsLowercase(value: string): boolean {
|
package/src/route-styles.ts
CHANGED
|
@@ -7,6 +7,18 @@ export async function collectRouteCssFiles(options: {
|
|
|
7
7
|
appDir: string;
|
|
8
8
|
pageFile: string;
|
|
9
9
|
projectRoot: string;
|
|
10
|
+
}): Promise<string[]> {
|
|
11
|
+
return await collectRouteCssFilesFromSources({
|
|
12
|
+
...options,
|
|
13
|
+
readSource: (file) => readFile(file, "utf8"),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function collectRouteCssFilesFromSources(options: {
|
|
18
|
+
appDir: string;
|
|
19
|
+
pageFile: string;
|
|
20
|
+
projectRoot: string;
|
|
21
|
+
readSource: (file: string) => Promise<string | undefined> | string | undefined;
|
|
10
22
|
}): Promise<string[]> {
|
|
11
23
|
const shellFiles = (await existingRouteShellCandidates(options.appDir, options.pageFile, isFile))
|
|
12
24
|
.map((candidate) => candidate.file);
|
|
@@ -15,7 +27,11 @@ export async function collectRouteCssFiles(options: {
|
|
|
15
27
|
const cssFiles: string[] = [];
|
|
16
28
|
|
|
17
29
|
for (const file of files) {
|
|
18
|
-
const source = await
|
|
30
|
+
const source = await options.readSource(file);
|
|
31
|
+
|
|
32
|
+
if (source === undefined) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
19
35
|
|
|
20
36
|
for (const reference of collectStaticImportReferences({ code: source, filename: file })) {
|
|
21
37
|
const cssFile = resolveCssImport({
|
package/src/serve.ts
CHANGED
|
@@ -82,7 +82,7 @@ interface BuiltRuntimeCacheEntry {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const builtRuntimeCache = new Map<string, BuiltRuntimeCacheEntry>();
|
|
85
|
-
const builtPublicAssetCache = new Map<string, BuiltPublicAsset
|
|
85
|
+
const builtPublicAssetCache = new Map<string, BuiltPublicAsset>();
|
|
86
86
|
|
|
87
87
|
interface BuiltPublicAsset {
|
|
88
88
|
bytes: Uint8Array;
|
|
@@ -681,10 +681,6 @@ async function readBuiltPublicAsset(
|
|
|
681
681
|
const cacheKey = `${outDir}\0${normalized}`;
|
|
682
682
|
const cached = builtPublicAssetCache.get(cacheKey);
|
|
683
683
|
|
|
684
|
-
if (cached === null) {
|
|
685
|
-
return undefined;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
684
|
if (cached !== undefined) {
|
|
689
685
|
return bytesResponse(cached.bytes, {
|
|
690
686
|
headers: cached.headers,
|
|
@@ -701,11 +697,20 @@ async function readBuiltPublicAsset(
|
|
|
701
697
|
|
|
702
698
|
return bytesResponse(bytes, { headers });
|
|
703
699
|
} catch {
|
|
704
|
-
builtPublicAssetCache.set(`${outDir}\0${normalized}`, null);
|
|
705
700
|
return undefined;
|
|
706
701
|
}
|
|
707
702
|
}
|
|
708
703
|
|
|
704
|
+
export const __readBuiltPublicAssetForTest = readBuiltPublicAsset;
|
|
705
|
+
|
|
706
|
+
export function __clearBuiltPublicAssetCacheForTest(): void {
|
|
707
|
+
builtPublicAssetCache.clear();
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
export function __getBuiltPublicAssetCacheSizeForTest(): number {
|
|
711
|
+
return builtPublicAssetCache.size;
|
|
712
|
+
}
|
|
713
|
+
|
|
709
714
|
async function readBuiltRuntime(options: {
|
|
710
715
|
immutable?: boolean | undefined;
|
|
711
716
|
outDir: string;
|
package/src/vite.ts
CHANGED
|
@@ -176,7 +176,7 @@ export function createAppRouterVitePlugin(options: AppRouterVitePluginOptions):
|
|
|
176
176
|
enforce: "pre",
|
|
177
177
|
name: "mreact-router",
|
|
178
178
|
config(userConfig) {
|
|
179
|
-
userVitePlugins = userConfig.plugins;
|
|
179
|
+
userVitePlugins = routeTransformUserVitePlugins(userConfig.plugins);
|
|
180
180
|
|
|
181
181
|
return {
|
|
182
182
|
optimizeDeps: {
|
|
@@ -197,7 +197,7 @@ export function createAppRouterVitePlugin(options: AppRouterVitePluginOptions):
|
|
|
197
197
|
return () => {
|
|
198
198
|
const middlewareOptions: AppRouterViteRuntimeMiddlewareOptions = {
|
|
199
199
|
...options,
|
|
200
|
-
navigationScanVitePlugins: userVitePlugins,
|
|
200
|
+
navigationScanVitePlugins: userVitePlugins ?? [],
|
|
201
201
|
viteDevServer: server,
|
|
202
202
|
vitePlugins: server.config.plugins,
|
|
203
203
|
};
|
|
@@ -370,6 +370,40 @@ function isCompatSourcePath(filename: string): boolean {
|
|
|
370
370
|
|
|
371
371
|
export const mreactRouter = createAppRouterVitePlugin;
|
|
372
372
|
|
|
373
|
+
function routeTransformUserVitePlugins(
|
|
374
|
+
pluginOptions: readonly PluginOption[] | undefined,
|
|
375
|
+
): PluginOption[] {
|
|
376
|
+
const plugins: PluginOption[] = [];
|
|
377
|
+
const visit = (option: PluginOption | null | false | undefined): void => {
|
|
378
|
+
if (option === false || option === null || option === undefined) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (Array.isArray(option)) {
|
|
383
|
+
for (const child of option) {
|
|
384
|
+
visit(child);
|
|
385
|
+
}
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (typeof option === "object" && "then" in option) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (typeof option === "object" && mreactRouterConfigKey in option) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
plugins.push(option);
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
for (const option of pluginOptions ?? []) {
|
|
401
|
+
visit(option);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return plugins;
|
|
405
|
+
}
|
|
406
|
+
|
|
373
407
|
export function mreactRouterConfigFromPlugins(
|
|
374
408
|
plugins: readonly unknown[],
|
|
375
409
|
): ResolvedAppRouterProject | undefined {
|
|
@@ -450,6 +484,7 @@ async function handleAppRouterViteRequest(
|
|
|
450
484
|
}
|
|
451
485
|
|
|
452
486
|
const request = nodeRequestToWebRequest(incoming, origin);
|
|
487
|
+
const routeTransformPlugins = options.navigationScanVitePlugins ?? options.vitePlugins;
|
|
453
488
|
|
|
454
489
|
await sendResponse(
|
|
455
490
|
outgoing,
|
|
@@ -464,11 +499,12 @@ async function handleAppRouterViteRequest(
|
|
|
464
499
|
navigationScripts: await devNavigationScripts(
|
|
465
500
|
project.routesDir,
|
|
466
501
|
options.clientRouteInferenceCache,
|
|
467
|
-
|
|
502
|
+
routeTransformPlugins,
|
|
468
503
|
),
|
|
469
504
|
request,
|
|
470
505
|
routeCache: options.routeCache,
|
|
471
506
|
serverActions: options.serverActions,
|
|
507
|
+
vitePlugins: routeTransformPlugins,
|
|
472
508
|
}),
|
|
473
509
|
);
|
|
474
510
|
} catch (error) {
|
|
@@ -501,7 +537,7 @@ export async function renderAppRouterClientAsset(
|
|
|
501
537
|
}
|
|
502
538
|
|
|
503
539
|
const code = await readFile(route.file, "utf8");
|
|
504
|
-
const clientSource = stripRouteClientOnlyExports(code);
|
|
540
|
+
const clientSource = stripRouteClientOnlyExports(code, route.file);
|
|
505
541
|
let references: Awaited<ReturnType<typeof collectClientRouteReferences>>;
|
|
506
542
|
|
|
507
543
|
try {
|
|
@@ -565,7 +601,7 @@ async function renderAppRouterClientRouteDevModule(
|
|
|
565
601
|
}
|
|
566
602
|
|
|
567
603
|
const code = await readFile(route.file, "utf8");
|
|
568
|
-
const clientSource = stripRouteClientOnlyExports(code);
|
|
604
|
+
const clientSource = stripRouteClientOnlyExports(code, route.file);
|
|
569
605
|
const references = await collectClientRouteReferences({
|
|
570
606
|
appDir,
|
|
571
607
|
code: clientSource,
|