@timber-js/app 0.1.56 → 0.1.57
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 +65 -82
- package/src/index.ts +4 -3
- package/src/plugins/chunks.ts +20 -195
package/dist/plugins/chunks.d.ts
CHANGED
|
@@ -1,56 +1,37 @@
|
|
|
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
|
import type { Plugin } from 'vite';
|
|
24
19
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* Returns a chunk name for vendor modules and small shared app code,
|
|
28
|
-
* or undefined to let Rollup's default splitting handle route code.
|
|
29
|
-
*/
|
|
30
|
-
export declare function assignChunk(id: string): string | undefined;
|
|
31
|
-
/**
|
|
32
|
-
* Group timber's internal 'use client' modules into the vendor-timber chunk.
|
|
20
|
+
* Check if a module is part of the timber framework runtime.
|
|
33
21
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
22
|
+
* Matches both monorepo paths (packages/timber-app/...) and consumer
|
|
23
|
+
* project paths (node_modules/@timber-js/app/...) so identification
|
|
24
|
+
* is consistent regardless of how timber is installed.
|
|
37
25
|
*
|
|
38
|
-
*
|
|
39
|
-
* to prevent thin facade wrappers from becoming individual chunks. This handles
|
|
40
|
-
* the RSC client reference facade problem where each 'use client' module gets
|
|
41
|
-
* a ~100-300 byte re-export wrapper chunk.
|
|
26
|
+
* Exported for use in tests (singleton-audit, etc.).
|
|
42
27
|
*/
|
|
43
|
-
export declare function
|
|
44
|
-
id: string;
|
|
45
|
-
normalizedId: string;
|
|
46
|
-
serverChunk: string;
|
|
47
|
-
}): string | undefined;
|
|
28
|
+
export declare function isTimberRuntime(id: string): boolean;
|
|
48
29
|
/**
|
|
49
30
|
* Create the timber-chunks Vite plugin.
|
|
50
31
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
32
|
+
* Currently a no-op — Rolldown's default splitting produces the desired
|
|
33
|
+
* output (one main bundle + per-route chunks). The plugin is retained as
|
|
34
|
+
* a hook point for future chunking adjustments if needed.
|
|
54
35
|
*/
|
|
55
36
|
export declare function timberChunks(): Plugin;
|
|
56
37
|
//# sourceMappingURL=chunks.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunks.d.ts","sourceRoot":"","sources":["../../src/plugins/chunks.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"chunks.d.ts","sourceRoot":"","sources":["../../src/plugins/chunks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAIrC"}
|
package/package.json
CHANGED
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
* that depend on these APIs are safe to call from any environment —
|
|
26
26
|
* they return null or no-op when the APIs aren't available.
|
|
27
27
|
*
|
|
28
|
+
* Singleton guarantee: With the simplified chunking strategy (LOCAL-337),
|
|
29
|
+
* this module lives in exactly one client chunk. The previous globalThis +
|
|
30
|
+
* Symbol.for workaround for cross-chunk duplication is no longer needed.
|
|
31
|
+
* See design/27-chunking-strategy.md.
|
|
32
|
+
*
|
|
28
33
|
* See design/19-client-navigation.md §"NavigationContext"
|
|
29
34
|
*/
|
|
30
35
|
|
|
@@ -47,33 +52,17 @@ export interface NavigationState {
|
|
|
47
52
|
* The context is created lazily to avoid calling createContext at module
|
|
48
53
|
* level. In the RSC environment, React.createContext doesn't exist —
|
|
49
54
|
* calling it at import time would crash the server.
|
|
50
|
-
*
|
|
51
|
-
* IMPORTANT: Context instances are stored on globalThis, NOT in module-
|
|
52
|
-
* level variables. The RSC client bundler duplicates this module across
|
|
53
|
-
* the browser-entry chunk (index) and client-reference chunk (shared-app)
|
|
54
|
-
* because both entry graphs import it. Module-level variables would create
|
|
55
|
-
* separate singleton instances per chunk — the provider in TransitionRoot
|
|
56
|
-
* (index chunk) would use context A while the consumer in LinkStatusProvider
|
|
57
|
-
* (shared-app chunk) reads from context B. globalThis guarantees a single
|
|
58
|
-
* instance regardless of how many times the module is duplicated.
|
|
59
|
-
*
|
|
60
|
-
* See design/19-client-navigation.md §"Singleton Context Guarantee"
|
|
61
55
|
*/
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const PENDING_CTX_KEY = Symbol.for('__timber_pending_nav_ctx');
|
|
57
|
+
let _navCtx: React.Context<NavigationState | null> | undefined;
|
|
58
|
+
let _pendingNavCtx: React.Context<string | null> | undefined;
|
|
66
59
|
|
|
67
60
|
function getOrCreateContext(): React.Context<NavigationState | null> | undefined {
|
|
68
|
-
|
|
69
|
-
| React.Context<NavigationState | null>
|
|
70
|
-
| undefined;
|
|
71
|
-
if (existing !== undefined) return existing;
|
|
61
|
+
if (_navCtx !== undefined) return _navCtx;
|
|
72
62
|
// createContext may not exist in the RSC environment
|
|
73
63
|
if (typeof React.createContext === 'function') {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return ctx;
|
|
64
|
+
_navCtx = React.createContext<NavigationState | null>(null);
|
|
65
|
+
return _navCtx;
|
|
77
66
|
}
|
|
78
67
|
return undefined;
|
|
79
68
|
}
|
|
@@ -130,27 +119,15 @@ export function NavigationProvider({
|
|
|
130
119
|
* NavigationProvider with the correct params/pathname.
|
|
131
120
|
*
|
|
132
121
|
* This is NOT used by hooks directly — hooks read from React context.
|
|
133
|
-
*
|
|
134
|
-
* Stored on globalThis (like the context instances above) because the
|
|
135
|
-
* router lives in the shared-app chunk while renderRoot lives in the
|
|
136
|
-
* index chunk. Module-level variables would be separate per chunk.
|
|
137
122
|
*/
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
function _getNavStateStore(): { current: NavigationState } {
|
|
141
|
-
const g = globalThis as Record<symbol, unknown>;
|
|
142
|
-
if (!g[NAV_STATE_KEY]) {
|
|
143
|
-
g[NAV_STATE_KEY] = { current: { params: {}, pathname: '/' } };
|
|
144
|
-
}
|
|
145
|
-
return g[NAV_STATE_KEY] as { current: NavigationState };
|
|
146
|
-
}
|
|
123
|
+
let _navState: NavigationState = { params: {}, pathname: '/' };
|
|
147
124
|
|
|
148
125
|
export function setNavigationState(state: NavigationState): void {
|
|
149
|
-
|
|
126
|
+
_navState = state;
|
|
150
127
|
}
|
|
151
128
|
|
|
152
129
|
export function getNavigationState(): NavigationState {
|
|
153
|
-
return
|
|
130
|
+
return _navState;
|
|
154
131
|
}
|
|
155
132
|
|
|
156
133
|
// ---------------------------------------------------------------------------
|
|
@@ -161,21 +138,13 @@ export function getNavigationState(): NavigationState {
|
|
|
161
138
|
* Separate context for the in-flight navigation URL. Provided by
|
|
162
139
|
* TransitionRoot (urgent useState), consumed by LinkStatusProvider
|
|
163
140
|
* and useNavigationPending.
|
|
164
|
-
*
|
|
165
|
-
* Uses globalThis via Symbol.for for the same reason as NavigationContext
|
|
166
|
-
* above — the bundler duplicates this module across chunks, and module-
|
|
167
|
-
* level variables would create separate context instances.
|
|
168
141
|
*/
|
|
169
142
|
|
|
170
143
|
function getOrCreatePendingContext(): React.Context<string | null> | undefined {
|
|
171
|
-
|
|
172
|
-
| React.Context<string | null>
|
|
173
|
-
| undefined;
|
|
174
|
-
if (existing !== undefined) return existing;
|
|
144
|
+
if (_pendingNavCtx !== undefined) return _pendingNavCtx;
|
|
175
145
|
if (typeof React.createContext === 'function') {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return ctx;
|
|
146
|
+
_pendingNavCtx = React.createContext<string | null>(null);
|
|
147
|
+
return _pendingNavCtx;
|
|
179
148
|
}
|
|
180
149
|
return undefined;
|
|
181
150
|
}
|
|
@@ -13,8 +13,13 @@
|
|
|
13
13
|
* width over ~30s using ease-out timing. When navigation completes, the bar
|
|
14
14
|
* snaps to 100% and fades out over 200ms. No JS animation loops (RAF, setInterval).
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Phase transitions are derived synchronously during render (React's
|
|
17
|
+
* getDerivedStateFromProps pattern) — no useEffect needed for state tracking.
|
|
18
|
+
* The finishing → hidden cleanup uses onTransitionEnd from the CSS transition.
|
|
19
|
+
*
|
|
20
|
+
* When delay > 0, CSS animation-delay + a visibility keyframe ensure the bar
|
|
21
|
+
* stays invisible during the delay period. If navigation finishes before the
|
|
22
|
+
* delay, the bar was never visible so the finish transition is also invisible.
|
|
18
23
|
*
|
|
19
24
|
* See design/19-client-navigation.md §"useNavigationPending()"
|
|
20
25
|
* See LOCAL-336 for design decisions.
|
|
@@ -22,7 +27,7 @@
|
|
|
22
27
|
|
|
23
28
|
'use client';
|
|
24
29
|
|
|
25
|
-
import { useState,
|
|
30
|
+
import { useState, createElement } from 'react';
|
|
26
31
|
import { usePendingNavigationUrl } from './navigation-context.js';
|
|
27
32
|
|
|
28
33
|
// ─── Types ───────────────────────────────────────────────────────
|
|
@@ -36,7 +41,7 @@ export interface TopLoaderConfig {
|
|
|
36
41
|
height?: number;
|
|
37
42
|
/** Show subtle glow/shadow effect. Default: true. */
|
|
38
43
|
shadow?: boolean;
|
|
39
|
-
/** Delay in ms before showing the bar. Default:
|
|
44
|
+
/** Delay in ms before showing the bar. Default: 0. */
|
|
40
45
|
delay?: number;
|
|
41
46
|
/** CSS z-index. Default: 1600. */
|
|
42
47
|
zIndex?: number;
|
|
@@ -47,20 +52,22 @@ export interface TopLoaderConfig {
|
|
|
47
52
|
const DEFAULT_COLOR = '#2299DD';
|
|
48
53
|
const DEFAULT_HEIGHT = 3;
|
|
49
54
|
const DEFAULT_SHADOW = true;
|
|
50
|
-
const DEFAULT_DELAY =
|
|
55
|
+
const DEFAULT_DELAY = 0;
|
|
51
56
|
const DEFAULT_Z_INDEX = 1600;
|
|
52
57
|
|
|
53
|
-
// ─── Keyframes
|
|
58
|
+
// ─── Keyframes ───────────────────────────────────────────────────
|
|
54
59
|
|
|
55
60
|
// Unique keyframes name to avoid collisions with user styles.
|
|
56
|
-
const
|
|
61
|
+
const CRAWL_KEYFRAMES = '__timber_top_loader_crawl';
|
|
62
|
+
const APPEAR_KEYFRAMES = '__timber_top_loader_appear';
|
|
57
63
|
|
|
58
|
-
// Track whether the @keyframes
|
|
64
|
+
// Track whether the @keyframes rules have been injected into the document.
|
|
59
65
|
let keyframesInjected = false;
|
|
60
66
|
|
|
61
67
|
/**
|
|
62
|
-
* Inject the @keyframes
|
|
63
|
-
* Uses a <style> tag so the
|
|
68
|
+
* Inject the @keyframes rules into the document head once.
|
|
69
|
+
* Called during render (idempotent). Uses a <style> tag so the
|
|
70
|
+
* animations are available for inline-styled elements.
|
|
64
71
|
*/
|
|
65
72
|
function ensureKeyframes(): void {
|
|
66
73
|
if (keyframesInjected) return;
|
|
@@ -68,10 +75,14 @@ function ensureKeyframes(): void {
|
|
|
68
75
|
|
|
69
76
|
const style = document.createElement('style');
|
|
70
77
|
style.textContent = `
|
|
71
|
-
@keyframes ${
|
|
78
|
+
@keyframes ${CRAWL_KEYFRAMES} {
|
|
72
79
|
0% { width: 0%; }
|
|
73
80
|
100% { width: 90%; }
|
|
74
81
|
}
|
|
82
|
+
@keyframes ${APPEAR_KEYFRAMES} {
|
|
83
|
+
from { opacity: 0; }
|
|
84
|
+
to { opacity: 1; }
|
|
85
|
+
}
|
|
75
86
|
`;
|
|
76
87
|
document.head.appendChild(style);
|
|
77
88
|
keyframesInjected = true;
|
|
@@ -83,8 +94,15 @@ function ensureKeyframes(): void {
|
|
|
83
94
|
* Internal top-loader component. Injected by TransitionRoot.
|
|
84
95
|
*
|
|
85
96
|
* Reads pending navigation state from PendingNavigationContext.
|
|
86
|
-
*
|
|
87
|
-
*
|
|
97
|
+
* Phase transitions are derived synchronously during render:
|
|
98
|
+
*
|
|
99
|
+
* hidden → crawling: when isPending becomes true
|
|
100
|
+
* crawling → finishing: when isPending becomes false
|
|
101
|
+
* finishing → hidden: when CSS transition ends (onTransitionEnd)
|
|
102
|
+
* finishing → crawling: when isPending becomes true again
|
|
103
|
+
*
|
|
104
|
+
* No useEffect — all state changes are either derived during render
|
|
105
|
+
* (getDerivedStateFromProps pattern) or triggered by DOM events.
|
|
88
106
|
*/
|
|
89
107
|
export function TopLoader({ config }: { config?: TopLoaderConfig }): React.ReactElement | null {
|
|
90
108
|
const pendingUrl = usePendingNavigationUrl();
|
|
@@ -96,75 +114,23 @@ export function TopLoader({ config }: { config?: TopLoaderConfig }): React.React
|
|
|
96
114
|
const delay = config?.delay ?? DEFAULT_DELAY;
|
|
97
115
|
const zIndex = config?.zIndex ?? DEFAULT_Z_INDEX;
|
|
98
116
|
|
|
99
|
-
// Track visibility states:
|
|
100
|
-
// - 'hidden': no bar shown
|
|
101
|
-
// - 'crawling': bar is animating from 0% to 90%
|
|
102
|
-
// - 'finishing': bar is snapping to 100% and fading out
|
|
103
117
|
const [phase, setPhase] = useState<'hidden' | 'crawling' | 'finishing'>('hidden');
|
|
104
|
-
const delayTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
105
|
-
const finishTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
106
118
|
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}, []);
|
|
111
|
-
|
|
112
|
-
// Handle pending state changes
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
if (isPending) {
|
|
115
|
-
// Navigation started — wait for delay before showing the bar
|
|
116
|
-
// Clear any finish timer from a previous navigation
|
|
117
|
-
if (finishTimerRef.current !== null) {
|
|
118
|
-
clearTimeout(finishTimerRef.current);
|
|
119
|
-
finishTimerRef.current = null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (delay > 0) {
|
|
123
|
-
delayTimerRef.current = setTimeout(() => {
|
|
124
|
-
delayTimerRef.current = null;
|
|
125
|
-
setPhase('crawling');
|
|
126
|
-
}, delay);
|
|
127
|
-
} else {
|
|
128
|
-
setPhase('crawling');
|
|
129
|
-
}
|
|
130
|
-
} else {
|
|
131
|
-
// Navigation ended (or was never pending)
|
|
132
|
-
// Clear the delay timer — if it hasn't fired, the bar never shows
|
|
133
|
-
if (delayTimerRef.current !== null) {
|
|
134
|
-
clearTimeout(delayTimerRef.current);
|
|
135
|
-
delayTimerRef.current = null;
|
|
136
|
-
// Bar was never shown — stay hidden
|
|
137
|
-
if (phase === 'hidden') return;
|
|
138
|
-
}
|
|
119
|
+
// ─── Synchronous phase derivation (getDerivedStateFromProps) ──
|
|
120
|
+
// React allows setState during render if the value changes — it
|
|
121
|
+
// immediately re-renders with the updated state before committing.
|
|
139
122
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}, 200);
|
|
147
|
-
}
|
|
148
|
-
// If already 'finishing' or 'hidden', do nothing
|
|
149
|
-
}
|
|
123
|
+
if (isPending && (phase === 'hidden' || phase === 'finishing')) {
|
|
124
|
+
setPhase('crawling');
|
|
125
|
+
}
|
|
126
|
+
if (!isPending && phase === 'crawling') {
|
|
127
|
+
setPhase('finishing');
|
|
128
|
+
}
|
|
150
129
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- phase is read but intentionally not a dependency
|
|
158
|
-
}, [isPending, delay]);
|
|
159
|
-
|
|
160
|
-
// Cleanup finish timer on unmount
|
|
161
|
-
useEffect(() => {
|
|
162
|
-
return () => {
|
|
163
|
-
if (finishTimerRef.current !== null) {
|
|
164
|
-
clearTimeout(finishTimerRef.current);
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
}, []);
|
|
130
|
+
// Inject keyframes on first visible render (idempotent)
|
|
131
|
+
if (phase !== 'hidden') {
|
|
132
|
+
ensureKeyframes();
|
|
133
|
+
}
|
|
168
134
|
|
|
169
135
|
if (phase === 'hidden') return null;
|
|
170
136
|
|
|
@@ -185,11 +151,17 @@ export function TopLoader({ config }: { config?: TopLoaderConfig }): React.React
|
|
|
185
151
|
backgroundColor: color,
|
|
186
152
|
...(phase === 'crawling'
|
|
187
153
|
? {
|
|
188
|
-
//
|
|
189
|
-
animation
|
|
154
|
+
// Crawl from 0% to 90% over 30s. When delay > 0, both the crawl
|
|
155
|
+
// and a visibility animation are delayed — the bar stays at width 0%
|
|
156
|
+
// and opacity 0 during the delay, then appears and starts crawling.
|
|
157
|
+
// With delay 0, the appear animation is instant (0s duration, no delay).
|
|
158
|
+
animation: [
|
|
159
|
+
`${CRAWL_KEYFRAMES} 30s ease-out ${delay}ms forwards`,
|
|
160
|
+
`${APPEAR_KEYFRAMES} 0s ${delay}ms both`,
|
|
161
|
+
].join(', '),
|
|
190
162
|
}
|
|
191
163
|
: {
|
|
192
|
-
// Finishing: snap to 100% and fade out
|
|
164
|
+
// Finishing: snap to 100% and fade out over 200ms.
|
|
193
165
|
width: '100%',
|
|
194
166
|
opacity: 0,
|
|
195
167
|
transition: 'width 200ms ease, opacity 200ms ease',
|
|
@@ -201,6 +173,17 @@ export function TopLoader({ config }: { config?: TopLoaderConfig }): React.React
|
|
|
201
173
|
: {}),
|
|
202
174
|
};
|
|
203
175
|
|
|
176
|
+
// Clean up the finishing phase when the CSS transition completes.
|
|
177
|
+
// onTransitionEnd fires once per transitioned property — we act on
|
|
178
|
+
// the first one (opacity) and ignore subsequent (width).
|
|
179
|
+
const handleTransitionEnd = phase === 'finishing'
|
|
180
|
+
? (e: React.TransitionEvent) => {
|
|
181
|
+
if (e.propertyName === 'opacity') {
|
|
182
|
+
setPhase('hidden');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
: undefined;
|
|
186
|
+
|
|
204
187
|
return createElement(
|
|
205
188
|
'div',
|
|
206
189
|
{
|
|
@@ -208,6 +191,6 @@ export function TopLoader({ config }: { config?: TopLoaderConfig }): React.React
|
|
|
208
191
|
'aria-hidden': 'true',
|
|
209
192
|
'data-timber-top-loader': '',
|
|
210
193
|
},
|
|
211
|
-
createElement('div', { style: barStyle })
|
|
194
|
+
createElement('div', { style: barStyle, onTransitionEnd: handleTransitionEnd })
|
|
212
195
|
);
|
|
213
196
|
}
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { timberServerActionExports } from './plugins/server-action-exports';
|
|
|
18
18
|
import { timberBuildManifest } from './plugins/build-manifest';
|
|
19
19
|
import { timberDevLogs } from './plugins/dev-logs';
|
|
20
20
|
import { timberReactProd } from './plugins/react-prod';
|
|
21
|
-
import { timberChunks
|
|
21
|
+
import { timberChunks } from './plugins/chunks';
|
|
22
22
|
import { timberServerBundle } from './plugins/server-bundle';
|
|
23
23
|
import { timberAdapterBuild } from './plugins/adapter-build';
|
|
24
24
|
import { timberBuildReport } from './plugins/build-report';
|
|
@@ -129,7 +129,7 @@ export interface TimberUserConfig {
|
|
|
129
129
|
height?: number;
|
|
130
130
|
/** Show subtle glow/shadow effect. Default: true. */
|
|
131
131
|
shadow?: boolean;
|
|
132
|
-
/** Delay in ms before showing the bar (avoids flash on fast navs). Default:
|
|
132
|
+
/** Delay in ms before showing the bar (avoids flash on fast navs). Default: 0. */
|
|
133
133
|
delay?: number;
|
|
134
134
|
/** CSS z-index. Default: 1600. */
|
|
135
135
|
zIndex?: number;
|
|
@@ -400,7 +400,8 @@ export function timber(config?: TimberUserConfig): PluginOption[] {
|
|
|
400
400
|
ssr: 'virtual:timber-ssr-entry',
|
|
401
401
|
client: 'virtual:timber-browser-entry',
|
|
402
402
|
},
|
|
403
|
-
clientChunks
|
|
403
|
+
// No custom clientChunks — Rolldown handles natural code splitting.
|
|
404
|
+
// See design/27-chunking-strategy.md and LOCAL-337.
|
|
404
405
|
});
|
|
405
406
|
}
|
|
406
407
|
);
|