@reckona/mreact-router 0.0.65 → 0.0.67

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.
Files changed (59) hide show
  1. package/package.json +13 -12
  2. package/src/actions.ts +1130 -0
  3. package/src/adapters/aws-lambda.ts +993 -0
  4. package/src/adapters/cloudflare.ts +1286 -0
  5. package/src/adapters/devtools.ts +5 -0
  6. package/src/adapters/edge.ts +70 -0
  7. package/src/adapters/node.ts +126 -0
  8. package/src/adapters/static.ts +61 -0
  9. package/src/app-router-globals.ts +19 -0
  10. package/src/assets.ts +113 -0
  11. package/src/build.ts +2948 -0
  12. package/src/bundle-pipeline.ts +496 -0
  13. package/src/cache-config.ts +35 -0
  14. package/src/cache-stats.ts +54 -0
  15. package/src/cache.ts +418 -0
  16. package/src/cli-options.ts +296 -0
  17. package/src/cli.ts +94 -0
  18. package/src/client.ts +3398 -0
  19. package/src/config.ts +146 -0
  20. package/src/cookies.ts +113 -0
  21. package/src/csp.ts +103 -0
  22. package/src/csrf.ts +132 -0
  23. package/src/deferred.ts +52 -0
  24. package/src/dev-server.ts +262 -0
  25. package/src/file-conventions.ts +88 -0
  26. package/src/http.ts +128 -0
  27. package/src/i18n.ts +98 -0
  28. package/src/import-policy.ts +261 -0
  29. package/src/index.ts +221 -0
  30. package/src/link.ts +47 -0
  31. package/src/logger.ts +149 -0
  32. package/src/module-runner.ts +554 -0
  33. package/src/multipart.ts +577 -0
  34. package/src/native-escape.ts +53 -0
  35. package/src/native-route-matcher.ts +173 -0
  36. package/src/navigation-state.ts +98 -0
  37. package/src/navigation.ts +215 -0
  38. package/src/prerender-store.ts +233 -0
  39. package/src/render.ts +5187 -0
  40. package/src/route-path.ts +5 -0
  41. package/src/route-shells.ts +47 -0
  42. package/src/route-source.ts +224 -0
  43. package/src/route-styles.ts +91 -0
  44. package/src/routes.ts +362 -0
  45. package/src/runtime-cache.ts +8 -0
  46. package/src/runtime-state.ts +37 -0
  47. package/src/security-headers.ts +134 -0
  48. package/src/serve.ts +1265 -0
  49. package/src/session.ts +238 -0
  50. package/src/source-jsx.ts +30 -0
  51. package/src/source-modules.ts +69 -0
  52. package/src/stream-list.ts +45 -0
  53. package/src/trace.ts +114 -0
  54. package/src/types.ts +155 -0
  55. package/src/upgrade.ts +8 -0
  56. package/src/vite-config.ts +63 -0
  57. package/src/vite-plugin-cache-key.ts +53 -0
  58. package/src/vite.ts +690 -0
  59. package/src/workspace-packages.ts +67 -0
