@reckona/mreact-router 0.0.164 → 0.0.166
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions.d.ts +6 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +71 -8
- package/dist/actions.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +21 -8
- package/dist/build.js.map +1 -1
- package/dist/render.d.ts +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +42 -16
- package/dist/render.js.map +1 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +1 -0
- package/dist/serve.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +106 -8
- package/src/build.ts +29 -11
- package/src/render.ts +76 -25
- package/src/serve.ts +1 -0
package/src/actions.ts
CHANGED
|
@@ -43,6 +43,8 @@ const formFieldModuleId = "__mreact_module_id";
|
|
|
43
43
|
const formFieldExportName = "__mreact_export_name";
|
|
44
44
|
const formFieldNonce = "__mreact_action_nonce";
|
|
45
45
|
const formFieldActionToken = "__mreact_action_token";
|
|
46
|
+
const csrfTokenPlaceholder = "__mreact_action_csrf_placeholder__";
|
|
47
|
+
const actionNoncePlaceholder = "__mreact_action_nonce_placeholder__";
|
|
46
48
|
const actionTokenSecret = process.env.MREACT_SERVER_ACTION_SECRET ?? defaultActionTokenSecret();
|
|
47
49
|
// Bounded default replay store for form-action nonces. The previous
|
|
48
50
|
// implementation was an unbounded Set that grew with every successful
|
|
@@ -175,6 +177,7 @@ export interface PreparedRouteActions {
|
|
|
175
177
|
code: string;
|
|
176
178
|
csrfToken?: string;
|
|
177
179
|
diagnostics?: ServerActionInferenceDiagnostic[] | undefined;
|
|
180
|
+
htmlReplacements?: readonly (readonly [string, string])[] | undefined;
|
|
178
181
|
// True when the cookie should be (re)set on the response. False means
|
|
179
182
|
// the incoming request already carried a valid CSRF cookie that the
|
|
180
183
|
// render is reusing -- skipping Set-Cookie avoids cookie thrash across
|
|
@@ -185,6 +188,7 @@ export interface PreparedRouteActions {
|
|
|
185
188
|
|
|
186
189
|
interface ActionReference {
|
|
187
190
|
exportName: string;
|
|
191
|
+
expression?: string | undefined;
|
|
188
192
|
inferred: boolean;
|
|
189
193
|
moduleId: string;
|
|
190
194
|
}
|
|
@@ -210,6 +214,7 @@ export async function prepareRouteServerActions(options: {
|
|
|
210
214
|
appDir: string;
|
|
211
215
|
code: string;
|
|
212
216
|
formActionReferences?: readonly PreparedFormActionReference[] | undefined;
|
|
217
|
+
placeholders?: boolean | undefined;
|
|
213
218
|
pageFile: string;
|
|
214
219
|
request?: Request | undefined;
|
|
215
220
|
}): Promise<PreparedRouteActions> {
|
|
@@ -248,10 +253,13 @@ export async function prepareRouteServerActions(options: {
|
|
|
248
253
|
const csrfToken = existingToken ?? randomUUID();
|
|
249
254
|
const csrfTokenIsNew = existingToken === undefined;
|
|
250
255
|
const actionNonce = randomUUID();
|
|
256
|
+
const usePlaceholders =
|
|
257
|
+
options.placeholders === true && canUseStableFormActionPlaceholders(references);
|
|
251
258
|
const lowered = lowerFormActions({
|
|
252
259
|
actionNonce,
|
|
253
260
|
code: options.code,
|
|
254
261
|
csrfToken,
|
|
262
|
+
placeholders: usePlaceholders,
|
|
255
263
|
references,
|
|
256
264
|
});
|
|
257
265
|
|
|
@@ -271,9 +279,45 @@ export async function prepareRouteServerActions(options: {
|
|
|
271
279
|
csrfTokenIsNew,
|
|
272
280
|
diagnostics,
|
|
273
281
|
hasFormActions: true,
|
|
282
|
+
...(usePlaceholders
|
|
283
|
+
? { htmlReplacements: formActionHtmlReplacements({ actionNonce, csrfToken, references }) }
|
|
284
|
+
: {}),
|
|
274
285
|
};
|
|
275
286
|
}
|
|
276
287
|
|
|
288
|
+
export function prepareRouteServerActionPlaceholders(options: {
|
|
289
|
+
code: string;
|
|
290
|
+
formActionReferences: readonly PreparedFormActionReference[];
|
|
291
|
+
}): string {
|
|
292
|
+
const references = formActionReferenceMap(options.code, options.formActionReferences);
|
|
293
|
+
|
|
294
|
+
if (references.size === 0) {
|
|
295
|
+
return options.code;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!canUseStableFormActionPlaceholders(references)) {
|
|
299
|
+
return options.code;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return lowerFormActions({
|
|
303
|
+
actionNonce: actionNoncePlaceholder,
|
|
304
|
+
code: options.code,
|
|
305
|
+
csrfToken: csrfTokenPlaceholder,
|
|
306
|
+
placeholders: true,
|
|
307
|
+
references,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function canUseStableFormActionPlaceholders(
|
|
312
|
+
references: ReadonlyMap<string, ActionReference>,
|
|
313
|
+
): boolean {
|
|
314
|
+
return [...references.values()].every(
|
|
315
|
+
(reference) =>
|
|
316
|
+
reference.expression === reference.exportName &&
|
|
317
|
+
/^[A-Za-z_$][\w$]*$/u.test(reference.expression),
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
277
321
|
function formActionReferenceMap(
|
|
278
322
|
code: string,
|
|
279
323
|
references: readonly PreparedFormActionReference[],
|
|
@@ -287,6 +331,7 @@ function formActionReferenceMap(
|
|
|
287
331
|
formActionOccurrenceKey(reference),
|
|
288
332
|
{
|
|
289
333
|
exportName: reference.exportName,
|
|
334
|
+
expression: reference.expression,
|
|
290
335
|
inferred: reference.inferred,
|
|
291
336
|
moduleId: reference.moduleId,
|
|
292
337
|
},
|
|
@@ -820,6 +865,7 @@ function lowerFormActions(options: {
|
|
|
820
865
|
actionNonce: string;
|
|
821
866
|
code: string;
|
|
822
867
|
csrfToken: string;
|
|
868
|
+
placeholders?: boolean | undefined;
|
|
823
869
|
references: Map<string, ActionReference>;
|
|
824
870
|
}): string {
|
|
825
871
|
let code = options.code;
|
|
@@ -837,6 +883,7 @@ function lowerFormActions(options: {
|
|
|
837
883
|
actionNonce: options.actionNonce,
|
|
838
884
|
csrfToken: options.csrfToken,
|
|
839
885
|
opening,
|
|
886
|
+
placeholders: options.placeholders === true,
|
|
840
887
|
reference,
|
|
841
888
|
referenceName: formReference.expression,
|
|
842
889
|
});
|
|
@@ -851,6 +898,39 @@ function lowerFormActions(options: {
|
|
|
851
898
|
return code;
|
|
852
899
|
}
|
|
853
900
|
|
|
901
|
+
function formActionHtmlReplacements(options: {
|
|
902
|
+
actionNonce: string;
|
|
903
|
+
csrfToken: string;
|
|
904
|
+
references: ReadonlyMap<string, ActionReference>;
|
|
905
|
+
}): readonly (readonly [string, string])[] {
|
|
906
|
+
const replacements: Array<readonly [string, string]> = [
|
|
907
|
+
[csrfTokenPlaceholder, options.csrfToken],
|
|
908
|
+
[actionNoncePlaceholder, options.actionNonce],
|
|
909
|
+
];
|
|
910
|
+
const seenTokens = new Set<string>();
|
|
911
|
+
|
|
912
|
+
for (const reference of options.references.values()) {
|
|
913
|
+
const placeholder = actionTokenPlaceholder(reference);
|
|
914
|
+
|
|
915
|
+
if (seenTokens.has(placeholder)) {
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
seenTokens.add(placeholder);
|
|
920
|
+
replacements.push([
|
|
921
|
+
placeholder,
|
|
922
|
+
formActionToken({
|
|
923
|
+
csrfToken: options.csrfToken,
|
|
924
|
+
exportName: reference.exportName,
|
|
925
|
+
moduleId: reference.moduleId,
|
|
926
|
+
nonce: options.actionNonce,
|
|
927
|
+
}),
|
|
928
|
+
]);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
return replacements;
|
|
932
|
+
}
|
|
933
|
+
|
|
854
934
|
function formActionOccurrenceKey(reference: {
|
|
855
935
|
end: number;
|
|
856
936
|
expression: string;
|
|
@@ -875,6 +955,7 @@ function lowerFormActionOpening(options: {
|
|
|
875
955
|
actionNonce: string;
|
|
876
956
|
csrfToken: string;
|
|
877
957
|
opening: string;
|
|
958
|
+
placeholders: boolean;
|
|
878
959
|
reference: ActionReference;
|
|
879
960
|
referenceName: string;
|
|
880
961
|
}): string {
|
|
@@ -897,22 +978,39 @@ function lowerFormActionOpening(options: {
|
|
|
897
978
|
const hidden = [
|
|
898
979
|
hiddenInput(formFieldModuleId, options.reference.moduleId),
|
|
899
980
|
hiddenInput(formFieldExportName, options.reference.exportName),
|
|
900
|
-
hiddenInput(
|
|
901
|
-
|
|
981
|
+
hiddenInput(
|
|
982
|
+
formCsrfFieldName,
|
|
983
|
+
options.placeholders ? csrfTokenPlaceholder : options.csrfToken,
|
|
984
|
+
),
|
|
985
|
+
hiddenInput(
|
|
986
|
+
formFieldNonce,
|
|
987
|
+
options.placeholders ? actionNoncePlaceholder : options.actionNonce,
|
|
988
|
+
),
|
|
902
989
|
hiddenInput(
|
|
903
990
|
formFieldActionToken,
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
991
|
+
options.placeholders
|
|
992
|
+
? actionTokenPlaceholder(options.reference)
|
|
993
|
+
: formActionToken({
|
|
994
|
+
csrfToken: options.csrfToken,
|
|
995
|
+
exportName: options.reference.exportName,
|
|
996
|
+
moduleId: options.reference.moduleId,
|
|
997
|
+
nonce: options.actionNonce,
|
|
998
|
+
}),
|
|
910
999
|
),
|
|
911
1000
|
].join("");
|
|
912
1001
|
|
|
913
1002
|
return `<form${attrs} method="post" action="/_mreact/actions">${hidden}`;
|
|
914
1003
|
}
|
|
915
1004
|
|
|
1005
|
+
function actionTokenPlaceholder(reference: ActionReference): string {
|
|
1006
|
+
return `__mreact_action_token_${createHash("sha256")
|
|
1007
|
+
.update(reference.moduleId)
|
|
1008
|
+
.update("\0")
|
|
1009
|
+
.update(reference.exportName)
|
|
1010
|
+
.digest("hex")
|
|
1011
|
+
.slice(0, 16)}__`;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
916
1014
|
function escapeRegExp(value: string): string {
|
|
917
1015
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
918
1016
|
}
|
package/src/build.ts
CHANGED
|
@@ -92,6 +92,7 @@ import { collectRouteCssFilesFromSources, collectSpecialBoundaryFiles } from "./
|
|
|
92
92
|
import { existingRouteShellCandidates } from "./route-shells.js";
|
|
93
93
|
import { sourceModuleCandidates } from "./source-modules.js";
|
|
94
94
|
import { collectBuildInferredServerActions } from "./server-action-inference.js";
|
|
95
|
+
import { prepareRouteServerActionPlaceholders } from "./actions.js";
|
|
95
96
|
import { viteDefineCacheKey, vitePluginsCacheKey } from "./vite-plugin-cache-key.js";
|
|
96
97
|
import { workspacePackageFile } from "./workspace-packages.js";
|
|
97
98
|
import { prependTailwindSourceDirectives } from "./tailwind-source.js";
|
|
@@ -511,15 +512,9 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
511
512
|
}),
|
|
512
513
|
),
|
|
513
514
|
]);
|
|
514
|
-
const actionRenderBundleExcludedFiles = new Set(
|
|
515
|
-
[...serverActionManifest.routeReferences.entries()]
|
|
516
|
-
.filter(([, references]) => references.length > 0)
|
|
517
|
-
.map(([file]) => file),
|
|
518
|
-
);
|
|
519
515
|
const { artifacts: serverModules, sharedChunks: serverModuleSharedChunks } = await (
|
|
520
516
|
shouldTrackBuildPhases === false
|
|
521
517
|
? buildServerModuleArtifacts({
|
|
522
|
-
actionRenderBundleExcludedFiles,
|
|
523
518
|
bundleRequestRuntimePackages: shouldBuildAwsLambda,
|
|
524
519
|
bundleCache: new Map(),
|
|
525
520
|
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
@@ -529,13 +524,13 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
529
524
|
project,
|
|
530
525
|
projectRoot: project.projectRoot,
|
|
531
526
|
routes,
|
|
527
|
+
serverActionReferencesByFile: serverActionManifest.routeReferences,
|
|
532
528
|
sourceAnalysis,
|
|
533
529
|
serverTransformCache,
|
|
534
530
|
vitePlugins,
|
|
535
531
|
})
|
|
536
532
|
: timeBuildPhase(timingSink, progressSink, "serverModules", () =>
|
|
537
533
|
buildServerModuleArtifacts({
|
|
538
|
-
actionRenderBundleExcludedFiles,
|
|
539
534
|
bundleRequestRuntimePackages: shouldBuildAwsLambda,
|
|
540
535
|
bundleCache: new Map(),
|
|
541
536
|
clientRouteInferenceCache: serverClientRouteInferenceCache,
|
|
@@ -545,6 +540,7 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
|
|
|
545
540
|
project,
|
|
546
541
|
projectRoot: project.projectRoot,
|
|
547
542
|
routes,
|
|
543
|
+
serverActionReferencesByFile: serverActionManifest.routeReferences,
|
|
548
544
|
sourceAnalysis,
|
|
549
545
|
serverTransformCache,
|
|
550
546
|
vitePlugins,
|
|
@@ -990,6 +986,16 @@ function buildSourceAnalysisForFile(
|
|
|
990
986
|
return sourceAnalysis.byFile.get(relative(projectRoot, file).split(sep).join("/"));
|
|
991
987
|
}
|
|
992
988
|
|
|
989
|
+
function canUseBuildServerActionPlaceholders(
|
|
990
|
+
references: readonly BuiltServerActionExpressionReference[],
|
|
991
|
+
): boolean {
|
|
992
|
+
return references.every(
|
|
993
|
+
(reference) =>
|
|
994
|
+
reference.expression === reference.exportName &&
|
|
995
|
+
/^[A-Za-z_$][\w$]*$/u.test(reference.expression),
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
|
|
993
999
|
async function buildPublicAssetManifest(
|
|
994
1000
|
project: ResolvedAppRouterProject,
|
|
995
1001
|
clientDir: string,
|
|
@@ -2062,7 +2068,6 @@ function routePathFromParams(route: AppRoute, params: StaticParams): string {
|
|
|
2062
2068
|
}
|
|
2063
2069
|
|
|
2064
2070
|
async function buildServerModuleArtifacts(options: {
|
|
2065
|
-
actionRenderBundleExcludedFiles?: ReadonlySet<string> | undefined;
|
|
2066
2071
|
bundleRequestRuntimePackages: boolean;
|
|
2067
2072
|
bundleCache: Map<string, Promise<RouterBundleOutput>>;
|
|
2068
2073
|
cacheDir?: string | undefined;
|
|
@@ -2073,6 +2078,7 @@ async function buildServerModuleArtifacts(options: {
|
|
|
2073
2078
|
project: ResolvedAppRouterProject;
|
|
2074
2079
|
projectRoot: string;
|
|
2075
2080
|
routes: readonly AppRoute[];
|
|
2081
|
+
serverActionReferencesByFile: ReadonlyMap<string, readonly BuiltServerActionExpressionReference[]>;
|
|
2076
2082
|
sourceAnalysis: BuildSourceAnalysisScope;
|
|
2077
2083
|
serverTransformCache: ServerTransformCache;
|
|
2078
2084
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
@@ -2193,6 +2199,14 @@ async function buildServerModuleArtifacts(options: {
|
|
|
2193
2199
|
const route = routeByFile.get(file);
|
|
2194
2200
|
const routeAnalysis = options.sourceAnalysis.byRouteFile.get(file);
|
|
2195
2201
|
const artifact: BuiltServerModuleArtifact = {};
|
|
2202
|
+
const routeActionReferences = options.serverActionReferencesByFile.get(file) ?? [];
|
|
2203
|
+
const renderSource =
|
|
2204
|
+
routeActionReferences.length === 0
|
|
2205
|
+
? source
|
|
2206
|
+
: prepareRouteServerActionPlaceholders({
|
|
2207
|
+
code: source,
|
|
2208
|
+
formActionReferences: routeActionReferences,
|
|
2209
|
+
});
|
|
2196
2210
|
|
|
2197
2211
|
if (
|
|
2198
2212
|
requestArtifactFiles.has(file) ||
|
|
@@ -2282,8 +2296,11 @@ async function buildServerModuleArtifacts(options: {
|
|
|
2282
2296
|
? (["stream", "string"] as const)
|
|
2283
2297
|
: (["string"] as const);
|
|
2284
2298
|
const code =
|
|
2285
|
-
routeAnalysis
|
|
2286
|
-
|
|
2299
|
+
routeActionReferences.length === 0 && routeAnalysis !== undefined
|
|
2300
|
+
? routeAnalysis.routeCode
|
|
2301
|
+
: route === undefined
|
|
2302
|
+
? renderSource
|
|
2303
|
+
: stripRouteBuildExports(renderSource, absoluteFile);
|
|
2287
2304
|
const clientInference =
|
|
2288
2305
|
routeAnalysis === undefined
|
|
2289
2306
|
? await inferClientRouteModule({
|
|
@@ -2367,7 +2384,8 @@ async function buildServerModuleArtifacts(options: {
|
|
|
2367
2384
|
|
|
2368
2385
|
const shouldWriteRenderBundle =
|
|
2369
2386
|
options.prebundleServerComponents &&
|
|
2370
|
-
|
|
2387
|
+
(routeActionReferences.length === 0 ||
|
|
2388
|
+
canUseBuildServerActionPlaceholders(routeActionReferences));
|
|
2371
2389
|
const bundleCode = shouldWriteRenderBundle
|
|
2372
2390
|
? await buildServerComponentBundleArtifactCode({
|
|
2373
2391
|
clientRouteInferenceCache: options.clientRouteInferenceCache,
|
package/src/render.ts
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
type AppRouterServerActionOptions,
|
|
48
48
|
type PreparedFormActionReference,
|
|
49
49
|
dispatchServerActionRequest,
|
|
50
|
+
prepareRouteServerActionPlaceholders,
|
|
50
51
|
prepareRouteServerActions,
|
|
51
52
|
} from "./actions.js";
|
|
52
53
|
import { serverActionCookie } from "./csrf.js";
|
|
@@ -76,10 +77,12 @@ import {
|
|
|
76
77
|
hasLoaderExport,
|
|
77
78
|
isStreamRouteSource,
|
|
78
79
|
routeClosureMayUseAwaitBoundary,
|
|
80
|
+
stripRouteBuildExports,
|
|
81
|
+
stripRouteModuleExports,
|
|
79
82
|
stripRouteClientOnlyExports,
|
|
83
|
+
stripRouteClientSource,
|
|
80
84
|
stripRouteLoaderOnlyExports,
|
|
81
85
|
stripRouteMetadataOnlyExports,
|
|
82
|
-
stripRouteModuleExports,
|
|
83
86
|
} from "./route-source.js";
|
|
84
87
|
import { emitRouterLog, logDurationMs, logNow, type AppRouterLogger } from "./logger.js";
|
|
85
88
|
import {
|
|
@@ -231,6 +234,10 @@ export async function preloadBuiltRequestModules(options: {
|
|
|
231
234
|
serverModules?: ReadonlyMap<string, BuiltServerModuleArtifact> | undefined;
|
|
232
235
|
serverModuleCacheVersion: string;
|
|
233
236
|
serverSourceFiles: ReadonlyMap<string, string>;
|
|
237
|
+
serverActionReferencesByFile?: ReadonlyMap<
|
|
238
|
+
string,
|
|
239
|
+
readonly PreparedFormActionReference[]
|
|
240
|
+
> | undefined;
|
|
234
241
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
235
242
|
}): Promise<void> {
|
|
236
243
|
const clientRouteInferenceCache = createClientRouteInferenceCache();
|
|
@@ -276,21 +283,31 @@ export async function preloadBuiltRequestModules(options: {
|
|
|
276
283
|
}
|
|
277
284
|
|
|
278
285
|
if (options.includeRenderModules !== false) {
|
|
286
|
+
const actionReferences = options.serverActionReferencesByFile?.get(route.file) ?? [];
|
|
287
|
+
const analysisCode =
|
|
288
|
+
actionReferences.length === 0
|
|
289
|
+
? code
|
|
290
|
+
: prepareRouteServerActionPlaceholders({
|
|
291
|
+
code,
|
|
292
|
+
formActionReferences: actionReferences,
|
|
293
|
+
});
|
|
279
294
|
const analysis = await analyzeRouteSource({
|
|
280
295
|
appDir: options.appDir,
|
|
281
296
|
artifact: options.serverModules?.get(route.file)?.analysis,
|
|
282
|
-
code,
|
|
297
|
+
buildRenderCode: analysisCode !== code,
|
|
298
|
+
code: analysisCode,
|
|
283
299
|
filename: route.file,
|
|
284
300
|
routePath: route.path,
|
|
285
301
|
clientRouteInferenceCache,
|
|
286
|
-
serverModuleCacheVersion:
|
|
302
|
+
serverModuleCacheVersion:
|
|
303
|
+
actionReferences.length === 0 ? options.serverModuleCacheVersion : undefined,
|
|
287
304
|
serverSourceFiles: options.serverSourceFiles,
|
|
288
305
|
vitePlugins: options.vitePlugins,
|
|
289
306
|
});
|
|
290
307
|
await preloadBuiltPageRouteModules({
|
|
291
308
|
...options,
|
|
292
309
|
analysis,
|
|
293
|
-
code,
|
|
310
|
+
code: analysisCode,
|
|
294
311
|
file: route.file,
|
|
295
312
|
});
|
|
296
313
|
}
|
|
@@ -1017,6 +1034,7 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
1017
1034
|
appDir: options.appDir,
|
|
1018
1035
|
code: originalCode,
|
|
1019
1036
|
formActionReferences: options.serverActionReferencesByFile?.get(matched.route.file),
|
|
1037
|
+
placeholders: true,
|
|
1020
1038
|
pageFile: matched.route.file,
|
|
1021
1039
|
request: options.request,
|
|
1022
1040
|
});
|
|
@@ -1031,6 +1049,7 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
1031
1049
|
? originalAnalysis
|
|
1032
1050
|
: await analyzeRouteSource({
|
|
1033
1051
|
appDir: options.appDir,
|
|
1052
|
+
buildRenderCode: preparedActions.htmlReplacements !== undefined,
|
|
1034
1053
|
code,
|
|
1035
1054
|
filename: matched.route.file,
|
|
1036
1055
|
routePath: matched.route.path,
|
|
@@ -1049,7 +1068,7 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
1049
1068
|
const dataPromise = routeAnalysis.hasLoader
|
|
1050
1069
|
? loadRouteDataWithInstrumentation({
|
|
1051
1070
|
appDir: options.appDir,
|
|
1052
|
-
code,
|
|
1071
|
+
code: originalCode,
|
|
1053
1072
|
context: {
|
|
1054
1073
|
env: options.env,
|
|
1055
1074
|
params: matched.params,
|
|
@@ -1226,13 +1245,16 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
1226
1245
|
html = injectQueryState(html, dehydrate(queryClient));
|
|
1227
1246
|
const response = withOptionalActionCookie(
|
|
1228
1247
|
htmlResponse(
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1248
|
+
applyActionHtmlReplacements(
|
|
1249
|
+
`<!DOCTYPE html>${clientNavigationHeadTags({
|
|
1250
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
1251
|
+
currentStyleSheets: clientStyleSheets,
|
|
1252
|
+
currentScript: clientRoute ? clientScript : undefined,
|
|
1253
|
+
currentNavigationScript: clientRoute ? undefined : navigationScript,
|
|
1254
|
+
routeScripts: options.clientScripts,
|
|
1255
|
+
})}${html}`,
|
|
1256
|
+
preparedActions.htmlReplacements,
|
|
1257
|
+
),
|
|
1236
1258
|
{
|
|
1237
1259
|
headers: responseHeadersForMetadata(metadata, options.request, {
|
|
1238
1260
|
"x-mreact-stream": "1",
|
|
@@ -1249,7 +1271,7 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
1249
1271
|
let streamData: unknown;
|
|
1250
1272
|
let streamDataPromise = dataPromise ?? Promise.resolve(undefined);
|
|
1251
1273
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1252
|
-
if (loadingFile === undefined || routeLoaderMayReturnControlResponse(
|
|
1274
|
+
if (loadingFile === undefined || routeLoaderMayReturnControlResponse(originalCode)) {
|
|
1253
1275
|
try {
|
|
1254
1276
|
streamData = dataPromise === undefined ? undefined : await dataPromise;
|
|
1255
1277
|
} finally {
|
|
@@ -1508,13 +1530,16 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
1508
1530
|
|
|
1509
1531
|
const response = withOptionalActionCookie(
|
|
1510
1532
|
htmlResponse(
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1533
|
+
applyActionHtmlReplacements(
|
|
1534
|
+
`<!DOCTYPE html>${clientNavigationHeadTags({
|
|
1535
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
1536
|
+
currentStyleSheets: clientStyleSheets,
|
|
1537
|
+
currentScript: clientRoute ? clientScript : undefined,
|
|
1538
|
+
currentNavigationScript: clientRoute ? undefined : navigationScript,
|
|
1539
|
+
routeScripts: options.clientScripts,
|
|
1540
|
+
})}${html}`,
|
|
1541
|
+
preparedActions.htmlReplacements,
|
|
1542
|
+
),
|
|
1518
1543
|
{
|
|
1519
1544
|
headers: responseHeadersForMetadata(metadata, options.request),
|
|
1520
1545
|
},
|
|
@@ -1632,6 +1657,23 @@ function withOptionalActionCookie(
|
|
|
1632
1657
|
return response;
|
|
1633
1658
|
}
|
|
1634
1659
|
|
|
1660
|
+
function applyActionHtmlReplacements(
|
|
1661
|
+
html: string,
|
|
1662
|
+
replacements: readonly (readonly [string, string])[] | undefined,
|
|
1663
|
+
): string {
|
|
1664
|
+
if (replacements === undefined || replacements.length === 0) {
|
|
1665
|
+
return html;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
let next = html;
|
|
1669
|
+
|
|
1670
|
+
for (const [placeholder, value] of replacements) {
|
|
1671
|
+
next = next.replaceAll(placeholder, escapeHtmlAttribute(value));
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
return next;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1635
1677
|
function modulePreloadTags(script: string | undefined, assetBaseUrl: string | undefined): string {
|
|
1636
1678
|
return script === undefined
|
|
1637
1679
|
? ""
|
|
@@ -2768,6 +2810,7 @@ function warnProductionDynamicServerTransform(
|
|
|
2768
2810
|
async function analyzeRouteSource(options: {
|
|
2769
2811
|
appDir: string;
|
|
2770
2812
|
artifact?: BuiltRouteSourceAnalysisSummary | undefined;
|
|
2813
|
+
buildRenderCode?: boolean | undefined;
|
|
2771
2814
|
clientRouteInferenceCache: ClientRouteInferenceCache;
|
|
2772
2815
|
code: string;
|
|
2773
2816
|
define?: UserConfig["define"] | undefined;
|
|
@@ -2791,7 +2834,7 @@ async function analyzeRouteSource(options: {
|
|
|
2791
2834
|
return analysis;
|
|
2792
2835
|
}
|
|
2793
2836
|
|
|
2794
|
-
const cacheKey = `${options.serverModuleCacheVersion ?? "dev"}\0${options.filename}\0${sourceHash}\0${viteDefineCacheKey(options.define)}\0${vitePluginsCacheKey(options.vitePlugins)}`;
|
|
2837
|
+
const cacheKey = `${options.serverModuleCacheVersion ?? "dev"}\0${options.filename}\0${sourceHash}\0${options.buildRenderCode === true ? "build-render" : "render"}\0${viteDefineCacheKey(options.define)}\0${vitePluginsCacheKey(options.vitePlugins)}`;
|
|
2795
2838
|
const cached = readRouterRuntimeCacheEntry(
|
|
2796
2839
|
routeSourceAnalysisCache,
|
|
2797
2840
|
cacheKey,
|
|
@@ -2838,6 +2881,7 @@ function routeSourceAnalysisFromArtifact(
|
|
|
2838
2881
|
|
|
2839
2882
|
async function analyzeRouteSourceUncached(options: {
|
|
2840
2883
|
appDir: string;
|
|
2884
|
+
buildRenderCode?: boolean | undefined;
|
|
2841
2885
|
clientRouteInferenceCache: ClientRouteInferenceCache;
|
|
2842
2886
|
code: string;
|
|
2843
2887
|
define?: UserConfig["define"] | undefined;
|
|
@@ -2846,11 +2890,14 @@ async function analyzeRouteSourceUncached(options: {
|
|
|
2846
2890
|
serverSourceFiles?: ReadonlyMap<string, string> | undefined;
|
|
2847
2891
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
2848
2892
|
}): Promise<RouteSourceAnalysis> {
|
|
2849
|
-
const routeCode =
|
|
2893
|
+
const routeCode =
|
|
2894
|
+
options.buildRenderCode === true
|
|
2895
|
+
? stripRouteBuildExports(options.code, options.filename)
|
|
2896
|
+
: stripRouteModuleExports(options.code, options.filename);
|
|
2850
2897
|
const clientInference = await inferClientRouteModule({
|
|
2851
2898
|
appDir: options.appDir,
|
|
2852
2899
|
cache: options.clientRouteInferenceCache,
|
|
2853
|
-
code:
|
|
2900
|
+
code: stripRouteClientSource({ code: options.code, filename: options.filename }),
|
|
2854
2901
|
filename: options.filename,
|
|
2855
2902
|
routePath: options.routePath,
|
|
2856
2903
|
vitePlugins: options.vitePlugins,
|
|
@@ -2956,6 +3003,7 @@ async function loadServerModule(
|
|
|
2956
3003
|
const artifact = serverModules?.get(sourcefile)?.string;
|
|
2957
3004
|
const codeHash = memoizedHashText(code);
|
|
2958
3005
|
const prebuiltCode = prebuiltServerComponentModuleCode(artifact, code, codeHash);
|
|
3006
|
+
const usesPrebuiltCompiledCode = artifact?.code === code;
|
|
2959
3007
|
if (
|
|
2960
3008
|
artifact !== undefined &&
|
|
2961
3009
|
prebuiltServerModuleOutputMatches(artifact, code, codeHash) &&
|
|
@@ -2990,7 +3038,7 @@ async function loadServerModule(
|
|
|
2990
3038
|
label: "Server component",
|
|
2991
3039
|
}),
|
|
2992
3040
|
],
|
|
2993
|
-
...(prebuiltCode === undefined
|
|
3041
|
+
...(prebuiltCode === undefined && !usesPrebuiltCompiledCode
|
|
2994
3042
|
? {
|
|
2995
3043
|
resolveDir: dirname(sourcefile),
|
|
2996
3044
|
serverSourceTransform: {
|
|
@@ -3002,6 +3050,7 @@ async function loadServerModule(
|
|
|
3002
3050
|
},
|
|
3003
3051
|
}
|
|
3004
3052
|
: {}),
|
|
3053
|
+
...(usesPrebuiltCompiledCode ? { resolveDir: dirname(sourcefile) } : {}),
|
|
3005
3054
|
sourcefile,
|
|
3006
3055
|
vitePlugins,
|
|
3007
3056
|
});
|
|
@@ -3611,6 +3660,7 @@ async function loadServerStreamModule(
|
|
|
3611
3660
|
const artifactCode = serverModules?.get(sourcefile)?.stream;
|
|
3612
3661
|
const codeHash = memoizedHashText(code);
|
|
3613
3662
|
const prebuiltCode = prebuiltServerComponentModuleCode(artifactCode, code, codeHash, outputOptions);
|
|
3663
|
+
const usesPrebuiltCompiledCode = artifactCode?.code === code;
|
|
3614
3664
|
if (
|
|
3615
3665
|
artifactCode !== undefined &&
|
|
3616
3666
|
prebuiltServerModuleOutputOptionsMatch(artifactCode, outputOptions) &&
|
|
@@ -3646,7 +3696,7 @@ async function loadServerStreamModule(
|
|
|
3646
3696
|
label: "Server stream component",
|
|
3647
3697
|
}),
|
|
3648
3698
|
],
|
|
3649
|
-
...(prebuiltCode === undefined
|
|
3699
|
+
...(prebuiltCode === undefined && !usesPrebuiltCompiledCode
|
|
3650
3700
|
? {
|
|
3651
3701
|
resolveDir: dirname(sourcefile),
|
|
3652
3702
|
serverSourceTransform: {
|
|
@@ -3658,6 +3708,7 @@ async function loadServerStreamModule(
|
|
|
3658
3708
|
},
|
|
3659
3709
|
}
|
|
3660
3710
|
: {}),
|
|
3711
|
+
...(usesPrebuiltCompiledCode ? { resolveDir: dirname(sourcefile) } : {}),
|
|
3661
3712
|
sourcefile,
|
|
3662
3713
|
vitePlugins,
|
|
3663
3714
|
});
|
package/src/serve.ts
CHANGED
|
@@ -358,6 +358,7 @@ async function preloadBuiltAppRuntimeWithRuntime(options: {
|
|
|
358
358
|
serverModules: options.runtime.serverModules,
|
|
359
359
|
serverModuleCacheVersion: options.runtime.serverModuleCacheVersion,
|
|
360
360
|
serverSourceFiles: options.runtime.serverSourceFiles,
|
|
361
|
+
serverActionReferencesByFile: options.runtime.serverActionReferencesByFile,
|
|
361
362
|
includeRenderModules: plan.includeRenderModules,
|
|
362
363
|
});
|
|
363
364
|
}
|