@reckona/mreact-router 0.0.66 → 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,496 @@
1
+ import { builtinModules } from "node:module";
2
+ import { dirname } from "node:path";
3
+ import {
4
+ build as viteBuild,
5
+ type InlineConfig,
6
+ type PluginOption,
7
+ type Plugin as VitePlugin,
8
+ } from "vite";
9
+ import { workspacePackageFile } from "./workspace-packages.js";
10
+
11
+ export interface RouterBundleOptions {
12
+ base?: string | undefined;
13
+ code: string;
14
+ define?: Record<string, string> | undefined;
15
+ filename: string;
16
+ minify?: boolean | undefined;
17
+ outfile?: string | undefined;
18
+ platform: "browser" | "node";
19
+ preserveExports?: boolean | undefined;
20
+ plugins?: readonly RouterCompatPlugin[] | undefined;
21
+ vitePlugins?: readonly PluginOption[] | undefined;
22
+ sourceMap?: boolean | undefined;
23
+ target?: string | undefined;
24
+ }
25
+
26
+ export interface RouterBundleModulesOptions {
27
+ base?: string | undefined;
28
+ define?: Record<string, string> | undefined;
29
+ entries: readonly RouterBundleEntryOptions[];
30
+ minify?: boolean | undefined;
31
+ platform: "browser" | "node";
32
+ plugins?: readonly RouterCompatPlugin[] | undefined;
33
+ root?: string | undefined;
34
+ vitePlugins?: readonly PluginOption[] | undefined;
35
+ sourceMap?: boolean | undefined;
36
+ target?: string | undefined;
37
+ }
38
+
39
+ export interface RouterBundleEntryOptions {
40
+ code: string;
41
+ filename: string;
42
+ name: string;
43
+ }
44
+
45
+ export interface RouterBundleOutput {
46
+ assets?: RouterBundleAssetOutput[] | undefined;
47
+ code: string;
48
+ map?: string | undefined;
49
+ }
50
+
51
+ export interface RouterBundleModulesOutput {
52
+ assets?: RouterBundleAssetOutput[] | undefined;
53
+ chunks: RouterBundleChunkOutput[];
54
+ }
55
+
56
+ export interface RouterBundleChunkOutput {
57
+ code: string;
58
+ fileName: string;
59
+ imports: string[];
60
+ isEntry: boolean;
61
+ map?: string | undefined;
62
+ name: string;
63
+ }
64
+
65
+ export interface RouterBundleAssetOutput {
66
+ fileName: string;
67
+ source: string | Uint8Array;
68
+ }
69
+
70
+ interface RouterBundlerChunk {
71
+ code: string;
72
+ fileName: string;
73
+ imports?: string[] | undefined;
74
+ isEntry?: boolean | undefined;
75
+ name?: string | undefined;
76
+ type: "chunk";
77
+ }
78
+
79
+ interface RouterBundlerAsset {
80
+ fileName: string;
81
+ source: string | Uint8Array;
82
+ type: "asset";
83
+ }
84
+
85
+ interface RouterBundlerOutput {
86
+ output: Array<RouterBundlerAsset | RouterBundlerChunk>;
87
+ }
88
+
89
+ export interface RouterCompatPlugin {
90
+ name: string;
91
+ setup(buildApi: RouterCompatBuildApi): void;
92
+ }
93
+
94
+ export interface RouterCompatBuildApi {
95
+ onLoad(
96
+ options: { filter: RegExp; namespace?: string | undefined },
97
+ callback: (args: { path: string }) => RouterCompatLoadResult | Promise<RouterCompatLoadResult>,
98
+ ): void;
99
+ onResolve(
100
+ options: { filter: RegExp },
101
+ callback: (args: {
102
+ importer?: string | undefined;
103
+ path: string;
104
+ resolveDir: string;
105
+ }) => RouterCompatResolveResult | Promise<RouterCompatResolveResult>,
106
+ ): void;
107
+ }
108
+
109
+ export type RouterCompatLoadResult =
110
+ | {
111
+ contents: string;
112
+ loader?: string | undefined;
113
+ resolveDir?: string | undefined;
114
+ }
115
+ | undefined;
116
+
117
+ export type RouterCompatResolveResult =
118
+ | {
119
+ errors?: Array<{ text: string }> | undefined;
120
+ external?: boolean | undefined;
121
+ namespace?: string | undefined;
122
+ path?: string | undefined;
123
+ }
124
+ | undefined;
125
+
126
+ const nodeBuiltinSpecifiers = new Set(
127
+ builtinModules.flatMap((name) => [name, `node:${name}`]),
128
+ );
129
+
130
+ export async function bundleRouterModule(options: RouterBundleOptions): Promise<RouterBundleOutput> {
131
+ const entryId = `${options.filename}?mreact-router-entry`;
132
+ const outfile = options.outfile ?? "entry.js";
133
+ const config = {
134
+ base: options.base ?? "/",
135
+ configFile: false,
136
+ ...(options.define === undefined ? {} : { define: options.define }),
137
+ logLevel: "silent",
138
+ plugins: [
139
+ rejectNodeBuiltinsForBrowserPlugin(options.platform),
140
+ mreactJsxRuntimeAliasPlugin(),
141
+ ...(options.vitePlugins ?? []),
142
+ virtualEntryPlugin(entryId, options.code),
143
+ ...(options.plugins ?? []).map(routerCompatPlugin),
144
+ ],
145
+ publicDir: false,
146
+ root: dirname(options.filename),
147
+ ssr: {
148
+ noExternal: true,
149
+ },
150
+ build: {
151
+ emptyOutDir: false,
152
+ lib: options.preserveExports === true
153
+ ? {
154
+ entry: entryId,
155
+ fileName: () => outfile,
156
+ formats: ["es"],
157
+ }
158
+ : false,
159
+ minify: options.minify === true,
160
+ sourcemap: options.sourceMap === true,
161
+ ssr: options.platform === "node",
162
+ target: options.target ?? "es2022",
163
+ write: false,
164
+ rolldownOptions: {
165
+ input: entryId,
166
+ output: {
167
+ codeSplitting: false,
168
+ entryFileNames: outfile,
169
+ format: "es",
170
+ },
171
+ },
172
+ },
173
+ } satisfies InlineConfig;
174
+ const result = await viteBuild(config);
175
+ const output = (Array.isArray(result) ? result[0] : result) as RouterBundlerOutput | undefined;
176
+
177
+ if (output === undefined || !("output" in output)) {
178
+ throw new Error(`Failed to bundle ${options.filename}: Vite/Rolldown produced no output.`);
179
+ }
180
+
181
+ const chunk = output.output.find((item): item is RouterBundlerChunk => item.type === "chunk");
182
+ const map = output.output.find(
183
+ (item): item is RouterBundlerAsset =>
184
+ item.type === "asset" && item.fileName === `${chunk?.fileName ?? outfile}.map`,
185
+ );
186
+ const assets = output.output
187
+ .filter((item): item is RouterBundlerAsset =>
188
+ item.type === "asset" && item.fileName !== `${chunk?.fileName ?? outfile}.map`
189
+ )
190
+ .map((asset) => ({ fileName: asset.fileName, source: asset.source }));
191
+
192
+ if (chunk === undefined) {
193
+ throw new Error(`Failed to bundle ${options.filename}: Vite/Rolldown produced no chunk.`);
194
+ }
195
+
196
+ return {
197
+ ...(assets.length === 0 ? {} : { assets }),
198
+ code: stripSourceMappingUrl(chunk.code),
199
+ ...(map !== undefined && typeof map.source === "string" ? { map: map.source } : {}),
200
+ };
201
+ }
202
+
203
+ export async function bundleRouterModules(
204
+ options: RouterBundleModulesOptions,
205
+ ): Promise<RouterBundleModulesOutput> {
206
+ if (options.entries.length === 0) {
207
+ return { chunks: [] };
208
+ }
209
+
210
+ const entries = new Map(
211
+ options.entries.map((entry) => [
212
+ `${entry.filename}?mreact-router-entry=${encodeURIComponent(entry.name)}`,
213
+ entry,
214
+ ]),
215
+ );
216
+ const input = Object.fromEntries(
217
+ Array.from(entries, ([entryId, entry]) => [entry.name, entryId]),
218
+ );
219
+ const config = {
220
+ base: options.base ?? "/",
221
+ configFile: false,
222
+ ...(options.define === undefined ? {} : { define: options.define }),
223
+ logLevel: "silent",
224
+ plugins: [
225
+ rejectNodeBuiltinsForBrowserPlugin(options.platform),
226
+ mreactJsxRuntimeAliasPlugin(),
227
+ ...(options.vitePlugins ?? []),
228
+ virtualEntriesPlugin(entries),
229
+ ...(options.plugins ?? []).map(routerCompatPlugin),
230
+ ],
231
+ publicDir: false,
232
+ root: options.root ?? dirname(options.entries[0]?.filename ?? process.cwd()),
233
+ ssr: {
234
+ noExternal: true,
235
+ },
236
+ build: {
237
+ emptyOutDir: false,
238
+ minify: options.minify === true,
239
+ sourcemap: options.sourceMap === true,
240
+ ssr: options.platform === "node",
241
+ target: options.target ?? "es2022",
242
+ write: false,
243
+ rolldownOptions: {
244
+ input,
245
+ output: {
246
+ chunkFileNames: "assets/chunks/[name].[hash].js",
247
+ entryFileNames: "assets/routes/[name].[hash].js",
248
+ format: "es",
249
+ hashCharacters: "hex",
250
+ },
251
+ },
252
+ },
253
+ } satisfies InlineConfig;
254
+ const result = await viteBuild(config);
255
+ const output = (Array.isArray(result) ? result[0] : result) as RouterBundlerOutput | undefined;
256
+
257
+ if (output === undefined || !("output" in output)) {
258
+ throw new Error("Failed to bundle client routes: Vite/Rolldown produced no output.");
259
+ }
260
+
261
+ const mapAssets = new Map(
262
+ output.output
263
+ .filter((item): item is RouterBundlerAsset =>
264
+ item.type === "asset" && item.fileName.endsWith(".map")
265
+ )
266
+ .flatMap((asset) =>
267
+ typeof asset.source === "string" ? [[asset.fileName, asset.source] as const] : []
268
+ ),
269
+ );
270
+ const chunks = output.output
271
+ .filter((item): item is RouterBundlerChunk => item.type === "chunk")
272
+ .map((chunk) => {
273
+ const map = mapAssets.get(`${chunk.fileName}.map`);
274
+
275
+ return {
276
+ code: stripSourceMappingUrl(chunk.code),
277
+ fileName: chunk.fileName,
278
+ imports: chunk.imports ?? [],
279
+ isEntry: chunk.isEntry === true,
280
+ ...(map === undefined ? {} : { map }),
281
+ name: chunk.name ?? chunk.fileName,
282
+ };
283
+ });
284
+ const assets = output.output
285
+ .filter((item): item is RouterBundlerAsset =>
286
+ item.type === "asset" && !item.fileName.endsWith(".map")
287
+ )
288
+ .map((asset) => ({ fileName: asset.fileName, source: asset.source }));
289
+
290
+ return {
291
+ ...(assets.length === 0 ? {} : { assets }),
292
+ chunks,
293
+ };
294
+ }
295
+
296
+ function mreactJsxRuntimeAliasPlugin(): VitePlugin {
297
+ const runtimePaths = new Map([
298
+ [
299
+ "react/jsx-dev-runtime",
300
+ workspacePackageFile({
301
+ currentFileUrl: import.meta.url,
302
+ entry: "jsx-dev-runtime",
303
+ monorepoDir: "react",
304
+ packageName: "@reckona/mreact",
305
+ }),
306
+ ],
307
+ [
308
+ "react/jsx-runtime",
309
+ workspacePackageFile({
310
+ currentFileUrl: import.meta.url,
311
+ entry: "jsx-runtime",
312
+ monorepoDir: "react",
313
+ packageName: "@reckona/mreact",
314
+ }),
315
+ ],
316
+ ]);
317
+
318
+ return {
319
+ name: "mreact-router-jsx-runtime-alias",
320
+ enforce: "pre",
321
+ resolveId(id) {
322
+ return runtimePaths.get(id);
323
+ },
324
+ };
325
+ }
326
+
327
+ function stripSourceMappingUrl(code: string): string {
328
+ return code.replace(/\n?\/\/# sourceMappingURL=[^\n]+\.map\s*$/u, "");
329
+ }
330
+
331
+ function virtualEntryPlugin(entryId: string, code: string): VitePlugin {
332
+ return {
333
+ name: "mreact-router-virtual-entry",
334
+ enforce: "pre",
335
+ resolveId(id) {
336
+ return id === entryId ? entryId : undefined;
337
+ },
338
+ load(id) {
339
+ return id === entryId ? { code, moduleType: moduleTypeForFilename(entryId) } : undefined;
340
+ },
341
+ };
342
+ }
343
+
344
+ function virtualEntriesPlugin(entries: ReadonlyMap<string, RouterBundleEntryOptions>): VitePlugin {
345
+ return {
346
+ name: "mreact-router-virtual-entries",
347
+ enforce: "pre",
348
+ resolveId(id) {
349
+ return entries.has(id) ? id : undefined;
350
+ },
351
+ load(id) {
352
+ const entry = entries.get(id);
353
+
354
+ return entry === undefined ? undefined : { code: entry.code, moduleType: moduleTypeForFilename(id) };
355
+ },
356
+ };
357
+ }
358
+
359
+ function moduleTypeForFilename(filename: string): "js" | "jsx" | "ts" | "tsx" {
360
+ const clean = cleanViteId(filename);
361
+
362
+ if (/\.(?:mreact\.)?[cm]?tsx$/u.test(clean) || clean.endsWith(".jsx")) {
363
+ return "tsx";
364
+ }
365
+
366
+ if (/\.(?:mreact\.)?[cm]?ts$/u.test(clean)) {
367
+ return "ts";
368
+ }
369
+
370
+ return "js";
371
+ }
372
+
373
+ function rejectNodeBuiltinsForBrowserPlugin(platform: RouterBundleOptions["platform"]): VitePlugin {
374
+ return {
375
+ name: "mreact-router-browser-node-builtins",
376
+ enforce: "pre",
377
+ resolveId(id) {
378
+ if (platform === "browser" && nodeBuiltinSpecifiers.has(id)) {
379
+ this.error(`Browser build cannot import Node builtin ${JSON.stringify(id)}.`);
380
+ }
381
+
382
+ return undefined;
383
+ },
384
+ };
385
+ }
386
+
387
+ function routerCompatPlugin(plugin: RouterCompatPlugin): VitePlugin {
388
+ const resolvers: Array<{
389
+ filter: RegExp;
390
+ callback: Parameters<RouterCompatBuildApi["onResolve"]>[1];
391
+ }> = [];
392
+ const loaders: Array<{
393
+ filter: RegExp;
394
+ namespace?: string | undefined;
395
+ callback: Parameters<RouterCompatBuildApi["onLoad"]>[1];
396
+ }> = [];
397
+
398
+ plugin.setup({
399
+ onLoad(options, callback) {
400
+ loaders.push({ ...options, callback });
401
+ },
402
+ onResolve(options, callback) {
403
+ resolvers.push({ ...options, callback });
404
+ },
405
+ });
406
+
407
+ return {
408
+ name: plugin.name,
409
+ enforce: "pre",
410
+ async resolveId(source, importer) {
411
+ for (const resolver of resolvers) {
412
+ if (!resolver.filter.test(source)) {
413
+ continue;
414
+ }
415
+
416
+ const resolved = await resolver.callback({
417
+ importer: importer === undefined ? undefined : cleanViteId(importer),
418
+ path: source,
419
+ resolveDir: importer === undefined ? "" : dirname(cleanViteId(importer)),
420
+ });
421
+
422
+ if (resolved === undefined) {
423
+ continue;
424
+ }
425
+
426
+ if (resolved.errors !== undefined && resolved.errors.length > 0) {
427
+ this.error(resolved.errors.map((error) => error.text).join("\n"));
428
+ }
429
+
430
+ if (resolved.path === undefined) {
431
+ continue;
432
+ }
433
+
434
+ if (resolved.external === true) {
435
+ return { external: true, id: resolved.path };
436
+ }
437
+
438
+ return resolved.namespace === undefined
439
+ ? resolved.path
440
+ : compatVirtualId(resolved.namespace, resolved.path);
441
+ }
442
+
443
+ return undefined;
444
+ },
445
+ async load(id) {
446
+ const virtual = parseCompatVirtualId(id);
447
+ const path = virtual?.path ?? cleanViteId(id);
448
+ const namespace = virtual?.namespace;
449
+
450
+ for (const loader of loaders) {
451
+ if (loader.namespace !== namespace || !loader.filter.test(path)) {
452
+ continue;
453
+ }
454
+
455
+ const loaded = await loader.callback({ path });
456
+
457
+ if (loaded !== undefined) {
458
+ return loaded.contents;
459
+ }
460
+ }
461
+
462
+ return undefined;
463
+ },
464
+ };
465
+ }
466
+
467
+ function compatVirtualId(namespace: string, path: string): string {
468
+ return `\0mreact-router-compat:${encodeURIComponent(namespace)}:${encodeURIComponent(path)}`;
469
+ }
470
+
471
+ function parseCompatVirtualId(id: string): { namespace: string; path: string } | undefined {
472
+ const prefix = "\0mreact-router-compat:";
473
+
474
+ if (!id.startsWith(prefix)) {
475
+ return undefined;
476
+ }
477
+
478
+ const rest = id.slice(prefix.length);
479
+ const separator = rest.indexOf(":");
480
+
481
+ if (separator === -1) {
482
+ return undefined;
483
+ }
484
+
485
+ return {
486
+ namespace: decodeURIComponent(rest.slice(0, separator)),
487
+ path: decodeURIComponent(rest.slice(separator + 1)),
488
+ };
489
+ }
490
+
491
+ function cleanViteId(id: string): string {
492
+ const withoutPrefix = id.startsWith("/@fs/") ? id.slice("/@fs".length) : id;
493
+ const queryIndex = withoutPrefix.indexOf("?");
494
+
495
+ return queryIndex === -1 ? withoutPrefix : withoutPrefix.slice(0, queryIndex);
496
+ }
@@ -0,0 +1,35 @@
1
+ export type RouterCacheLimitName =
2
+ | "COMPOSED_ROUTE_METADATA"
3
+ | "MIDDLEWARE_MODULE"
4
+ | "ROUTE_LOADER_MODULE"
5
+ | "ROUTE_OUT_OF_ORDER_BOUNDARY_ANALYSIS"
6
+ | "ROUTE_SOURCE_ANALYSIS"
7
+ | "SERVER_ROUTE_MODULE"
8
+ | "SERVER_SOURCE_FILE"
9
+ | "SERVER_SOURCE_TRANSFORM"
10
+ | "SERVER_TRANSFORM"
11
+ | "SOURCE_MODULE";
12
+
13
+ export function resolveRouterCacheLimit(
14
+ name: RouterCacheLimitName,
15
+ defaultLimit: number,
16
+ env: Record<string, string | undefined> = process.env,
17
+ ): number {
18
+ const specificValue = parseCacheLimit(env[`MREACT_ROUTER_CACHE_${name}_MAX_ENTRIES`]);
19
+
20
+ if (specificValue !== undefined) {
21
+ return specificValue;
22
+ }
23
+
24
+ return parseCacheLimit(env.MREACT_ROUTER_CACHE_MAX_ENTRIES) ?? defaultLimit;
25
+ }
26
+
27
+ function parseCacheLimit(value: string | undefined): number | undefined {
28
+ if (value === undefined || value.trim() === "") {
29
+ return undefined;
30
+ }
31
+
32
+ const parsed = Number(value);
33
+
34
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
35
+ }
@@ -0,0 +1,54 @@
1
+ export interface RouterRuntimeCacheStat {
2
+ evictions: number;
3
+ hits: number;
4
+ maxEntries: number;
5
+ misses: number;
6
+ name: string;
7
+ size: number;
8
+ }
9
+
10
+ export interface RouterRuntimeCacheCounters {
11
+ evictions: number;
12
+ hits: number;
13
+ misses: number;
14
+ }
15
+
16
+ export function createRouterRuntimeCacheCounters(): RouterRuntimeCacheCounters {
17
+ return {
18
+ evictions: 0,
19
+ hits: 0,
20
+ misses: 0,
21
+ };
22
+ }
23
+
24
+ export function readRouterRuntimeCacheEntry<K, V>(
25
+ cache: ReadonlyMap<K, V>,
26
+ key: K,
27
+ counters: RouterRuntimeCacheCounters,
28
+ ): V | undefined {
29
+ const cached = cache.get(key);
30
+
31
+ if (cached === undefined) {
32
+ counters.misses += 1;
33
+ } else {
34
+ counters.hits += 1;
35
+ }
36
+
37
+ return cached;
38
+ }
39
+
40
+ export function routerRuntimeCacheStat(
41
+ name: string,
42
+ cache: ReadonlyMap<unknown, unknown>,
43
+ maxEntries: number,
44
+ counters: RouterRuntimeCacheCounters,
45
+ ): RouterRuntimeCacheStat {
46
+ return {
47
+ evictions: counters.evictions,
48
+ hits: counters.hits,
49
+ maxEntries,
50
+ misses: counters.misses,
51
+ name,
52
+ size: cache.size,
53
+ };
54
+ }