@reckona/mreact-router 0.0.131 → 0.0.134
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/adapters/cloudflare.js +1 -1
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/static.js +5 -1
- package/dist/adapters/static.js.map +1 -1
- package/dist/build.d.ts +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +136 -41
- package/dist/build.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +188 -6
- package/dist/client.js.map +1 -1
- package/dist/http.d.ts +1 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +7 -1
- package/dist/http.js.map +1 -1
- package/dist/module-runner.d.ts +4 -1
- package/dist/module-runner.d.ts.map +1 -1
- package/dist/module-runner.js +3 -2
- package/dist/module-runner.js.map +1 -1
- package/dist/render.d.ts +6 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +84 -46
- package/dist/render.js.map +1 -1
- package/dist/routes.js +1 -1
- package/dist/routes.js.map +1 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +110 -17
- package/dist/serve.js.map +1 -1
- package/dist/vite-plugin-cache-key.d.ts +2 -1
- package/dist/vite-plugin-cache-key.d.ts.map +1 -1
- package/dist/vite-plugin-cache-key.js +9 -0
- package/dist/vite-plugin-cache-key.js.map +1 -1
- package/dist/vite.d.ts +2 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +2 -0
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/adapters/cloudflare.ts +1 -1
- package/src/adapters/static.ts +7 -1
- package/src/build.ts +201 -53
- package/src/client.ts +329 -8
- package/src/http.ts +9 -1
- package/src/module-runner.ts +8 -2
- package/src/render.ts +117 -21
- package/src/routes.ts +1 -1
- package/src/serve.ts +167 -18
- package/src/vite-plugin-cache-key.ts +12 -1
- package/src/vite.ts +4 -0
package/src/routes.ts
CHANGED
|
@@ -262,7 +262,7 @@ async function collectRouteFiles(directory: string, rootDirectory = directory):
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
function shouldSkipRouteScanDirectory(name: string): boolean {
|
|
265
|
-
return name === ".vite" || name === "node_modules";
|
|
265
|
+
return name === ".vite" || name === "__tests__" || name === "node_modules";
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
function appFileConventionForRelativeFile(
|
package/src/serve.ts
CHANGED
|
@@ -21,7 +21,13 @@ import {
|
|
|
21
21
|
type RenderAppRequestOptions,
|
|
22
22
|
} from "./render.js";
|
|
23
23
|
import type { RouterInstrumentation } from "./trace.js";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
bytesResponse,
|
|
26
|
+
htmlResponse,
|
|
27
|
+
nodeRequestToWebRequest,
|
|
28
|
+
rawNodeRequestUrl,
|
|
29
|
+
sendResponse,
|
|
30
|
+
} from "./http.js";
|
|
25
31
|
import {
|
|
26
32
|
emitRouterLog,
|
|
27
33
|
logDurationMs,
|
|
@@ -40,6 +46,7 @@ interface BuiltRuntime {
|
|
|
40
46
|
appDir: string;
|
|
41
47
|
allowedSourceDirs: readonly string[];
|
|
42
48
|
assetBaseUrl?: string | undefined;
|
|
49
|
+
clientAssetPaths: ReadonlySet<string>;
|
|
43
50
|
clientScripts: ReadonlyMap<string, string>;
|
|
44
51
|
clientStylesByFile: ReadonlyMap<string, readonly string[]>;
|
|
45
52
|
clientStyles: ReadonlyMap<string, readonly string[]>;
|
|
@@ -373,8 +380,14 @@ async function renderBuiltAppRequestWithRuntime(
|
|
|
373
380
|
const url = new URL(options.request.url);
|
|
374
381
|
const timing = createBuiltRenderTiming(options.logger);
|
|
375
382
|
|
|
376
|
-
|
|
377
|
-
|
|
383
|
+
const clientAssetPathname = builtClientAssetPathname(options.request, url);
|
|
384
|
+
|
|
385
|
+
if (clientAssetPathname !== undefined) {
|
|
386
|
+
return readBuiltClientAsset(
|
|
387
|
+
options.outDir,
|
|
388
|
+
clientAssetPathname,
|
|
389
|
+
options.runtime.clientAssetPaths,
|
|
390
|
+
);
|
|
378
391
|
}
|
|
379
392
|
|
|
380
393
|
if (options.request.method === "GET" || options.request.method === "HEAD") {
|
|
@@ -650,12 +663,15 @@ export async function startServer(
|
|
|
650
663
|
};
|
|
651
664
|
}
|
|
652
665
|
|
|
653
|
-
async function readBuiltClientAsset(
|
|
666
|
+
async function readBuiltClientAsset(
|
|
667
|
+
outDir: string,
|
|
668
|
+
pathname: string,
|
|
669
|
+
allowedPaths: ReadonlySet<string>,
|
|
670
|
+
): Promise<Response> {
|
|
654
671
|
const clientPrefix = "/_mreact/client/";
|
|
655
|
-
const
|
|
656
|
-
const normalized = normalize(relativePath);
|
|
672
|
+
const normalized = safeBuiltClientAssetPath(pathname.slice(clientPrefix.length));
|
|
657
673
|
|
|
658
|
-
if (normalized.
|
|
674
|
+
if (normalized === undefined || !allowedPaths.has(normalized)) {
|
|
659
675
|
return new Response("Not Found", { status: 404 });
|
|
660
676
|
}
|
|
661
677
|
|
|
@@ -670,6 +686,86 @@ async function readBuiltClientAsset(outDir: string, pathname: string): Promise<R
|
|
|
670
686
|
}
|
|
671
687
|
}
|
|
672
688
|
|
|
689
|
+
function builtClientAssetPathname(request: Request, url: URL): string | undefined {
|
|
690
|
+
const rawUrl = rawNodeRequestUrl(request);
|
|
691
|
+
const rawPathname = rawUrl?.split(/[?#]/, 1)[0];
|
|
692
|
+
const pathname = rawPathname === undefined || rawPathname === "" ? url.pathname : rawPathname;
|
|
693
|
+
const normalizedPathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
694
|
+
|
|
695
|
+
return normalizedPathname.startsWith("/_mreact/client/") ? normalizedPathname : undefined;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function safeBuiltClientAssetPath(relativePath: string): string | undefined {
|
|
699
|
+
let decoded: string;
|
|
700
|
+
|
|
701
|
+
try {
|
|
702
|
+
decoded = decodeURIComponent(relativePath);
|
|
703
|
+
} catch {
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (decoded.includes("\\") || decoded.includes("\0")) {
|
|
708
|
+
return undefined;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (decoded.split("/").some((segment) => segment === "..")) {
|
|
712
|
+
return undefined;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const normalized = normalize(decoded);
|
|
716
|
+
|
|
717
|
+
if (isAbsolute(normalized) || normalized === ".." || normalized.startsWith(`..${sep}`)) {
|
|
718
|
+
return undefined;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return normalized;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function builtClientAssetPaths(manifest: {
|
|
725
|
+
assets?: readonly string[] | undefined;
|
|
726
|
+
routes: readonly ClientRouteManifestEntry[];
|
|
727
|
+
}): ReadonlySet<string> {
|
|
728
|
+
const paths = new Set<string>(["manifest.json"]);
|
|
729
|
+
|
|
730
|
+
for (const route of manifest.routes) {
|
|
731
|
+
for (const asset of [
|
|
732
|
+
route.script,
|
|
733
|
+
route.sourceMap,
|
|
734
|
+
route.navigationScript,
|
|
735
|
+
...(route.css ?? []),
|
|
736
|
+
...(route.imports ?? []),
|
|
737
|
+
]) {
|
|
738
|
+
const path = safeClientManifestAssetPath(asset);
|
|
739
|
+
|
|
740
|
+
if (path !== undefined) {
|
|
741
|
+
paths.add(path);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
for (const asset of manifest.assets ?? []) {
|
|
747
|
+
const path = safeClientManifestAssetPath(asset);
|
|
748
|
+
|
|
749
|
+
if (path !== undefined) {
|
|
750
|
+
paths.add(path);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return paths;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function safeClientManifestAssetPath(asset: string | undefined): string | undefined {
|
|
758
|
+
if (asset === undefined || asset === "" || asset.startsWith("/") || asset.includes("\\")) {
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const segments = asset.split("/");
|
|
763
|
+
|
|
764
|
+
return segments.some((segment) => segment === "" || segment === "." || segment === "..")
|
|
765
|
+
? undefined
|
|
766
|
+
: segments.join("/");
|
|
767
|
+
}
|
|
768
|
+
|
|
673
769
|
async function readBuiltPublicAsset(
|
|
674
770
|
outDir: string,
|
|
675
771
|
pathname: string,
|
|
@@ -734,9 +830,12 @@ async function readBuiltRuntime(options: {
|
|
|
734
830
|
return cached.runtime;
|
|
735
831
|
}
|
|
736
832
|
|
|
833
|
+
const serverManifestPath = join(outDir, "server", "manifest.json");
|
|
834
|
+
const clientManifestPath = join(outDir, "client", "manifest.json");
|
|
835
|
+
const importPolicyPath = join(outDir, "server", "import-policy.json");
|
|
737
836
|
const [serverManifestText, clientManifestText, importPolicyText] = await Promise.all([
|
|
738
|
-
|
|
739
|
-
|
|
837
|
+
readRequiredBuiltArtifactText(serverManifestPath, "built app server manifest"),
|
|
838
|
+
readRequiredBuiltArtifactText(clientManifestPath, "built app client manifest"),
|
|
740
839
|
readBuiltImportPolicyText(outDir),
|
|
741
840
|
]);
|
|
742
841
|
|
|
@@ -751,10 +850,13 @@ async function readBuiltRuntime(options: {
|
|
|
751
850
|
|
|
752
851
|
const runtime = materializeBuiltRuntime({
|
|
753
852
|
clientManifestText,
|
|
853
|
+
clientManifestPath,
|
|
754
854
|
importPolicyText,
|
|
855
|
+
importPolicyPath,
|
|
755
856
|
outDir,
|
|
756
857
|
runtimeDir,
|
|
757
858
|
serverManifestText,
|
|
859
|
+
serverManifestPath,
|
|
758
860
|
});
|
|
759
861
|
|
|
760
862
|
builtRuntimeCache.set(cacheKey, {
|
|
@@ -769,16 +871,24 @@ async function readBuiltRuntime(options: {
|
|
|
769
871
|
|
|
770
872
|
async function materializeBuiltRuntime(options: {
|
|
771
873
|
clientManifestText: string;
|
|
874
|
+
clientManifestPath: string;
|
|
875
|
+
importPolicyPath: string;
|
|
772
876
|
importPolicyText: string | undefined;
|
|
773
877
|
outDir: string;
|
|
774
878
|
runtimeDir: string;
|
|
775
879
|
serverManifestText: string;
|
|
880
|
+
serverManifestPath: string;
|
|
776
881
|
}): Promise<BuiltRuntime> {
|
|
777
|
-
const serverManifest =
|
|
778
|
-
|
|
882
|
+
const serverManifest = parseBuiltJsonArtifact<BuiltServerManifest>(
|
|
883
|
+
options.serverManifestText,
|
|
884
|
+
options.serverManifestPath,
|
|
885
|
+
"built app server manifest",
|
|
886
|
+
);
|
|
887
|
+
const clientManifest = parseBuiltJsonArtifact<{
|
|
888
|
+
assets?: readonly string[];
|
|
779
889
|
routes: ClientRouteManifestEntry[];
|
|
780
890
|
styles?: Array<{ css?: readonly string[]; file: string }>;
|
|
781
|
-
};
|
|
891
|
+
}>(options.clientManifestText, options.clientManifestPath, "built app client manifest");
|
|
782
892
|
const appDir = await materializeBuiltServerApp(options.runtimeDir, serverManifest);
|
|
783
893
|
const projectRoot = appDir;
|
|
784
894
|
const routesDir = join(projectRoot, serverManifest.routesDir ?? "");
|
|
@@ -860,7 +970,10 @@ async function materializeBuiltRuntime(options: {
|
|
|
860
970
|
const allowedSourceDirs = (serverManifest.allowedSourceDirs ?? [""]).map((directory) =>
|
|
861
971
|
join(projectRoot, directory),
|
|
862
972
|
);
|
|
863
|
-
const generatedImportPolicy = builtGeneratedImportPolicy(
|
|
973
|
+
const generatedImportPolicy = builtGeneratedImportPolicy(
|
|
974
|
+
options.importPolicyText,
|
|
975
|
+
options.importPolicyPath,
|
|
976
|
+
);
|
|
864
977
|
|
|
865
978
|
return {
|
|
866
979
|
appDir: routesDir,
|
|
@@ -868,6 +981,7 @@ async function materializeBuiltRuntime(options: {
|
|
|
868
981
|
...(serverManifest.assetBaseUrl === undefined
|
|
869
982
|
? {}
|
|
870
983
|
: { assetBaseUrl: serverManifest.assetBaseUrl }),
|
|
984
|
+
clientAssetPaths: builtClientAssetPaths(clientManifest),
|
|
871
985
|
clientScripts,
|
|
872
986
|
clientStylesByFile,
|
|
873
987
|
clientStyles,
|
|
@@ -898,27 +1012,30 @@ async function materializeBuiltRuntime(options: {
|
|
|
898
1012
|
}
|
|
899
1013
|
|
|
900
1014
|
async function readBuiltImportPolicyText(outDir: string): Promise<string | undefined> {
|
|
1015
|
+
const policyPath = join(outDir, "server", "import-policy.json");
|
|
1016
|
+
|
|
901
1017
|
try {
|
|
902
|
-
return await readFile(
|
|
1018
|
+
return await readFile(policyPath, "utf8");
|
|
903
1019
|
} catch (error) {
|
|
904
1020
|
if (isMissingFileError(error)) {
|
|
905
1021
|
return undefined;
|
|
906
1022
|
}
|
|
907
1023
|
|
|
908
|
-
throw error;
|
|
1024
|
+
throw builtArtifactReadError("built app import policy", policyPath, error);
|
|
909
1025
|
}
|
|
910
1026
|
}
|
|
911
1027
|
|
|
912
1028
|
function builtGeneratedImportPolicy(
|
|
913
1029
|
importPolicyText: string | undefined,
|
|
1030
|
+
importPolicyPath: string,
|
|
914
1031
|
): AppRouterImportPolicy | undefined {
|
|
915
1032
|
if (importPolicyText === undefined) {
|
|
916
1033
|
return undefined;
|
|
917
1034
|
}
|
|
918
1035
|
|
|
919
|
-
const artifact =
|
|
1036
|
+
const artifact = parseBuiltJsonArtifact<{
|
|
920
1037
|
runtimePackages?: unknown;
|
|
921
|
-
};
|
|
1038
|
+
}>(importPolicyText, importPolicyPath, "built app import policy");
|
|
922
1039
|
const runtimePackages = Array.isArray(artifact.runtimePackages)
|
|
923
1040
|
? artifact.runtimePackages.filter((name): name is string => typeof name === "string")
|
|
924
1041
|
: [];
|
|
@@ -961,6 +1078,31 @@ function isMissingFileError(error: unknown): boolean {
|
|
|
961
1078
|
);
|
|
962
1079
|
}
|
|
963
1080
|
|
|
1081
|
+
async function readRequiredBuiltArtifactText(labelPath: string, label: string): Promise<string> {
|
|
1082
|
+
try {
|
|
1083
|
+
return await readFile(labelPath, "utf8");
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
throw builtArtifactReadError(label, labelPath, error);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function builtArtifactReadError(label: string, artifactPath: string, error: unknown): Error {
|
|
1090
|
+
const prefix = isMissingFileError(error) ? "Missing" : "Unable to read";
|
|
1091
|
+
const detail = error instanceof Error && error.message !== "" ? `: ${error.message}` : "";
|
|
1092
|
+
|
|
1093
|
+
return new Error(`${prefix} ${label}: ${artifactPath}${detail}`, { cause: error });
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function parseBuiltJsonArtifact<T>(text: string, artifactPath: string, label: string): T {
|
|
1097
|
+
try {
|
|
1098
|
+
return JSON.parse(text) as T;
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
const detail = error instanceof Error && error.message !== "" ? `: ${error.message}` : "";
|
|
1101
|
+
|
|
1102
|
+
throw new Error(`Invalid ${label}: ${artifactPath}${detail}`, { cause: error });
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
964
1106
|
async function loadBuiltServerModuleArtifacts(
|
|
965
1107
|
runtime: BuiltRuntime,
|
|
966
1108
|
files: Iterable<string>,
|
|
@@ -1008,7 +1150,11 @@ async function loadBuiltServerModuleArtifact(
|
|
|
1008
1150
|
mergeBuiltServerModuleArtifacts(
|
|
1009
1151
|
existing,
|
|
1010
1152
|
hydrateBuiltServerModuleArtifact(
|
|
1011
|
-
|
|
1153
|
+
parseBuiltJsonArtifact<BuiltServerModuleArtifact>(
|
|
1154
|
+
text,
|
|
1155
|
+
artifactPath,
|
|
1156
|
+
`built server module artifact for ${file}`,
|
|
1157
|
+
),
|
|
1012
1158
|
builtServerDirForArtifactPath(artifactPath),
|
|
1013
1159
|
),
|
|
1014
1160
|
),
|
|
@@ -1016,6 +1162,9 @@ async function loadBuiltServerModuleArtifact(
|
|
|
1016
1162
|
})
|
|
1017
1163
|
.catch((error) => {
|
|
1018
1164
|
runtime.serverModuleArtifactLoads.delete(`${kind}\0${file}`);
|
|
1165
|
+
if (isMissingFileError(error)) {
|
|
1166
|
+
throw builtArtifactReadError(`built server module artifact for ${file}`, artifactPath, error);
|
|
1167
|
+
}
|
|
1019
1168
|
throw error;
|
|
1020
1169
|
});
|
|
1021
1170
|
runtime.serverModuleArtifactLoads.set(`${kind}\0${file}`, loaded);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PluginOption } from "vite";
|
|
1
|
+
import type { PluginOption, UserConfig } from "vite";
|
|
2
2
|
|
|
3
3
|
const pluginObjectIds = new WeakMap<object, number>();
|
|
4
4
|
let nextPluginObjectId = 1;
|
|
@@ -13,6 +13,17 @@ export function vitePluginsCacheKey(plugins: readonly PluginOption[] | undefined
|
|
|
13
13
|
.join("\0");
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export function viteDefineCacheKey(define: UserConfig["define"] | undefined): string {
|
|
17
|
+
if (define === undefined) {
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return Object.entries(define)
|
|
22
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
23
|
+
.map(([key, value]) => `${key}:${String(value)}`)
|
|
24
|
+
.join("\0");
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
function flattenVitePluginOptions(
|
|
17
28
|
options: readonly PluginOption[],
|
|
18
29
|
): Array<{ name?: string; objectId: number }> {
|
package/src/vite.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type Connect,
|
|
12
12
|
type Plugin,
|
|
13
13
|
type PluginOption,
|
|
14
|
+
type UserConfig,
|
|
14
15
|
type ViteDevServer,
|
|
15
16
|
} from "vite";
|
|
16
17
|
import type { AppRouterServerActionOptions } from "./actions.js";
|
|
@@ -46,6 +47,7 @@ import { workspacePackageFile } from "./workspace-packages.js";
|
|
|
46
47
|
|
|
47
48
|
export interface AppRouterViteMiddlewareOptions extends AppRouterProjectOptions {
|
|
48
49
|
allowedHosts?: readonly string[] | undefined;
|
|
50
|
+
define?: UserConfig["define"] | undefined;
|
|
49
51
|
hostPolicy?: RequestHostPolicy | undefined;
|
|
50
52
|
importPolicy?: AppRouterImportPolicy | undefined;
|
|
51
53
|
routeCache?: AppRouterCache | undefined;
|
|
@@ -203,6 +205,7 @@ export function createAppRouterVitePlugin(options: AppRouterVitePluginOptions):
|
|
|
203
205
|
return () => {
|
|
204
206
|
const middlewareOptions: AppRouterViteRuntimeMiddlewareOptions = {
|
|
205
207
|
...options,
|
|
208
|
+
define: server.config.define,
|
|
206
209
|
navigationScanVitePlugins: userVitePlugins ?? [],
|
|
207
210
|
viteDevServer: server,
|
|
208
211
|
vitePlugins: server.config.plugins,
|
|
@@ -503,6 +506,7 @@ async function handleAppRouterViteRequest(
|
|
|
503
506
|
outgoing,
|
|
504
507
|
await renderAppRequest({
|
|
505
508
|
appDir: project.routesDir,
|
|
509
|
+
define: options.define,
|
|
506
510
|
importPolicy: {
|
|
507
511
|
...options.importPolicy,
|
|
508
512
|
allowedSourceDirs: project.allowedSourceDirs,
|