@timber-js/app 0.1.1 → 0.1.3
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.d.ts.map +1 -1
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/adapters/cloudflare.ts +325 -0
- package/src/adapters/nitro.ts +366 -0
- package/src/adapters/types.ts +63 -0
- package/src/cache/index.ts +91 -0
- package/src/cache/redis-handler.ts +91 -0
- package/src/cache/register-cached-function.ts +99 -0
- package/src/cache/singleflight.ts +26 -0
- package/src/cache/stable-stringify.ts +21 -0
- package/src/cache/timber-cache.ts +116 -0
- package/src/cli.ts +201 -0
- package/src/client/browser-entry.ts +663 -0
- package/src/client/error-boundary.tsx +209 -0
- package/src/client/form.tsx +200 -0
- package/src/client/head.ts +61 -0
- package/src/client/history.ts +46 -0
- package/src/client/index.ts +60 -0
- package/src/client/link-navigate-interceptor.tsx +62 -0
- package/src/client/link-status-provider.tsx +40 -0
- package/src/client/link.tsx +310 -0
- package/src/client/nuqs-adapter.tsx +117 -0
- package/src/client/router-ref.ts +25 -0
- package/src/client/router.ts +563 -0
- package/src/client/segment-cache.ts +194 -0
- package/src/client/segment-context.ts +57 -0
- package/src/client/ssr-data.ts +95 -0
- package/src/client/types.ts +4 -0
- package/src/client/unload-guard.ts +34 -0
- package/src/client/use-cookie.ts +122 -0
- package/src/client/use-link-status.ts +46 -0
- package/src/client/use-navigation-pending.ts +47 -0
- package/src/client/use-params.ts +71 -0
- package/src/client/use-pathname.ts +43 -0
- package/src/client/use-query-states.ts +133 -0
- package/src/client/use-router.ts +77 -0
- package/src/client/use-search-params.ts +74 -0
- package/src/client/use-selected-layout-segment.ts +110 -0
- package/src/content/index.ts +13 -0
- package/src/cookies/define-cookie.ts +137 -0
- package/src/cookies/index.ts +9 -0
- package/src/fonts/ast.ts +359 -0
- package/src/fonts/css.ts +68 -0
- package/src/fonts/fallbacks.ts +248 -0
- package/src/fonts/google.ts +332 -0
- package/src/fonts/local.ts +177 -0
- package/src/fonts/types.ts +88 -0
- package/src/index.ts +420 -0
- package/src/plugins/adapter-build.ts +118 -0
- package/src/plugins/build-manifest.ts +323 -0
- package/src/plugins/build-report.ts +353 -0
- package/src/plugins/cache-transform.ts +199 -0
- package/src/plugins/chunks.ts +90 -0
- package/src/plugins/content.ts +136 -0
- package/src/plugins/dev-error-overlay.ts +230 -0
- package/src/plugins/dev-logs.ts +280 -0
- package/src/plugins/dev-server.ts +391 -0
- package/src/plugins/dynamic-transform.ts +161 -0
- package/src/plugins/entries.ts +214 -0
- package/src/plugins/fonts.ts +581 -0
- package/src/plugins/mdx.ts +179 -0
- package/src/plugins/react-prod.ts +56 -0
- package/src/plugins/routing.ts +419 -0
- package/src/plugins/server-action-exports.ts +220 -0
- package/src/plugins/server-bundle.ts +113 -0
- package/src/plugins/shims.ts +168 -0
- package/src/plugins/static-build.ts +207 -0
- package/src/routing/codegen.ts +396 -0
- package/src/routing/index.ts +14 -0
- package/src/routing/interception.ts +173 -0
- package/src/routing/scanner.ts +487 -0
- package/src/routing/status-file-lint.ts +114 -0
- package/src/routing/types.ts +100 -0
- package/src/search-params/analyze.ts +192 -0
- package/src/search-params/codecs.ts +153 -0
- package/src/search-params/create.ts +314 -0
- package/src/search-params/index.ts +23 -0
- package/src/search-params/registry.ts +31 -0
- package/src/server/access-gate.tsx +142 -0
- package/src/server/action-client.ts +473 -0
- package/src/server/action-handler.ts +325 -0
- package/src/server/actions.ts +236 -0
- package/src/server/asset-headers.ts +81 -0
- package/src/server/body-limits.ts +102 -0
- package/src/server/build-manifest.ts +234 -0
- package/src/server/canonicalize.ts +90 -0
- package/src/server/client-module-map.ts +58 -0
- package/src/server/csrf.ts +79 -0
- package/src/server/deny-renderer.ts +302 -0
- package/src/server/dev-logger.ts +419 -0
- package/src/server/dev-span-processor.ts +78 -0
- package/src/server/dev-warnings.ts +282 -0
- package/src/server/early-hints-sender.ts +55 -0
- package/src/server/early-hints.ts +142 -0
- package/src/server/error-boundary-wrapper.ts +69 -0
- package/src/server/error-formatter.ts +184 -0
- package/src/server/flush.ts +182 -0
- package/src/server/form-data.ts +176 -0
- package/src/server/form-flash.ts +93 -0
- package/src/server/html-injectors.ts +445 -0
- package/src/server/index.ts +222 -0
- package/src/server/instrumentation.ts +136 -0
- package/src/server/logger.ts +145 -0
- package/src/server/manifest-status-resolver.ts +215 -0
- package/src/server/metadata-render.ts +527 -0
- package/src/server/metadata-routes.ts +189 -0
- package/src/server/metadata.ts +263 -0
- package/src/server/middleware-runner.ts +32 -0
- package/src/server/nuqs-ssr-provider.tsx +63 -0
- package/src/server/pipeline.ts +555 -0
- package/src/server/prerender.ts +139 -0
- package/src/server/primitives.ts +264 -0
- package/src/server/proxy.ts +43 -0
- package/src/server/request-context.ts +554 -0
- package/src/server/route-element-builder.ts +395 -0
- package/src/server/route-handler.ts +153 -0
- package/src/server/route-matcher.ts +316 -0
- package/src/server/rsc-entry/api-handler.ts +112 -0
- package/src/server/rsc-entry/error-renderer.ts +177 -0
- package/src/server/rsc-entry/helpers.ts +147 -0
- package/src/server/rsc-entry/index.ts +688 -0
- package/src/server/rsc-entry/ssr-bridge.ts +18 -0
- package/src/server/slot-resolver.ts +359 -0
- package/src/server/ssr-entry.ts +161 -0
- package/src/server/ssr-render.ts +200 -0
- package/src/server/status-code-resolver.ts +282 -0
- package/src/server/tracing.ts +281 -0
- package/src/server/tree-builder.ts +354 -0
- package/src/server/types.ts +150 -0
- package/src/shims/font-google.ts +67 -0
- package/src/shims/headers.ts +11 -0
- package/src/shims/image.ts +48 -0
- package/src/shims/link.ts +9 -0
- package/src/shims/navigation-client.ts +52 -0
- package/src/shims/navigation.ts +31 -0
- package/src/shims/server-only-noop.js +5 -0
- package/src/utils/directive-parser.ts +529 -0
- package/src/utils/format.ts +10 -0
- package/src/utils/startup-timer.ts +102 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* timber-dynamic-transform — Vite sub-plugin for 'use dynamic' directive.
|
|
3
|
+
*
|
|
4
|
+
* Detects `'use dynamic'` directives in server component function bodies
|
|
5
|
+
* and transforms them into `markDynamic()` runtime calls. The directive
|
|
6
|
+
* declares a dynamic boundary — the component and its subtree opt out of
|
|
7
|
+
* the pre-rendered shell and render per-request.
|
|
8
|
+
*
|
|
9
|
+
* - In `output: 'static'` mode, `'use dynamic'` is a build error.
|
|
10
|
+
* - In standard SSR routes (no prerender.ts), the directive is a no-op
|
|
11
|
+
* (everything is already per-request), but the transform still runs
|
|
12
|
+
* so the runtime can skip unnecessary work.
|
|
13
|
+
*
|
|
14
|
+
* Design doc: design/15-future-prerendering.md §"'use dynamic'"
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { Plugin } from 'vite';
|
|
18
|
+
import type { PluginContext } from '#/index.js';
|
|
19
|
+
import { findFunctionsWithDirective, containsDirective } from '#/utils/directive-parser.js';
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Detection
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Quick check: does this source file contain 'use dynamic' anywhere?
|
|
27
|
+
* Used as a fast bail-out before doing expensive AST parsing.
|
|
28
|
+
*/
|
|
29
|
+
export function containsUseDynamic(code: string): boolean {
|
|
30
|
+
return containsDirective(code, 'use dynamic');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Transform
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
interface TransformResult {
|
|
38
|
+
code: string;
|
|
39
|
+
map?: null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Find function declarations/expressions containing 'use dynamic' and
|
|
44
|
+
* transform them into markDynamic() calls.
|
|
45
|
+
*
|
|
46
|
+
* Input:
|
|
47
|
+
* ```tsx
|
|
48
|
+
* export default async function AddToCartButton({ productId }) {
|
|
49
|
+
* 'use dynamic'
|
|
50
|
+
* const user = await getUser()
|
|
51
|
+
* return <button>Add to cart</button>
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* Output:
|
|
56
|
+
* ```tsx
|
|
57
|
+
* import { markDynamic as __markDynamic } from '@timber/app/runtime';
|
|
58
|
+
* export default async function AddToCartButton({ productId }) {
|
|
59
|
+
* __markDynamic();
|
|
60
|
+
* const user = await getUser()
|
|
61
|
+
* return <button>Add to cart</button>
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* The markDynamic() call registers the component boundary as dynamic
|
|
66
|
+
* at render time. The pre-render pass uses this to know which subtrees
|
|
67
|
+
* to skip and leave as holes for per-request rendering.
|
|
68
|
+
*/
|
|
69
|
+
export function transformUseDynamic(code: string): TransformResult | null {
|
|
70
|
+
if (!containsUseDynamic(code)) return null;
|
|
71
|
+
|
|
72
|
+
const functions = findFunctionsWithDirective(code, 'use dynamic');
|
|
73
|
+
if (functions.length === 0) return null;
|
|
74
|
+
|
|
75
|
+
// Replace directive strings with __markDynamic() calls, processing
|
|
76
|
+
// from end to start to preserve source offsets
|
|
77
|
+
let result = code;
|
|
78
|
+
for (const fn of functions) {
|
|
79
|
+
// Replace the directive in the body content
|
|
80
|
+
const cleanBody = fn.bodyContent.replace(/['"]use dynamic['"];?/, '__markDynamic();');
|
|
81
|
+
// Reconstruct: replace the body content between braces
|
|
82
|
+
result = result.slice(0, fn.bodyStart) + cleanBody + result.slice(fn.bodyEnd);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Add the import at the top
|
|
86
|
+
result = `import { markDynamic as __markDynamic } from '@timber/app/runtime';\n` + result;
|
|
87
|
+
|
|
88
|
+
return { code: result, map: null };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Static mode validation
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* In `output: 'static'` mode, `'use dynamic'` is a build error.
|
|
97
|
+
* Static mode renders everything at build time — there is no per-request
|
|
98
|
+
* rendering to opt into.
|
|
99
|
+
*/
|
|
100
|
+
export function validateNoDynamicInStaticMode(
|
|
101
|
+
code: string
|
|
102
|
+
): { message: string; line?: number } | null {
|
|
103
|
+
if (!containsUseDynamic(code)) return null;
|
|
104
|
+
|
|
105
|
+
const functions = findFunctionsWithDirective(code, 'use dynamic');
|
|
106
|
+
if (functions.length === 0) return null;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
message:
|
|
110
|
+
`'use dynamic' cannot be used in static mode (output: 'static'). ` +
|
|
111
|
+
`Static mode renders all content at build time — there is no per-request rendering. ` +
|
|
112
|
+
`Remove the directive or switch to output: 'server'.`,
|
|
113
|
+
line: functions[functions.length - 1].directiveLine, // First occurrence (sorted descending)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Vite Plugin
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create the timber-dynamic-transform Vite plugin.
|
|
123
|
+
*
|
|
124
|
+
* In server mode: transforms 'use dynamic' into markDynamic() calls.
|
|
125
|
+
* In static mode: rejects 'use dynamic' as a build error.
|
|
126
|
+
*/
|
|
127
|
+
export function timberDynamicTransform(ctx: PluginContext): Plugin {
|
|
128
|
+
const isStatic = ctx.config.output === 'static';
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
name: 'timber-dynamic-transform',
|
|
132
|
+
|
|
133
|
+
transform(code: string, id: string) {
|
|
134
|
+
// Skip node_modules
|
|
135
|
+
if (id.includes('node_modules')) return null;
|
|
136
|
+
|
|
137
|
+
// Only check files in the app directory
|
|
138
|
+
if (!id.includes('/app/') && !id.startsWith('app/')) return null;
|
|
139
|
+
|
|
140
|
+
// Only check JS/TS files
|
|
141
|
+
if (!/\.[jt]sx?$/.test(id)) return null;
|
|
142
|
+
|
|
143
|
+
// Quick bail-out
|
|
144
|
+
if (!containsUseDynamic(code)) return null;
|
|
145
|
+
|
|
146
|
+
// In static mode, 'use dynamic' is a build error
|
|
147
|
+
if (isStatic) {
|
|
148
|
+
const error = validateNoDynamicInStaticMode(code);
|
|
149
|
+
if (error) {
|
|
150
|
+
this.error(
|
|
151
|
+
`[timber] Static mode error in ${id}${error.line ? `:${error.line}` : ''}: ${error.message}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// In server mode, transform the directive
|
|
158
|
+
return transformUseDynamic(code);
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* timber-entries — Vite sub-plugin for entry virtual module resolution.
|
|
3
|
+
*
|
|
4
|
+
* Resolves virtual:timber-rsc-entry, virtual:timber-ssr-entry,
|
|
5
|
+
* virtual:timber-browser-entry to real TypeScript files, and generates
|
|
6
|
+
* virtual:timber-config as serialized runtime config.
|
|
7
|
+
*
|
|
8
|
+
* Entry modules are real .ts files — NOT codegen strings. The only
|
|
9
|
+
* generated code is virtual:timber-config (serialized runtime config).
|
|
10
|
+
*
|
|
11
|
+
* Design docs: 18-build-system.md §"Entry Generation", §"Virtual Modules"
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Plugin } from 'vite';
|
|
15
|
+
import { resolve, dirname } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import type { PluginContext } from '#/index.js';
|
|
19
|
+
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
|
|
22
|
+
// In workspace dev, __dirname is src/plugins/ and src/ is the parent.
|
|
23
|
+
// In published package, entries.ts is bundled into dist/index.js so
|
|
24
|
+
// __dirname is dist/ and src/ is a sibling directory.
|
|
25
|
+
const SRC_DIR = existsSync(resolve(__dirname, '..', 'server', 'rsc-entry', 'index.ts'))
|
|
26
|
+
? resolve(__dirname, '..')
|
|
27
|
+
: resolve(__dirname, '..', 'src');
|
|
28
|
+
|
|
29
|
+
// ─── Virtual Module IDs ──────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const VIRTUAL_IDS = {
|
|
32
|
+
rscEntry: 'virtual:timber-rsc-entry',
|
|
33
|
+
ssrEntry: 'virtual:timber-ssr-entry',
|
|
34
|
+
browserEntry: 'virtual:timber-browser-entry',
|
|
35
|
+
config: 'virtual:timber-config',
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Map from virtual module IDs to their resolved file paths.
|
|
40
|
+
*
|
|
41
|
+
* These point to .ts source files that Vite transpiles at runtime.
|
|
42
|
+
* They import virtual modules (virtual:timber-config, etc.) so they
|
|
43
|
+
* cannot be pre-compiled — they must be processed by Vite's pipeline
|
|
44
|
+
* with timber's plugin active. The .ts files are included in the
|
|
45
|
+
* published package via the `files` field.
|
|
46
|
+
*/
|
|
47
|
+
const ENTRY_FILE_MAP: Record<string, string> = {
|
|
48
|
+
[VIRTUAL_IDS.rscEntry]: resolve(SRC_DIR, 'server', 'rsc-entry', 'index.ts'),
|
|
49
|
+
[VIRTUAL_IDS.ssrEntry]: resolve(SRC_DIR, 'server', 'ssr-entry.ts'),
|
|
50
|
+
[VIRTUAL_IDS.browserEntry]: resolve(SRC_DIR, 'client', 'browser-entry.ts'),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** The \0-prefixed resolved ID for virtual:timber-config */
|
|
54
|
+
const RESOLVED_CONFIG_ID = `\0${VIRTUAL_IDS.config}`;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Strip the \0 prefix from a module ID.
|
|
58
|
+
*
|
|
59
|
+
* The RSC plugin generates browser entry imports using already-resolved
|
|
60
|
+
* \0-prefixed IDs. Vite's import-analysis cannot resolve these.
|
|
61
|
+
* See 18-build-system.md §"Resolution Quirks".
|
|
62
|
+
*/
|
|
63
|
+
function stripNullPrefix(id: string): string {
|
|
64
|
+
return id.startsWith('\0') ? id.slice(1) : id;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Strip a root directory prefix from a module ID.
|
|
69
|
+
*
|
|
70
|
+
* Vite prefixes virtual module IDs with the project root when resolving
|
|
71
|
+
* SSR build entries. We need to handle both `virtual:timber-rsc-entry`
|
|
72
|
+
* and `<root>/virtual:timber-rsc-entry`.
|
|
73
|
+
* See 18-build-system.md §"Resolution Quirks".
|
|
74
|
+
*/
|
|
75
|
+
function stripRootPrefix(id: string, root: string): string {
|
|
76
|
+
if (id.startsWith(root)) {
|
|
77
|
+
// Remove root + path separator
|
|
78
|
+
const stripped = id.slice(root.length);
|
|
79
|
+
// Handle both /virtual:... and \virtual:... (Windows)
|
|
80
|
+
if (stripped.startsWith('/') || stripped.startsWith('\\')) {
|
|
81
|
+
return stripped.slice(1);
|
|
82
|
+
}
|
|
83
|
+
return stripped;
|
|
84
|
+
}
|
|
85
|
+
return id;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Generate the virtual:timber-config module source.
|
|
90
|
+
*
|
|
91
|
+
* Serializes output mode and feature flags for runtime consumption.
|
|
92
|
+
*/
|
|
93
|
+
function generateConfigModule(ctx: PluginContext): string {
|
|
94
|
+
// Resolve cookie secrets: `secret` shorthand expands to `secrets: [secret]`
|
|
95
|
+
const cookieSecrets = ctx.config.cookies?.secrets ??
|
|
96
|
+
(ctx.config.cookies?.secret ? [ctx.config.cookies.secret] : undefined);
|
|
97
|
+
|
|
98
|
+
const runtimeConfig = {
|
|
99
|
+
output: ctx.config.output ?? 'server',
|
|
100
|
+
csrf: ctx.config.csrf ?? true,
|
|
101
|
+
allowedOrigins: ctx.config.allowedOrigins,
|
|
102
|
+
clientJavascript: ctx.clientJavascript,
|
|
103
|
+
dev: ctx.dev ?? false,
|
|
104
|
+
slowPhaseMs: ctx.config.dev?.slowPhaseMs ?? 200,
|
|
105
|
+
cookieSecrets,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return [
|
|
109
|
+
'// Auto-generated runtime config — do not edit.',
|
|
110
|
+
'// Generated by timber-entries plugin.',
|
|
111
|
+
'',
|
|
112
|
+
`const config = ${JSON.stringify(runtimeConfig, null, 2)};`,
|
|
113
|
+
'',
|
|
114
|
+
'export default config;',
|
|
115
|
+
].join('\n');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Create the timber-entries Vite plugin.
|
|
120
|
+
*
|
|
121
|
+
* Hooks: resolveId, load
|
|
122
|
+
*/
|
|
123
|
+
export function timberEntries(ctx: PluginContext): Plugin {
|
|
124
|
+
return {
|
|
125
|
+
name: 'timber-entries',
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Resolve virtual entry and config module IDs.
|
|
129
|
+
*
|
|
130
|
+
* Handles:
|
|
131
|
+
* - virtual:timber-rsc-entry → real file path
|
|
132
|
+
* - virtual:timber-ssr-entry → real file path
|
|
133
|
+
* - virtual:timber-browser-entry → real file path
|
|
134
|
+
* - virtual:timber-config → \0-prefixed virtual ID
|
|
135
|
+
* - \0 prefix stripping (RSC plugin re-imports)
|
|
136
|
+
* - Root prefix stripping (SSR build entries)
|
|
137
|
+
*/
|
|
138
|
+
resolveId(id: string) {
|
|
139
|
+
// Step 1: Strip \0 prefix if present
|
|
140
|
+
let cleanId = stripNullPrefix(id);
|
|
141
|
+
|
|
142
|
+
// Step 2: Strip root prefix if present
|
|
143
|
+
cleanId = stripRootPrefix(cleanId, ctx.root);
|
|
144
|
+
|
|
145
|
+
// Check entry file map (real files)
|
|
146
|
+
if (cleanId in ENTRY_FILE_MAP) {
|
|
147
|
+
return ENTRY_FILE_MAP[cleanId];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Check config virtual module
|
|
151
|
+
if (cleanId === VIRTUAL_IDS.config) {
|
|
152
|
+
return RESOLVED_CONFIG_ID;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null;
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Load the virtual:timber-config module.
|
|
160
|
+
*
|
|
161
|
+
* Entry files (rsc/ssr/browser) are real TypeScript files that Vite
|
|
162
|
+
* processes normally. Only virtual:timber-config needs generated code.
|
|
163
|
+
*/
|
|
164
|
+
load(id: string) {
|
|
165
|
+
if (id === RESOLVED_CONFIG_ID) {
|
|
166
|
+
return generateConfigModule(ctx);
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Rename "rsc-entry" chunks in the client build to "rsc-client-entry".
|
|
173
|
+
*
|
|
174
|
+
* The RSC plugin creates client reference facades named after the RSC
|
|
175
|
+
* entry (virtual:timber-rsc-entry), producing chunks like "rsc-entry-XYZ.js"
|
|
176
|
+
* in the client output. This is confusing since those chunks contain client
|
|
177
|
+
* components, not server code. Renaming clarifies their purpose.
|
|
178
|
+
*/
|
|
179
|
+
generateBundle(_options, bundle) {
|
|
180
|
+
if ((this as any).environment?.name !== 'client') return;
|
|
181
|
+
|
|
182
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
183
|
+
if (chunk.type !== 'chunk') continue;
|
|
184
|
+
if (!chunk.name?.startsWith('rsc-entry')) continue;
|
|
185
|
+
|
|
186
|
+
const newFileName = fileName.replace('rsc-entry', 'rsc-client-entry');
|
|
187
|
+
// Extract just the basename for matching code references like "./rsc-entry-XYZ.js"
|
|
188
|
+
const oldBase = fileName.split('/').pop()!;
|
|
189
|
+
const newBase = newFileName.split('/').pop()!;
|
|
190
|
+
|
|
191
|
+
chunk.fileName = newFileName;
|
|
192
|
+
chunk.name = chunk.name.replace('rsc-entry', 'rsc-client-entry');
|
|
193
|
+
bundle[newFileName] = chunk;
|
|
194
|
+
delete bundle[fileName];
|
|
195
|
+
|
|
196
|
+
// Update import references in other chunks
|
|
197
|
+
for (const other of Object.values(bundle)) {
|
|
198
|
+
if (other.type !== 'chunk') continue;
|
|
199
|
+
if (other.code.includes(oldBase)) {
|
|
200
|
+
other.code = other.code.replaceAll(oldBase, newBase);
|
|
201
|
+
}
|
|
202
|
+
if (other.imports) {
|
|
203
|
+
other.imports = other.imports.map((i) => (i === fileName ? newFileName : i));
|
|
204
|
+
}
|
|
205
|
+
if (other.dynamicImports) {
|
|
206
|
+
other.dynamicImports = other.dynamicImports.map((i) =>
|
|
207
|
+
i === fileName ? newFileName : i
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|