@@ -0,0 +1,5 @@
1
+ export function normalizeRoutePath(pathname: string): string {
2
+ const withoutTrailing = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
3
+
4
+ return withoutTrailing === "" ? "/" : withoutTrailing;
5
+ }
@@ -0,0 +1,47 @@
1
+ import { dirname, join, relative } from "node:path";
2
+
3
+ export type RouteShellKind = "layout" | "template";
4
+
5
+ export interface RouteShellCandidate {
6
+ directory: string;
7
+ file: string;
8
+ kind: RouteShellKind;
9
+ }
10
+
11
+ const routeShellFiles = [
12
+ ["layout.tsx", "layout"],
13
+ ["layout.mreact.tsx", "layout"],
14
+ ["template.tsx", "template"],
15
+ ["template.mreact.tsx", "template"],
16
+ ] as const satisfies readonly (readonly [string, RouteShellKind])[];
17
+
18
+ export function routeShellCandidates(rootDir: string, pageFile: string): RouteShellCandidate[] {
19
+ const relativeDir = relative(rootDir, dirname(pageFile));
20
+ const parts = relativeDir === "" ? [] : relativeDir.split(/[\\/]/);
21
+ const directories = [rootDir];
22
+
23
+ for (let index = 0; index < parts.length; index += 1) {
24
+ directories.push(join(rootDir, ...parts.slice(0, index + 1)));
25
+ }
26
+
27
+ return directories.flatMap((directory) =>
28
+ routeShellFiles.map(([filename, kind]) => ({
29
+ directory,
30
+ file: join(directory, filename),
31
+ kind,
32
+ }))
33
+ );
34
+ }
35
+
36
+ export async function existingRouteShellCandidates(
37
+ rootDir: string,
38
+ pageFile: string,
39
+ exists: (file: string) => Promise<boolean>,
40
+ ): Promise<RouteShellCandidate[]> {
41
+ const candidates = routeShellCandidates(rootDir, pageFile);
42
+ const existing = await Promise.all(candidates.map(async (candidate) =>
43
+ await exists(candidate.file) ? candidate : undefined
44
+ ));
45
+
46
+ return existing.filter((candidate): candidate is RouteShellCandidate => candidate !== undefined);
47
+ }
@@ -0,0 +1,224 @@
1
+ import { dirname, isAbsolute, join } from "node:path";
2
+ import {
3
+ collectTopLevelValueExportNames,
4
+ collectJsxComponentRootNames,
5
+ collectStaticImportReferences,
6
+ demoteTopLevelExportDeclarations,
7
+ hasModuleDirective,
8
+ hasTopLevelExportDeclaration,
9
+ stripTopLevelExportDeclarations,
10
+ } from "@reckona/mreact-compiler";
11
+ import type { StaticImportReference } from "@reckona/mreact-compiler";
12
+ import { sourceModuleCandidates } from "./source-modules.js";
13
+
14
+ const routeModuleExportNames = [
15
+ "auth",
16
+ "generateStaticParams",
17
+ "loader",
18
+ "middleware",
19
+ "prerender",
20
+ "revalidate",
21
+ "stream",
22
+ ] as const;
23
+ const routeClientOnlyExportNames = [
24
+ ...routeModuleExportNames,
25
+ "generateMetadata",
26
+ "metadata",
27
+ ] as const;
28
+ const routeRequestRenderExportNames = ["default", "slots"] as const;
29
+ const routeRenderExportNames = new Set<string>(["default", "slots"]);
30
+ const routeRequestExportNames = new Set<string>([
31
+ ...routeClientOnlyExportNames,
32
+ "generateMetadata",
33
+ "metadata",
34
+ ]);
35
+ const routeLoaderOnlyExportNames = new Set<string>(["loader"]);
36
+ const routeMetadataOnlyExportNames = new Set<string>(["generateMetadata", "metadata"]);
37
+
38
+ export function stripRouteModuleExports(code: string): string {
39
+ return demoteRouteHelperExports(stripTopLevelExportDeclarations({
40
+ code,
41
+ names: routeModuleExportNames,
42
+ }));
43
+ }
44
+
45
+ export function stripRouteClientOnlyExports(code: string): string {
46
+ return demoteRouteHelperExports(stripTopLevelExportDeclarations({
47
+ code,
48
+ names: routeClientOnlyExportNames,
49
+ }));
50
+ }
51
+
52
+ export function stripRouteBuildExports(code: string): string {
53
+ return stripRouteClientOnlyExports(code);
54
+ }
55
+
56
+ export function stripRouteRequestOnlyExports(code: string): string {
57
+ return demoteRouteHelperExports(
58
+ stripTopLevelExportDeclarations({
59
+ code,
60
+ names: routeRequestRenderExportNames,
61
+ }),
62
+ routeRequestExportNames,
63
+ );
64
+ }
65
+
66
+ export function stripRouteLoaderOnlyExports(code: string): string {
67
+ return demoteRouteHelperExports(
68
+ stripTopLevelExportDeclarations({
69
+ code,
70
+ names: ["auth", "default", "generateMetadata", "generateStaticParams", "metadata", "middleware", "prerender", "revalidate", "slots", "stream"],
71
+ }),
72
+ routeLoaderOnlyExportNames,
73
+ );
74
+ }
75
+
76
+ export function stripRouteMetadataOnlyExports(code: string): string {
77
+ return demoteRouteHelperExports(
78
+ stripTopLevelExportDeclarations({
79
+ code,
80
+ names: ["auth", "default", "generateStaticParams", "loader", "middleware", "prerender", "revalidate", "slots", "stream"],
81
+ }),
82
+ routeMetadataOnlyExportNames,
83
+ );
84
+ }
85
+
86
+ export function stripRouteConfigExports(code: string): string {
87
+ return stripTopLevelExportDeclarations({
88
+ code,
89
+ names: ["auth", "prerender", "revalidate", "stream"],
90
+ });
91
+ }
92
+
93
+ export function isStreamRouteSource(code: string): boolean {
94
+ return hasTopLevelExportDeclaration({ code, names: ["stream"] });
95
+ }
96
+
97
+ export function mayUseAwaitBoundarySource(code: string): boolean {
98
+ return collectJsxComponentRootNames({ code }).includes("Await");
99
+ }
100
+
101
+ export function routeClosureMayUseAwaitBoundary(options: {
102
+ filename: string;
103
+ files: Record<string, string>;
104
+ projectRoot: string;
105
+ seen?: Set<string> | undefined;
106
+ source: string;
107
+ }): boolean {
108
+ const seen = options.seen ?? new Set<string>();
109
+ if (
110
+ seen.has(options.filename) ||
111
+ hasModuleDirective({ code: options.source, directive: "use client" })
112
+ ) {
113
+ return false;
114
+ }
115
+
116
+ seen.add(options.filename);
117
+
118
+ try {
119
+ if (mayUseAwaitBoundarySource(options.source)) {
120
+ return true;
121
+ }
122
+
123
+ const sourceFilename = sourceFilenameForCompiler(options.projectRoot, options.filename);
124
+ const jsxComponentRoots = new Set(
125
+ collectJsxComponentRootNames({
126
+ code: options.source,
127
+ filename: sourceFilename,
128
+ }),
129
+ );
130
+
131
+ for (const reference of collectStaticImportReferences({
132
+ code: options.source,
133
+ filename: sourceFilename,
134
+ })) {
135
+ if (!isRenderedStaticImportReference(reference, jsxComponentRoots)) {
136
+ continue;
137
+ }
138
+
139
+ const resolved = resolveLocalSourceImport(options.files, options.filename, reference.source);
140
+
141
+ if (resolved === undefined) {
142
+ continue;
143
+ }
144
+
145
+ const importedSource = options.files[resolved];
146
+
147
+ if (
148
+ importedSource !== undefined &&
149
+ routeClosureMayUseAwaitBoundary({
150
+ filename: resolved,
151
+ files: options.files,
152
+ projectRoot: options.projectRoot,
153
+ seen,
154
+ source: importedSource,
155
+ })
156
+ ) {
157
+ return true;
158
+ }
159
+ }
160
+
161
+ return false;
162
+ } finally {
163
+ seen.delete(options.filename);
164
+ }
165
+ }
166
+
167
+ export function hasPrerenderExport(code: string): boolean {
168
+ return hasTopLevelExportDeclaration({ code, names: ["prerender"] });
169
+ }
170
+
171
+ export function hasGenerateStaticParamsExport(code: string): boolean {
172
+ return hasTopLevelExportDeclaration({ code, names: ["generateStaticParams"] });
173
+ }
174
+
175
+ export function hasLoaderExport(code: string): boolean {
176
+ return hasTopLevelExportDeclaration({ code, names: ["loader"] });
177
+ }
178
+
179
+ function demoteRouteHelperExports(
180
+ code: string,
181
+ preservedExportNames: ReadonlySet<string> = routeRenderExportNames,
182
+ ): string {
183
+ const helperNames = collectTopLevelValueExportNames({ code })
184
+ .filter((name) => !preservedExportNames.has(name) && startsLowercase(name));
185
+
186
+ return helperNames.length === 0
187
+ ? code
188
+ : demoteTopLevelExportDeclarations({ code, names: helperNames });
189
+ }
190
+
191
+ function startsLowercase(value: string): boolean {
192
+ return /^[a-z]/.test(value);
193
+ }
194
+
195
+ function isRenderedStaticImportReference(
196
+ reference: StaticImportReference,
197
+ jsxComponentRoots: ReadonlySet<string>,
198
+ ): boolean {
199
+ return reference.localNames.some((localName) => jsxComponentRoots.has(localName));
200
+ }
201
+
202
+ function resolveLocalSourceImport(
203
+ files: Record<string, string>,
204
+ importer: string,
205
+ specifier: string,
206
+ ): string | undefined {
207
+ if (!specifier.startsWith(".")) {
208
+ return undefined;
209
+ }
210
+
211
+ const base = join(dirname(importer), specifier);
212
+
213
+ for (const candidate of sourceModuleCandidates(base)) {
214
+ if (files[candidate] !== undefined) {
215
+ return candidate;
216
+ }
217
+ }
218
+
219
+ return undefined;
220
+ }
221
+
222
+ function sourceFilenameForCompiler(projectRoot: string, filename: string): string {
223
+ return isAbsolute(filename) ? filename : join(projectRoot, filename);
224
+ }
@@ -0,0 +1,91 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
3
+ import { collectStaticImportReferences } from "@reckona/mreact-compiler";
4
+ import { existingRouteShellCandidates } from "./route-shells.js";
5
+
6
+ export async function collectRouteCssFiles(options: {
7
+ appDir: string;
8
+ pageFile: string;
9
+ projectRoot: string;
10
+ }): Promise<string[]> {
11
+ const shellFiles = (await existingRouteShellCandidates(options.appDir, options.pageFile, isFile))
12
+ .map((candidate) => candidate.file);
13
+ const files = [...shellFiles, options.pageFile];
14
+ const seen = new Set<string>();
15
+ const cssFiles: string[] = [];
16
+
17
+ for (const file of files) {
18
+ const source = await readFile(file, "utf8");
19
+
20
+ for (const reference of collectStaticImportReferences({ code: source, filename: file })) {
21
+ const cssFile = resolveCssImport({
22
+ importer: file,
23
+ projectRoot: options.projectRoot,
24
+ source: reference.source,
25
+ });
26
+
27
+ if (cssFile === undefined || seen.has(cssFile)) {
28
+ continue;
29
+ }
30
+
31
+ seen.add(cssFile);
32
+ cssFiles.push(cssFile);
33
+ }
34
+ }
35
+
36
+ return cssFiles;
37
+ }
38
+
39
+ export async function collectRouteCssHrefs(options: {
40
+ appDir: string;
41
+ hrefPrefix?: string | undefined;
42
+ pageFile: string;
43
+ projectRoot: string;
44
+ }): Promise<string[]> {
45
+ return (await collectRouteCssFiles(options)).map((file) => {
46
+ const href = `/${relative(options.projectRoot, file).split(sep).join("/")}`;
47
+
48
+ return options.hrefPrefix === undefined ? href : `${options.hrefPrefix}${href.slice(1)}`;
49
+ });
50
+ }
51
+
52
+ function resolveCssImport(options: {
53
+ importer: string;
54
+ projectRoot: string;
55
+ source: string;
56
+ }): string | undefined {
57
+ if (!isCssSource(options.source)) {
58
+ return undefined;
59
+ }
60
+
61
+ const resolved = isAbsolute(options.source)
62
+ ? normalizeInsideProject(options.projectRoot, options.source)
63
+ : options.source.startsWith(".")
64
+ ? normalizeInsideProject(options.projectRoot, resolve(dirname(options.importer), options.source))
65
+ : undefined;
66
+
67
+ return resolved;
68
+ }
69
+
70
+ function normalizeInsideProject(projectRoot: string, file: string): string | undefined {
71
+ const normalized = resolve(file);
72
+ const root = resolve(projectRoot);
73
+ const relativePath = relative(root, normalized);
74
+
75
+ return relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath)
76
+ ? undefined
77
+ : normalized;
78
+ }
79
+
80
+ function isCssSource(source: string): boolean {
81
+ return /\.(?:css|pcss|postcss|scss|sass|less|styl|stylus)$/u.test(source);
82
+ }
83
+
84
+ async function isFile(path: string): Promise<boolean> {
85
+ try {
86
+ await access(path);
87
+ return true;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }