@timber-js/app 0.2.0-alpha.112 → 0.2.0-alpha.113
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/dist/index.js +67 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/routing/segment-codegen.d.ts +25 -0
- package/dist/routing/segment-codegen.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/plugins/routing.ts +43 -1
- package/src/routing/segment-codegen.ts +73 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/plugins/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/plugins/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAoBlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA0F1D,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAmNxD"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _segment type generation and directory-to-route mapping.
|
|
3
|
+
*
|
|
4
|
+
* Generates per-route _segment.d.ts files in .timber/types/ that mirror
|
|
5
|
+
* the app/ directory structure. TypeScript's rootDirs feature makes these
|
|
6
|
+
* importable as './_segment' from route files.
|
|
7
|
+
*
|
|
8
|
+
* Design doc: design/41-global-params.md §The _segment Module
|
|
9
|
+
*/
|
|
10
|
+
import type { RouteTree } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Build a map of absolute filesystem directory → route pattern.
|
|
13
|
+
* Used by resolveId to map a _segment import's importer to the correct route.
|
|
14
|
+
*
|
|
15
|
+
* Walks every segment (not just leaves) so layouts and middleware
|
|
16
|
+
* at any depth can import _segment.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string, string>;
|
|
19
|
+
/**
|
|
20
|
+
* Generate per-route _segment.d.ts files in .timber/types/ mirroring
|
|
21
|
+
* the app/ directory structure. These files are importable via TypeScript's
|
|
22
|
+
* rootDirs feature.
|
|
23
|
+
*/
|
|
24
|
+
export declare function writeSegmentTypes(routeTree: RouteTree, appDir: string, root: string): Promise<void>;
|
|
25
|
+
//# sourceMappingURL=segment-codegen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment-codegen.d.ts","sourceRoot":"","sources":["../../src/routing/segment-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBvF;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timber-js/app",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.113",
|
|
4
4
|
"description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
package/src/plugins/routing.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { join } from 'node:path';
|
|
|
15
15
|
import { scanRoutes } from '../routing/scanner.js';
|
|
16
16
|
import { generateRouteMap } from '../routing/codegen.js';
|
|
17
17
|
import { collectInterceptionRewrites } from '../routing/interception.js';
|
|
18
|
+
import { buildDirToRouteMap, writeSegmentTypes } from '../routing/segment-codegen.js';
|
|
18
19
|
import {
|
|
19
20
|
collectDynamicSegments,
|
|
20
21
|
findSchemaFile,
|
|
@@ -55,6 +56,13 @@ const RESOLVED_SEARCH_PARAMS_REGISTRY_ID = `\0${SEARCH_PARAMS_REGISTRY_ID}`;
|
|
|
55
56
|
const SCHEMA_ID = 'virtual:timber-schema';
|
|
56
57
|
const RESOLVED_SCHEMA_ID = `\0${SCHEMA_ID}`;
|
|
57
58
|
|
|
59
|
+
// TIM-935: _segment virtual module prefix.
|
|
60
|
+
//
|
|
61
|
+
// Each route directory gets a virtual module that exports the route
|
|
62
|
+
// pattern string. `import { route } from './_segment'` resolves to
|
|
63
|
+
// `\0_segment:<route-pattern>` which returns `export const route = '...'`.
|
|
64
|
+
const SEGMENT_PREFIX = '\0_segment:';
|
|
65
|
+
|
|
58
66
|
/**
|
|
59
67
|
* File convention names we track for changes that require manifest regeneration.
|
|
60
68
|
*/
|
|
@@ -101,6 +109,7 @@ function writeCodegen(ctx: PluginContext): void {
|
|
|
101
109
|
Promise.all([
|
|
102
110
|
writeFile(routesPath, content, 'utf-8'),
|
|
103
111
|
writeFile(envPath, TIMBER_ENV_DTS, 'utf-8'),
|
|
112
|
+
writeSegmentTypes(ctx.routeTree!, ctx.appDir, ctx.root),
|
|
104
113
|
])
|
|
105
114
|
)
|
|
106
115
|
.catch((err) => {
|
|
@@ -113,6 +122,9 @@ export function timberRouting(ctx: PluginContext): Plugin {
|
|
|
113
122
|
/** Track warned files to avoid repeating warnings on rescan. */
|
|
114
123
|
const warnedFiles = new Set<string>();
|
|
115
124
|
|
|
125
|
+
/** Maps absolute filesystem directory → route pattern for _segment resolution. */
|
|
126
|
+
let dirToRoute: Map<string, string> = new Map();
|
|
127
|
+
|
|
116
128
|
function rescan(): void {
|
|
117
129
|
// Check app/ directory exists before scanning
|
|
118
130
|
const appDirWarning = checkAppDirExists(ctx.appDir);
|
|
@@ -137,6 +149,7 @@ export function timberRouting(ctx: PluginContext): Plugin {
|
|
|
137
149
|
pageExtensions: ctx.config.pageExtensions,
|
|
138
150
|
});
|
|
139
151
|
ctx.timer.end('route-scan');
|
|
152
|
+
dirToRoute = buildDirToRouteMap(ctx.routeTree, ctx.appDir);
|
|
140
153
|
writeCodegen(ctx);
|
|
141
154
|
|
|
142
155
|
// Dev: warn about dynamic segments not registered in app/schema.ts.
|
|
@@ -165,7 +178,7 @@ export function timberRouting(ctx: PluginContext): Plugin {
|
|
|
165
178
|
* - <root>/virtual:timber-route-manifest (Vite SSR build root prefix)
|
|
166
179
|
* - \0virtual:timber-route-manifest (RSC plugin re-imports)
|
|
167
180
|
*/
|
|
168
|
-
resolveId(id: string) {
|
|
181
|
+
resolveId(id: string, importer?: string) {
|
|
169
182
|
// Strip \0 prefix if present (RSC plugin generates these)
|
|
170
183
|
const cleanId = id.startsWith('\0') ? id.slice(1) : id;
|
|
171
184
|
|
|
@@ -191,6 +204,22 @@ export function timberRouting(ctx: PluginContext): Plugin {
|
|
|
191
204
|
return RESOLVED_SCHEMA_ID;
|
|
192
205
|
}
|
|
193
206
|
|
|
207
|
+
// TIM-935: _segment virtual module.
|
|
208
|
+
// Resolves `import { route } from './_segment'` based on the
|
|
209
|
+
// importing file's directory in the app/ tree.
|
|
210
|
+
if (
|
|
211
|
+
importer &&
|
|
212
|
+
(cleanId === './_segment' || cleanId === './_segment.js' || cleanId === './_segment.ts')
|
|
213
|
+
) {
|
|
214
|
+
// Strip \0 prefix from importer if present
|
|
215
|
+
const cleanImporter = importer.startsWith('\0') ? importer.slice(1) : importer;
|
|
216
|
+
const importerDir = cleanImporter.substring(0, cleanImporter.lastIndexOf('/'));
|
|
217
|
+
const routePattern = dirToRoute.get(importerDir);
|
|
218
|
+
if (routePattern !== undefined) {
|
|
219
|
+
return `${SEGMENT_PREFIX}${routePattern}`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
194
223
|
return null;
|
|
195
224
|
},
|
|
196
225
|
|
|
@@ -220,6 +249,12 @@ export function timberRouting(ctx: PluginContext): Plugin {
|
|
|
220
249
|
return generateSchemaModule(ctx.appDir);
|
|
221
250
|
}
|
|
222
251
|
|
|
252
|
+
// TIM-935: _segment virtual module — exports the route pattern string.
|
|
253
|
+
if (id.startsWith(SEGMENT_PREFIX)) {
|
|
254
|
+
const routePattern = id.slice(SEGMENT_PREFIX.length);
|
|
255
|
+
return `export const route = ${JSON.stringify(routePattern)};\n`;
|
|
256
|
+
}
|
|
257
|
+
|
|
223
258
|
return null;
|
|
224
259
|
},
|
|
225
260
|
|
|
@@ -347,6 +382,13 @@ function invalidateManifest(server: ViteDevServer): void {
|
|
|
347
382
|
env.moduleGraph.invalidateModule(mod);
|
|
348
383
|
}
|
|
349
384
|
}
|
|
385
|
+
|
|
386
|
+
// TIM-935: Invalidate all _segment virtual modules (prefixed \0_segment:)
|
|
387
|
+
for (const mod of env.moduleGraph.idToModuleMap.values()) {
|
|
388
|
+
if (mod.id?.startsWith(SEGMENT_PREFIX)) {
|
|
389
|
+
env.moduleGraph.invalidateModule(mod);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
350
392
|
}
|
|
351
393
|
|
|
352
394
|
// Trigger HMR full reload — route changes are structural
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _segment type generation and directory-to-route mapping.
|
|
3
|
+
*
|
|
4
|
+
* Generates per-route _segment.d.ts files in .timber/types/ that mirror
|
|
5
|
+
* the app/ directory structure. TypeScript's rootDirs feature makes these
|
|
6
|
+
* importable as './_segment' from route files.
|
|
7
|
+
*
|
|
8
|
+
* Design doc: design/41-global-params.md §The _segment Module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import type { RouteTree, SegmentNode } from './types.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build a map of absolute filesystem directory → route pattern.
|
|
17
|
+
* Used by resolveId to map a _segment import's importer to the correct route.
|
|
18
|
+
*
|
|
19
|
+
* Walks every segment (not just leaves) so layouts and middleware
|
|
20
|
+
* at any depth can import _segment.
|
|
21
|
+
*/
|
|
22
|
+
export function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string, string> {
|
|
23
|
+
const map = new Map<string, string>();
|
|
24
|
+
|
|
25
|
+
function walk(node: SegmentNode, fsPath: string): void {
|
|
26
|
+
const routePattern = node.urlPath || '/';
|
|
27
|
+
map.set(fsPath, routePattern);
|
|
28
|
+
|
|
29
|
+
for (const child of node.children) {
|
|
30
|
+
const childDir = join(fsPath, child.segmentName);
|
|
31
|
+
walk(child, childDir);
|
|
32
|
+
}
|
|
33
|
+
for (const [slotName, slotNode] of Object.entries(node.slots)) {
|
|
34
|
+
const slotDir = join(fsPath, slotName);
|
|
35
|
+
walk(slotNode, slotDir);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
walk(tree.root, appDir);
|
|
40
|
+
return map;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Generate per-route _segment.d.ts files in .timber/types/ mirroring
|
|
45
|
+
* the app/ directory structure. These files are importable via TypeScript's
|
|
46
|
+
* rootDirs feature.
|
|
47
|
+
*/
|
|
48
|
+
export async function writeSegmentTypes(
|
|
49
|
+
routeTree: RouteTree,
|
|
50
|
+
appDir: string,
|
|
51
|
+
root: string
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
// Compute the relative prefix from project root to appDir.
|
|
54
|
+
// e.g. if appDir is /project/src/app, relative is 'src/app'
|
|
55
|
+
const relative = appDir.slice(root.length + 1);
|
|
56
|
+
const typesBase = join(root, '.timber', 'types', relative);
|
|
57
|
+
const map = buildDirToRouteMap(routeTree, appDir);
|
|
58
|
+
|
|
59
|
+
const writes: Promise<void>[] = [];
|
|
60
|
+
for (const [fsDir, routePattern] of map) {
|
|
61
|
+
// Mirror the app/ structure under .timber/types/
|
|
62
|
+
const relDir = fsDir.slice(appDir.length); // e.g. '/[artistSlug]/[year]'
|
|
63
|
+
const segmentDir = join(typesBase, relDir);
|
|
64
|
+
const segmentPath = join(segmentDir, '_segment.d.ts');
|
|
65
|
+
const content = `export const route: '${routePattern}';\n`;
|
|
66
|
+
|
|
67
|
+
writes.push(
|
|
68
|
+
mkdir(segmentDir, { recursive: true }).then(() => writeFile(segmentPath, content, 'utf-8'))
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await Promise.all(writes);
|
|
73
|
+
}
|