@reckona/mreact-router 0.0.97 → 0.0.99

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 (92) hide show
  1. package/README.md +4 -0
  2. package/dist/actions.d.ts +7 -0
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +26 -2
  5. package/dist/actions.js.map +1 -1
  6. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  7. package/dist/adapters/aws-lambda.js +10 -7
  8. package/dist/adapters/aws-lambda.js.map +1 -1
  9. package/dist/build.d.ts +38 -1
  10. package/dist/build.d.ts.map +1 -1
  11. package/dist/build.js +1087 -304
  12. package/dist/build.js.map +1 -1
  13. package/dist/bundle-pipeline.d.ts +7 -0
  14. package/dist/bundle-pipeline.d.ts.map +1 -1
  15. package/dist/bundle-pipeline.js +121 -87
  16. package/dist/bundle-pipeline.js.map +1 -1
  17. package/dist/cache.d.ts.map +1 -1
  18. package/dist/cache.js +10 -8
  19. package/dist/cache.js.map +1 -1
  20. package/dist/cli-options.d.ts +1 -0
  21. package/dist/cli-options.d.ts.map +1 -1
  22. package/dist/cli-options.js +16 -0
  23. package/dist/cli-options.js.map +1 -1
  24. package/dist/cli.js +1 -0
  25. package/dist/cli.js.map +1 -1
  26. package/dist/client.d.ts +6 -1
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +113 -8
  29. package/dist/client.js.map +1 -1
  30. package/dist/csrf.d.ts.map +1 -1
  31. package/dist/csrf.js +7 -3
  32. package/dist/csrf.js.map +1 -1
  33. package/dist/http.d.ts.map +1 -1
  34. package/dist/http.js +18 -1
  35. package/dist/http.js.map +1 -1
  36. package/dist/import-policy.d.ts.map +1 -1
  37. package/dist/import-policy.js +7 -3
  38. package/dist/import-policy.js.map +1 -1
  39. package/dist/index.d.ts +4 -3
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/link.d.ts +5 -2
  43. package/dist/link.d.ts.map +1 -1
  44. package/dist/link.js +92 -3
  45. package/dist/link.js.map +1 -1
  46. package/dist/logger.d.ts +7 -1
  47. package/dist/logger.d.ts.map +1 -1
  48. package/dist/logger.js.map +1 -1
  49. package/dist/module-runner.d.ts +2 -0
  50. package/dist/module-runner.d.ts.map +1 -1
  51. package/dist/module-runner.js +1 -0
  52. package/dist/module-runner.js.map +1 -1
  53. package/dist/native-route-matcher.d.ts +2 -1
  54. package/dist/native-route-matcher.d.ts.map +1 -1
  55. package/dist/native-route-matcher.js +13 -2
  56. package/dist/native-route-matcher.js.map +1 -1
  57. package/dist/render.d.ts.map +1 -1
  58. package/dist/render.js +117 -2
  59. package/dist/render.js.map +1 -1
  60. package/dist/route-source.d.ts +4 -4
  61. package/dist/route-source.d.ts.map +1 -1
  62. package/dist/route-source.js +8 -8
  63. package/dist/route-source.js.map +1 -1
  64. package/dist/route-styles.d.ts +6 -0
  65. package/dist/route-styles.d.ts.map +1 -1
  66. package/dist/route-styles.js +10 -1
  67. package/dist/route-styles.js.map +1 -1
  68. package/dist/serve.d.ts +5 -0
  69. package/dist/serve.d.ts.map +1 -1
  70. package/dist/serve.js +7 -4
  71. package/dist/serve.js.map +1 -1
  72. package/package.json +11 -11
  73. package/src/actions.ts +42 -2
  74. package/src/adapters/aws-lambda.ts +33 -16
  75. package/src/build.ts +1578 -397
  76. package/src/bundle-pipeline.ts +136 -88
  77. package/src/cache.ts +13 -8
  78. package/src/cli-options.ts +20 -0
  79. package/src/cli.ts +1 -0
  80. package/src/client.ts +145 -14
  81. package/src/csrf.ts +8 -3
  82. package/src/http.ts +21 -1
  83. package/src/import-policy.ts +8 -3
  84. package/src/index.ts +5 -0
  85. package/src/link.ts +129 -8
  86. package/src/logger.ts +9 -1
  87. package/src/module-runner.ts +4 -0
  88. package/src/native-route-matcher.ts +13 -2
  89. package/src/render.ts +177 -2
  90. package/src/route-source.ts +8 -8
  91. package/src/route-styles.ts +17 -1
  92. package/src/serve.ts +11 -6
