@timber-js/app 0.2.0-alpha.114 → 0.2.0-alpha.115
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/_chunks/actions-ILhDUzES.js.map +1 -1
- package/dist/index.js +42 -25
- package/dist/index.js.map +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/routing/segment-codegen.d.ts +22 -9
- package/dist/routing/segment-codegen.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/plugins/routing.ts +26 -17
- package/src/routing/segment-codegen.ts +46 -21
- package/src/server/request-context.ts +1 -1
|
@@ -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;AAwBlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA2F1D,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAuNxD"}
|
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* $segment type generation and directory-to-route mapping.
|
|
3
3
|
*
|
|
4
|
-
* Generates per-route
|
|
4
|
+
* Generates per-route $segment.d.ts files in .timber/types/ that mirror
|
|
5
5
|
* the app/ directory structure. TypeScript's rootDirs feature makes these
|
|
6
|
-
* importable as '
|
|
6
|
+
* importable as './$segment' from route files.
|
|
7
7
|
*
|
|
8
|
-
* Design doc: design/41-global-params.md §The
|
|
8
|
+
* Design doc: design/41-global-params.md §The $segment Module
|
|
9
9
|
*/
|
|
10
10
|
import type { RouteTree } from './types.js';
|
|
11
|
+
/** Info carried per directory for type generation. */
|
|
12
|
+
export interface SegmentInfo {
|
|
13
|
+
/** The URL route pattern (groups and slots stripped). e.g. '/[artistSlug]/[year]' */
|
|
14
|
+
route: string;
|
|
15
|
+
/** The full tree path including groups and slots — uniquely identifies a segment.
|
|
16
|
+
* e.g. '/(browse)/[artistSlug]/@shows/[year]' */
|
|
17
|
+
segmentPath: string;
|
|
18
|
+
}
|
|
11
19
|
/**
|
|
12
|
-
* Build a map of absolute filesystem directory →
|
|
13
|
-
* Used by resolveId to map a
|
|
20
|
+
* Build a map of absolute filesystem directory → segment info.
|
|
21
|
+
* Used by resolveId to map a $segment import's importer to the correct route,
|
|
22
|
+
* and by writeSegmentTypes to generate per-route $segment.d.ts files.
|
|
14
23
|
*
|
|
15
24
|
* Walks every segment (not just leaves) so layouts and middleware
|
|
16
|
-
* at any depth can import
|
|
25
|
+
* at any depth can import $segment.
|
|
17
26
|
*/
|
|
18
|
-
export declare function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string,
|
|
27
|
+
export declare function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string, SegmentInfo>;
|
|
19
28
|
/**
|
|
20
|
-
* Generate
|
|
29
|
+
* Generate the full $segment.d.ts content for a segment.
|
|
30
|
+
*/
|
|
31
|
+
export declare function generateSegmentDts(info: SegmentInfo): string;
|
|
32
|
+
/**
|
|
33
|
+
* Generate per-route $segment.d.ts files in .timber/types/ mirroring
|
|
21
34
|
* the app/ directory structure. These files are importable via TypeScript's
|
|
22
35
|
* rootDirs feature.
|
|
23
36
|
*/
|
|
@@ -1 +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
|
|
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,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC1B,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;IACd;sDACkD;IAClD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAuB5F;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAM5D;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.115",
|
|
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,7 +15,11 @@ 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 {
|
|
18
|
+
import {
|
|
19
|
+
buildDirToRouteMap,
|
|
20
|
+
writeSegmentTypes,
|
|
21
|
+
type SegmentInfo,
|
|
22
|
+
} from '../routing/segment-codegen.js';
|
|
19
23
|
import {
|
|
20
24
|
collectDynamicSegments,
|
|
21
25
|
findSchemaFile,
|
|
@@ -56,12 +60,13 @@ const RESOLVED_SEARCH_PARAMS_REGISTRY_ID = `\0${SEARCH_PARAMS_REGISTRY_ID}`;
|
|
|
56
60
|
const SCHEMA_ID = 'virtual:timber-schema';
|
|
57
61
|
const RESOLVED_SCHEMA_ID = `\0${SCHEMA_ID}`;
|
|
58
62
|
|
|
59
|
-
// TIM-935:
|
|
63
|
+
// TIM-935: $segment virtual module prefix.
|
|
64
|
+
// Resolved ID encodes segment info as JSON after the prefix.
|
|
60
65
|
//
|
|
61
66
|
// Each route directory gets a virtual module that exports the route
|
|
62
|
-
//
|
|
63
|
-
// `\
|
|
64
|
-
const SEGMENT_PREFIX = '\
|
|
67
|
+
// and segmentPath. `import { route, segmentPath } from './$segment'`
|
|
68
|
+
// resolves to `\0$segment:<json>`.
|
|
69
|
+
const SEGMENT_PREFIX = '\0$segment:';
|
|
65
70
|
|
|
66
71
|
/**
|
|
67
72
|
* File convention names we track for changes that require manifest regeneration.
|
|
@@ -122,8 +127,8 @@ export function timberRouting(ctx: PluginContext): Plugin {
|
|
|
122
127
|
/** Track warned files to avoid repeating warnings on rescan. */
|
|
123
128
|
const warnedFiles = new Set<string>();
|
|
124
129
|
|
|
125
|
-
/** Maps absolute filesystem directory →
|
|
126
|
-
let dirToRoute: Map<string,
|
|
130
|
+
/** Maps absolute filesystem directory → segment info for $segment resolution. */
|
|
131
|
+
let dirToRoute: Map<string, SegmentInfo> = new Map();
|
|
127
132
|
|
|
128
133
|
function rescan(): void {
|
|
129
134
|
// Check app/ directory exists before scanning
|
|
@@ -204,19 +209,19 @@ export function timberRouting(ctx: PluginContext): Plugin {
|
|
|
204
209
|
return RESOLVED_SCHEMA_ID;
|
|
205
210
|
}
|
|
206
211
|
|
|
207
|
-
// TIM-935:
|
|
208
|
-
// Resolves `import { route } from '
|
|
212
|
+
// TIM-935: $segment virtual module.
|
|
213
|
+
// Resolves `import { route } from './$segment'` based on the
|
|
209
214
|
// importing file's directory in the app/ tree.
|
|
210
215
|
if (
|
|
211
216
|
importer &&
|
|
212
|
-
(cleanId === '
|
|
217
|
+
(cleanId === './$segment' || cleanId === './$segment.js' || cleanId === './$segment.ts')
|
|
213
218
|
) {
|
|
214
219
|
// Strip \0 prefix from importer if present
|
|
215
220
|
const cleanImporter = importer.startsWith('\0') ? importer.slice(1) : importer;
|
|
216
221
|
const importerDir = cleanImporter.substring(0, cleanImporter.lastIndexOf('/'));
|
|
217
|
-
const
|
|
218
|
-
if (
|
|
219
|
-
return `${SEGMENT_PREFIX}${
|
|
222
|
+
const segmentInfo = dirToRoute.get(importerDir);
|
|
223
|
+
if (segmentInfo !== undefined) {
|
|
224
|
+
return `${SEGMENT_PREFIX}${JSON.stringify(segmentInfo)}`;
|
|
220
225
|
}
|
|
221
226
|
}
|
|
222
227
|
|
|
@@ -249,10 +254,14 @@ export function timberRouting(ctx: PluginContext): Plugin {
|
|
|
249
254
|
return generateSchemaModule(ctx.appDir);
|
|
250
255
|
}
|
|
251
256
|
|
|
252
|
-
// TIM-935:
|
|
257
|
+
// TIM-935: $segment virtual module — exports route and segmentPath.
|
|
253
258
|
if (id.startsWith(SEGMENT_PREFIX)) {
|
|
254
|
-
const
|
|
255
|
-
return
|
|
259
|
+
const info: SegmentInfo = JSON.parse(id.slice(SEGMENT_PREFIX.length));
|
|
260
|
+
return [
|
|
261
|
+
`export const ROUTE = ${JSON.stringify(info.route)};`,
|
|
262
|
+
`export const SEGMENT_PATH = ${JSON.stringify(info.segmentPath)};`,
|
|
263
|
+
'',
|
|
264
|
+
].join('\n');
|
|
256
265
|
}
|
|
257
266
|
|
|
258
267
|
return null;
|
|
@@ -383,7 +392,7 @@ function invalidateManifest(server: ViteDevServer): void {
|
|
|
383
392
|
}
|
|
384
393
|
}
|
|
385
394
|
|
|
386
|
-
// TIM-935: Invalidate all
|
|
395
|
+
// TIM-935: Invalidate all $segment virtual modules (prefixed \0$segment:)
|
|
387
396
|
for (const mod of env.moduleGraph.idToModuleMap.values()) {
|
|
388
397
|
if (mod.id?.startsWith(SEGMENT_PREFIX)) {
|
|
389
398
|
env.moduleGraph.invalidateModule(mod);
|
|
@@ -1,47 +1,72 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* $segment type generation and directory-to-route mapping.
|
|
3
3
|
*
|
|
4
|
-
* Generates per-route
|
|
4
|
+
* Generates per-route $segment.d.ts files in .timber/types/ that mirror
|
|
5
5
|
* the app/ directory structure. TypeScript's rootDirs feature makes these
|
|
6
|
-
* importable as '
|
|
6
|
+
* importable as './$segment' from route files.
|
|
7
7
|
*
|
|
8
|
-
* Design doc: design/41-global-params.md §The
|
|
8
|
+
* Design doc: design/41-global-params.md §The $segment Module
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { writeFile, mkdir } from 'node:fs/promises';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
13
|
import type { RouteTree, SegmentNode } from './types.js';
|
|
14
14
|
|
|
15
|
+
/** Info carried per directory for type generation. */
|
|
16
|
+
export interface SegmentInfo {
|
|
17
|
+
/** The URL route pattern (groups and slots stripped). e.g. '/[artistSlug]/[year]' */
|
|
18
|
+
route: string;
|
|
19
|
+
/** The full tree path including groups and slots — uniquely identifies a segment.
|
|
20
|
+
* e.g. '/(browse)/[artistSlug]/@shows/[year]' */
|
|
21
|
+
segmentPath: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
/**
|
|
16
|
-
* Build a map of absolute filesystem directory →
|
|
17
|
-
* Used by resolveId to map a
|
|
25
|
+
* Build a map of absolute filesystem directory → segment info.
|
|
26
|
+
* Used by resolveId to map a $segment import's importer to the correct route,
|
|
27
|
+
* and by writeSegmentTypes to generate per-route $segment.d.ts files.
|
|
18
28
|
*
|
|
19
29
|
* Walks every segment (not just leaves) so layouts and middleware
|
|
20
|
-
* at any depth can import
|
|
30
|
+
* at any depth can import $segment.
|
|
21
31
|
*/
|
|
22
|
-
export function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string,
|
|
23
|
-
const map = new Map<string,
|
|
32
|
+
export function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string, SegmentInfo> {
|
|
33
|
+
const map = new Map<string, SegmentInfo>();
|
|
24
34
|
|
|
25
|
-
function walk(node: SegmentNode, fsPath: string): void {
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
function walk(node: SegmentNode, fsPath: string, treePath: string): void {
|
|
36
|
+
map.set(fsPath, {
|
|
37
|
+
route: node.urlPath || '/',
|
|
38
|
+
segmentPath: treePath || '/',
|
|
39
|
+
});
|
|
28
40
|
|
|
29
41
|
for (const child of node.children) {
|
|
30
42
|
const childDir = join(fsPath, child.segmentName);
|
|
31
|
-
|
|
43
|
+
const childTreePath = `${treePath}/${child.segmentName}`;
|
|
44
|
+
walk(child, childDir, childTreePath);
|
|
32
45
|
}
|
|
33
46
|
for (const [slotName, slotNode] of Object.entries(node.slots)) {
|
|
34
|
-
const slotDir = join(fsPath, slotName);
|
|
35
|
-
|
|
47
|
+
const slotDir = join(fsPath, `@${slotName}`);
|
|
48
|
+
const slotTreePath = `${treePath}/@${slotName}`;
|
|
49
|
+
walk(slotNode, slotDir, slotTreePath);
|
|
36
50
|
}
|
|
37
51
|
}
|
|
38
52
|
|
|
39
|
-
walk(tree.root, appDir);
|
|
53
|
+
walk(tree.root, appDir, '');
|
|
40
54
|
return map;
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
/**
|
|
44
|
-
* Generate
|
|
58
|
+
* Generate the full $segment.d.ts content for a segment.
|
|
59
|
+
*/
|
|
60
|
+
export function generateSegmentDts(info: SegmentInfo): string {
|
|
61
|
+
return [
|
|
62
|
+
`export const ROUTE: '${info.route}';`,
|
|
63
|
+
`export const SEGMENT_PATH: '${info.segmentPath}';`,
|
|
64
|
+
'',
|
|
65
|
+
].join('\n');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generate per-route $segment.d.ts files in .timber/types/ mirroring
|
|
45
70
|
* the app/ directory structure. These files are importable via TypeScript's
|
|
46
71
|
* rootDirs feature.
|
|
47
72
|
*/
|
|
@@ -57,15 +82,15 @@ export async function writeSegmentTypes(
|
|
|
57
82
|
const map = buildDirToRouteMap(routeTree, appDir);
|
|
58
83
|
|
|
59
84
|
const writes: Promise<void>[] = [];
|
|
60
|
-
for (const [fsDir,
|
|
85
|
+
for (const [fsDir, info] of map) {
|
|
61
86
|
// Mirror the app/ structure under .timber/types/
|
|
62
87
|
const relDir = fsDir.slice(appDir.length); // e.g. '/[artistSlug]/[year]'
|
|
63
88
|
const segmentDir = join(typesBase, relDir);
|
|
64
|
-
const
|
|
65
|
-
const content =
|
|
89
|
+
const segmentFile = join(segmentDir, '$segment.d.ts');
|
|
90
|
+
const content = generateSegmentDts(info);
|
|
66
91
|
|
|
67
92
|
writes.push(
|
|
68
|
-
mkdir(segmentDir, { recursive: true }).then(() => writeFile(
|
|
93
|
+
mkdir(segmentDir, { recursive: true }).then(() => writeFile(segmentFile, content, 'utf-8'))
|
|
69
94
|
);
|
|
70
95
|
}
|
|
71
96
|
|
|
@@ -115,7 +115,7 @@ export function getSegmentParams(route?: string): Record<string, string | string
|
|
|
115
115
|
|
|
116
116
|
// Dev-mode validation: warn when the route argument expects dynamic
|
|
117
117
|
// segments that don't exist on the actual matched route. This catches
|
|
118
|
-
// bugs like passing a child route's
|
|
118
|
+
// bugs like passing a child route's $segment to a parent layout.
|
|
119
119
|
// See design/41-global-params.md §Runtime Validation
|
|
120
120
|
if (route && isDebug() && store.matchedRoutePattern) {
|
|
121
121
|
const expected = extractDynamicSegments(route);
|