@reckona/mreact-router 0.0.74 → 0.0.76

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/README.md CHANGED
@@ -48,7 +48,7 @@ removal after `0.1.0`.
48
48
 
49
49
  Production client source maps are disabled by default. Set `clientSourceMaps: "linked"` to emit public `.js.map` files beside route scripts and include `sourceMappingURL` comments, or set `clientSourceMaps: "hidden"` to emit upload-only maps under `.mreact/source-maps/client/` without exposing them in the client manifest. The CLI accepts the same modes with `mreact-router build --client-source-maps=hidden`, `linked`, or `none`.
50
50
 
51
- `mreact-router dev` reads the same config, preserves route-agnostic Vite plugins and CSS settings, and uses `server.port` from `vite.config.ts` when `PORT` is not set. This keeps Vite CSS transforms, Playwright `webServer` setups, and local dev commands on the same configured port.
51
+ `mreact-router dev` reads the same config, preserves route-agnostic Vite plugins and CSS settings, and uses `server.port` from `vite.config.ts` when neither `--port` nor `PORT` is set. Use `mreact-router dev --port 15174` for one-off E2E or local port overrides without creating a separate Vite config. This keeps Vite CSS transforms, Playwright `webServer` setups, and local dev commands on the same configured port.
52
52
 
53
53
  For TypeScript projects that type-check route modules directly, include the
54
54
  app-router global declarations so route files can use `<Slot />` without a
@@ -291,6 +291,8 @@ MREACT_ROUTER_LOG=requests mreact-router dev
291
291
 
292
292
  Each line includes method, path, status, duration, and runtime. Query strings, headers, and request bodies are intentionally omitted.
293
293
 
294
+ Built Node output binds to `127.0.0.1` by default. Use `mreact-router start .mreact --host 0.0.0.0 --host-policy=strict` or `HOST=0.0.0.0 MREACT_ROUTER_HOST_POLICY=strict mreact-router start .mreact` inside containers behind explicit port publishing or a reverse proxy. This bind address is separate from Host header trust; configure `--allowed-hosts`, `MREACT_ROUTER_ALLOWED_HOSTS`, or `hostPolicy` for public deployments.
295
+
294
296
  Use `mreact-router --help`, `mreact-router help build`, or command-level help such as `mreact-router build --help` to inspect supported commands, build targets, and generated artifacts.
295
297
 
296
298
  Server-only pages can opt into the lightweight navigation runtime without becoming hydrated client routes:
