@timber-js/app 0.2.0-alpha.182 → 0.2.0-alpha.184
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/{build-output-helper-DF5uScqS.js → build-output-helper-DOGYFb_X.js} +47 -9
- package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +1 -0
- package/dist/_chunks/{cloudflare-AHoWYTYr.js → cloudflare-CnT5Lr7U.js} +21 -3
- package/dist/_chunks/{cloudflare-AHoWYTYr.js.map → cloudflare-CnT5Lr7U.js.map} +1 -1
- package/dist/_chunks/plugin-context---kTF5v8.js.map +1 -1
- package/dist/adapters/build-output-helper.d.ts +6 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -1
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/adapters/cloudflare.d.ts +10 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +2 -2
- package/dist/adapters/nitro.d.ts +14 -0
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +14 -1
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +1 -1
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/types.d.ts +14 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/client/internal.js +113 -11
- package/dist/client/internal.js.map +1 -1
- package/dist/client/rsc-fetch.d.ts +9 -0
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/index.js +437 -200
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +12 -0
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +4 -3
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +55 -2
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +12 -0
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/sitemap-generator.d.ts +1 -1
- package/dist/server/sitemap-generator.d.ts.map +1 -1
- package/dist/server/static-generator.d.ts +106 -0
- package/dist/server/static-generator.d.ts.map +1 -0
- package/docs/learn/13-configuration.mdx +5 -1
- package/package.json +1 -1
- package/src/adapters/build-output-helper.ts +40 -1
- package/src/adapters/cloudflare.ts +43 -1
- package/src/adapters/nitro.ts +58 -2
- package/src/adapters/shared.ts +42 -11
- package/src/adapters/types.ts +14 -0
- package/src/client/browser-entry/index.ts +7 -1
- package/src/client/rsc-fetch.ts +187 -19
- package/src/index.ts +1 -1
- package/src/plugin-context.ts +12 -0
- package/src/plugins/adapter-build.ts +5 -0
- package/src/plugins/build-report.ts +6 -12
- package/src/plugins/static-build.ts +391 -2
- package/src/server/rsc-entry/index.ts +24 -0
- package/src/server/sitemap-generator.ts +1 -1
- package/src/server/static-generator.ts +692 -0
- package/dist/_chunks/build-output-helper-DF5uScqS.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
20
20
|
import { styleText } from "node:util";
|
|
21
21
|
import { constants, createGzip, gzipSync } from "node:zlib";
|
|
22
22
|
import { createHash, randomUUID } from "node:crypto";
|
|
23
|
-
import { copyFile, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
23
|
+
import { copyFile, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
24
24
|
//#region src/plugins/content.ts
|
|
25
25
|
var CONFIG_FILE_NAMES$1 = [
|
|
26
26
|
"content-collections.ts",
|
|
@@ -3491,7 +3491,7 @@ function filterBySubsets(faces, subsets) {
|
|
|
3491
3491
|
* Generate a content hash for font data.
|
|
3492
3492
|
* Returns the first 8 hex chars of the SHA-256 hash.
|
|
3493
3493
|
*/
|
|
3494
|
-
function contentHash(data) {
|
|
3494
|
+
function contentHash$1(data) {
|
|
3495
3495
|
return createHash("sha256").update(data).digest("hex").slice(0, 8);
|
|
3496
3496
|
}
|
|
3497
3497
|
/**
|
|
@@ -3502,7 +3502,7 @@ function contentHash(data) {
|
|
|
3502
3502
|
*/
|
|
3503
3503
|
function hashedFontFilename(face, data) {
|
|
3504
3504
|
const slug = face.family.toLowerCase().replace(/\s+/g, "-");
|
|
3505
|
-
const hash = contentHash(data);
|
|
3505
|
+
const hash = contentHash$1(data);
|
|
3506
3506
|
return `${slug}-${face.subset}-${face.weight}-${face.style}-${hash}.woff2`;
|
|
3507
3507
|
}
|
|
3508
3508
|
/**
|
|
@@ -4733,7 +4733,7 @@ var FontPipeline = class {
|
|
|
4733
4733
|
const absolutePath = normalize(resolve(src.path));
|
|
4734
4734
|
if (!existsSync(absolutePath)) continue;
|
|
4735
4735
|
const data = readFileSync(absolutePath);
|
|
4736
|
-
src.hashedBasename = hashedFontBasename(src.path, contentHash(data));
|
|
4736
|
+
src.hashedBasename = hashedFontBasename(src.path, contentHash$1(data));
|
|
4737
4737
|
}
|
|
4738
4738
|
this._entries.set(entry.id, entry);
|
|
4739
4739
|
}
|
|
@@ -6386,6 +6386,222 @@ function timberFonts(ctx) {
|
|
|
6386
6386
|
};
|
|
6387
6387
|
}
|
|
6388
6388
|
//#endregion
|
|
6389
|
+
//#region src/plugins/adapter-build.ts
|
|
6390
|
+
function timberAdapterBuild(ctx) {
|
|
6391
|
+
return {
|
|
6392
|
+
name: "timber-adapter-build",
|
|
6393
|
+
buildApp: {
|
|
6394
|
+
order: "post",
|
|
6395
|
+
async handler() {
|
|
6396
|
+
if (ctx.dev) return;
|
|
6397
|
+
const adapter = ctx.config.adapter;
|
|
6398
|
+
if (!adapter || typeof adapter.buildOutput !== "function") return;
|
|
6399
|
+
const buildDir = ctx.buildDir;
|
|
6400
|
+
if (ctx.buildManifest) await mergeRscAssetsManifest(buildDir, ctx.root, ctx.buildManifest, ctx.routeClientRefKeys);
|
|
6401
|
+
let manifestInit;
|
|
6402
|
+
if (ctx.buildManifest) {
|
|
6403
|
+
const manifest = ctx.clientJavascript.disabled ? {
|
|
6404
|
+
...ctx.buildManifest,
|
|
6405
|
+
js: {},
|
|
6406
|
+
modulepreload: {}
|
|
6407
|
+
} : ctx.buildManifest;
|
|
6408
|
+
manifestInit = `globalThis.__TIMBER_BUILD_MANIFEST__ = ${JSON.stringify(manifest)};\n`;
|
|
6409
|
+
if (ctx.deploymentId) manifestInit += `globalThis.__TIMBER_DEPLOYMENT_ID__ = ${JSON.stringify(ctx.deploymentId)};\n`;
|
|
6410
|
+
}
|
|
6411
|
+
if (ctx.clientJavascript.disabled) await stripJsFromRscAssetsManifests(buildDir);
|
|
6412
|
+
const adapterConfig = {
|
|
6413
|
+
root: ctx.root,
|
|
6414
|
+
output: ctx.config.output ?? "server",
|
|
6415
|
+
clientJavascriptDisabled: ctx.clientJavascript.disabled,
|
|
6416
|
+
manifestInit,
|
|
6417
|
+
staticPagesDir: (ctx.config.output ?? "server") === "static" ? join(buildDir, "static-pages") : void 0,
|
|
6418
|
+
staticContentTypes: ctx.staticContentTypes
|
|
6419
|
+
};
|
|
6420
|
+
await adapter.buildOutput(adapterConfig, buildDir);
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
};
|
|
6424
|
+
}
|
|
6425
|
+
/**
|
|
6426
|
+
* Parse the RSC assets manifest at `manifestPath`. Tries JSON.parse first
|
|
6427
|
+
* (fast path for plain-string manifests); falls back to a dynamic import()
|
|
6428
|
+
* for manifests that contain runtime expressions from renderBuiltUrl.
|
|
6429
|
+
* Returns null if both paths fail.
|
|
6430
|
+
*/
|
|
6431
|
+
async function parseRscManifest(manifestPath, content) {
|
|
6432
|
+
const jsonStr = content.replace(/^export default\s*/, "").replace(/;?\s*$/, "");
|
|
6433
|
+
try {
|
|
6434
|
+
return JSON.parse(jsonStr);
|
|
6435
|
+
} catch {}
|
|
6436
|
+
try {
|
|
6437
|
+
return (await import(`${pathToFileURL(manifestPath).href}?t=${Date.now()}`)).default ?? null;
|
|
6438
|
+
} catch (err) {
|
|
6439
|
+
swallow(err, `failed to evaluate RSC assets manifest: ${manifestPath}`, { level: "warn" });
|
|
6440
|
+
return null;
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
/**
|
|
6444
|
+
* Replace all `"js": [...]` array contents with `"js": []` using balanced
|
|
6445
|
+
* bracket matching. Handles nested brackets from runtime expressions like
|
|
6446
|
+
* `__rbu("path/to/[slug]/file.js")`.
|
|
6447
|
+
*/
|
|
6448
|
+
function clearJsArrays(src) {
|
|
6449
|
+
const pattern = /"js"\s*:\s*\[/g;
|
|
6450
|
+
let result = "";
|
|
6451
|
+
let lastEnd = 0;
|
|
6452
|
+
let match;
|
|
6453
|
+
while ((match = pattern.exec(src)) !== null) {
|
|
6454
|
+
const arrayStart = match.index + match[0].length;
|
|
6455
|
+
let depth = 1;
|
|
6456
|
+
let i = arrayStart;
|
|
6457
|
+
while (i < src.length && depth > 0) {
|
|
6458
|
+
if (src[i] === "[") depth++;
|
|
6459
|
+
else if (src[i] === "]") depth--;
|
|
6460
|
+
i++;
|
|
6461
|
+
}
|
|
6462
|
+
result += src.slice(lastEnd, match.index) + "\"js\": []";
|
|
6463
|
+
lastEnd = i;
|
|
6464
|
+
}
|
|
6465
|
+
result += src.slice(lastEnd);
|
|
6466
|
+
return result;
|
|
6467
|
+
}
|
|
6468
|
+
/**
|
|
6469
|
+
* Replace `"bootstrapScriptContent": <value>` with an empty string. The
|
|
6470
|
+
* value may be a JS expression (e.g., concatenation with runtime helpers),
|
|
6471
|
+
* not a simple string literal. Scans forward from the colon, tracking
|
|
6472
|
+
* balanced parens and brackets, and stops at the next unbalanced `,` or `}`.
|
|
6473
|
+
*/
|
|
6474
|
+
function clearBootstrapScriptContent(src) {
|
|
6475
|
+
const idx = src.indexOf("\"bootstrapScriptContent\"");
|
|
6476
|
+
if (idx === -1) return src;
|
|
6477
|
+
const colonIdx = src.indexOf(":", idx + 24);
|
|
6478
|
+
if (colonIdx === -1) return src;
|
|
6479
|
+
let valStart = colonIdx + 1;
|
|
6480
|
+
while (valStart < src.length && /\s/.test(src[valStart])) valStart++;
|
|
6481
|
+
let i = valStart;
|
|
6482
|
+
let depth = 0;
|
|
6483
|
+
let inString = false;
|
|
6484
|
+
let stringChar = "";
|
|
6485
|
+
while (i < src.length) {
|
|
6486
|
+
const ch = src[i];
|
|
6487
|
+
if (inString) {
|
|
6488
|
+
if (ch === "\\") {
|
|
6489
|
+
i += 2;
|
|
6490
|
+
continue;
|
|
6491
|
+
}
|
|
6492
|
+
if (ch === stringChar) inString = false;
|
|
6493
|
+
} else if (ch === "\"" || ch === "'") {
|
|
6494
|
+
inString = true;
|
|
6495
|
+
stringChar = ch;
|
|
6496
|
+
} else if (ch === "(" || ch === "[") depth++;
|
|
6497
|
+
else if (ch === ")" || ch === "]") depth--;
|
|
6498
|
+
else if (depth === 0 && (ch === "," || ch === "}" || ch === "\n")) break;
|
|
6499
|
+
i++;
|
|
6500
|
+
}
|
|
6501
|
+
return src.slice(0, idx) + "\"bootstrapScriptContent\": \"\"" + src.slice(i);
|
|
6502
|
+
}
|
|
6503
|
+
/**
|
|
6504
|
+
* Strip JS references from the RSC plugin's assets manifest files.
|
|
6505
|
+
*
|
|
6506
|
+
* The RSC plugin writes `__vite_rsc_assets_manifest.js` to rsc/ and ssr/
|
|
6507
|
+
* as standalone files (not Rollup chunks), so generateBundle can't
|
|
6508
|
+
* intercept them. This rewrites the files on disk after all builds
|
|
6509
|
+
* complete but before the adapter copies them to the output directory.
|
|
6510
|
+
*/
|
|
6511
|
+
/**
|
|
6512
|
+
* Read the RSC plugin's assets manifest and merge per-route CSS and
|
|
6513
|
+
* per-route JS modulepreloads into the build manifest.
|
|
6514
|
+
*
|
|
6515
|
+
* CSS: `serverResources` maps server component paths to CSS URLs.
|
|
6516
|
+
* JS: `clientReferenceDeps` maps reference hashes to JS dep URLs.
|
|
6517
|
+
* Combined with `routeClientRefKeys` (from the RSC generateBundle
|
|
6518
|
+
* module graph walk) to produce per-route modulepreload entries.
|
|
6519
|
+
*/
|
|
6520
|
+
async function mergeRscAssetsManifest(buildDir, root, buildManifest, routeClientRefKeys) {
|
|
6521
|
+
const manifestPath = join(buildDir, "rsc", "__vite_rsc_assets_manifest.js");
|
|
6522
|
+
let content;
|
|
6523
|
+
try {
|
|
6524
|
+
content = await readFile(manifestPath, "utf-8");
|
|
6525
|
+
} catch {
|
|
6526
|
+
return;
|
|
6527
|
+
}
|
|
6528
|
+
const manifest = await parseRscManifest(manifestPath, content);
|
|
6529
|
+
if (!manifest) return;
|
|
6530
|
+
const serverResources = manifest.serverResources;
|
|
6531
|
+
if (serverResources) {
|
|
6532
|
+
for (const [relativePath, deps] of Object.entries(serverResources)) if (deps.css && deps.css.length > 0) {
|
|
6533
|
+
const absolutePath = join(root, relativePath);
|
|
6534
|
+
buildManifest.css[absolutePath] = deps.css;
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
if (routeClientRefKeys && routeClientRefKeys.size > 0) {
|
|
6538
|
+
const clientReferenceDeps = manifest.clientReferenceDeps;
|
|
6539
|
+
if (clientReferenceDeps) for (const [filePath, refKeys] of routeClientRefKeys) {
|
|
6540
|
+
const jsUrls = /* @__PURE__ */ new Set();
|
|
6541
|
+
for (const key of refKeys) {
|
|
6542
|
+
const deps = clientReferenceDeps[key];
|
|
6543
|
+
if (deps?.js) for (const url of deps.js) jsUrls.add(url);
|
|
6544
|
+
}
|
|
6545
|
+
if (jsUrls.size > 0) buildManifest.modulepreload[filePath] = Array.from(jsUrls);
|
|
6546
|
+
}
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
async function stripJsFromRscAssetsManifests(buildDir) {
|
|
6550
|
+
const manifestName = "__vite_rsc_assets_manifest.js";
|
|
6551
|
+
const paths = [join(buildDir, "rsc", manifestName), join(buildDir, "ssr", manifestName)];
|
|
6552
|
+
for (const path of paths) {
|
|
6553
|
+
let content;
|
|
6554
|
+
try {
|
|
6555
|
+
content = await readFile(path, "utf-8");
|
|
6556
|
+
} catch {
|
|
6557
|
+
continue;
|
|
6558
|
+
}
|
|
6559
|
+
const jsonStr = content.replace(/^export default\s*/, "").replace(/;?\s*$/, "");
|
|
6560
|
+
let isStrictJson = true;
|
|
6561
|
+
let manifest = null;
|
|
6562
|
+
try {
|
|
6563
|
+
manifest = JSON.parse(jsonStr);
|
|
6564
|
+
} catch {
|
|
6565
|
+
isStrictJson = false;
|
|
6566
|
+
}
|
|
6567
|
+
if (!isStrictJson) {
|
|
6568
|
+
let rewritten = content;
|
|
6569
|
+
rewritten = clearJsArrays(rewritten);
|
|
6570
|
+
rewritten = clearBootstrapScriptContent(rewritten);
|
|
6571
|
+
await writeFile(path, rewritten);
|
|
6572
|
+
continue;
|
|
6573
|
+
}
|
|
6574
|
+
const deps = manifest?.clientReferenceDeps;
|
|
6575
|
+
if (deps) for (const entry of Object.values(deps)) entry.js = [];
|
|
6576
|
+
if (manifest) manifest.bootstrapScriptContent = "";
|
|
6577
|
+
await writeFile(path, `export default ${JSON.stringify(manifest, null, 2)};\n`);
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6580
|
+
//#endregion
|
|
6581
|
+
//#region src/server/flight-scripts.ts
|
|
6582
|
+
/**
|
|
6583
|
+
* Shared Flight data script generation.
|
|
6584
|
+
*
|
|
6585
|
+
* Both html-injectors.ts (Web Streams / dev) and node-stream-transforms.ts
|
|
6586
|
+
* (Node streams / production) use this module to generate inline `<script>`
|
|
6587
|
+
* tags for RSC Flight data injection.
|
|
6588
|
+
*
|
|
6589
|
+
* The init script goes in `<head>` as part of the HTML shell (guaranteed
|
|
6590
|
+
* to execute before any streaming chunk scripts). Push scripts are emitted
|
|
6591
|
+
* as RSC chunks arrive during streaming.
|
|
6592
|
+
*
|
|
6593
|
+
* See design/02-rendering-pipeline.md, LOCAL-415
|
|
6594
|
+
*/
|
|
6595
|
+
/**
|
|
6596
|
+
* Escape a JSON string for safe embedding inside an HTML `<script>` tag.
|
|
6597
|
+
*
|
|
6598
|
+
* Prevents XSS via `<\/script>` injection and handles Unicode line/paragraph
|
|
6599
|
+
* separators that are valid JSON but invalid in JS string literals (pre-ES2019).
|
|
6600
|
+
*/
|
|
6601
|
+
function htmlEscapeJsonString(str) {
|
|
6602
|
+
return str.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
6603
|
+
}
|
|
6604
|
+
//#endregion
|
|
6389
6605
|
//#region src/plugins/static-build.ts
|
|
6390
6606
|
/**
|
|
6391
6607
|
* Patterns that indicate dynamic per-request API usage.
|
|
@@ -6436,12 +6652,207 @@ function detectDynamicApis(code, fileId) {
|
|
|
6436
6652
|
return errors;
|
|
6437
6653
|
}
|
|
6438
6654
|
/**
|
|
6655
|
+
* Generate a `_redirects` file from collected redirect entries.
|
|
6656
|
+
* Uses the Cloudflare Pages / Netlify format: `/from /to statusCode`
|
|
6657
|
+
*
|
|
6658
|
+
* @internal Exported for testing.
|
|
6659
|
+
*/
|
|
6660
|
+
function generateRedirectsFile(redirects) {
|
|
6661
|
+
return redirects.map((r) => `${r.from} ${r.to} ${r.status}`).join("\n") + "\n";
|
|
6662
|
+
}
|
|
6663
|
+
/** Write a file atomically: temp sibling + rename. */
|
|
6664
|
+
async function writeFileAtomic$1(path, data) {
|
|
6665
|
+
const tmp = `${path}.tmp`;
|
|
6666
|
+
await writeFile(tmp, data);
|
|
6667
|
+
await rename(tmp, path);
|
|
6668
|
+
}
|
|
6669
|
+
/**
|
|
6670
|
+
* Determine the output file path for a static page entry.
|
|
6671
|
+
*
|
|
6672
|
+
* Pages: /about → about/index.html, / → index.html
|
|
6673
|
+
* RSC: /about → _rsc/about.rsc, / → _rsc/index.rsc
|
|
6674
|
+
* API: /feed.xml → feed.xml, /api/data → api/data
|
|
6675
|
+
*/
|
|
6676
|
+
function staticOutputPath(entry) {
|
|
6677
|
+
const urlPath = entry.urlPath === "/" ? "" : entry.urlPath;
|
|
6678
|
+
if (entry.kind === "rsc") return `_rsc${urlPath || "/index"}.rsc`;
|
|
6679
|
+
if (entry.kind === "api") return urlPath.startsWith("/") ? urlPath.slice(1) : urlPath || "index";
|
|
6680
|
+
if (!urlPath) return "index.html";
|
|
6681
|
+
if (urlPath === "/_timber_not_found") return "404.html";
|
|
6682
|
+
return `${urlPath.startsWith("/") ? urlPath.slice(1) : urlPath}/index.html`;
|
|
6683
|
+
}
|
|
6684
|
+
/**
|
|
6685
|
+
* Load the built RSC entry and run the static generation pass.
|
|
6686
|
+
*
|
|
6687
|
+
* Unlike prebuilt capture, this needs the FULL pipeline handler, so
|
|
6688
|
+
* __TIMBER_CAPTURE_MODE__ must be false.
|
|
6689
|
+
*/
|
|
6690
|
+
async function runStaticGeneration(ctx) {
|
|
6691
|
+
const entryPath = join(ctx.buildDir, "rsc", "index.js");
|
|
6692
|
+
if (!existsSync(entryPath)) throw new Error(`[timber] Static generation: built RSC entry not found at ${entryPath} — the build output is missing`);
|
|
6693
|
+
const g = globalThis;
|
|
6694
|
+
if (ctx.buildManifest) g.__TIMBER_BUILD_MANIFEST__ ??= ctx.buildManifest;
|
|
6695
|
+
if (ctx.deploymentId) g.__TIMBER_DEPLOYMENT_ID__ ??= ctx.deploymentId;
|
|
6696
|
+
g.__TIMBER_CAPTURE_MODE__ = false;
|
|
6697
|
+
if (ctx.buildManifest) await mergeRscAssetsManifest(ctx.buildDir, ctx.root, ctx.buildManifest, ctx.routeClientRefKeys);
|
|
6698
|
+
console.log("[timber] Static generation: building site…");
|
|
6699
|
+
let mod;
|
|
6700
|
+
try {
|
|
6701
|
+
mod = await import(
|
|
6702
|
+
/* @vite-ignore */
|
|
6703
|
+
`${pathToFileURL(entryPath).href}?t=${Date.now()}`
|
|
6704
|
+
);
|
|
6705
|
+
} catch (error) {
|
|
6706
|
+
throw new Error(`[timber] Static generation: failed to load the built RSC entry (${entryPath}). The full request handler must initialize for static rendering.`, { cause: error });
|
|
6707
|
+
}
|
|
6708
|
+
const generate = mod.__timber_generateStaticSite;
|
|
6709
|
+
if (typeof generate !== "function") throw new Error("[timber] Static generation: built RSC entry does not export __timber_generateStaticSite — rebuild from a clean build directory");
|
|
6710
|
+
const staticDir = join(ctx.buildDir, "static-pages");
|
|
6711
|
+
await rm(join(staticDir, "_rsc"), {
|
|
6712
|
+
recursive: true,
|
|
6713
|
+
force: true
|
|
6714
|
+
}).catch(() => {});
|
|
6715
|
+
await mkdir(staticDir, { recursive: true });
|
|
6716
|
+
const sitemapConfig = ctx.config.sitemap;
|
|
6717
|
+
const autoSitemapEnabled = Boolean(sitemapConfig?.enabled && sitemapConfig?.baseUrl);
|
|
6718
|
+
const contentTypes = {};
|
|
6719
|
+
const summary = await generate({
|
|
6720
|
+
autoSitemapEnabled,
|
|
6721
|
+
renderTimeoutMs: ctx.config.renderTimeoutMs,
|
|
6722
|
+
onEntry: async (entry) => {
|
|
6723
|
+
const relPath = staticOutputPath(entry);
|
|
6724
|
+
const fullPath = resolve(staticDir, relPath);
|
|
6725
|
+
const rel = relative(staticDir, fullPath);
|
|
6726
|
+
if (rel.startsWith("..") || resolve(staticDir, rel) !== fullPath) throw new Error(`[timber] Static generation: output path "${relPath}" escapes the static output directory — check generateStaticSegmentParams for path traversal characters in ${entry.urlPath}`);
|
|
6727
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
6728
|
+
await writeFileAtomic$1(fullPath, entry.body);
|
|
6729
|
+
if (entry.kind === "rsc" && entry.headers["x-timber-params"]) await writeFileAtomic$1(fullPath.replace(/\.rsc$/, ".params.json"), entry.headers["x-timber-params"]);
|
|
6730
|
+
if (entry.kind === "api" && !extname(relPath) && entry.contentType) contentTypes[entry.urlPath] = entry.contentType;
|
|
6731
|
+
}
|
|
6732
|
+
});
|
|
6733
|
+
const totalUrls = summary.rendered + summary.errors.length;
|
|
6734
|
+
console.log(`[timber] Static generation: rendered ${summary.rendered}/${totalUrls} entries.` + (summary.errors.length > 0 ? ` ${summary.errors.length} failed.` : "") + (summary.redirects.length > 0 ? ` ${summary.redirects.length} redirect(s).` : ""));
|
|
6735
|
+
if (summary.errors.length > 0) {
|
|
6736
|
+
const details = summary.errors.map((e) => ` - ${e.urlPath}: ${e.message}`).join("\n");
|
|
6737
|
+
throw new Error(`[timber] Static generation failed:\n${details}`);
|
|
6738
|
+
}
|
|
6739
|
+
if (Object.keys(contentTypes).length > 0) ctx.staticContentTypes = contentTypes;
|
|
6740
|
+
if (!ctx.clientJavascript.disabled) {
|
|
6741
|
+
const rscManifest = await hashRscPayloads(staticDir);
|
|
6742
|
+
if (Object.keys(rscManifest).length > 0) {
|
|
6743
|
+
await injectRscManifestIntoHtml(staticDir, rscManifest);
|
|
6744
|
+
ctx.rscManifest = rscManifest;
|
|
6745
|
+
console.log(`[timber] Static generation: hashed ${Object.keys(rscManifest).length} RSC flight payloads`);
|
|
6746
|
+
}
|
|
6747
|
+
}
|
|
6748
|
+
if (summary.redirects.length > 0) {
|
|
6749
|
+
const generated = generateRedirectsFile(summary.redirects);
|
|
6750
|
+
const redirectsPath = join(staticDir, "_redirects");
|
|
6751
|
+
const userRedirects = await readUserRedirects(ctx.root);
|
|
6752
|
+
if (userRedirects !== null) {
|
|
6753
|
+
await writeFileAtomic$1(redirectsPath, generated + "\n" + userRedirects);
|
|
6754
|
+
console.log(`[timber] Static generation: wrote _redirects with ${summary.redirects.length} generated rule(s), merged with user-authored _redirects`);
|
|
6755
|
+
} else {
|
|
6756
|
+
await writeFileAtomic$1(redirectsPath, generated);
|
|
6757
|
+
console.log(`[timber] Static generation: wrote _redirects with ${summary.redirects.length} rule(s)`);
|
|
6758
|
+
}
|
|
6759
|
+
}
|
|
6760
|
+
return summary;
|
|
6761
|
+
}
|
|
6762
|
+
/**
|
|
6763
|
+
* Compute a short content hash for immutable caching.
|
|
6764
|
+
* Uses 8 chars of base64url-encoded SHA-256, matching Vite's asset hash style.
|
|
6765
|
+
*/
|
|
6766
|
+
function contentHash(data) {
|
|
6767
|
+
return createHash("sha256").update(data).digest("base64url").slice(0, 8);
|
|
6768
|
+
}
|
|
6769
|
+
/**
|
|
6770
|
+
* Walk a directory recursively and return all file paths.
|
|
6771
|
+
*/
|
|
6772
|
+
async function walkDir(dir) {
|
|
6773
|
+
const results = [];
|
|
6774
|
+
let entries;
|
|
6775
|
+
try {
|
|
6776
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
6777
|
+
} catch {
|
|
6778
|
+
return results;
|
|
6779
|
+
}
|
|
6780
|
+
for (const entry of entries) {
|
|
6781
|
+
const full = join(dir, entry.name);
|
|
6782
|
+
if (entry.isDirectory()) results.push(...await walkDir(full));
|
|
6783
|
+
else results.push(full);
|
|
6784
|
+
}
|
|
6785
|
+
return results;
|
|
6786
|
+
}
|
|
6787
|
+
/**
|
|
6788
|
+
* Hash all RSC flight payloads and params sidecars in `_rsc/`, rename
|
|
6789
|
+
* them with content hashes, and inject a manifest into each HTML file.
|
|
6790
|
+
*
|
|
6791
|
+
* Before: `_rsc/index.rsc`, `_rsc/about.rsc`
|
|
6792
|
+
* After: `_rsc/index-B7YxEKdN.rsc`, `_rsc/about-C8ZzFLfO.rsc`
|
|
6793
|
+
*
|
|
6794
|
+
* The manifest maps the original `_rsc/` URL to the hashed filename:
|
|
6795
|
+
* `{"/_rsc/index.rsc":"/_rsc/index-B7YxEKdN.rsc", ...}`
|
|
6796
|
+
*
|
|
6797
|
+
* Injected into HTML as:
|
|
6798
|
+
* `<script>window.__TIMBER_RSC_MANIFEST__={...}<\/script>`
|
|
6799
|
+
*
|
|
6800
|
+
* @internal Exported for testing.
|
|
6801
|
+
*/
|
|
6802
|
+
async function hashRscPayloads(staticDir) {
|
|
6803
|
+
const files = await walkDir(join(staticDir, "_rsc"));
|
|
6804
|
+
const manifest = {};
|
|
6805
|
+
for (const filePath of files) {
|
|
6806
|
+
if (!filePath.endsWith(".rsc") && !filePath.endsWith(".params.json")) continue;
|
|
6807
|
+
const hash = contentHash(await readFile(filePath));
|
|
6808
|
+
const rel = relative(staticDir, filePath).replace(/\\/g, "/");
|
|
6809
|
+
const ext = filePath.endsWith(".params.json") ? ".params.json" : ".rsc";
|
|
6810
|
+
const hashedPath = `${filePath.slice(0, -ext.length)}-${hash}${ext}`;
|
|
6811
|
+
await rename(filePath, hashedPath);
|
|
6812
|
+
const hashedRel = relative(staticDir, hashedPath).replace(/\\/g, "/");
|
|
6813
|
+
manifest[`/${rel}`] = `/${hashedRel}`;
|
|
6814
|
+
}
|
|
6815
|
+
return manifest;
|
|
6816
|
+
}
|
|
6817
|
+
/**
|
|
6818
|
+
* Inject the RSC manifest into all HTML files as a `<script>` tag
|
|
6819
|
+
* before `</head>`. This lets the client resolve hashed RSC URLs
|
|
6820
|
+
* without an extra network request.
|
|
6821
|
+
*
|
|
6822
|
+
* @internal Exported for testing.
|
|
6823
|
+
*/
|
|
6824
|
+
async function injectRscManifestIntoHtml(staticDir, manifest) {
|
|
6825
|
+
const htmlFiles = (await walkDir(staticDir)).filter((f) => f.endsWith(".html"));
|
|
6826
|
+
const scriptTag = `<script>window.__TIMBER_RSC_MANIFEST__=${htmlEscapeJsonString(JSON.stringify(manifest))}<\/script>`;
|
|
6827
|
+
for (const htmlFile of htmlFiles) {
|
|
6828
|
+
let html = await readFile(htmlFile, "utf-8");
|
|
6829
|
+
const headClose = html.indexOf("</head>");
|
|
6830
|
+
if (headClose !== -1) html = html.slice(0, headClose) + scriptTag + html.slice(headClose);
|
|
6831
|
+
else html = scriptTag + html;
|
|
6832
|
+
await writeFile(htmlFile, html);
|
|
6833
|
+
}
|
|
6834
|
+
}
|
|
6835
|
+
/**
|
|
6836
|
+
* Read a user-authored `_redirects` file from the project's `public/`
|
|
6837
|
+
* directory. Returns `null` if no file exists.
|
|
6838
|
+
*
|
|
6839
|
+
* @internal Exported for testing.
|
|
6840
|
+
*/
|
|
6841
|
+
async function readUserRedirects(root) {
|
|
6842
|
+
const candidates = [join(root, "public", "_redirects")];
|
|
6843
|
+
for (const path of candidates) try {
|
|
6844
|
+
return await readFile(path, "utf-8");
|
|
6845
|
+
} catch {}
|
|
6846
|
+
return null;
|
|
6847
|
+
}
|
|
6848
|
+
/**
|
|
6439
6849
|
* Create the timber-static-build Vite plugin.
|
|
6440
6850
|
*
|
|
6441
6851
|
* Only active when output: 'static' is configured.
|
|
6442
6852
|
*
|
|
6443
6853
|
* Hooks:
|
|
6444
6854
|
* - transform: Validates source files for static mode violations
|
|
6855
|
+
* - buildApp (post): Renders all routes to static HTML after the build
|
|
6445
6856
|
*/
|
|
6446
6857
|
function timberStaticBuild(ctx) {
|
|
6447
6858
|
return {
|
|
@@ -6463,6 +6874,21 @@ function timberStaticBuild(ctx) {
|
|
|
6463
6874
|
this.error(messages.join("\n\n"));
|
|
6464
6875
|
}
|
|
6465
6876
|
return null;
|
|
6877
|
+
},
|
|
6878
|
+
/**
|
|
6879
|
+
* After all environments are built, render every route to static HTML.
|
|
6880
|
+
*
|
|
6881
|
+
* Runs with order: 'post' so the RSC plugin's buildApp completes first,
|
|
6882
|
+
* producing the built entry we import. Must run BEFORE the adapter
|
|
6883
|
+
* build copies output to its platform-specific directory.
|
|
6884
|
+
*/
|
|
6885
|
+
buildApp: {
|
|
6886
|
+
order: "post",
|
|
6887
|
+
async handler() {
|
|
6888
|
+
if (ctx.dev) return;
|
|
6889
|
+
if (!(ctx.config.output === "static")) return;
|
|
6890
|
+
await runStaticGeneration(ctx);
|
|
6891
|
+
}
|
|
6466
6892
|
}
|
|
6467
6893
|
};
|
|
6468
6894
|
}
|
|
@@ -9102,196 +9528,6 @@ function timberCloudflareWasm(ctx) {
|
|
|
9102
9528
|
}];
|
|
9103
9529
|
}
|
|
9104
9530
|
//#endregion
|
|
9105
|
-
//#region src/plugins/adapter-build.ts
|
|
9106
|
-
function timberAdapterBuild(ctx) {
|
|
9107
|
-
return {
|
|
9108
|
-
name: "timber-adapter-build",
|
|
9109
|
-
buildApp: {
|
|
9110
|
-
order: "post",
|
|
9111
|
-
async handler() {
|
|
9112
|
-
if (ctx.dev) return;
|
|
9113
|
-
const adapter = ctx.config.adapter;
|
|
9114
|
-
if (!adapter || typeof adapter.buildOutput !== "function") return;
|
|
9115
|
-
const buildDir = ctx.buildDir;
|
|
9116
|
-
if (ctx.buildManifest) await mergeRscAssetsManifest(buildDir, ctx.root, ctx.buildManifest, ctx.routeClientRefKeys);
|
|
9117
|
-
let manifestInit;
|
|
9118
|
-
if (ctx.buildManifest) {
|
|
9119
|
-
const manifest = ctx.clientJavascript.disabled ? {
|
|
9120
|
-
...ctx.buildManifest,
|
|
9121
|
-
js: {},
|
|
9122
|
-
modulepreload: {}
|
|
9123
|
-
} : ctx.buildManifest;
|
|
9124
|
-
manifestInit = `globalThis.__TIMBER_BUILD_MANIFEST__ = ${JSON.stringify(manifest)};\n`;
|
|
9125
|
-
if (ctx.deploymentId) manifestInit += `globalThis.__TIMBER_DEPLOYMENT_ID__ = ${JSON.stringify(ctx.deploymentId)};\n`;
|
|
9126
|
-
}
|
|
9127
|
-
if (ctx.clientJavascript.disabled) await stripJsFromRscAssetsManifests(buildDir);
|
|
9128
|
-
const adapterConfig = {
|
|
9129
|
-
root: ctx.root,
|
|
9130
|
-
output: ctx.config.output ?? "server",
|
|
9131
|
-
clientJavascriptDisabled: ctx.clientJavascript.disabled,
|
|
9132
|
-
manifestInit
|
|
9133
|
-
};
|
|
9134
|
-
await adapter.buildOutput(adapterConfig, buildDir);
|
|
9135
|
-
}
|
|
9136
|
-
}
|
|
9137
|
-
};
|
|
9138
|
-
}
|
|
9139
|
-
/**
|
|
9140
|
-
* Parse the RSC assets manifest at `manifestPath`. Tries JSON.parse first
|
|
9141
|
-
* (fast path for plain-string manifests); falls back to a dynamic import()
|
|
9142
|
-
* for manifests that contain runtime expressions from renderBuiltUrl.
|
|
9143
|
-
* Returns null if both paths fail.
|
|
9144
|
-
*/
|
|
9145
|
-
async function parseRscManifest(manifestPath, content) {
|
|
9146
|
-
const jsonStr = content.replace(/^export default\s*/, "").replace(/;?\s*$/, "");
|
|
9147
|
-
try {
|
|
9148
|
-
return JSON.parse(jsonStr);
|
|
9149
|
-
} catch {}
|
|
9150
|
-
try {
|
|
9151
|
-
return (await import(`${pathToFileURL(manifestPath).href}?t=${Date.now()}`)).default ?? null;
|
|
9152
|
-
} catch (err) {
|
|
9153
|
-
swallow(err, `failed to evaluate RSC assets manifest: ${manifestPath}`, { level: "warn" });
|
|
9154
|
-
return null;
|
|
9155
|
-
}
|
|
9156
|
-
}
|
|
9157
|
-
/**
|
|
9158
|
-
* Replace all `"js": [...]` array contents with `"js": []` using balanced
|
|
9159
|
-
* bracket matching. Handles nested brackets from runtime expressions like
|
|
9160
|
-
* `__rbu("path/to/[slug]/file.js")`.
|
|
9161
|
-
*/
|
|
9162
|
-
function clearJsArrays(src) {
|
|
9163
|
-
const pattern = /"js"\s*:\s*\[/g;
|
|
9164
|
-
let result = "";
|
|
9165
|
-
let lastEnd = 0;
|
|
9166
|
-
let match;
|
|
9167
|
-
while ((match = pattern.exec(src)) !== null) {
|
|
9168
|
-
const arrayStart = match.index + match[0].length;
|
|
9169
|
-
let depth = 1;
|
|
9170
|
-
let i = arrayStart;
|
|
9171
|
-
while (i < src.length && depth > 0) {
|
|
9172
|
-
if (src[i] === "[") depth++;
|
|
9173
|
-
else if (src[i] === "]") depth--;
|
|
9174
|
-
i++;
|
|
9175
|
-
}
|
|
9176
|
-
result += src.slice(lastEnd, match.index) + "\"js\": []";
|
|
9177
|
-
lastEnd = i;
|
|
9178
|
-
}
|
|
9179
|
-
result += src.slice(lastEnd);
|
|
9180
|
-
return result;
|
|
9181
|
-
}
|
|
9182
|
-
/**
|
|
9183
|
-
* Replace `"bootstrapScriptContent": <value>` with an empty string. The
|
|
9184
|
-
* value may be a JS expression (e.g., concatenation with runtime helpers),
|
|
9185
|
-
* not a simple string literal. Scans forward from the colon, tracking
|
|
9186
|
-
* balanced parens and brackets, and stops at the next unbalanced `,` or `}`.
|
|
9187
|
-
*/
|
|
9188
|
-
function clearBootstrapScriptContent(src) {
|
|
9189
|
-
const idx = src.indexOf("\"bootstrapScriptContent\"");
|
|
9190
|
-
if (idx === -1) return src;
|
|
9191
|
-
const colonIdx = src.indexOf(":", idx + 24);
|
|
9192
|
-
if (colonIdx === -1) return src;
|
|
9193
|
-
let valStart = colonIdx + 1;
|
|
9194
|
-
while (valStart < src.length && /\s/.test(src[valStart])) valStart++;
|
|
9195
|
-
let i = valStart;
|
|
9196
|
-
let depth = 0;
|
|
9197
|
-
let inString = false;
|
|
9198
|
-
let stringChar = "";
|
|
9199
|
-
while (i < src.length) {
|
|
9200
|
-
const ch = src[i];
|
|
9201
|
-
if (inString) {
|
|
9202
|
-
if (ch === "\\") {
|
|
9203
|
-
i += 2;
|
|
9204
|
-
continue;
|
|
9205
|
-
}
|
|
9206
|
-
if (ch === stringChar) inString = false;
|
|
9207
|
-
} else if (ch === "\"" || ch === "'") {
|
|
9208
|
-
inString = true;
|
|
9209
|
-
stringChar = ch;
|
|
9210
|
-
} else if (ch === "(" || ch === "[") depth++;
|
|
9211
|
-
else if (ch === ")" || ch === "]") depth--;
|
|
9212
|
-
else if (depth === 0 && (ch === "," || ch === "}" || ch === "\n")) break;
|
|
9213
|
-
i++;
|
|
9214
|
-
}
|
|
9215
|
-
return src.slice(0, idx) + "\"bootstrapScriptContent\": \"\"" + src.slice(i);
|
|
9216
|
-
}
|
|
9217
|
-
/**
|
|
9218
|
-
* Strip JS references from the RSC plugin's assets manifest files.
|
|
9219
|
-
*
|
|
9220
|
-
* The RSC plugin writes `__vite_rsc_assets_manifest.js` to rsc/ and ssr/
|
|
9221
|
-
* as standalone files (not Rollup chunks), so generateBundle can't
|
|
9222
|
-
* intercept them. This rewrites the files on disk after all builds
|
|
9223
|
-
* complete but before the adapter copies them to the output directory.
|
|
9224
|
-
*/
|
|
9225
|
-
/**
|
|
9226
|
-
* Read the RSC plugin's assets manifest and merge per-route CSS and
|
|
9227
|
-
* per-route JS modulepreloads into the build manifest.
|
|
9228
|
-
*
|
|
9229
|
-
* CSS: `serverResources` maps server component paths to CSS URLs.
|
|
9230
|
-
* JS: `clientReferenceDeps` maps reference hashes to JS dep URLs.
|
|
9231
|
-
* Combined with `routeClientRefKeys` (from the RSC generateBundle
|
|
9232
|
-
* module graph walk) to produce per-route modulepreload entries.
|
|
9233
|
-
*/
|
|
9234
|
-
async function mergeRscAssetsManifest(buildDir, root, buildManifest, routeClientRefKeys) {
|
|
9235
|
-
const manifestPath = join(buildDir, "rsc", "__vite_rsc_assets_manifest.js");
|
|
9236
|
-
let content;
|
|
9237
|
-
try {
|
|
9238
|
-
content = await readFile(manifestPath, "utf-8");
|
|
9239
|
-
} catch {
|
|
9240
|
-
return;
|
|
9241
|
-
}
|
|
9242
|
-
const manifest = await parseRscManifest(manifestPath, content);
|
|
9243
|
-
if (!manifest) return;
|
|
9244
|
-
const serverResources = manifest.serverResources;
|
|
9245
|
-
if (serverResources) {
|
|
9246
|
-
for (const [relativePath, deps] of Object.entries(serverResources)) if (deps.css && deps.css.length > 0) {
|
|
9247
|
-
const absolutePath = join(root, relativePath);
|
|
9248
|
-
buildManifest.css[absolutePath] = deps.css;
|
|
9249
|
-
}
|
|
9250
|
-
}
|
|
9251
|
-
if (routeClientRefKeys && routeClientRefKeys.size > 0) {
|
|
9252
|
-
const clientReferenceDeps = manifest.clientReferenceDeps;
|
|
9253
|
-
if (clientReferenceDeps) for (const [filePath, refKeys] of routeClientRefKeys) {
|
|
9254
|
-
const jsUrls = /* @__PURE__ */ new Set();
|
|
9255
|
-
for (const key of refKeys) {
|
|
9256
|
-
const deps = clientReferenceDeps[key];
|
|
9257
|
-
if (deps?.js) for (const url of deps.js) jsUrls.add(url);
|
|
9258
|
-
}
|
|
9259
|
-
if (jsUrls.size > 0) buildManifest.modulepreload[filePath] = Array.from(jsUrls);
|
|
9260
|
-
}
|
|
9261
|
-
}
|
|
9262
|
-
}
|
|
9263
|
-
async function stripJsFromRscAssetsManifests(buildDir) {
|
|
9264
|
-
const manifestName = "__vite_rsc_assets_manifest.js";
|
|
9265
|
-
const paths = [join(buildDir, "rsc", manifestName), join(buildDir, "ssr", manifestName)];
|
|
9266
|
-
for (const path of paths) {
|
|
9267
|
-
let content;
|
|
9268
|
-
try {
|
|
9269
|
-
content = await readFile(path, "utf-8");
|
|
9270
|
-
} catch {
|
|
9271
|
-
continue;
|
|
9272
|
-
}
|
|
9273
|
-
const jsonStr = content.replace(/^export default\s*/, "").replace(/;?\s*$/, "");
|
|
9274
|
-
let isStrictJson = true;
|
|
9275
|
-
let manifest = null;
|
|
9276
|
-
try {
|
|
9277
|
-
manifest = JSON.parse(jsonStr);
|
|
9278
|
-
} catch {
|
|
9279
|
-
isStrictJson = false;
|
|
9280
|
-
}
|
|
9281
|
-
if (!isStrictJson) {
|
|
9282
|
-
let rewritten = content;
|
|
9283
|
-
rewritten = clearJsArrays(rewritten);
|
|
9284
|
-
rewritten = clearBootstrapScriptContent(rewritten);
|
|
9285
|
-
await writeFile(path, rewritten);
|
|
9286
|
-
continue;
|
|
9287
|
-
}
|
|
9288
|
-
const deps = manifest?.clientReferenceDeps;
|
|
9289
|
-
if (deps) for (const entry of Object.values(deps)) entry.js = [];
|
|
9290
|
-
if (manifest) manifest.bootstrapScriptContent = "";
|
|
9291
|
-
await writeFile(path, `export default ${JSON.stringify(manifest, null, 2)};\n`);
|
|
9292
|
-
}
|
|
9293
|
-
}
|
|
9294
|
-
//#endregion
|
|
9295
9531
|
//#region src/plugins/build-report.ts
|
|
9296
9532
|
/**
|
|
9297
9533
|
* timber-build-report — Post-build route summary table.
|
|
@@ -9315,14 +9551,15 @@ var ROUTE_TYPE_ICONS = {
|
|
|
9315
9551
|
/**
|
|
9316
9552
|
* Classify a route by its segment chain and output mode.
|
|
9317
9553
|
*
|
|
9318
|
-
* In server mode (default), all pages are dynamic (rendered per-request)
|
|
9319
|
-
*
|
|
9320
|
-
*
|
|
9554
|
+
* In server mode (default), all pages are dynamic (rendered per-request)
|
|
9555
|
+
* and API routes (route.ts) are classified as function.
|
|
9556
|
+
* In static mode, everything is pre-rendered at build time — all routes
|
|
9557
|
+
* are static regardless of dynamic segments or route.ts presence.
|
|
9321
9558
|
*/
|
|
9322
9559
|
function classifyRoute(segments, outputMode = "server") {
|
|
9560
|
+
if (outputMode === "static") return "static";
|
|
9323
9561
|
if (segments[segments.length - 1]?.route) return "function";
|
|
9324
|
-
|
|
9325
|
-
return segments.some((s) => s.segmentType === "dynamic" || s.segmentType === "catch-all" || s.segmentType === "optional-catch-all") ? "dynamic" : "static";
|
|
9562
|
+
return "dynamic";
|
|
9326
9563
|
}
|
|
9327
9564
|
function green(text) {
|
|
9328
9565
|
return `\x1b[92m${text}\x1b[39m`;
|
|
@@ -10227,13 +10464,13 @@ function timber(config) {
|
|
|
10227
10464
|
timberRouting(ctx),
|
|
10228
10465
|
timberEntries(ctx),
|
|
10229
10466
|
timberBuildManifest(ctx),
|
|
10230
|
-
timberStaticBuild(ctx),
|
|
10231
10467
|
timberFonts(ctx),
|
|
10232
10468
|
timberMdx(ctx),
|
|
10233
10469
|
timberRequestDep(ctx),
|
|
10234
10470
|
timberCacheTransform(ctx),
|
|
10235
10471
|
timberPrerenderSugar(ctx),
|
|
10236
10472
|
timberPrebuilt(ctx),
|
|
10473
|
+
timberStaticBuild(ctx),
|
|
10237
10474
|
timberContent(ctx),
|
|
10238
10475
|
timberServerBundle(),
|
|
10239
10476
|
timberCloudflareWasm(ctx),
|