@rangojs/router 0.0.0-experimental.103 → 0.0.0-experimental.105
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 +4 -4
- package/dist/vite/index.js +181 -34
- package/package.json +3 -2
- package/skills/host-router/SKILL.md +45 -20
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/vite/discovery/discover-routers.ts +20 -22
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/state.ts +17 -0
- package/src/vite/rango.ts +16 -4
- package/src/vite/router-discovery.ts +34 -2
- package/src/vite/utils/forward-user-plugins.ts +164 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
package/README.md
CHANGED
|
@@ -943,9 +943,9 @@ import { createHostRouter } from "@rangojs/router/host";
|
|
|
943
943
|
|
|
944
944
|
const hostRouter = createHostRouter();
|
|
945
945
|
|
|
946
|
-
hostRouter.host(["*.localhost"]).
|
|
947
|
-
hostRouter.host(["localhost"]).
|
|
948
|
-
hostRouter.fallback().
|
|
946
|
+
hostRouter.host(["*.localhost"]).lazy(() => import("./apps/admin/handler.js"));
|
|
947
|
+
hostRouter.host(["localhost"]).lazy(() => import("./apps/site/handler.js"));
|
|
948
|
+
hostRouter.fallback().lazy(() => import("./apps/site/handler.js"));
|
|
949
949
|
|
|
950
950
|
export default {
|
|
951
951
|
async fetch(request, env, ctx) {
|
|
@@ -954,7 +954,7 @@ export default {
|
|
|
954
954
|
};
|
|
955
955
|
```
|
|
956
956
|
|
|
957
|
-
|
|
957
|
+
Use `.lazy(() => import("./sub-app"))` to mount a lazily-imported sub-app (a module whose `default` export is a handler or nested host router), and `.map((request) => Response)` for an inline request handler. Only `.lazy()` mounts are imported during build-time discovery; `.map(() => import(...))` is a type error. Each sub-app has its own `createRouter()` and `urls()`. Patterns are matched in registration order — register more specific patterns (subdomains) before catch-alls.
|
|
958
958
|
|
|
959
959
|
## Meta Tags
|
|
960
960
|
|
package/dist/vite/index.js
CHANGED
|
@@ -2040,7 +2040,7 @@ import { resolve } from "node:path";
|
|
|
2040
2040
|
// package.json
|
|
2041
2041
|
var package_default = {
|
|
2042
2042
|
name: "@rangojs/router",
|
|
2043
|
-
version: "0.0.0-experimental.
|
|
2043
|
+
version: "0.0.0-experimental.105",
|
|
2044
2044
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
2045
2045
|
keywords: [
|
|
2046
2046
|
"react",
|
|
@@ -2205,7 +2205,8 @@ var package_default = {
|
|
|
2205
2205
|
peerDependencies: {
|
|
2206
2206
|
"@cloudflare/vite-plugin": "^1.25.0",
|
|
2207
2207
|
"@vitejs/plugin-rsc": "^0.5.23",
|
|
2208
|
-
react: "
|
|
2208
|
+
react: ">=19.2.6 <20",
|
|
2209
|
+
"react-dom": ">=19.2.6 <20",
|
|
2209
2210
|
vite: "^7.3.0"
|
|
2210
2211
|
},
|
|
2211
2212
|
peerDependenciesMeta: {
|
|
@@ -3777,6 +3778,8 @@ function createDiscoveryState(entryPath, opts) {
|
|
|
3777
3778
|
projectRoot: "",
|
|
3778
3779
|
isBuildMode: false,
|
|
3779
3780
|
userResolveAlias: void 0,
|
|
3781
|
+
userRunnerConfig: void 0,
|
|
3782
|
+
userResolvePlugins: [],
|
|
3780
3783
|
scanFilter: void 0,
|
|
3781
3784
|
cachedRouterFiles: void 0,
|
|
3782
3785
|
opts,
|
|
@@ -3839,9 +3842,12 @@ function checkSelfGenWrite(state, filePath, consume) {
|
|
|
3839
3842
|
|
|
3840
3843
|
// src/vite/utils/manifest-utils.ts
|
|
3841
3844
|
function flattenLeafEntries(prefixTree, routeManifest, result) {
|
|
3842
|
-
function visit(node) {
|
|
3845
|
+
function visit(node, ancestorStaticPrefixes) {
|
|
3843
3846
|
const children = node.children || {};
|
|
3844
3847
|
if (Object.keys(children).length === 0 && node.routes && node.routes.length > 0) {
|
|
3848
|
+
if (ancestorStaticPrefixes.has(node.staticPrefix)) {
|
|
3849
|
+
return;
|
|
3850
|
+
}
|
|
3845
3851
|
const routes = {};
|
|
3846
3852
|
for (const name of node.routes) {
|
|
3847
3853
|
if (name in routeManifest) {
|
|
@@ -3850,13 +3856,15 @@ function flattenLeafEntries(prefixTree, routeManifest, result) {
|
|
|
3850
3856
|
}
|
|
3851
3857
|
result.push({ staticPrefix: node.staticPrefix, routes });
|
|
3852
3858
|
} else {
|
|
3859
|
+
const nextAncestors = new Set(ancestorStaticPrefixes);
|
|
3860
|
+
nextAncestors.add(node.staticPrefix);
|
|
3853
3861
|
for (const child of Object.values(children)) {
|
|
3854
|
-
visit(child);
|
|
3862
|
+
visit(child, nextAncestors);
|
|
3855
3863
|
}
|
|
3856
3864
|
}
|
|
3857
3865
|
}
|
|
3858
3866
|
for (const node of Object.values(prefixTree)) {
|
|
3859
|
-
visit(node);
|
|
3867
|
+
visit(node, /* @__PURE__ */ new Set());
|
|
3860
3868
|
}
|
|
3861
3869
|
}
|
|
3862
3870
|
function buildRouteToStaticPrefix(prefixTree, result) {
|
|
@@ -4433,6 +4441,80 @@ async function renderStaticHandlers(state, rscEnv, registry) {
|
|
|
4433
4441
|
);
|
|
4434
4442
|
}
|
|
4435
4443
|
|
|
4444
|
+
// src/vite/discovery/discovery-errors.ts
|
|
4445
|
+
function indent(text, pad) {
|
|
4446
|
+
return text.split("\n").map((line) => line.length > 0 ? pad + line : line).join("\n");
|
|
4447
|
+
}
|
|
4448
|
+
async function invokeLazyMount(loader, context, errors) {
|
|
4449
|
+
try {
|
|
4450
|
+
await loader();
|
|
4451
|
+
} catch (error) {
|
|
4452
|
+
errors.push({ context, error });
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
function isLazyMount(route) {
|
|
4456
|
+
return !!route && route.kind === "lazy" && typeof route.handler === "function";
|
|
4457
|
+
}
|
|
4458
|
+
async function resolveHostRouterHandlers(hostRegistry) {
|
|
4459
|
+
const errors = [];
|
|
4460
|
+
for (const [hostId, entry] of hostRegistry) {
|
|
4461
|
+
for (const route of entry.routes) {
|
|
4462
|
+
if (isLazyMount(route)) {
|
|
4463
|
+
await invokeLazyMount(
|
|
4464
|
+
route.handler,
|
|
4465
|
+
`host "${hostId}" route handler`,
|
|
4466
|
+
errors
|
|
4467
|
+
);
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
if (isLazyMount(entry.fallback)) {
|
|
4471
|
+
await invokeLazyMount(
|
|
4472
|
+
entry.fallback.handler,
|
|
4473
|
+
`host "${hostId}" fallback handler`,
|
|
4474
|
+
errors
|
|
4475
|
+
);
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
return errors;
|
|
4479
|
+
}
|
|
4480
|
+
function formatNoRoutersError(entryPath, errors) {
|
|
4481
|
+
const base = `[rsc-router] No routers found in registry after importing ${entryPath}`;
|
|
4482
|
+
if (errors.length === 0) {
|
|
4483
|
+
return base;
|
|
4484
|
+
}
|
|
4485
|
+
const formatted = errors.map(({ context, error }) => {
|
|
4486
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
4487
|
+
const detail = err.stack ?? err.message;
|
|
4488
|
+
return ` - while resolving ${context}:
|
|
4489
|
+
${indent(detail, " ")}`;
|
|
4490
|
+
}).join("\n");
|
|
4491
|
+
return `${base}
|
|
4492
|
+
|
|
4493
|
+
${errors.length} error(s) were caught during host-router discovery and likely explain why no routers were registered:
|
|
4494
|
+
${formatted}`;
|
|
4495
|
+
}
|
|
4496
|
+
function toCause(errors) {
|
|
4497
|
+
if (errors.length === 0) return void 0;
|
|
4498
|
+
if (errors.length === 1) return errors[0].error;
|
|
4499
|
+
return new AggregateError(
|
|
4500
|
+
errors.map((e) => e.error),
|
|
4501
|
+
"Multiple host-router handlers failed during discovery"
|
|
4502
|
+
);
|
|
4503
|
+
}
|
|
4504
|
+
var DiscoveryError = class _DiscoveryError extends Error {
|
|
4505
|
+
constructor(entryPath, caught) {
|
|
4506
|
+
super(formatNoRoutersError(entryPath, caught));
|
|
4507
|
+
const cause = toCause(caught);
|
|
4508
|
+
if (cause !== void 0) {
|
|
4509
|
+
this.cause = cause;
|
|
4510
|
+
}
|
|
4511
|
+
this.name = "DiscoveryError";
|
|
4512
|
+
this.entryPath = entryPath;
|
|
4513
|
+
this.caught = caught;
|
|
4514
|
+
Object.setPrototypeOf(this, _DiscoveryError.prototype);
|
|
4515
|
+
}
|
|
4516
|
+
};
|
|
4517
|
+
|
|
4436
4518
|
// src/vite/discovery/discover-routers.ts
|
|
4437
4519
|
var debug10 = createRangoDebugger(NS.discovery);
|
|
4438
4520
|
async function discoverRouters(state, rscEnv) {
|
|
@@ -4449,27 +4531,17 @@ async function discoverRouters(state, rscEnv) {
|
|
|
4449
4531
|
);
|
|
4450
4532
|
let registry = serverMod.RouterRegistry;
|
|
4451
4533
|
if (!registry || registry.size === 0) {
|
|
4534
|
+
const discoveryErrors = [];
|
|
4452
4535
|
try {
|
|
4453
4536
|
const hostRegistry = serverMod.HostRouterRegistry;
|
|
4454
4537
|
if (hostRegistry && hostRegistry.size > 0) {
|
|
4455
4538
|
console.log(
|
|
4456
4539
|
`[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
|
|
4457
4540
|
);
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
await route.handler();
|
|
4463
|
-
} catch {
|
|
4464
|
-
}
|
|
4465
|
-
}
|
|
4466
|
-
}
|
|
4467
|
-
if (entry.fallback && typeof entry.fallback.handler === "function") {
|
|
4468
|
-
try {
|
|
4469
|
-
await entry.fallback.handler();
|
|
4470
|
-
} catch {
|
|
4471
|
-
}
|
|
4472
|
-
}
|
|
4541
|
+
const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
|
|
4542
|
+
discoveryErrors.push(...handlerErrors);
|
|
4543
|
+
for (const { context, error } of handlerErrors) {
|
|
4544
|
+
debug10?.("caught error while resolving %s: %O", context, error);
|
|
4473
4545
|
}
|
|
4474
4546
|
const freshServerMod = await rscEnv.runner.import(
|
|
4475
4547
|
"@rangojs/router/server"
|
|
@@ -4480,12 +4552,11 @@ async function discoverRouters(state, rscEnv) {
|
|
|
4480
4552
|
registry = freshRegistry;
|
|
4481
4553
|
}
|
|
4482
4554
|
}
|
|
4483
|
-
} catch {
|
|
4555
|
+
} catch (error) {
|
|
4556
|
+
discoveryErrors.push({ context: "host-router discovery", error });
|
|
4484
4557
|
}
|
|
4485
4558
|
if (!registry || registry.size === 0) {
|
|
4486
|
-
throw new
|
|
4487
|
-
`[rsc-router] No routers found in registry after importing ${state.resolvedEntryPath}`
|
|
4488
|
-
);
|
|
4559
|
+
throw new DiscoveryError(state.resolvedEntryPath, discoveryErrors);
|
|
4489
4560
|
}
|
|
4490
4561
|
}
|
|
4491
4562
|
const buildMod = await timed(
|
|
@@ -5162,6 +5233,52 @@ function createDiscoveryGate(s, debug11) {
|
|
|
5162
5233
|
};
|
|
5163
5234
|
}
|
|
5164
5235
|
|
|
5236
|
+
// src/vite/utils/forward-user-plugins.ts
|
|
5237
|
+
function isDenied(name) {
|
|
5238
|
+
return name.startsWith("vite:") || name === "rsc" || name.startsWith("rsc:") || name.startsWith("@rangojs/router") || name.startsWith("@cloudflare/vite-plugin") || name.startsWith("vite-plugin-cloudflare");
|
|
5239
|
+
}
|
|
5240
|
+
function hasResolutionHooks(p) {
|
|
5241
|
+
return Boolean(p.resolveId || p.load);
|
|
5242
|
+
}
|
|
5243
|
+
function stripToResolutionHooks(p) {
|
|
5244
|
+
const stripped = { name: p.name };
|
|
5245
|
+
if (p.enforce) stripped.enforce = p.enforce;
|
|
5246
|
+
if (p.applyToEnvironment)
|
|
5247
|
+
stripped.applyToEnvironment = p.applyToEnvironment;
|
|
5248
|
+
if (p.resolveId) stripped.resolveId = p.resolveId;
|
|
5249
|
+
if (p.load) stripped.load = p.load;
|
|
5250
|
+
return stripped;
|
|
5251
|
+
}
|
|
5252
|
+
function selectForwardableResolvePlugins(plugins) {
|
|
5253
|
+
if (!plugins) return [];
|
|
5254
|
+
const forwarded = [];
|
|
5255
|
+
for (const p of plugins) {
|
|
5256
|
+
const name = p?.name;
|
|
5257
|
+
if (!name || isDenied(name)) continue;
|
|
5258
|
+
if (!hasResolutionHooks(p)) continue;
|
|
5259
|
+
forwarded.push(stripToResolutionHooks(p));
|
|
5260
|
+
}
|
|
5261
|
+
return forwarded;
|
|
5262
|
+
}
|
|
5263
|
+
function pickForwardedRunnerConfig(config) {
|
|
5264
|
+
const r = config.resolve ?? {};
|
|
5265
|
+
const resolve10 = {};
|
|
5266
|
+
if (r.alias !== void 0) resolve10.alias = r.alias;
|
|
5267
|
+
if (r.dedupe !== void 0) resolve10.dedupe = r.dedupe;
|
|
5268
|
+
if (r.conditions !== void 0) resolve10.conditions = r.conditions;
|
|
5269
|
+
if (r.mainFields !== void 0) resolve10.mainFields = r.mainFields;
|
|
5270
|
+
if (r.extensions !== void 0) resolve10.extensions = r.extensions;
|
|
5271
|
+
if (r.preserveSymlinks !== void 0)
|
|
5272
|
+
resolve10.preserveSymlinks = r.preserveSymlinks;
|
|
5273
|
+
const userEsbuild = config.esbuild;
|
|
5274
|
+
const esbuild = userEsbuild && typeof userEsbuild === "object" ? { ...userEsbuild, jsx: "automatic", jsxImportSource: "react" } : { jsx: "automatic", jsxImportSource: "react" };
|
|
5275
|
+
return {
|
|
5276
|
+
resolve: resolve10,
|
|
5277
|
+
define: config.define,
|
|
5278
|
+
esbuild
|
|
5279
|
+
};
|
|
5280
|
+
}
|
|
5281
|
+
|
|
5165
5282
|
// src/vite/router-discovery.ts
|
|
5166
5283
|
var debugDiscovery = createRangoDebugger(NS.discovery);
|
|
5167
5284
|
var debugRoutes = createRangoDebugger(NS.routes);
|
|
@@ -5184,14 +5301,23 @@ function ensureCloudflareProtocolLoaderRegistered() {
|
|
|
5184
5301
|
async function createTempRscServer(state, options = {}) {
|
|
5185
5302
|
ensureCloudflareProtocolLoaderRegistered();
|
|
5186
5303
|
const { default: rsc } = await import("@vitejs/plugin-rsc");
|
|
5304
|
+
const runnerConfig = state.userRunnerConfig;
|
|
5305
|
+
const resolveConfig = runnerConfig?.resolve ?? {
|
|
5306
|
+
alias: state.userResolveAlias
|
|
5307
|
+
};
|
|
5308
|
+
const esbuildConfig = runnerConfig?.esbuild ?? {
|
|
5309
|
+
jsx: "automatic",
|
|
5310
|
+
jsxImportSource: "react"
|
|
5311
|
+
};
|
|
5187
5312
|
return createViteServer({
|
|
5188
5313
|
root: state.projectRoot,
|
|
5189
5314
|
configFile: false,
|
|
5190
5315
|
server: { middlewareMode: true },
|
|
5191
5316
|
appType: "custom",
|
|
5192
5317
|
logLevel: "silent",
|
|
5193
|
-
resolve:
|
|
5194
|
-
|
|
5318
|
+
resolve: resolveConfig,
|
|
5319
|
+
...runnerConfig?.define ? { define: runnerConfig.define } : {},
|
|
5320
|
+
esbuild: esbuildConfig,
|
|
5195
5321
|
...options.cacheDir && { cacheDir: options.cacheDir },
|
|
5196
5322
|
plugins: [
|
|
5197
5323
|
rsc({
|
|
@@ -5209,7 +5335,11 @@ async function createTempRscServer(state, options = {}) {
|
|
|
5209
5335
|
// Dev prerender must use dev-mode IDs (path-based) to match the workerd
|
|
5210
5336
|
// runtime. forceBuild produces hashed IDs for production bundle consistency.
|
|
5211
5337
|
exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
|
|
5212
|
-
exposeRouterId()
|
|
5338
|
+
exposeRouterId(),
|
|
5339
|
+
// Forwarded user resolution plugins (e.g. vite-tsconfig-paths). Stripped
|
|
5340
|
+
// to resolveId/load and placed last so framework resolution runs first;
|
|
5341
|
+
// Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
|
|
5342
|
+
...state.userResolvePlugins
|
|
5213
5343
|
]
|
|
5214
5344
|
});
|
|
5215
5345
|
}
|
|
@@ -5292,6 +5422,10 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
5292
5422
|
viteCommand = config.command;
|
|
5293
5423
|
viteMode = config.mode;
|
|
5294
5424
|
s.userResolveAlias = config.resolve.alias;
|
|
5425
|
+
s.userRunnerConfig = pickForwardedRunnerConfig(config);
|
|
5426
|
+
s.userResolvePlugins = selectForwardableResolvePlugins(
|
|
5427
|
+
config.plugins
|
|
5428
|
+
);
|
|
5295
5429
|
if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
|
|
5296
5430
|
s.resolvedEntryPath = opts.routerPathRef.path;
|
|
5297
5431
|
}
|
|
@@ -5987,7 +6121,8 @@ ${err.stack}` : null
|
|
|
5987
6121
|
].filter(Boolean).join("\n");
|
|
5988
6122
|
throw new Error(
|
|
5989
6123
|
`[rsc-router] Build-time router discovery failed:
|
|
5990
|
-
${details}
|
|
6124
|
+
${details}`,
|
|
6125
|
+
{ cause: err }
|
|
5991
6126
|
);
|
|
5992
6127
|
} finally {
|
|
5993
6128
|
delete globalThis.__rscRouterDiscoveryActive;
|
|
@@ -6213,7 +6348,15 @@ async function rango(options) {
|
|
|
6213
6348
|
esbuildOptions: sharedEsbuildOptions
|
|
6214
6349
|
},
|
|
6215
6350
|
resolve: {
|
|
6216
|
-
alias: rangoAliases
|
|
6351
|
+
alias: rangoAliases,
|
|
6352
|
+
// Force a single React/React-DOM copy across all three RSC
|
|
6353
|
+
// environments. RSC requires exactly one react/react-dom instance
|
|
6354
|
+
// per environment runtime; consumer install topologies (pnpm
|
|
6355
|
+
// strict layout, experimental React pins, third-party "use client"
|
|
6356
|
+
// packages) can otherwise resolve duplicate copies, causing
|
|
6357
|
+
// "Invalid hook call" / lost context. Child environments inherit
|
|
6358
|
+
// this root dedupe, and Vite merges it with any consumer dedupe.
|
|
6359
|
+
dedupe: ["react", "react-dom"]
|
|
6217
6360
|
},
|
|
6218
6361
|
build: {
|
|
6219
6362
|
rollupOptions: { onwarn }
|
|
@@ -6240,10 +6383,6 @@ async function rango(options) {
|
|
|
6240
6383
|
build: {
|
|
6241
6384
|
outDir: "./dist/rsc/ssr"
|
|
6242
6385
|
},
|
|
6243
|
-
resolve: {
|
|
6244
|
-
// Ensure single React instance in SSR child environment
|
|
6245
|
-
dedupe: ["react", "react-dom"]
|
|
6246
|
-
},
|
|
6247
6386
|
// Pre-bundle SSR entry and React for proper module linking with childEnvironments
|
|
6248
6387
|
// All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
|
|
6249
6388
|
optimizeDeps: {
|
|
@@ -6339,7 +6478,15 @@ ${list}`);
|
|
|
6339
6478
|
rollupOptions: { onwarn }
|
|
6340
6479
|
},
|
|
6341
6480
|
resolve: {
|
|
6342
|
-
alias: rangoAliases
|
|
6481
|
+
alias: rangoAliases,
|
|
6482
|
+
// Force a single React/React-DOM copy across all three RSC
|
|
6483
|
+
// environments. RSC requires exactly one react/react-dom instance
|
|
6484
|
+
// per environment runtime; consumer install topologies (pnpm
|
|
6485
|
+
// strict layout, experimental React pins, third-party "use client"
|
|
6486
|
+
// packages) can otherwise resolve duplicate copies, causing
|
|
6487
|
+
// "Invalid hook call" / lost context. Child environments inherit
|
|
6488
|
+
// this root dedupe, and Vite merges it with any consumer dedupe.
|
|
6489
|
+
dedupe: ["react", "react-dom"]
|
|
6343
6490
|
},
|
|
6344
6491
|
environments: {
|
|
6345
6492
|
client: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rangojs/router",
|
|
3
|
-
"version": "0.0.0-experimental.
|
|
3
|
+
"version": "0.0.0-experimental.105",
|
|
4
4
|
"description": "Django-inspired RSC router with composable URL patterns",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -165,7 +165,8 @@
|
|
|
165
165
|
"peerDependencies": {
|
|
166
166
|
"@cloudflare/vite-plugin": "^1.25.0",
|
|
167
167
|
"@vitejs/plugin-rsc": "^0.5.23",
|
|
168
|
-
"react": "
|
|
168
|
+
"react": ">=19.2.6 <20",
|
|
169
|
+
"react-dom": ">=19.2.6 <20",
|
|
169
170
|
"vite": "^7.3.0"
|
|
170
171
|
},
|
|
171
172
|
"peerDependenciesMeta": {
|
|
@@ -22,9 +22,9 @@ import { createHostRouter } from "@rangojs/router/host";
|
|
|
22
22
|
|
|
23
23
|
const router = createHostRouter();
|
|
24
24
|
|
|
25
|
-
router.host(["."]).
|
|
26
|
-
router.host(["admin.*"]).
|
|
27
|
-
router.host(["api.*"]).
|
|
25
|
+
router.host(["."]).lazy(() => import("./apps/main"));
|
|
26
|
+
router.host(["admin.*"]).lazy(() => import("./apps/admin"));
|
|
27
|
+
router.host(["api.*"]).lazy(() => import("./apps/api"));
|
|
28
28
|
|
|
29
29
|
export default {
|
|
30
30
|
fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
@@ -33,7 +33,31 @@ export default {
|
|
|
33
33
|
};
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
## Inline handlers (`.map`) vs lazy mounts (`.lazy`)
|
|
37
|
+
|
|
38
|
+
A host pattern maps to one of two things, and you pick the method by intent:
|
|
39
|
+
|
|
40
|
+
| Method | Argument | Use for |
|
|
41
|
+
| ------- | ------------------------------ | ------------------------------------------------------------ |
|
|
42
|
+
| `.map` | `(request, input) => Response` | An inline request handler that produces a response directly. |
|
|
43
|
+
| `.lazy` | `() => import("./sub-app")` | A lazily-imported handler or nested host router (a sub-app). |
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Lazy mount: the module's default export is a handler or a HostRouter.
|
|
47
|
+
router.host(["admin.*"]).lazy(() => import("./apps/admin"));
|
|
48
|
+
|
|
49
|
+
// Inline handler: returns a Response itself (sync or async).
|
|
50
|
+
router.host(["health.*"]).map(() => new Response("ok"));
|
|
51
|
+
router
|
|
52
|
+
.host(["echo.*"])
|
|
53
|
+
.map((request) => new Response(new URL(request.url).pathname));
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Why two methods instead of one overloaded `.map()`:
|
|
57
|
+
|
|
58
|
+
- **Build-time discovery** invokes only `.lazy()` mounts (to trigger each sub-app's `createRouter()` registration). Inline `.map()` handlers are never invoked during discovery, so they can't crash it or pollute its errors.
|
|
59
|
+
- `.map(() => import("./sub-app"))` is a **type error** — a lazy import resolves to a module, not a `Response`. Use `.lazy()` for imports. (If the types are bypassed, e.g. from JS, a `.map()` handler that resolves to a module throws a clear `HostRouterError` at request time instead of returning the module.)
|
|
60
|
+
- A lazy loader may declare an ignored parameter (`.lazy((_request?) => import("./x"))`); `.lazy()` accepts it because intent is explicit, not inferred from the signature.
|
|
37
61
|
|
|
38
62
|
## Pattern Syntax
|
|
39
63
|
|
|
@@ -65,8 +89,8 @@ const hosts = defineHosts({
|
|
|
65
89
|
app: [".", "www.*"],
|
|
66
90
|
});
|
|
67
91
|
|
|
68
|
-
router.host(hosts.admin).
|
|
69
|
-
router.host(hosts.app).
|
|
92
|
+
router.host(hosts.admin).lazy(() => import("./apps/admin"));
|
|
93
|
+
router.host(hosts.app).lazy(() => import("./apps/main"));
|
|
70
94
|
```
|
|
71
95
|
|
|
72
96
|
Returns a frozen object — keys are autocompleted by TypeScript.
|
|
@@ -88,7 +112,7 @@ router.use(async (request, input, next) => {
|
|
|
88
112
|
router
|
|
89
113
|
.host(["admin.*"])
|
|
90
114
|
.use(requireAuth)
|
|
91
|
-
.
|
|
115
|
+
.lazy(() => import("./apps/admin"));
|
|
92
116
|
```
|
|
93
117
|
|
|
94
118
|
Middleware signature: `(request: Request, input: RouterRequestInput, next: () => Promise<Response>) => Promise<Response>`
|
|
@@ -179,40 +203,41 @@ const request = createTestRequest({
|
|
|
179
203
|
});
|
|
180
204
|
|
|
181
205
|
// Test which route would match (without executing)
|
|
182
|
-
router.test("admin.example.com"); // { pattern, handler } | null
|
|
206
|
+
router.test("admin.example.com"); // { pattern, handler, kind } | null
|
|
183
207
|
```
|
|
184
208
|
|
|
185
209
|
## Error Types
|
|
186
210
|
|
|
187
211
|
All errors extend `HostRouterError`:
|
|
188
212
|
|
|
189
|
-
| Error | When
|
|
190
|
-
| ----------------------------- |
|
|
191
|
-
| `InvalidPatternError` | Pattern is empty, non-string, or has spaces
|
|
192
|
-
| `HostOverrideNotAllowedError` | Cookie override from disallowed host
|
|
193
|
-
| `InvalidHostnameError` | Cookie value isn't a valid hostname
|
|
194
|
-
| `HostValidationError` | Custom `validate` function threw
|
|
195
|
-
| `NoRouteMatchError` | No host pattern matched the request
|
|
196
|
-
| `InvalidHandlerError` | Handler is not a function
|
|
213
|
+
| Error | When |
|
|
214
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
215
|
+
| `InvalidPatternError` | Pattern is empty, non-string, or has spaces |
|
|
216
|
+
| `HostOverrideNotAllowedError` | Cookie override from disallowed host |
|
|
217
|
+
| `InvalidHostnameError` | Cookie value isn't a valid hostname |
|
|
218
|
+
| `HostValidationError` | Custom `validate` function threw |
|
|
219
|
+
| `NoRouteMatchError` | No host pattern matched the request |
|
|
220
|
+
| `InvalidHandlerError` | Handler is not a function, or a lazy mount resolved to a module without a usable `default` export |
|
|
221
|
+
| `HostRouterError` | A `.map()` inline handler resolved to a module namespace (a misused lazy import — use `.lazy()`) |
|
|
197
222
|
|
|
198
223
|
See the fallback section above for a `NoRouteMatchError` catch example.
|
|
199
224
|
|
|
200
225
|
## Nesting Host Routers
|
|
201
226
|
|
|
202
|
-
A lazy
|
|
227
|
+
A lazy mount can resolve to another `HostRouter`:
|
|
203
228
|
|
|
204
229
|
```typescript
|
|
205
230
|
// apps/regional.ts
|
|
206
231
|
import { createHostRouter } from "@rangojs/router/host";
|
|
207
232
|
|
|
208
233
|
const regional = createHostRouter();
|
|
209
|
-
regional.host(["us.*"]).
|
|
210
|
-
regional.host(["eu.*"]).
|
|
234
|
+
regional.host(["us.*"]).lazy(() => import("./regions/us"));
|
|
235
|
+
regional.host(["eu.*"]).lazy(() => import("./regions/eu"));
|
|
211
236
|
|
|
212
237
|
export default regional;
|
|
213
238
|
```
|
|
214
239
|
|
|
215
240
|
```typescript
|
|
216
241
|
// host-router.ts
|
|
217
|
-
router.host(["**.regional.example.com"]).
|
|
242
|
+
router.host(["**.regional.example.com"]).lazy(() => import("./apps/regional"));
|
|
218
243
|
```
|
package/src/host/index.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* const router = createHostRouter();
|
|
13
13
|
*
|
|
14
|
-
* router.host(['.']).
|
|
15
|
-
* router.host(['admin.*']).
|
|
14
|
+
* router.host(['.']).lazy(() => import('./apps/main'));
|
|
15
|
+
* router.host(['admin.*']).lazy(() => import('./apps/admin'));
|
|
16
16
|
*
|
|
17
17
|
* export default {
|
|
18
18
|
* fetch(request) {
|