@@ -307,7 +309,11 @@ export default function Page() {
307
309
 
308
310
  The build manifest records this separately from `client: true`, emits a shared navigation runtime asset, prefetches client route scripts when present, and falls back to `x-mreact-navigation: 1` HTML prefetches for server-only targets.
309
311
 
310
- For Cloudflare Workers, `mreact-router build --target=cloudflare` emits `.mreact/cloudflare/worker.mjs`, `.mreact/cloudflare/route-modules.mjs`, and per-route module chunks for non-prerendered and dynamic App Router pages plus `route.ts` server routes and metadata conventions, so deployments do not need a hand-written Worker entrypoint or Vite-only `import.meta.glob()` transforms. User Vite plugins from the project config are forwarded into router bundle builds after the `mreact-router` plugin itself is removed, so MDX-style syntax transforms and custom content loaders can participate in server, client, Cloudflare, and prerender bundles. Client assets, route stylesheet assets imported by pages/layouts/templates, and copied public assets are served only when they appear in the generated manifest allow-list. Dynamic routes resolve modules through a build-time registry keyed by `route.file`, not by constructing module ids from request input. Generated Cloudflare route modules preserve app-router layout/template shells and named slots for both string and `stream = true` pages, including route-local `<Await>` boundaries and local server-component imports. Generated Cloudflare server route modules dispatch method exports such as `GET`, `POST`, and `ALL` with decoded dynamic params plus `context.env`, `context.context`, `context.request`, and `context.route`, so handlers can use Worker bindings directly. The Cloudflare adapter marks streamed HTML with `Cache-Control: no-transform` and `Content-Encoding: identity` so Workers compression does not gzip-buffer the first shell before placeholders can paint. Pass `onResponse` to add cross-cutting response headers to rendered, static, asset, reload, not-found, and error responses. If a generated Cloudflare route module cannot produce the `data-mreact-route-id` marker contract required by client navigation, the adapter returns a reload signal for `x-mreact-navigation: 1` requests so the browser performs a normal document navigation without first buffering the full HTML response.
312
+ For Cloudflare Workers, `mreact-router build --target=cloudflare` emits `.mreact/cloudflare/worker.mjs`, `.mreact/cloudflare/route-modules.mjs`, and per-route module chunks for non-prerendered and dynamic App Router pages plus `route.ts` server routes and metadata conventions, so deployments do not need a hand-written Worker entrypoint or Vite-only `import.meta.glob()` transforms. Deploy that output with a Worker config whose `main` is `.mreact/cloudflare/worker.mjs` and whose `ASSETS` binding points at `.mreact/client`.
313
+
314
+ For Cloudflare Pages advanced mode, run `mreact-router package cloudflare-pages --from .mreact --out .mreact/pages` after `mreact-router build --target=cloudflare`, then deploy with `wrangler pages deploy .mreact/pages`. The package command creates a Pages output directory with bundled `_worker.js`, `_mreact/client/*` static route assets, root public assets, and `mreact-cloudflare-pages-artifact.json`. Pages provides the `ASSETS` binding to `_worker.js`; mreact's generated worker reads that binding and serves only manifest-listed route assets and public files. The package step bundles the generated Worker entry, route registry, route chunks, and router adapter code, so application deploy scripts do not need to copy `worker.mjs` manually or decide whether Wrangler should bundle package imports.
315
+
316
+ User Vite plugins from the project config are forwarded into router bundle builds after the `mreact-router` plugin itself is removed, so MDX-style syntax transforms and custom content loaders can participate in server, client, Cloudflare, and prerender bundles. Client assets, route stylesheet assets imported by pages/layouts/templates, and copied public assets are served only when they appear in the generated manifest allow-list. Dynamic routes resolve modules through a build-time registry keyed by `route.file`, not by constructing module ids from request input. Generated Cloudflare route modules preserve app-router layout/template shells and named slots for both string and `stream = true` pages, including route-local `<Await>` boundaries and local server-component imports. Generated Cloudflare server route modules dispatch method exports such as `GET`, `POST`, and `ALL` with decoded dynamic params plus `context.env`, `context.context`, `context.request`, and `context.route`, so handlers can use Worker bindings directly. The Cloudflare adapter marks streamed HTML with `Cache-Control: no-transform` and `Content-Encoding: identity` so Workers compression does not gzip-buffer the first shell before placeholders can paint. Pass `onResponse` to add cross-cutting response headers to rendered, static, asset, reload, not-found, and error responses. If a generated Cloudflare route module cannot produce the `data-mreact-route-id` marker contract required by client navigation, the adapter returns a reload signal for `x-mreact-navigation: 1` requests so the browser performs a normal document navigation without first buffering the full HTML response.
311
317
 
312
318
  For AWS Lambda, use `createPreloadedAwsLambdaRequestHandler()` with API Gateway
313
319
  HTTP API v2 or Lambda Function URL payload format 2.0:
package/dist/build.d.ts CHANGED
@@ -26,10 +26,24 @@ export interface AwsLambdaArtifactManifest {
26
26
  totalBytes: number;
27
27
  version: 1;
28
28
  }
29
+ export interface CloudflarePagesArtifactManifest {
30
+ files: Array<{
31
+ bytes: number;
32
+ path: string;
33
+ }>;
34
+ runtime: "cloudflare-pages";
35
+ totalBytes: number;
36
+ version: 1;
37
+ worker: "_worker.js";
38
+ }
29
39
  export interface PackageAwsLambdaArtifactOptions {
30
40
  fromDir: string;
31
41
  outDir: string;
32
42
  }
43
+ export interface PackageCloudflarePagesArtifactOptions {
44
+ fromDir: string;
45
+ outDir: string;
46
+ }
33
47
  export interface BuiltServerManifest {
34
48
  allowedSourceDirs?: readonly string[];
35
49
  assetBaseUrl?: string;
@@ -96,4 +110,5 @@ export interface BuiltPrerenderedRoute {
96
110
  }
97
111
  export declare function buildApp(options: BuildAppOptions): Promise<BuildAppResult>;
98
112
  export declare function packageAwsLambdaArtifact(options: PackageAwsLambdaArtifactOptions): Promise<AwsLambdaArtifactManifest>;
113
+ export declare function packageCloudflarePagesArtifact(options: PackageCloudflarePagesArtifactOptions): Promise<CloudflarePagesArtifactManifest>;
99
114
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
package/dist/build.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { copyFile, cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
3
3
  import { builtinModules } from "node:module";
4
- import { dirname, join, relative, sep } from "node:path";
4
+ import { dirname, join, relative, resolve, sep } from "node:path";
5
5
  import { collectStaticImportReferences, collectTopLevelValueExportNames, formatDiagnostic, hasModuleDirective, transform, } from "@reckona/mreact-compiler";
6
6
  import { transformCompilerModuleContext } from "@reckona/mreact-compiler/internal";
7
7
  import { buildClientRouteBatchOutput, buildNavigationRuntimeBundle, clientScriptForPath, compilerModuleContextForSource, collectClientRouteReferences, createClientRouteInferenceCache, detectClientNavigationHint, detectNavigationRuntimeHint, formatClientRouteInferenceDiagnostic, inferClientRouteModule, routeIdForPath, } from "./client.js";
@@ -65,7 +65,7 @@ export async function buildApp(options) {
65
65
  routes,
66
66
  vitePlugins,
67
67
  });
68
- const generatedImportPolicy = buildGeneratedImportPolicy({
68
+ const generatedImportPolicy = await buildGeneratedImportPolicy({
69
69
  files,
70
70
  projectRoot: project.projectRoot,
71
71
  routes,
@@ -257,15 +257,18 @@ const frameworkRuntimePackages = new Set([
257
257
  "@reckona/mreact-router",
258
258
  "@reckona/mreact-server",
259
259
  ]);
260
- function buildGeneratedImportPolicy(options) {
260
+ const maxRuntimePackageManifestReads = 1000;
261
+ async function buildGeneratedImportPolicy(options) {
261
262
  const routePackages = new Map();
262
263
  const allPackages = new Set();
263
264
  const relativeRoutesDir = relative(options.projectRoot, options.routesDir);
265
+ const packageJsonLookupCache = new Map();
264
266
  for (const route of options.routes) {
265
267
  const file = relative(options.projectRoot, route.file);
266
- const packages = collectRuntimePackagesForFile({
268
+ const packages = await collectRuntimePackagesForFile({
267
269
  file,
268
270
  files: options.files,
271
+ packageJsonLookupCache,
269
272
  projectRoot: options.projectRoot,
270
273
  seen: new Set(),
271
274
  });
@@ -280,9 +283,10 @@ function buildGeneratedImportPolicy(options) {
280
283
  .map((file) => (relativeRoutesDir === "" ? file : `${relativeRoutesDir}/${file}`))
281
284
  .find((file) => options.files[file] !== undefined);
282
285
  if (middlewareFile !== undefined) {
283
- const packages = collectRuntimePackagesForFile({
286
+ const packages = await collectRuntimePackagesForFile({
284
287
  file: middlewareFile,
285
288
  files: options.files,
289
+ packageJsonLookupCache,
286
290
  projectRoot: options.projectRoot,
287
291
  seen: new Set(),
288
292
  });
@@ -299,7 +303,7 @@ function buildGeneratedImportPolicy(options) {
299
303
  version: 1,
300
304
  };
301
305
  }
302
- function collectRuntimePackagesForFile(options) {
306
+ async function collectRuntimePackagesForFile(options) {
303
307
  if (options.seen.has(options.file)) {
304
308
  return [];
305
309
  }
@@ -319,14 +323,22 @@ function collectRuntimePackagesForFile(options) {
319
323
  filename: join(options.projectRoot, options.file),
320
324
  })) {
321
325
  if (isRuntimePackageSpecifier(reference.source)) {
322
- packages.add(runtimePackageNameForSpecifier(reference.source));
326
+ const packageName = runtimePackageNameForSpecifier(reference.source);
327
+ packages.add(packageName);
328
+ for (const optionalPackageName of await collectRuntimeOptionalPackages({
329
+ packageJsonLookupCache: options.packageJsonLookupCache,
330
+ packageName,
331
+ projectRoot: options.projectRoot,
332
+ })) {
333
+ packages.add(optionalPackageName);
334
+ }
323
335
  continue;
324
336
  }
325
337
  const localFile = resolveBuildLocalSourceImport(options.files, options.file, reference.source);
326
338
  if (localFile === undefined) {
327
339
  continue;
328
340
  }
329
- for (const packageName of collectRuntimePackagesForFile({
341
+ for (const packageName of await collectRuntimePackagesForFile({
330
342
  ...options,
331
343
  file: localFile,
332
344
  })) {
@@ -336,12 +348,99 @@ function collectRuntimePackagesForFile(options) {
336
348
  options.seen.delete(options.file);
337
349
  return [...packages].sort();
338
350
  }
351
+ async function collectRuntimeOptionalPackages(options) {
352
+ const optionalPackages = new Set();
353
+ const seenPackageJson = new Set();
354
+ const queue = [
355
+ { packageName: options.packageName, optional: false, startDir: options.projectRoot },
356
+ ];
357
+ for (let index = 0; index < queue.length && seenPackageJson.size < maxRuntimePackageManifestReads; index += 1) {
358
+ const item = queue[index];
359
+ if (item === undefined || !isValidRuntimePackageName(item.packageName)) {
360
+ continue;
361
+ }
362
+ const manifest = await readRuntimePackageManifest({
363
+ cache: options.packageJsonLookupCache,
364
+ packageName: item.packageName,
365
+ startDir: item.startDir,
366
+ });
367
+ if (manifest === undefined || seenPackageJson.has(manifest.packageJsonPath)) {
368
+ continue;
369
+ }
370
+ seenPackageJson.add(manifest.packageJsonPath);
371
+ if (item.optional && isRuntimePackageName(item.packageName)) {
372
+ optionalPackages.add(item.packageName);
373
+ }
374
+ const packageDir = dirname(manifest.packageJsonPath);
375
+ for (const dependencyName of Object.keys(manifest.dependencies ?? {})) {
376
+ if (isValidRuntimePackageName(dependencyName)) {
377
+ queue.push({ packageName: dependencyName, optional: false, startDir: packageDir });
378
+ }
379
+ }
380
+ for (const optionalDependencyName of Object.keys(manifest.optionalDependencies ?? {})) {
381
+ if (isValidRuntimePackageName(optionalDependencyName)) {
382
+ queue.push({ packageName: optionalDependencyName, optional: true, startDir: packageDir });
383
+ }
384
+ }
385
+ }
386
+ return [...optionalPackages].sort();
387
+ }
388
+ async function readRuntimePackageManifest(options) {
389
+ const key = `${options.startDir}\0${options.packageName}`;
390
+ const cached = options.cache.get(key);
391
+ const manifest = cached ??
392
+ findRuntimePackageJson(options.packageName, options.startDir).then(async (packageJsonPath) => {
393
+ if (packageJsonPath === undefined) {
394
+ return undefined;
395
+ }
396
+ try {
397
+ const json = JSON.parse(await readFile(packageJsonPath, "utf8"));
398
+ return { ...json, packageJsonPath };
399
+ }
400
+ catch {
401
+ return undefined;
402
+ }
403
+ });
404
+ if (cached === undefined) {
405
+ options.cache.set(key, manifest);
406
+ }
407
+ return (await manifest);
408
+ }
409
+ async function findRuntimePackageJson(packageName, startDir) {
410
+ if (!isValidRuntimePackageName(packageName)) {
411
+ return undefined;
412
+ }
413
+ let current = startDir;
414
+ while (true) {
415
+ const candidate = join(current, "node_modules", ...packageName.split("/"), "package.json");
416
+ try {
417
+ if ((await stat(candidate)).isFile()) {
418
+ return candidate;
419
+ }
420
+ }
421
+ catch {
422
+ // Keep walking toward the filesystem root.
423
+ }
424
+ const parent = dirname(current);
425
+ if (parent === current) {
426
+ return undefined;
427
+ }
428
+ current = parent;
429
+ }
430
+ }
339
431
  function isRuntimePackageSpecifier(specifier) {
340
432
  if (specifier.startsWith(".") || specifier.startsWith("/") || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(specifier)) {
341
433
  return false;
342
434
  }
343
435
  const packageName = runtimePackageNameForSpecifier(specifier);
344
- return !nodeBuiltinPackages.has(specifier) && !frameworkRuntimePackages.has(packageName);
436
+ return !nodeBuiltinPackages.has(specifier) && isRuntimePackageName(packageName);
437
+ }
438
+ function isRuntimePackageName(packageName) {
439
+ return !frameworkRuntimePackages.has(packageName);
440
+ }
441
+ function isValidRuntimePackageName(packageName) {
442
+ return (/^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/u.test(packageName) &&
443
+ !packageName.includes(".."));
345
444
  }
346
445
  function runtimePackageNameForSpecifier(specifier) {
347
446
  if (!specifier.startsWith("@")) {
@@ -1680,6 +1779,10 @@ function cloudflareWorkspaceRuntimePlugin() {
1680
1779
  ["@reckona/mreact-compat/scheduler", packageFile("react-compat", "@reckona/mreact-compat", "scheduler")],
1681
1780
  ["@reckona/mreact-query", packageFile("query", "@reckona/mreact-query", "index")],
1682
1781
  ["@reckona/mreact-reactive-core", packageFile("reactive-core", "@reckona/mreact-reactive-core", "index")],
1782
+ [
1783
+ "@reckona/mreact-router/adapters/cloudflare",
1784
+ packageFile("router", "@reckona/mreact-router", "adapters/cloudflare"),
1785
+ ],
1683
1786
  ["@reckona/mreact-router/link", routerLinkPath],
1684
1787
  ["@reckona/mreact-router/runtime-state", routerRuntimeStatePath],
1685
1788
  ["@reckona/mreact-router/session", packageFile("router", "@reckona/mreact-router", "session")],
@@ -1997,7 +2100,7 @@ export async function packageAwsLambdaArtifact(options) {
1997
2100
  outDir: options.outDir,
1998
2101
  });
1999
2102
  await writeFile(join(options.outDir, "mreact-handler.mjs"), awsLambdaHandlerSource(".mreact"));
2000
- const files = await collectAwsLambdaArtifactFiles(options.outDir, "");
2103
+ const files = await collectArtifactFiles(options.outDir, "");
2001
2104
  const manifest = {
2002
2105
  files,
2003
2106
  handler: "mreact-handler.handler",
@@ -2008,6 +2111,48 @@ export async function packageAwsLambdaArtifact(options) {
2008
2111
  await writeFile(join(options.outDir, "mreact-lambda-artifact.json"), JSON.stringify(manifest, null, 2));
2009
2112
  return manifest;
2010
2113
  }
2114
+ export async function packageCloudflarePagesArtifact(options) {
2115
+ const clientManifestPath = join(options.fromDir, "client", "manifest.json");
2116
+ const workerPath = join(options.fromDir, "cloudflare", "worker.mjs");
2117
+ await assertRequiredBuildFile(clientManifestPath);
2118
+ await assertRequiredBuildFile(workerPath);
2119
+ await assertRequiredBuildFile(join(options.fromDir, "cloudflare", "route-modules.mjs"));
2120
+ assertPackageOutputDoesNotReplaceBuildOutput(options);
2121
+ const clientManifest = JSON.parse(await readFile(clientManifestPath, "utf8"));
2122
+ await rm(options.outDir, { force: true, recursive: true });
2123
+ await mkdir(options.outDir, { recursive: true });
2124
+ await cp(join(options.fromDir, "client"), join(options.outDir, "_mreact", "client"), {
2125
+ force: true,
2126
+ recursive: true,
2127
+ });
2128
+ await copyCloudflarePagesPublicAssets({
2129
+ clientDir: join(options.fromDir, "client"),
2130
+ outDir: options.outDir,
2131
+ publicAssets: clientManifest.publicAssets ?? [],
2132
+ });
2133
+ const bundledWorker = await bundleRouterModule({
2134
+ code: await readFile(workerPath, "utf8"),
2135
+ filename: workerPath,
2136
+ minify: true,
2137
+ modulePreload: false,
2138
+ outfile: "_worker.js",
2139
+ platform: "browser",
2140
+ plugins: [cloudflareWorkspaceRuntimePlugin()],
2141
+ preserveExports: true,
2142
+ target: "es2022",
2143
+ });
2144
+ await writeFile(join(options.outDir, "_worker.js"), bundledWorker.code);
2145
+ const files = await collectArtifactFiles(options.outDir, "");
2146
+ const manifest = {
2147
+ files,
2148
+ runtime: "cloudflare-pages",
2149
+ totalBytes: files.reduce((total, file) => total + file.bytes, 0),
2150
+ version: 1,
2151
+ worker: "_worker.js",
2152
+ };
2153
+ await writeFile(join(options.outDir, "mreact-cloudflare-pages-artifact.json"), JSON.stringify(manifest, null, 2));
2154
+ return manifest;
2155
+ }
2011
2156
  async function assertRequiredBuildFile(path) {
2012
2157
  try {
2013
2158
  const info = await stat(path);
@@ -2020,6 +2165,21 @@ async function assertRequiredBuildFile(path) {
2020
2165
  }
2021
2166
  throw new Error(`Missing required mreact build artifact: ${path}`);
2022
2167
  }
2168
+ function assertPackageOutputDoesNotReplaceBuildOutput(options) {
2169
+ if (resolve(options.fromDir) === resolve(options.outDir)) {
2170
+ throw new Error("Package output directory must be different from the mreact build output directory.");
2171
+ }
2172
+ }
2173
+ async function copyCloudflarePagesPublicAssets(options) {
2174
+ for (const asset of options.publicAssets) {
2175
+ if (!asset.startsWith("/") || asset.startsWith("//") || asset.includes("..")) {
2176
+ continue;
2177
+ }
2178
+ const relativeAsset = asset.slice(1);
2179
+ await mkdir(dirname(join(options.outDir, relativeAsset)), { recursive: true });
2180
+ await copyFile(join(options.clientDir, relativeAsset), join(options.outDir, relativeAsset));
2181
+ }
2182
+ }
2023
2183
  async function copyAwsLambdaProjectMetadata(options) {
2024
2184
  for (const file of [
2025
2185
  "package.json",
@@ -2039,14 +2199,14 @@ async function copyAwsLambdaProjectMetadata(options) {
2039
2199
  }
2040
2200
  }
2041
2201
  }
2042
- async function collectAwsLambdaArtifactFiles(rootDir, relativeDir) {
2202
+ async function collectArtifactFiles(rootDir, relativeDir) {
2043
2203
  const entries = await readdir(join(rootDir, relativeDir), { withFileTypes: true });
2044
2204
  const files = [];
2045
2205
  for (const entry of entries) {
2046
2206
  const relativePath = relativeDir === "" ? entry.name : `${relativeDir}/${entry.name}`;
2047
2207
  const absolutePath = join(rootDir, relativePath);
2048
2208
  if (entry.isDirectory()) {
2049
- files.push(...(await collectAwsLambdaArtifactFiles(rootDir, relativePath)));
2209
+ files.push(...(await collectArtifactFiles(rootDir, relativePath)));
2050
2210
  continue;
2051
2211
  }
2052
2212
  if (entry.isFile()) {