@timber-js/app 0.1.56 → 0.2.0-alpha.1
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/client/index.js +19 -42
- package/dist/client/index.js.map +1 -1
- package/dist/client/navigation-context.d.ts +5 -0
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +17 -5
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -149
- package/dist/index.js.map +1 -1
- package/dist/plugins/chunks.d.ts +20 -39
- package/dist/plugins/chunks.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/navigation-context.ts +16 -47
- package/src/client/top-loader.tsx +70 -83
- package/src/index.ts +4 -3
- package/src/plugins/chunks.ts +20 -195
package/src/plugins/chunks.ts
CHANGED
|
@@ -1,108 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* timber-chunks — Vite sub-plugin for
|
|
2
|
+
* timber-chunks — Vite sub-plugin for client chunk configuration.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Previously implemented a 5-tier cache strategy (vendor-react, vendor-timber,
|
|
5
|
+
* vendor-app, shared-app, shared-client) via manualChunks. This was removed
|
|
6
|
+
* in LOCAL-337 because the cache-tier separation added HTTP requests,
|
|
7
|
+
* complicated the architecture, and caused module duplication across chunks
|
|
8
|
+
* that required globalThis + Symbol.for workarounds.
|
|
5
9
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* The shared-app tier prevents tiny utility modules (constants, helpers,
|
|
13
|
-
* small UI components) from becoming individual chunks when shared across
|
|
14
|
-
* routes. Without this, Rolldown creates per-module chunks for any code
|
|
15
|
-
* shared between two or more entry points, producing many sub-1KB chunks.
|
|
16
|
-
*
|
|
17
|
-
* Server environments (RSC, SSR) are left to Vite's default chunking since
|
|
18
|
-
* Cloudflare Workers load all code from a single deployment bundle with no
|
|
19
|
-
* benefit from cache-tier separation.
|
|
10
|
+
* Current strategy: let Rolldown handle natural code splitting.
|
|
11
|
+
* Output is typically:
|
|
12
|
+
* - 1 main bundle (React + timber runtime + shared app code + user vendors)
|
|
13
|
+
* - Per-route chunks (page/layout components, only when route-specific)
|
|
14
|
+
* - rolldown-runtime (Rolldown's own output, unavoidable)
|
|
20
15
|
*
|
|
21
16
|
* Design docs: 27-chunking-strategy.md
|
|
22
17
|
*/
|
|
23
18
|
|
|
24
|
-
import { statSync } from 'node:fs';
|
|
25
19
|
import type { Plugin } from 'vite';
|
|
26
20
|
|
|
27
21
|
/**
|
|
28
|
-
*
|
|
29
|
-
* Modules under this size that aren't route files get merged into shared-app
|
|
30
|
-
* instead of getting their own tiny chunks.
|
|
31
|
-
*/
|
|
32
|
-
const SMALL_MODULE_THRESHOLD = 5 * 1024; // 5KB
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Route convention file basenames (without extension).
|
|
36
|
-
* These files define route segments and must stay in per-route chunks
|
|
37
|
-
* to preserve route-based code splitting.
|
|
38
|
-
*/
|
|
39
|
-
const ROUTE_FILE_BASENAMES = new Set([
|
|
40
|
-
'page',
|
|
41
|
-
'layout',
|
|
42
|
-
'loading',
|
|
43
|
-
'error',
|
|
44
|
-
'not-found',
|
|
45
|
-
'template',
|
|
46
|
-
'access',
|
|
47
|
-
'middleware',
|
|
48
|
-
'default',
|
|
49
|
-
'route',
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Cache for source file sizes to avoid repeated statSync calls.
|
|
54
|
-
* Populated lazily during the build.
|
|
55
|
-
*/
|
|
56
|
-
const sizeCache = new Map<string, number>();
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Get the source file size, with caching.
|
|
60
|
-
* Returns Infinity for virtual modules or files that can't be stat'd.
|
|
61
|
-
*/
|
|
62
|
-
function getSourceSize(id: string): number {
|
|
63
|
-
const cached = sizeCache.get(id);
|
|
64
|
-
if (cached !== undefined) return cached;
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const size = statSync(id).size;
|
|
68
|
-
sizeCache.set(id, size);
|
|
69
|
-
return size;
|
|
70
|
-
} catch {
|
|
71
|
-
sizeCache.set(id, Infinity);
|
|
72
|
-
return Infinity;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Extract the basename without extension from a module ID.
|
|
78
|
-
* e.g. '/project/app/dashboard/page.tsx' → 'page'
|
|
79
|
-
*/
|
|
80
|
-
function getBasename(id: string): string {
|
|
81
|
-
const lastSlash = id.lastIndexOf('/');
|
|
82
|
-
const filename = lastSlash >= 0 ? id.substring(lastSlash + 1) : id;
|
|
83
|
-
const dotIndex = filename.indexOf('.');
|
|
84
|
-
return dotIndex >= 0 ? filename.substring(0, dotIndex) : filename;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Check if a module is a React ecosystem package (tier 1).
|
|
89
|
-
*/
|
|
90
|
-
function isReactVendor(id: string): boolean {
|
|
91
|
-
return (
|
|
92
|
-
id.includes('node_modules/react-dom') ||
|
|
93
|
-
id.includes('node_modules/react/') ||
|
|
94
|
-
id.includes('node_modules/scheduler')
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Check if a module is part of the timber framework runtime (tier 2).
|
|
22
|
+
* Check if a module is part of the timber framework runtime.
|
|
100
23
|
*
|
|
101
24
|
* Matches both monorepo paths (packages/timber-app/...) and consumer
|
|
102
|
-
* project paths (node_modules/@timber-js/app/...) so
|
|
103
|
-
*
|
|
25
|
+
* project paths (node_modules/@timber-js/app/...) so identification
|
|
26
|
+
* is consistent regardless of how timber is installed.
|
|
27
|
+
*
|
|
28
|
+
* Exported for use in tests (singleton-audit, etc.).
|
|
104
29
|
*/
|
|
105
|
-
function isTimberRuntime(id: string): boolean {
|
|
30
|
+
export function isTimberRuntime(id: string): boolean {
|
|
106
31
|
return (
|
|
107
32
|
id.includes('/timber-app/') ||
|
|
108
33
|
id.includes('/@timber-js/app/') ||
|
|
@@ -111,115 +36,15 @@ function isTimberRuntime(id: string): boolean {
|
|
|
111
36
|
);
|
|
112
37
|
}
|
|
113
38
|
|
|
114
|
-
/**
|
|
115
|
-
* Check if a module is a user-installed node_modules dependency (tier 3).
|
|
116
|
-
* Excludes React ecosystem and timber runtime packages which have their own tiers.
|
|
117
|
-
*/
|
|
118
|
-
function isUserVendor(id: string): boolean {
|
|
119
|
-
return id.includes('node_modules/') && !isReactVendor(id) && !isTimberRuntime(id);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Check if a module is a route convention file that should stay per-route.
|
|
124
|
-
*/
|
|
125
|
-
function isRouteFile(id: string): boolean {
|
|
126
|
-
return ROUTE_FILE_BASENAMES.has(getBasename(id));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Categorize a module ID into a cache tier chunk name.
|
|
131
|
-
*
|
|
132
|
-
* Returns a chunk name for vendor modules and small shared app code,
|
|
133
|
-
* or undefined to let Rollup's default splitting handle route code.
|
|
134
|
-
*/
|
|
135
|
-
export function assignChunk(id: string): string | undefined {
|
|
136
|
-
// Tier 1: React ecosystem — changes on version bumps only
|
|
137
|
-
if (isReactVendor(id)) {
|
|
138
|
-
return 'vendor-react';
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Tier 2: timber framework runtime — changes on framework updates
|
|
142
|
-
if (isTimberRuntime(id)) {
|
|
143
|
-
return 'vendor-timber';
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Tier 3: User vendor libraries — changes on dependency updates
|
|
147
|
-
if (isUserVendor(id)) {
|
|
148
|
-
return 'vendor-app';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Tier 4: Small shared app modules — prevents tiny per-module chunks
|
|
152
|
-
// Skip route files (page, layout, etc.) to preserve route-based splitting.
|
|
153
|
-
// Skip virtual modules (contain \0 or don't start with /) as they have no
|
|
154
|
-
// meaningful source size.
|
|
155
|
-
if (!id.includes('\0') && id.startsWith('/') && !isRouteFile(id)) {
|
|
156
|
-
const size = getSourceSize(id);
|
|
157
|
-
if (size < SMALL_MODULE_THRESHOLD) {
|
|
158
|
-
return 'shared-app';
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Tier 5: Rollup's default splitting (per-route page/layout chunks, large shared modules)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Group timber's internal 'use client' modules into the vendor-timber chunk.
|
|
167
|
-
*
|
|
168
|
-
* The RSC plugin creates separate entry points for each 'use client' module,
|
|
169
|
-
* which manualChunks can't merge. This function is passed as the RSC plugin's
|
|
170
|
-
* `clientChunks` callback to group timber internals into a single chunk.
|
|
171
|
-
*
|
|
172
|
-
* User client components that are small (< 5KB) are grouped into shared-client
|
|
173
|
-
* to prevent thin facade wrappers from becoming individual chunks. This handles
|
|
174
|
-
* the RSC client reference facade problem where each 'use client' module gets
|
|
175
|
-
* a ~100-300 byte re-export wrapper chunk.
|
|
176
|
-
*/
|
|
177
|
-
export function assignClientChunk(meta: {
|
|
178
|
-
id: string;
|
|
179
|
-
normalizedId: string;
|
|
180
|
-
serverChunk: string;
|
|
181
|
-
}): string | undefined {
|
|
182
|
-
// Timber framework client modules → vendor-timber
|
|
183
|
-
// Match both monorepo paths (/timber-app/) and consumer paths (/@timber-js/app/)
|
|
184
|
-
if (meta.id.includes('/timber-app/') || meta.id.includes('/@timber-js/app/'))
|
|
185
|
-
return 'vendor-timber';
|
|
186
|
-
|
|
187
|
-
// Small user client components → shared-client (prevents facade micro-chunks)
|
|
188
|
-
if (!meta.id.includes('\0') && meta.id.startsWith('/')) {
|
|
189
|
-
const size = getSourceSize(meta.id);
|
|
190
|
-
if (size < SMALL_MODULE_THRESHOLD) {
|
|
191
|
-
return 'shared-client';
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Large user/third-party client components → default per-route splitting
|
|
196
|
-
}
|
|
197
|
-
|
|
198
39
|
/**
|
|
199
40
|
* Create the timber-chunks Vite plugin.
|
|
200
41
|
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
42
|
+
* Currently a no-op — Rolldown's default splitting produces the desired
|
|
43
|
+
* output (one main bundle + per-route chunks). The plugin is retained as
|
|
44
|
+
* a hook point for future chunking adjustments if needed.
|
|
204
45
|
*/
|
|
205
46
|
export function timberChunks(): Plugin {
|
|
206
47
|
return {
|
|
207
48
|
name: 'timber-chunks',
|
|
208
|
-
|
|
209
|
-
config() {
|
|
210
|
-
return {
|
|
211
|
-
environments: {
|
|
212
|
-
client: {
|
|
213
|
-
build: {
|
|
214
|
-
rollupOptions: {
|
|
215
|
-
output: {
|
|
216
|
-
manualChunks: assignChunk,
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
};
|
|
223
|
-
},
|
|
224
49
|
};
|
|
225
50
|
}
|