@@ -12,6 +12,9 @@ import { workspacePackageFile } from "./workspace-packages.js";
12
12
 
13
13
  export interface RouterBundleOptions {
14
14
  base?: string | undefined;
15
+ cache?: Map<string, Promise<RouterBundleOutput>> | undefined;
16
+ cacheDir?: string | undefined;
17
+ cacheKey?: string | undefined;
15
18
  code: string;
16
19
  define?: Record<string, string> | undefined;
17
20
  dropConsoleFunctions?: readonly string[] | undefined;
@@ -22,6 +25,7 @@ export interface RouterBundleOptions {
22
25
  outfile?: string | undefined;
23
26
  platform: "browser" | "node";
24
27
  preserveExports?: boolean | undefined;
28
+ root?: string | undefined;
25
29
  plugins?: readonly RouterCompatPlugin[] | undefined;
26
30
  vitePlugins?: readonly PluginOption[] | undefined;
27
31
  sourceMap?: boolean | undefined;
@@ -30,9 +34,12 @@ export interface RouterBundleOptions {
30
34
 
31
35
  export interface RouterBundleModulesOptions {
32
36
  base?: string | undefined;
37
+ cacheDir?: string | undefined;
38
+ chunkFileNames?: string | undefined;
33
39
  define?: Record<string, string> | undefined;
34
40
  dropConsoleFunctions?: readonly string[] | undefined;
35
41
  entries: readonly RouterBundleEntryOptions[];
42
+ entryFileNames?: string | undefined;
36
43
  minify?: boolean | undefined;
37
44
  platform: "browser" | "node";
38
45
  plugins?: readonly RouterCompatPlugin[] | undefined;
@@ -130,14 +137,122 @@ export type RouterCompatResolveResult =
130
137
  | undefined;
131
138
 
132
139
  const nodeBuiltinSpecifiers = new Set(builtinModules.flatMap((name) => [name, `node:${name}`]));
140
+ const mreactJsxRuntimeAliasPaths = new Map([
141
+ [
142
+ "react",
143
+ workspacePackageFile({
144
+ currentFileUrl: import.meta.url,
145
+ entry: "index",
146
+ monorepoDir: "react-compat",
147
+ packageName: "@reckona/mreact-compat",
148
+ }),
149
+ ],
150
+ [
151
+ "react-dom",
152
+ workspacePackageFile({
153
+ currentFileUrl: import.meta.url,
154
+ entry: "index",
155
+ monorepoDir: "react-compat",
156
+ packageName: "@reckona/mreact-compat",
157
+ }),
158
+ ],
159
+ [
160
+ "react-dom/client",
161
+ workspacePackageFile({
162
+ currentFileUrl: import.meta.url,
163
+ entry: "index",
164
+ monorepoDir: "react-compat",
165
+ packageName: "@reckona/mreact-compat",
166
+ }),
167
+ ],
168
+ [
169
+ "react-dom/server",
170
+ workspacePackageFile({
171
+ currentFileUrl: import.meta.url,
172
+ entry: "index",
173
+ monorepoDir: "react-compat",
174
+ packageName: "@reckona/mreact-compat",
175
+ }),
176
+ ],
177
+ [
178
+ "react/jsx-dev-runtime",
179
+ workspacePackageFile({
180
+ currentFileUrl: import.meta.url,
181
+ entry: "jsx-dev-runtime",
182
+ monorepoDir: "react-compat",
183
+ packageName: "@reckona/mreact-compat",
184
+ }),
185
+ ],
186
+ [
187
+ "react/jsx-runtime",
188
+ workspacePackageFile({
189
+ currentFileUrl: import.meta.url,
190
+ entry: "jsx-runtime",
191
+ monorepoDir: "react-compat",
192
+ packageName: "@reckona/mreact-compat",
193
+ }),
194
+ ],
195
+ [
196
+ "@reckona/mreact/jsx-dev-runtime",
197
+ workspacePackageFile({
198
+ currentFileUrl: import.meta.url,
199
+ entry: "jsx-dev-runtime",
200
+ monorepoDir: "react",
201
+ packageName: "@reckona/mreact",
202
+ }),
203
+ ],
204
+ [
205
+ "@reckona/mreact/jsx-runtime",
206
+ workspacePackageFile({
207
+ currentFileUrl: import.meta.url,
208
+ entry: "jsx-runtime",
209
+ monorepoDir: "react",
210
+ packageName: "@reckona/mreact",
211
+ }),
212
+ ],
213
+ [
214
+ "@reckona/mreact-compat/hooks",
215
+ workspacePackageFile({
216
+ currentFileUrl: import.meta.url,
217
+ entry: "hooks-entry",
218
+ monorepoDir: "react-compat",
219
+ packageName: "@reckona/mreact-compat",
220
+ }),
221
+ ],
222
+ ]);
133
223
 
134
224
  export async function bundleRouterModule(
135
225
  options: RouterBundleOptions,
226
+ ): Promise<RouterBundleOutput> {
227
+ if (options.cache !== undefined && options.cacheKey !== undefined) {
228
+ const cached = options.cache.get(options.cacheKey);
229
+
230
+ if (cached !== undefined) {
231
+ return cloneRouterBundleOutput(await cached);
232
+ }
233
+
234
+ const pending = bundleRouterModuleUncached(options);
235
+ options.cache.set(options.cacheKey, pending);
236
+
237
+ try {
238
+ return cloneRouterBundleOutput(await pending);
239
+ } catch (error) {
240
+ options.cache.delete(options.cacheKey);
241
+ throw error;
242
+ }
243
+ }
244
+
245
+ return await bundleRouterModuleUncached(options);
246
+ }
247
+
248
+ async function bundleRouterModuleUncached(
249
+ options: RouterBundleOptions,
136
250
  ): Promise<RouterBundleOutput> {
137
251
  const entryId = `${options.filename}?mreact-router-entry`;
138
252
  const outfile = options.outfile ?? "entry.js";
139
253
  const config = {
140
254
  base: options.base ?? "/",
255
+ ...(options.cacheDir === undefined ? {} : { cacheDir: options.cacheDir }),
141
256
  configFile: false,
142
257
  ...(options.define === undefined ? {} : { define: options.define }),
143
258
  logLevel: "silent",
@@ -157,7 +272,7 @@ export async function bundleRouterModule(
157
272
  ...(options.plugins ?? []).map(routerCompatPlugin),
158
273
  ],
159
274
  publicDir: false,
160
- root: dirname(options.filename),
275
+ root: options.root ?? dirname(options.filename),
161
276
  ssr: {
162
277
  noExternal: true,
163
278
  },
@@ -218,6 +333,22 @@ export async function bundleRouterModule(
218
333
  };
219
334
  }
220
335
 
336
+ function cloneRouterBundleOutput(output: RouterBundleOutput): RouterBundleOutput {
337
+ return {
338
+ ...(output.assets === undefined
339
+ ? {}
340
+ : {
341
+ assets: output.assets.map((asset) => ({
342
+ fileName: asset.fileName,
343
+ source:
344
+ typeof asset.source === "string" ? asset.source : new Uint8Array(asset.source),
345
+ })),
346
+ }),
347
+ code: output.code,
348
+ ...(output.map === undefined ? {} : { map: output.map }),
349
+ };
350
+ }
351
+
221
352
  export async function bundleRouterModules(
222
353
  options: RouterBundleModulesOptions,
223
354
  ): Promise<RouterBundleModulesOutput> {
@@ -236,6 +367,7 @@ export async function bundleRouterModules(
236
367
  );
237
368
  const config = {
238
369
  base: options.base ?? "/",
370
+ ...(options.cacheDir === undefined ? {} : { cacheDir: options.cacheDir }),
239
371
  configFile: false,
240
372
  ...(options.define === undefined ? {} : { define: options.define }),
241
373
  logLevel: "silent",
@@ -262,8 +394,8 @@ export async function bundleRouterModules(
262
394
  input,
263
395
  ...routerBundleTreeshakeOptions(options.dropConsoleFunctions),
264
396
  output: {
265
- chunkFileNames: "assets/chunks/[name].[hash].js",
266
- entryFileNames: "assets/routes/[name].[hash].js",
397
+ chunkFileNames: options.chunkFileNames ?? "assets/chunks/[name].[hash].js",
398
+ entryFileNames: options.entryFileNames ?? "assets/routes/[name].[hash].js",
267
399
  format: "es",
268
400
  hashCharacters: "hex",
269
401
  },
@@ -315,95 +447,11 @@ export async function bundleRouterModules(
315
447
  }
316
448
 
317
449
  function mreactJsxRuntimeAliasPlugin(): VitePlugin {
318
- const runtimePaths = new Map([
319
- [
320
- "react",
321
- workspacePackageFile({
322
- currentFileUrl: import.meta.url,
323
- entry: "index",
324
- monorepoDir: "react-compat",
325
- packageName: "@reckona/mreact-compat",
326
- }),
327
- ],
328
- [
329
- "react-dom",
330
- workspacePackageFile({
331
- currentFileUrl: import.meta.url,
332
- entry: "index",
333
- monorepoDir: "react-compat",
334
- packageName: "@reckona/mreact-compat",
335
- }),
336
- ],
337
- [
338
- "react-dom/client",
339
- workspacePackageFile({
340
- currentFileUrl: import.meta.url,
341
- entry: "index",
342
- monorepoDir: "react-compat",
343
- packageName: "@reckona/mreact-compat",
344
- }),
345
- ],
346
- [
347
- "react-dom/server",
348
- workspacePackageFile({
349
- currentFileUrl: import.meta.url,
350
- entry: "index",
351
- monorepoDir: "react-compat",
352
- packageName: "@reckona/mreact-compat",
353
- }),
354
- ],
355
- [
356
- "react/jsx-dev-runtime",
357
- workspacePackageFile({
358
- currentFileUrl: import.meta.url,
359
- entry: "jsx-dev-runtime",
360
- monorepoDir: "react-compat",
361
- packageName: "@reckona/mreact-compat",
362
- }),
363
- ],
364
- [
365
- "react/jsx-runtime",
366
- workspacePackageFile({
367
- currentFileUrl: import.meta.url,
368
- entry: "jsx-runtime",
369
- monorepoDir: "react-compat",
370
- packageName: "@reckona/mreact-compat",
371
- }),
372
- ],
373
- [
374
- "@reckona/mreact/jsx-dev-runtime",
375
- workspacePackageFile({
376
- currentFileUrl: import.meta.url,
377
- entry: "jsx-dev-runtime",
378
- monorepoDir: "react",
379
- packageName: "@reckona/mreact",
380
- }),
381
- ],
382
- [
383
- "@reckona/mreact/jsx-runtime",
384
- workspacePackageFile({
385
- currentFileUrl: import.meta.url,
386
- entry: "jsx-runtime",
387
- monorepoDir: "react",
388
- packageName: "@reckona/mreact",
389
- }),
390
- ],
391
- [
392
- "@reckona/mreact-compat/hooks",
393
- workspacePackageFile({
394
- currentFileUrl: import.meta.url,
395
- entry: "hooks-entry",
396
- monorepoDir: "react-compat",
397
- packageName: "@reckona/mreact-compat",
398
- }),
399
- ],
400
- ]);
401
-
402
450
  return {
403
451
  name: "mreact-router-jsx-runtime-alias",
404
452
  enforce: "pre",
405
453
  resolveId(id) {
406
- return runtimePaths.get(id);
454
+ return mreactJsxRuntimeAliasPaths.get(id);
407
455
  },
408
456
  };
409
457
  }
package/src/cache.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
1
3
  export interface RouteCachePolicy {
2
4
  cacheControl: string;
3
5
  revalidateSeconds: number;
@@ -35,6 +37,7 @@ interface AppRouterCacheState {
35
37
  activeContexts: RouteCacheContext[];
36
38
  invalidatedPaths: Set<string>;
37
39
  memoryCache: AppRouterCache;
40
+ storage: AsyncLocalStorage<RouteCacheContext>;
38
41
  }
39
42
 
40
43
  interface RouteCacheContext {
@@ -49,7 +52,9 @@ const cacheState = ((
49
52
  activeContexts: [],
50
53
  invalidatedPaths: new Set(),
51
54
  memoryCache: createMemoryRouteCache(),
55
+ storage: new AsyncLocalStorage<RouteCacheContext>(),
52
56
  });
57
+ cacheState.storage ??= new AsyncLocalStorage<RouteCacheContext>();
53
58
 
54
59
  export function createMemoryRouteCache(options: MemoryRouteCacheOptions = {}): AppRouterCache {
55
60
  const maxEntries = positiveIntegerOrDefault(options.maxEntries, 10_000);
@@ -188,7 +193,7 @@ export function routeCachePolicyFromSource(code: string): RouteCachePolicy | und
188
193
  }
189
194
 
190
195
  export function cacheControl(options: CacheControlOptions): void {
191
- const activeContext = cacheState.activeContexts.at(-1);
196
+ const activeContext = activeRouteCacheContext();
192
197
 
193
198
  if (activeContext === undefined) {
194
199
  throw new Error("cacheControl() must be called during an app router request.");
@@ -291,7 +296,7 @@ export async function cacheRouteResponse(options: {
291
296
 
292
297
  export function revalidatePath(path: string): void {
293
298
  const normalizedPath = normalizeRevalidationPath(path);
294
- const activeContext = cacheState.activeContexts.at(-1);
299
+ const activeContext = activeRouteCacheContext();
295
300
 
296
301
  if (activeContext !== undefined) {
297
302
  activeContext.revalidatedPaths.add(normalizedPath);
@@ -324,9 +329,7 @@ export async function withRouteCacheContext<T>(
324
329
  revalidatedPaths: new Set(),
325
330
  };
326
331
 
327
- cacheState.activeContexts.push(context);
328
-
329
- try {
332
+ return cacheState.storage.run(context, async () => {
330
333
  const value = await fn();
331
334
  const cachePolicy = context.cachePolicy;
332
335
  const revalidatedPaths = Array.from(context.revalidatedPaths);
@@ -336,9 +339,7 @@ export async function withRouteCacheContext<T>(
336
339
  }
337
340
 
338
341
  return { cachePolicy, revalidatedPaths, value };
339
- } finally {
340
- cacheState.activeContexts.pop();
341
- }
342
+ });
342
343
  }
343
344
 
344
345
  export function beginRouteCacheContext(cache: AppRouterCache | undefined): {
@@ -373,6 +374,10 @@ export function beginRouteCacheContext(cache: AppRouterCache | undefined): {
373
374
  };
374
375
  }
375
376
 
377
+ function activeRouteCacheContext(): RouteCacheContext | undefined {
378
+ return cacheState.storage.getStore() ?? cacheState.activeContexts.at(-1);
379
+ }
380
+
376
381
  // Host is excluded from the cache key to prevent attacker-supplied Host
377
382
  // headers from fragmenting / poisoning the cache (Issue 068). The Vary
378
383
  // dimension is the request path + query; same-origin reverse proxies are
@@ -13,6 +13,7 @@ export interface ParsedCliArguments {
13
13
  command: string;
14
14
  allowedHosts?: readonly string[] | undefined;
15
15
  from?: string | undefined;
16
+ handler?: string | undefined;
16
17
  help?: boolean | undefined;
17
18
  host?: string | undefined;
18
19
  hostPolicy?: RequestHostPolicy | undefined;
@@ -120,6 +121,17 @@ export function parseCliArguments(argv: readonly string[]): ParsedCliArguments {
120
121
  continue;
121
122
  }
122
123
 
124
+ if (value === "--handler") {
125
+ parsed.handler = readOptionValue(argv, index, "handler");
126
+ index += 1;
127
+ continue;
128
+ }
129
+
130
+ if (value.startsWith("--handler=")) {
131
+ parsed.handler = value.slice("--handler=".length);
132
+ continue;
133
+ }
134
+
123
135
  if (value.startsWith("--log=")) {
124
136
  parsed.log = parseCliRequestLogMode(value.slice("--log=".length));
125
137
  continue;
@@ -202,6 +214,8 @@ export function formatCliHelp(command?: string | undefined): string {
202
214
  " --out <dir> Output directory. Defaults to .lambda for aws-lambda and .mreact/pages for cloudflare-pages.",
203
215
  " --skip-runtime-dependency-check",
204
216
  " For aws-lambda only, skip the production node_modules check when a later deploy step installs dependencies into the package directory.",
217
+ " --handler <entry>",
218
+ " For aws-lambda only, bundle a custom handler entry into mreact-handler.mjs. App-local extensionless TypeScript imports are bundled; package imports stay external.",
205
219
  " -h, --help Show this help message.",
206
220
  "",
207
221
  "Examples:",
@@ -261,6 +275,8 @@ export function formatCliHelp(command?: string | undefined): string {
261
275
  " start [outDir] Serve built Node output.",
262
276
  " package aws-lambda --from .mreact --out .lambda",
263
277
  " Package a minimal AWS Lambda asset directory.",
278
+ " package aws-lambda --handler lambda/mreact-handler.ts",
279
+ " Bundle a custom Lambda handler with app-local server imports.",
264
280
  " package cloudflare-pages --from .mreact --out .mreact/pages",
265
281
  " Package Cloudflare Pages advanced mode output.",
266
282
  " help [command] Show help.",
@@ -449,5 +465,9 @@ function formatCliRequestLogEvent(event: AppRouterLogEvent): string {
449
465
  return `[mreact] ${event.method} ${event.path} ${event.status} render timing`;
450
466
  }
451
467
 
468
+ if (event.type === "router:csp:inline-nonce-warning") {
469
+ return `[mreact] ${event.path} CSP ${event.directive} will block inline <${event.tag}> without a matching nonce`;
470
+ }
471
+
452
472
  return `[mreact] ${event.method} ${event.path} ${event.runtime}`;
453
473
  }
package/src/cli.ts CHANGED
@@ -58,6 +58,7 @@ if (parsed !== undefined) {
58
58
  if (routeArg === "aws-lambda") {
59
59
  const manifest = await packageAwsLambdaArtifact({
60
60
  fromDir: resolve(parsed.from ?? ".mreact"),
61
+ ...(parsed.handler === undefined ? {} : { handlerEntry: resolve(parsed.handler) }),
61
62
  outDir: resolve(parsed.out ?? ".lambda"),
62
63
  skipRuntimeDependencyCheck: parsed.skipRuntimeDependencyCheck,
63
64
  });