filesystem-routing 0.2.0 → 0.3.0
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 +9 -2
- package/dist/analyze.js +33 -1
- package/dist/api.d.ts +8 -2
- package/dist/api.js +1 -1
- package/dist/manifest.d.ts +5 -0
- package/dist/vite/constants.js +1 -1
- package/dist/vite/index.d.ts +21 -0
- package/dist/vite/index.js +96 -9
- package/dist/vite/types.d.ts +3 -1
- package/dist/vite/types.js +15 -9
- package/package.json +6 -1
- package/types.d.ts +4 -2
package/README.md
CHANGED
|
@@ -241,8 +241,14 @@ The scanner produces flat `RouteManifestEntry` objects:
|
|
|
241
241
|
|
|
242
242
|
The Vite adapter serializes the manifest into the virtual module, turning
|
|
243
243
|
`$`-prefixed refs into dynamic imports and `$$`-prefixed refs into static
|
|
244
|
-
imports, each tree-shaken down to the picked exports.
|
|
245
|
-
the
|
|
244
|
+
imports, each tree-shaken down to the picked exports. Splitting is not always
|
|
245
|
+
a win — on a small app the per-chunk overhead can leave the split bundle
|
|
246
|
+
larger — so `fileRoutes({ codeSplitting: false })` delivers `$` refs eagerly
|
|
247
|
+
too (`{ src, require }`, statically imported); emission adapters branch on
|
|
248
|
+
the delivered shape, so no second switch exists on their side. With
|
|
249
|
+
`@solidjs/router` this requires the next release (newer than
|
|
250
|
+
`2.0.0-next.14`) — against `next.14` or older, enabling the option fails at
|
|
251
|
+
first render. The module serves two views of the same entries:
|
|
246
252
|
|
|
247
253
|
```ts
|
|
248
254
|
import routes, { pageRoutes } from "virtual:file-routes";
|
|
@@ -305,6 +311,7 @@ fileRoutes({
|
|
|
305
311
|
| `httpMethods` | emit `$GET`, `$POST`, … refs for uppercase handler exports; a module with handlers but no default export routes without being a page |
|
|
306
312
|
| `components` | set `false` to route without emitting `$component` refs, keeping page modules out of that environment's bundle |
|
|
307
313
|
| `buildInputs` | environments whose build takes every code-split route module as an entry |
|
|
314
|
+
| `codeSplitting` | set `false` to deliver every module ref as an eager static import — no `lazy()` components, zero dynamic imports, one bundle — for small apps where per-chunk overhead outweighs the split (defaults to `true`; with `@solidjs/router` requires the next release, newer than `2.0.0-next.14`) |
|
|
308
315
|
| `moduleId` | the id the manifest is served from |
|
|
309
316
|
| `optimizeDepsExclude` | escape hatch for packages that import the virtual module, kept out of dep prebundling (defaults to `[]`) |
|
|
310
317
|
| `types` | write a declaration typing the manifest as a literal tuple, kept in step with the route directory |
|
package/dist/analyze.js
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import { parseSync } from "oxc-parser";
|
|
4
|
+
let tsrxParseSync;
|
|
5
|
+
function loadTsrxParser(src) {
|
|
6
|
+
if (!tsrxParseSync) {
|
|
7
|
+
try {
|
|
8
|
+
const facade = createRequire(import.meta.url)("@tsrx/oxc/parser");
|
|
9
|
+
tsrxParseSync = facade.parseSync;
|
|
10
|
+
}
|
|
11
|
+
catch (cause) {
|
|
12
|
+
throw new Error(`Cannot analyze the TSRX route module ${src}: analyzing .tsrx exports requires ` +
|
|
13
|
+
`the optional peer dependency @tsrx/oxc. Install it (npm i -D @tsrx/oxc) ` +
|
|
14
|
+
`or keep route modules in .ts/.tsx.`, { cause });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return tsrxParseSync;
|
|
18
|
+
}
|
|
3
19
|
/**
|
|
4
20
|
* Analyze a route module's static exports.
|
|
5
21
|
*
|
|
@@ -8,7 +24,23 @@ import { parseSync } from "oxc-parser";
|
|
|
8
24
|
* a compiler that already performs export analysis can provide it instead.
|
|
9
25
|
*/
|
|
10
26
|
export function analyzeModule(src) {
|
|
11
|
-
const
|
|
27
|
+
const source = fs.readFileSync(src, "utf-8");
|
|
28
|
+
let result;
|
|
29
|
+
if (src.endsWith(".tsrx")) {
|
|
30
|
+
const parse = loadTsrxParser(src);
|
|
31
|
+
// The TSRX facade reports most syntax errors through `result.errors` but
|
|
32
|
+
// throws on some malformed sources; normalize both into the same shape.
|
|
33
|
+
try {
|
|
34
|
+
result = parse(src, source, { lang: "tsrx" });
|
|
35
|
+
}
|
|
36
|
+
catch (cause) {
|
|
37
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
38
|
+
throw new SyntaxError(`Failed to parse ${src}:\n${message}`, { cause });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
result = parseSync(src, source, { lang: "tsx" });
|
|
43
|
+
}
|
|
12
44
|
const error = result.errors[0];
|
|
13
45
|
if (error)
|
|
14
46
|
throw new SyntaxError(`Failed to parse ${src}:\n${error.codeframe || error.message}`);
|
package/dist/api.d.ts
CHANGED
|
@@ -39,9 +39,15 @@ export interface FileRouteHandlers {
|
|
|
39
39
|
$DELETE?: APIHandlerRef;
|
|
40
40
|
[key: string]: unknown;
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* A handler module ref as delivered: code-split (`import()`) by default,
|
|
44
|
+
* eager (`require()`) when the delivery adapter runs with code splitting off.
|
|
45
|
+
*/
|
|
46
|
+
export type APIHandlerRef = {
|
|
43
47
|
import(): Promise<Record<string, APIHandler>>;
|
|
44
|
-
}
|
|
48
|
+
} | {
|
|
49
|
+
require(): Record<string, APIHandler>;
|
|
50
|
+
};
|
|
45
51
|
export interface APIMatch {
|
|
46
52
|
handler: APIHandlerRef;
|
|
47
53
|
params?: Record<string, string>;
|
package/dist/api.js
CHANGED
|
@@ -101,7 +101,7 @@ export function createAPIHandler(routes, options = {}) {
|
|
|
101
101
|
const matched = match(stripPathBase(url.pathname, options.base ?? "/"), request.method);
|
|
102
102
|
if (!matched)
|
|
103
103
|
return next();
|
|
104
|
-
const mod = await matched.handler.import();
|
|
104
|
+
const mod = "require" in matched.handler ? matched.handler.require() : await matched.handler.import();
|
|
105
105
|
const fn = request.method === "HEAD" ? mod.HEAD || mod.GET : mod[request.method];
|
|
106
106
|
if (typeof fn !== "function")
|
|
107
107
|
return next();
|
package/dist/manifest.d.ts
CHANGED
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
* under decides how:
|
|
17
17
|
* - keys prefixed `$` become lazy refs: `{ src, import: () => import(...) }`
|
|
18
18
|
* - keys prefixed `$$` become eager refs: `{ require: () => ({ ...exports }) }`
|
|
19
|
+
*
|
|
20
|
+
* A delivery adapter configured without code splitting (the Vite adapter's
|
|
21
|
+
* `codeSplitting: false`) materializes `$` refs eagerly too, as
|
|
22
|
+
* `{ src, require }` — the delivered shape, not the key prefix, is the
|
|
23
|
+
* contract consumers branch on.
|
|
19
24
|
*/
|
|
20
25
|
export interface ModuleRef {
|
|
21
26
|
/** Absolute path of the source module. */
|
package/dist/vite/constants.js
CHANGED
package/dist/vite/index.d.ts
CHANGED
|
@@ -32,6 +32,21 @@ export interface FileRoutesOptions extends Pick<PageFileSystemRouterConfig, "com
|
|
|
32
32
|
* framework already configures.
|
|
33
33
|
*/
|
|
34
34
|
buildInputs?: string | string[];
|
|
35
|
+
/**
|
|
36
|
+
* Deliver `$`-prefixed module refs as code-split dynamic imports, each
|
|
37
|
+
* route its own chunk. The default. Set to `false` to deliver every ref
|
|
38
|
+
* eagerly instead — static imports, `require()`-shaped refs, zero dynamic
|
|
39
|
+
* imports in the output — for the apps where splitting loses: small apps
|
|
40
|
+
* whose per-chunk overhead outweighs the split, and targets that want a
|
|
41
|
+
* single bundle. Adapters need no switch of their own: refs are
|
|
42
|
+
* self-describing (`import()` is code-split, `require()` is eager), and
|
|
43
|
+
* emission adapters branch on the shape they are handed.
|
|
44
|
+
*
|
|
45
|
+
* With `@solidjs/router`, `codeSplitting: false` requires the next release
|
|
46
|
+
* (newer than `2.0.0-next.14`): its adapter must branch on eager refs, and
|
|
47
|
+
* against `next.14` or older, enabling this option fails at first render.
|
|
48
|
+
*/
|
|
49
|
+
codeSplitting?: boolean;
|
|
35
50
|
/**
|
|
36
51
|
* Packages that import the virtual module and must therefore stay out of
|
|
37
52
|
* esbuild's dependency prebundling, which cannot resolve it. Emission
|
|
@@ -65,5 +80,11 @@ export interface FileRoutesOptions extends Pick<PageFileSystemRouterConfig, "com
|
|
|
65
80
|
* flat manifest, and `pageRoutes` is the page entries nested by path with
|
|
66
81
|
* `(group)` segments stripped, so emission adapters don't each reimplement
|
|
67
82
|
* the tree.
|
|
83
|
+
*
|
|
84
|
+
* With `httpMethods` on, one router serves both sides of an SSR app: the
|
|
85
|
+
* handler refs it emits reach server-consumer environments only, and client
|
|
86
|
+
* manifests drop them (and handler-only API entries with them) so handler
|
|
87
|
+
* modules never enter a client build. An environment given its own router
|
|
88
|
+
* via `routers` is explicit and serves that router's manifest untouched.
|
|
68
89
|
*/
|
|
69
90
|
export declare function fileRoutes(options?: FileRoutesOptions): PluginOption[];
|
package/dist/vite/index.js
CHANGED
|
@@ -10,6 +10,45 @@ import { serializeTypes } from "./types.js";
|
|
|
10
10
|
export { DEFAULT_EXTENSIONS, moduleId };
|
|
11
11
|
export { sanitizeChunkFileName, toPickId, treeShake } from "./tree-shake.js";
|
|
12
12
|
export { fileSystemWatcher } from "./fs-watcher.js";
|
|
13
|
+
/**
|
|
14
|
+
* Handler refs are the `$`-prefixed all-uppercase keys the convention emits
|
|
15
|
+
* (`$GET`, `$POST`, ...). They exist for server dispatch only.
|
|
16
|
+
*/
|
|
17
|
+
const HANDLER_REF = /^\$[A-Z]+$/;
|
|
18
|
+
/**
|
|
19
|
+
* Whether a Vite environment runs on the server. `configEnvironment` sees
|
|
20
|
+
* `consumer` only when something set it explicitly, so fall back to Vite's
|
|
21
|
+
* own default: the `client` environment is the one client consumer.
|
|
22
|
+
*/
|
|
23
|
+
const isServerConsumer = (name, consumer) => (consumer ?? (name === "client" ? "client" : "server")) === "server";
|
|
24
|
+
/**
|
|
25
|
+
* The client's view of a shared server manifest: handler refs removed. A
|
|
26
|
+
* client bundle can never invoke a request handler, but serializing its ref
|
|
27
|
+
* would pull the handler module — and the server-only code it imports —
|
|
28
|
+
* into the client build. Handler-only entries (API routes) disappear
|
|
29
|
+
* entirely; pages that also export handlers keep their page refs.
|
|
30
|
+
*/
|
|
31
|
+
function stripHandlerRefs(routes) {
|
|
32
|
+
const stripped = [];
|
|
33
|
+
for (const route of routes) {
|
|
34
|
+
const handlerKeys = Object.keys(route).filter(key => HANDLER_REF.test(key));
|
|
35
|
+
if (!handlerKeys.length) {
|
|
36
|
+
stripped.push(route);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const rest = { ...route };
|
|
40
|
+
for (const key of handlerKeys)
|
|
41
|
+
delete rest[key];
|
|
42
|
+
// Entries carry `$component: undefined` placeholders; only a ref with a
|
|
43
|
+
// value keeps a handler-only entry alive. Otherwise not even the API
|
|
44
|
+
// path string reaches the client bundle.
|
|
45
|
+
if (rest.page ||
|
|
46
|
+
Object.keys(rest).some(key => key.startsWith("$") && rest[key] !== undefined)) {
|
|
47
|
+
stripped.push(rest);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return stripped;
|
|
51
|
+
}
|
|
13
52
|
/**
|
|
14
53
|
* The Vite delivery adapter for `filesystem-routing`.
|
|
15
54
|
*
|
|
@@ -22,9 +61,16 @@ export { fileSystemWatcher } from "./fs-watcher.js";
|
|
|
22
61
|
* flat manifest, and `pageRoutes` is the page entries nested by path with
|
|
23
62
|
* `(group)` segments stripped, so emission adapters don't each reimplement
|
|
24
63
|
* the tree.
|
|
64
|
+
*
|
|
65
|
+
* With `httpMethods` on, one router serves both sides of an SSR app: the
|
|
66
|
+
* handler refs it emits reach server-consumer environments only, and client
|
|
67
|
+
* manifests drop them (and handler-only API entries with them) so handler
|
|
68
|
+
* modules never enter a client build. An environment given its own router
|
|
69
|
+
* via `routers` is explicit and serves that router's manifest untouched.
|
|
25
70
|
*/
|
|
26
71
|
export function fileRoutes(options = {}) {
|
|
27
72
|
const virtualId = options.moduleId ?? moduleId;
|
|
73
|
+
const codeSplitting = options.codeSplitting !== false;
|
|
28
74
|
const buildInputs = options.buildInputs === undefined
|
|
29
75
|
? []
|
|
30
76
|
: Array.isArray(options.buildInputs)
|
|
@@ -47,7 +93,7 @@ export function fileRoutes(options = {}) {
|
|
|
47
93
|
if (!file || !router)
|
|
48
94
|
return;
|
|
49
95
|
const routes = (await router.getRoutes()) ?? [];
|
|
50
|
-
const contents = serializeTypes(virtualId, routes, buildRouteTree(routes.filter(route => route.page)), dirname(file));
|
|
96
|
+
const contents = serializeTypes(virtualId, routes, buildRouteTree(routes.filter(route => route.page)), dirname(file), codeSplitting);
|
|
51
97
|
// Only touch the file when it actually changes: every environment calls
|
|
52
98
|
// this, and rewriting it would churn the watcher for no reason.
|
|
53
99
|
let previous;
|
|
@@ -101,16 +147,27 @@ export function fileRoutes(options = {}) {
|
|
|
101
147
|
await writeTypes();
|
|
102
148
|
},
|
|
103
149
|
async configEnvironment(name, _config, env) {
|
|
150
|
+
// Without code splitting no ref becomes a dynamic import, so no
|
|
151
|
+
// route module needs to be an entry: they are all statically
|
|
152
|
+
// reachable from the virtual module.
|
|
153
|
+
if (!codeSplitting)
|
|
154
|
+
return;
|
|
104
155
|
if (env.command !== "build" || !buildInputs.includes(name))
|
|
105
156
|
return;
|
|
106
157
|
const router = getRouter(name);
|
|
107
158
|
if (!router)
|
|
108
159
|
return;
|
|
160
|
+
// A client environment on the shared router never serializes
|
|
161
|
+
// handler refs (see load), so their modules must not become its
|
|
162
|
+
// build entries either.
|
|
163
|
+
const routes = options.routers?.[name] || isServerConsumer(name, _config.consumer)
|
|
164
|
+
? await router.getRoutes()
|
|
165
|
+
: stripHandlerRefs(await router.getRoutes());
|
|
109
166
|
// Every code-split route module is an entry of its own, so the
|
|
110
167
|
// manifest's dynamic imports resolve to real chunks. `$$` refs are
|
|
111
168
|
// inlined into the manifest and need no entry.
|
|
112
169
|
const input = [];
|
|
113
|
-
for (const route of
|
|
170
|
+
for (const route of routes) {
|
|
114
171
|
for (const [key, ref] of Object.entries(route)) {
|
|
115
172
|
if (ref && key.startsWith("$") && !key.startsWith("$$")) {
|
|
116
173
|
input.push(toModuleId(ref));
|
|
@@ -131,8 +188,17 @@ export function fileRoutes(options = {}) {
|
|
|
131
188
|
const root = this.environment.config.root;
|
|
132
189
|
const isBuild = this.environment.mode === "build";
|
|
133
190
|
const js = jsCode();
|
|
134
|
-
const
|
|
135
|
-
const
|
|
191
|
+
const environmentName = this.environment.name;
|
|
192
|
+
const router = getRouter(environmentName);
|
|
193
|
+
let routes = (router ? await router.getRoutes() : []) ?? [];
|
|
194
|
+
// The shared router serves every environment, so the split is this
|
|
195
|
+
// adapter's job: client consumers get the manifest without handler
|
|
196
|
+
// refs. An explicit per-environment router already IS the split —
|
|
197
|
+
// serve whatever it emits.
|
|
198
|
+
if (!options.routers?.[environmentName] &&
|
|
199
|
+
!isServerConsumer(environmentName, this.environment.config.consumer)) {
|
|
200
|
+
routes = stripHandlerRefs(routes);
|
|
201
|
+
}
|
|
136
202
|
const serializeEntry = (entry) => JSON.stringify(entry, (key, value) => {
|
|
137
203
|
if (value === undefined)
|
|
138
204
|
return undefined;
|
|
@@ -150,6 +216,16 @@ export function fileRoutes(options = {}) {
|
|
|
150
216
|
}
|
|
151
217
|
else if (key.startsWith("$")) {
|
|
152
218
|
const buildId = toModuleId(value);
|
|
219
|
+
// With code splitting off the ref is delivered eagerly: a
|
|
220
|
+
// namespace import (named imports would fail on the synthetic
|
|
221
|
+
// `$css` pick) behind the `require()` shape eager refs carry,
|
|
222
|
+
// plus the `src` lazy refs already expose.
|
|
223
|
+
if (!codeSplitting) {
|
|
224
|
+
return {
|
|
225
|
+
src: relative(root, buildId),
|
|
226
|
+
require: `_$() => (${js.addNamespaceImport(buildId)})$_`
|
|
227
|
+
};
|
|
228
|
+
}
|
|
153
229
|
return {
|
|
154
230
|
src: relative(root, buildId),
|
|
155
231
|
build: isBuild ? `_$() => import('${buildId}')$_` : undefined,
|
|
@@ -192,6 +268,7 @@ export const pageRoutes = ${serializeTree(tree)};
|
|
|
192
268
|
}
|
|
193
269
|
function jsCode() {
|
|
194
270
|
const imports = new Map();
|
|
271
|
+
const namespaceImports = new Map();
|
|
195
272
|
let vars = 0;
|
|
196
273
|
function addNamedImport(name, source) {
|
|
197
274
|
let names = imports.get(source);
|
|
@@ -208,15 +285,25 @@ function jsCode() {
|
|
|
208
285
|
names[name] = alias;
|
|
209
286
|
return alias;
|
|
210
287
|
}
|
|
288
|
+
function addNamespaceImport(source) {
|
|
289
|
+
const existing = namespaceImports.get(source);
|
|
290
|
+
if (existing)
|
|
291
|
+
return existing;
|
|
292
|
+
const alias = "routeModule" + namespaceImports.size;
|
|
293
|
+
namespaceImports.set(source, alias);
|
|
294
|
+
return alias;
|
|
295
|
+
}
|
|
211
296
|
const getImportStatements = () => {
|
|
212
|
-
return [
|
|
213
|
-
.map(([source, names]) => `import { ${Object.entries(names)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
.
|
|
297
|
+
return [
|
|
298
|
+
...[...imports.entries()].map(([source, names]) => `import { ${Object.entries(names)
|
|
299
|
+
.map(([name, alias]) => `${name} as ${alias}`)
|
|
300
|
+
.join(", ")} } from '${source}';`),
|
|
301
|
+
...[...namespaceImports.entries()].map(([source, alias]) => `import * as ${alias} from '${source}';`)
|
|
302
|
+
].join("\n");
|
|
217
303
|
};
|
|
218
304
|
return {
|
|
219
305
|
addNamedImport,
|
|
306
|
+
addNamespaceImport,
|
|
220
307
|
getImportStatements
|
|
221
308
|
};
|
|
222
309
|
}
|
package/dist/vite/types.d.ts
CHANGED
|
@@ -7,4 +7,6 @@ import type { RouteTreeEntry } from "../tree.ts";
|
|
|
7
7
|
*/
|
|
8
8
|
export declare function serializeTypes(moduleId: string, routes: readonly RouteManifestEntry[], pageRoutes: readonly RouteTreeEntry[],
|
|
9
9
|
/** Directory the declaration is written to; module specifiers are relative to it. */
|
|
10
|
-
from: string
|
|
10
|
+
from: string,
|
|
11
|
+
/** Whether lazy refs are delivered code-split (the default) or eagerly. */
|
|
12
|
+
codeSplitting?: boolean): string;
|
package/dist/vite/types.js
CHANGED
|
@@ -23,6 +23,8 @@ const REF_TYPES = ` /** A code-split ref: the delivery adapter emits it as a dy
|
|
|
23
23
|
|
|
24
24
|
/** An eager ref: its picked exports are imported statically. */
|
|
25
25
|
export interface FileRouteEagerRef<M = Record<string, unknown>> {
|
|
26
|
+
/** The source module path — present when the convention emitted the ref lazy and \`codeSplitting: false\` delivered it eagerly. */
|
|
27
|
+
src?: string;
|
|
26
28
|
require(): M;
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -30,7 +32,7 @@ const REF_TYPES = ` /** A code-split ref: the delivery adapter emits it as a dy
|
|
|
30
32
|
export interface FileRouteEntry {
|
|
31
33
|
path: string;
|
|
32
34
|
page?: boolean;
|
|
33
|
-
$component?: FileRouteLazyRef<any>;
|
|
35
|
+
$component?: FileRouteLazyRef<any> | FileRouteEagerRef<any>;
|
|
34
36
|
$$route?: FileRouteEagerRef<any>;
|
|
35
37
|
[key: string]: unknown;
|
|
36
38
|
}
|
|
@@ -64,7 +66,7 @@ const isEagerRefKey = (key) => key.startsWith("$$");
|
|
|
64
66
|
* types intact. Absent refs are typed `?: undefined` rather than omitted, so
|
|
65
67
|
* an adapter can read `entry.$$route` uniformly across the tuple.
|
|
66
68
|
*/
|
|
67
|
-
function entryType(entry, indent, nested, from) {
|
|
69
|
+
function entryType(entry, indent, nested, from, codeSplitting) {
|
|
68
70
|
const inner = indent + " ";
|
|
69
71
|
const fields = [`path: ${JSON.stringify(entry.path)}`];
|
|
70
72
|
if (nested)
|
|
@@ -74,7 +76,9 @@ function entryType(entry, indent, nested, from) {
|
|
|
74
76
|
// so go by the value, not the key.
|
|
75
77
|
const refKeys = Object.keys(entry).filter(key => key.startsWith("$") && entry[key] !== undefined);
|
|
76
78
|
for (const key of refKeys) {
|
|
77
|
-
|
|
79
|
+
// With code splitting off, the delivery adapter materializes lazy refs
|
|
80
|
+
// eagerly, so the declaration describes them as delivered.
|
|
81
|
+
const kind = isEagerRefKey(key) || !codeSplitting ? "eager" : "lazy";
|
|
78
82
|
fields.push(`${key}: ${refType(entry[key], kind, from)}`);
|
|
79
83
|
}
|
|
80
84
|
// Reading an absent ref is how adapters branch, so keep the key present.
|
|
@@ -85,17 +89,17 @@ function entryType(entry, indent, nested, from) {
|
|
|
85
89
|
if (nested) {
|
|
86
90
|
const children = entry.children;
|
|
87
91
|
fields.push(children?.length
|
|
88
|
-
? `children: ${tupleType(children, inner, true, from)}`
|
|
92
|
+
? `children: ${tupleType(children, inner, true, from, codeSplitting)}`
|
|
89
93
|
: `children?: undefined`);
|
|
90
94
|
}
|
|
91
95
|
return `{\n${fields.map(field => `${inner}${field};`).join("\n")}\n${indent}}`;
|
|
92
96
|
}
|
|
93
|
-
function tupleType(entries, indent, nested, from) {
|
|
97
|
+
function tupleType(entries, indent, nested, from, codeSplitting) {
|
|
94
98
|
if (!entries.length)
|
|
95
99
|
return "readonly []";
|
|
96
100
|
const inner = indent + " ";
|
|
97
101
|
const members = entries
|
|
98
|
-
.map(entry => `${inner}${entryType(entry, inner, nested, from)}`)
|
|
102
|
+
.map(entry => `${inner}${entryType(entry, inner, nested, from, codeSplitting)}`)
|
|
99
103
|
.join(",\n");
|
|
100
104
|
return `readonly [\n${members}\n${indent}]`;
|
|
101
105
|
}
|
|
@@ -106,18 +110,20 @@ function tupleType(entries, indent, nested, from) {
|
|
|
106
110
|
*/
|
|
107
111
|
export function serializeTypes(moduleId, routes, pageRoutes,
|
|
108
112
|
/** Directory the declaration is written to; module specifiers are relative to it. */
|
|
109
|
-
from
|
|
113
|
+
from,
|
|
114
|
+
/** Whether lazy refs are delivered code-split (the default) or eagerly. */
|
|
115
|
+
codeSplitting = true) {
|
|
110
116
|
return `${HEADER}
|
|
111
117
|
|
|
112
118
|
declare module ${JSON.stringify(moduleId)} {
|
|
113
119
|
${REF_TYPES}
|
|
114
120
|
|
|
115
121
|
/** The flat route manifest, in scan order. */
|
|
116
|
-
const routes: ${tupleType(routes, " ", false, from)};
|
|
122
|
+
const routes: ${tupleType(routes, " ", false, from, codeSplitting)};
|
|
117
123
|
export default routes;
|
|
118
124
|
|
|
119
125
|
/** The page entries, nested by path with grouping segments stripped. */
|
|
120
|
-
export const pageRoutes: ${tupleType(pageRoutes, " ", true, from)};
|
|
126
|
+
export const pageRoutes: ${tupleType(pageRoutes, " ", true, from, codeSplitting)};
|
|
121
127
|
}
|
|
122
128
|
`;
|
|
123
129
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "filesystem-routing",
|
|
3
3
|
"description": "Router-neutral file-system routing: scans a route directory into a neutral route manifest with pluggable conventions and delivery adapters",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.3.0",
|
|
6
6
|
"author": "Ryan Carniato",
|
|
7
7
|
"homepage": "https://github.com/solidjs/filesystem-routing#readme",
|
|
8
8
|
"repository": {
|
|
@@ -63,15 +63,20 @@
|
|
|
63
63
|
"radix3": "^1.1.2"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
|
+
"@tsrx/oxc": ">=0.9.0",
|
|
66
67
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
67
68
|
},
|
|
68
69
|
"peerDependenciesMeta": {
|
|
70
|
+
"@tsrx/oxc": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
69
73
|
"vite": {
|
|
70
74
|
"optional": true
|
|
71
75
|
}
|
|
72
76
|
},
|
|
73
77
|
"devDependencies": {
|
|
74
78
|
"@changesets/cli": "^2.31.1",
|
|
79
|
+
"@tsrx/oxc": "^0.9.0",
|
|
75
80
|
"@types/node": "^22.10.0",
|
|
76
81
|
"prettier": "^3.4.1",
|
|
77
82
|
"typescript": "^5.7.2",
|
package/types.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ declare module "virtual:file-routes" {
|
|
|
22
22
|
|
|
23
23
|
/** An eager ref: its picked exports are imported statically. */
|
|
24
24
|
export interface FileRouteEagerRef {
|
|
25
|
+
/** The source module path — present when the convention emitted the ref lazy and `codeSplitting: false` delivered it eagerly. */
|
|
26
|
+
src?: string;
|
|
25
27
|
require(): Record<string, unknown>;
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -34,8 +36,8 @@ declare module "virtual:file-routes" {
|
|
|
34
36
|
path: string;
|
|
35
37
|
/** `true` when the module renders a page. */
|
|
36
38
|
page?: boolean;
|
|
37
|
-
/** The page component module
|
|
38
|
-
$component?: FileRouteLazyRef;
|
|
39
|
+
/** The page component module: code-split by default, an eager ref under `codeSplitting: false`. */
|
|
40
|
+
$component?: FileRouteLazyRef | FileRouteEagerRef;
|
|
39
41
|
/** The route config (`route` export), when the module has one. */
|
|
40
42
|
$$route?: FileRouteEagerRef;
|
|
41
43
|
/** Refs added by convention extensions, e.g. `$GET` handlers. */
|