@timber-js/app 0.2.0-alpha.166 → 0.2.0-alpha.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{actions-CDPfMp_I.js → actions-O_LsyCE4.js} +3 -3
- package/dist/_chunks/{actions-CDPfMp_I.js.map → actions-O_LsyCE4.js.map} +1 -1
- package/dist/_chunks/{cache-api-DygSeKCB.js → cache-api-B-lhk9p4.js} +107 -58
- package/dist/_chunks/cache-api-B-lhk9p4.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-EXGYPhI2.js → cli-schema-sync-B73L6pMq.js} +5 -3
- package/dist/_chunks/cli-schema-sync-B73L6pMq.js.map +1 -0
- package/dist/_chunks/cloudflare-AHoWYTYr.js +1188 -0
- package/dist/_chunks/cloudflare-AHoWYTYr.js.map +1 -0
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js +63 -0
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js.map +1 -0
- package/dist/_chunks/{logger-t3uxAmbX.js → logger-AWfuX-KJ.js} +2 -19
- package/dist/_chunks/logger-AWfuX-KJ.js.map +1 -0
- package/dist/_chunks/{walkers-DBVzXuWc.js → walkers-CoOC8Hga.js} +2 -2
- package/dist/_chunks/{walkers-DBVzXuWc.js.map → walkers-CoOC8Hga.js.map} +1 -1
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +10 -3
- package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts +12 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +2 -461
- package/dist/adapters/types.d.ts +2 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cache/cache-api.d.ts +33 -11
- package/dist/cache/cache-api.d.ts.map +1 -1
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cache/json-lossy-check.d.ts +11 -0
- package/dist/cache/json-lossy-check.d.ts.map +1 -0
- package/dist/cache/redis-handler.d.ts +42 -0
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/stores/cloudflare-kv.d.ts +1 -1
- package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -1
- package/dist/cache/stores/memory.d.ts +1 -1
- package/dist/cache/stores/memory.d.ts.map +1 -1
- package/dist/cache/stores/redis.d.ts +1 -1
- package/dist/cache/stores/redis.d.ts.map +1 -1
- package/dist/cache/stores/vercel.d.ts +1 -1
- package/dist/cache/stores/vercel.d.ts.map +1 -1
- package/dist/cache/tag-aware-handler.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cdn/cloudflare-purge.d.ts.map +1 -1
- package/dist/cdn/fastly-purge.d.ts.map +1 -1
- package/dist/cdn/workers-cache-purge.d.ts.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +1 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/child-segment-context.d.ts +2 -2
- package/dist/client/child-segment-context.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/history.d.ts +10 -0
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/internal.js +141 -116
- package/dist/client/internal.js.map +1 -1
- package/dist/client/router.d.ts +6 -0
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-update-context.d.ts +3 -3
- package/dist/client/segment-update-context.d.ts.map +1 -1
- package/dist/client/slot-outlet.d.ts +1 -1
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +2 -1
- package/dist/codec.js.map +1 -1
- package/dist/config-types.d.ts +16 -37
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/dev-tools/instrumentation.d.ts.map +1 -1
- package/dist/fonts/pipeline.d.ts +19 -0
- package/dist/fonts/pipeline.d.ts.map +1 -1
- package/dist/fonts/transform.d.ts.map +1 -1
- package/dist/fonts/virtual-modules.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +378 -109
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts +1 -0
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/cache.d.ts +8 -8
- package/dist/plugins/cache.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +27 -3
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/server/action-client.d.ts +1 -1
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +7 -0
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/body-limits.d.ts.map +1 -1
- package/dist/server/deny-boundary.d.ts +3 -1
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts.map +1 -1
- package/dist/server/index.js +20 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.js +28 -41
- package/dist/server/internal.js.map +1 -1
- package/dist/server/middleware-runner.d.ts +0 -17
- package/dist/server/middleware-runner.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts +6 -3
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +18 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/prebuilt/capture-state.d.ts.map +1 -1
- package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -1
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-timeout.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +9 -0
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts +14 -14
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +1 -0
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +8 -0
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +44 -74
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
- package/dist/server/safe-load.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/shared/redirect-type.d.ts +2 -2
- package/dist/shared/redirect-type.d.ts.map +1 -1
- package/dist/shims/font-google.d.ts.map +1 -1
- package/dist/shims/image.d.ts +120 -120
- package/dist/shims/image.d.ts.map +1 -1
- package/docs/api/32-api-cache.mdx +116 -4
- package/docs/api/34-api-config.mdx +0 -26
- package/docs/learn/09-caching.mdx +126 -10
- package/docs/learn/12-client-navigation.mdx +9 -1
- package/docs/learn/13-configuration.mdx +6 -8
- package/docs/learn/14-deploying.mdx +18 -18
- package/docs/more/03-coming-from-nextjs.mdx +2 -2
- package/docs/more/04-metadata-and-fonts.mdx +1 -1
- package/docs/more/50-ai-agent-instructions.mdx +5 -5
- package/package.json +6 -5
- package/src/adapters/cloudflare-kv-cache.ts +27 -6
- package/src/adapters/cloudflare.ts +63 -25
- package/src/adapters/types.ts +2 -0
- package/src/cache/cache-api.ts +84 -84
- package/src/cache/index.ts +1 -1
- package/src/cache/json-lossy-check.ts +75 -0
- package/src/cache/redis-handler.ts +65 -14
- package/src/cache/timber-cache.ts +10 -2
- package/src/client/browser-entry/index.ts +2 -0
- package/src/client/browser-entry/post-hydration.ts +16 -9
- package/src/client/browser-entry/router-init.ts +2 -0
- package/src/client/history.ts +11 -0
- package/src/client/router.ts +224 -208
- package/src/codec.ts +3 -1
- package/src/config-types.ts +16 -37
- package/src/config-validation.ts +7 -5
- package/src/fonts/pipeline.ts +32 -0
- package/src/fonts/transform.ts +30 -24
- package/src/plugins/adapter-build.ts +132 -13
- package/src/plugins/cache.ts +45 -30
- package/src/plugins/entries.ts +40 -43
- package/src/plugins/fonts.ts +30 -0
- package/src/plugins/server-bundle.ts +7 -15
- package/src/routing/scanner.ts +7 -0
- package/src/server/action-client.ts +1 -1
- package/src/server/als-registry.ts +7 -0
- package/src/server/deny-boundary.ts +6 -3
- package/src/server/middleware-runner.ts +0 -44
- package/src/server/param-coercion.ts +1 -0
- package/src/server/pipeline-phases.ts +11 -13
- package/src/server/pipeline.ts +51 -3
- package/src/server/route-element-builder.ts +59 -9
- package/src/server/rsc-entry/action-middleware-runner.ts +14 -14
- package/src/server/rsc-entry/index.ts +30 -40
- package/src/server/rsc-entry/render-route.ts +6 -2
- package/src/server/rsc-entry/rsc-payload.ts +21 -4
- package/src/server/rsc-entry/rsc-stream.ts +8 -0
- package/src/server/rsc-entry/wrap-action-dispatch.ts +109 -372
- package/dist/_chunks/cache-api-DygSeKCB.js.map +0 -1
- package/dist/_chunks/cli-schema-sync-EXGYPhI2.js.map +0 -1
- package/dist/_chunks/logger-t3uxAmbX.js.map +0 -1
- package/dist/_chunks/tree-match-D2l830j2.js +0 -102
- package/dist/_chunks/tree-match-D2l830j2.js.map +0 -1
- package/dist/adapters/cloudflare.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { a as __esmMin, o as __exportAll, s as __toCommonJS } from "./_chunks/dist-BA3u1z3W.js";
|
|
2
2
|
import { c as isDynamicMetadataExtension, l as isMetadataRouteServePath, r as canonicalize } from "./_chunks/canonicalize-P41GR6tY.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { i as
|
|
6
|
-
import { a as generateRouteMap, c as fileHasDirective, d as getPrerenderExport, i as validateSchemaAgainstRoutes, l as fileHasExport, n as collectInterceptionRewrites, o as fileHasAnyExport, s as fileHasDefaultExport, t as collectLeafRoutes, u as fileHasStarExport } from "./_chunks/walkers-DBVzXuWc.js";
|
|
3
|
+
import { h as swallow } from "./_chunks/logger-AWfuX-KJ.js";
|
|
4
|
+
import { i as scanRoutes } from "./_chunks/cli-schema-sync-B73L6pMq.js";
|
|
5
|
+
import { a as generateRouteMap, c as fileHasDirective, d as getPrerenderExport, i as validateSchemaAgainstRoutes, l as fileHasExport, n as collectInterceptionRewrites, o as fileHasAnyExport, s as fileHasDefaultExport, t as collectLeafRoutes, u as fileHasStarExport } from "./_chunks/walkers-CoOC8Hga.js";
|
|
7
6
|
import { a as resolveAppDir, c as createNoopTimer, n as loadTimberConfigFile, o as resolveBuildDir, r as mergeFileConfig, s as resolveClientJavascript, t as createPluginContext } from "./_chunks/plugin-context---kTF5v8.js";
|
|
8
7
|
import { t as fnv1aHash } from "./_chunks/fast-hash-D6hIVt1Y.js";
|
|
9
8
|
import { t as formatSize } from "./_chunks/format-CfwjgPz9.js";
|
|
@@ -107,11 +106,18 @@ function timberContent(ctx) {
|
|
|
107
106
|
};
|
|
108
107
|
}
|
|
109
108
|
//#endregion
|
|
110
|
-
//#region ../../node_modules/.pnpm/srvx@0.11.
|
|
111
|
-
|
|
109
|
+
//#region ../../node_modules/.pnpm/srvx@0.11.22/node_modules/srvx/dist/adapters/node.mjs
|
|
110
|
+
function sendNodeResponse(nodeRes, webRes) {
|
|
111
|
+
try {
|
|
112
|
+
return _sendNodeResponse(nodeRes, webRes, false) || Promise.resolve();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return Promise.reject(error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function _sendNodeResponse(nodeRes, webRes, detached) {
|
|
112
118
|
if (!webRes) {
|
|
113
119
|
nodeRes.statusCode = 500;
|
|
114
|
-
return endNodeResponse(nodeRes);
|
|
120
|
+
return endNodeResponse(nodeRes, detached);
|
|
115
121
|
}
|
|
116
122
|
if (webRes._toNodeResponse) {
|
|
117
123
|
const res = webRes._toNodeResponse();
|
|
@@ -123,18 +129,22 @@ async function sendNodeResponse(nodeRes, webRes) {
|
|
|
123
129
|
writeHead(nodeRes, res.status, res.statusText, res.headers);
|
|
124
130
|
nodeRes.write(res.body);
|
|
125
131
|
} else writeHead(nodeRes, res.status, res.statusText, res.headers);
|
|
126
|
-
return endNodeResponse(nodeRes);
|
|
132
|
+
return endNodeResponse(nodeRes, detached);
|
|
127
133
|
}
|
|
128
|
-
const rawHeaders = [
|
|
134
|
+
const rawHeaders = [];
|
|
135
|
+
for (const [key, value] of webRes.headers) rawHeaders.push(key, value);
|
|
129
136
|
writeHead(nodeRes, webRes.status, webRes.statusText, rawHeaders);
|
|
130
|
-
return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes);
|
|
137
|
+
return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes, detached);
|
|
131
138
|
}
|
|
132
139
|
function writeHead(nodeRes, status, statusText, rawHeaders) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
else nodeRes.writeHead(status, statusText, writeHeaders);
|
|
140
|
+
if (!nodeRes.headersSent) if (nodeRes.req?.httpVersion === "2.0") nodeRes.writeHead(status, rawHeaders);
|
|
141
|
+
else nodeRes.writeHead(status, statusText, rawHeaders);
|
|
136
142
|
}
|
|
137
|
-
function endNodeResponse(nodeRes) {
|
|
143
|
+
function endNodeResponse(nodeRes, detached) {
|
|
144
|
+
if (detached) {
|
|
145
|
+
nodeRes.end();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
138
148
|
return new Promise((resolve) => nodeRes.end(resolve));
|
|
139
149
|
}
|
|
140
150
|
function pipeBody(stream, nodeRes, status, statusText, headers) {
|
|
@@ -1227,10 +1237,11 @@ function validateConfig(config) {
|
|
|
1227
1237
|
suggestion: "Example: budget: { firstLoadJs: 150_000 } for a ~150 kB limit."
|
|
1228
1238
|
});
|
|
1229
1239
|
}
|
|
1240
|
+
const MOVED_TO_CACHE_FILE = /* @__PURE__ */ new Set(["cacheHandler", "cdnPurge"]);
|
|
1230
1241
|
for (const key of Object.keys(config)) if (!knownKeys.has(key)) errors.push({
|
|
1231
1242
|
field: key,
|
|
1232
1243
|
message: `Unknown config option: "${key}".`,
|
|
1233
|
-
suggestion: `Check for typos. Known options: ${[...knownKeys].sort().join(", ")}.`
|
|
1244
|
+
suggestion: MOVED_TO_CACHE_FILE.has(key) ? `"${key}" has moved to timber.cache.ts. Export cacheHandler as default, cdnPurge as a named export.` : `Check for typos. Known options: ${[...knownKeys].sort().join(", ")}.`
|
|
1234
1245
|
});
|
|
1235
1246
|
return errors;
|
|
1236
1247
|
}
|
|
@@ -1304,12 +1315,9 @@ var init_config_validation = __esmMin((() => {
|
|
|
1304
1315
|
debug: true,
|
|
1305
1316
|
clientJavascript: true,
|
|
1306
1317
|
adapter: true,
|
|
1307
|
-
cacheHandler: true,
|
|
1308
|
-
cdnPurge: true,
|
|
1309
1318
|
allowedOrigins: true,
|
|
1310
1319
|
csrf: true,
|
|
1311
1320
|
limits: true,
|
|
1312
|
-
actions: true,
|
|
1313
1321
|
forms: true,
|
|
1314
1322
|
pageExtensions: true,
|
|
1315
1323
|
slowRequestMs: true,
|
|
@@ -1323,6 +1331,7 @@ var init_config_validation = __esmMin((() => {
|
|
|
1323
1331
|
reactCompiler: true,
|
|
1324
1332
|
sitemap: true,
|
|
1325
1333
|
buildDir: true,
|
|
1334
|
+
clientSegmentCache: true,
|
|
1326
1335
|
topLoader: true,
|
|
1327
1336
|
budget: true
|
|
1328
1337
|
}));
|
|
@@ -1337,7 +1346,7 @@ var init_config_validation = __esmMin((() => {
|
|
|
1337
1346
|
"virtual:timber-config": "Runtime config (timber.config.ts)",
|
|
1338
1347
|
"virtual:timber-route-manifest": "Route manifest (app/ file tree)",
|
|
1339
1348
|
"virtual:timber-instrumentation": "Instrumentation (instrumentation.ts)",
|
|
1340
|
-
"virtual:timber-cache-handler": "Cache handler (
|
|
1349
|
+
"virtual:timber-cache-handler": "Cache handler (timber.cache.ts)",
|
|
1341
1350
|
"virtual:timber-build-manifest": "Build manifest (asset mapping)"
|
|
1342
1351
|
};
|
|
1343
1352
|
REQUIRED_PEERS = [
|
|
@@ -1466,6 +1475,105 @@ function compressWithGzip(body) {
|
|
|
1466
1475
|
return Readable.toWeb(gzip);
|
|
1467
1476
|
}
|
|
1468
1477
|
//#endregion
|
|
1478
|
+
//#region src/server/tree-match.ts
|
|
1479
|
+
/**
|
|
1480
|
+
* Match URL path parts against a segment tree.
|
|
1481
|
+
*
|
|
1482
|
+
* Walks the tree depth-first with backtracking, applying priority
|
|
1483
|
+
* ordering globally across group boundaries. Returns the matched
|
|
1484
|
+
* segment chain and collected params, or null if no route matches.
|
|
1485
|
+
*
|
|
1486
|
+
* By default, a match requires a terminal node (one with `page` or
|
|
1487
|
+
* `route`) at the leaf. Pass `{ requireTerminal: false }` to match
|
|
1488
|
+
* any segment (used by metadata route matching).
|
|
1489
|
+
*
|
|
1490
|
+
* @param root The root segment node to start matching from.
|
|
1491
|
+
* @param parts URL path parts (e.g. ['blog', 'hello-world']).
|
|
1492
|
+
* Empty array matches the root itself (if terminal).
|
|
1493
|
+
*/
|
|
1494
|
+
function matchUrlParts(root, parts, options) {
|
|
1495
|
+
const chain = [];
|
|
1496
|
+
const params = {};
|
|
1497
|
+
if (matchNode(root, parts, 0, chain, params, options?.requireTerminal ?? true)) return {
|
|
1498
|
+
chain,
|
|
1499
|
+
params
|
|
1500
|
+
};
|
|
1501
|
+
return null;
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Flatten children through group segments.
|
|
1505
|
+
*
|
|
1506
|
+
* Groups are transparent for URL matching — their non-group descendants
|
|
1507
|
+
* are returned with the chain of group nodes leading to them. This lets
|
|
1508
|
+
* the caller apply priority ordering (static > dynamic > catch-all >
|
|
1509
|
+
* optional-catch-all) across all groups uniformly instead of per-group.
|
|
1510
|
+
*/
|
|
1511
|
+
function collectEffectiveChildren(node, groupChain = []) {
|
|
1512
|
+
const result = [];
|
|
1513
|
+
for (const child of node.children) if (child.segmentType === "group") {
|
|
1514
|
+
const nested = collectEffectiveChildren(child, [...groupChain, child]);
|
|
1515
|
+
result.push(...nested);
|
|
1516
|
+
} else result.push({
|
|
1517
|
+
child,
|
|
1518
|
+
groupChain
|
|
1519
|
+
});
|
|
1520
|
+
return result;
|
|
1521
|
+
}
|
|
1522
|
+
/** A node is terminal if it can serve a response (has a page or route handler). */
|
|
1523
|
+
function isTerminal(node) {
|
|
1524
|
+
return !!(node.page || node.route);
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Recursive depth-first matcher with backtracking.
|
|
1528
|
+
*
|
|
1529
|
+
* Mutates `chain` and `params` in place for performance; backtracks
|
|
1530
|
+
* (restores previous state) when a candidate doesn't lead to a match.
|
|
1531
|
+
*/
|
|
1532
|
+
function matchNode(node, parts, index, chain, params, requireTerminal) {
|
|
1533
|
+
chain.push(node);
|
|
1534
|
+
if (index >= parts.length) {
|
|
1535
|
+
if (!requireTerminal || isTerminal(node)) return true;
|
|
1536
|
+
for (const child of node.children) if (child.segmentType === "group") {
|
|
1537
|
+
if (matchNode(child, parts, index, chain, params, requireTerminal)) return true;
|
|
1538
|
+
}
|
|
1539
|
+
const effective = collectEffectiveChildren(node);
|
|
1540
|
+
for (const { child, groupChain } of effective) if (child.segmentType === "optional-catch-all" && (!requireTerminal || isTerminal(child))) {
|
|
1541
|
+
chain.push(...groupChain, child);
|
|
1542
|
+
return true;
|
|
1543
|
+
}
|
|
1544
|
+
chain.pop();
|
|
1545
|
+
return false;
|
|
1546
|
+
}
|
|
1547
|
+
const part = parts[index];
|
|
1548
|
+
const effective = collectEffectiveChildren(node);
|
|
1549
|
+
for (const { child, groupChain } of effective) if (child.segmentType === "static" && child.segmentName === part) {
|
|
1550
|
+
chain.push(...groupChain);
|
|
1551
|
+
if (matchNode(child, parts, index + 1, chain, params, requireTerminal)) return true;
|
|
1552
|
+
chain.length -= groupChain.length;
|
|
1553
|
+
}
|
|
1554
|
+
for (const { child, groupChain } of effective) if (child.segmentType === "dynamic" && child.paramName) {
|
|
1555
|
+
chain.push(...groupChain);
|
|
1556
|
+
const prev = params[child.paramName];
|
|
1557
|
+
params[child.paramName] = part;
|
|
1558
|
+
if (matchNode(child, parts, index + 1, chain, params, requireTerminal)) return true;
|
|
1559
|
+
chain.length -= groupChain.length;
|
|
1560
|
+
if (prev !== void 0) params[child.paramName] = prev;
|
|
1561
|
+
else delete params[child.paramName];
|
|
1562
|
+
}
|
|
1563
|
+
for (const { child, groupChain } of effective) if (child.segmentType === "catch-all" && child.paramName && (!requireTerminal || isTerminal(child))) {
|
|
1564
|
+
chain.push(...groupChain, child);
|
|
1565
|
+
params[child.paramName] = parts.slice(index);
|
|
1566
|
+
return true;
|
|
1567
|
+
}
|
|
1568
|
+
for (const { child, groupChain } of effective) if (child.segmentType === "optional-catch-all" && child.paramName && (!requireTerminal || isTerminal(child))) {
|
|
1569
|
+
chain.push(...groupChain, child);
|
|
1570
|
+
params[child.paramName] = parts.slice(index);
|
|
1571
|
+
return true;
|
|
1572
|
+
}
|
|
1573
|
+
chain.pop();
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
//#endregion
|
|
1469
1577
|
//#region src/plugins/dev-server.ts
|
|
1470
1578
|
var RSC_ENTRY_ID = "virtual:timber-rsc-entry";
|
|
1471
1579
|
/**
|
|
@@ -1892,6 +2000,7 @@ function generateConfigModule(ctx) {
|
|
|
1892
2000
|
sitemap: ctx.config.sitemap,
|
|
1893
2001
|
forms: serializeFormsConfig(ctx.config.forms),
|
|
1894
2002
|
deploymentId: ctx.deploymentId ?? null,
|
|
2003
|
+
clientSegmentCache: ctx.config.clientSegmentCache ?? false,
|
|
1895
2004
|
devBrowserLogs: ctx.config.devBrowserLogs
|
|
1896
2005
|
};
|
|
1897
2006
|
return [
|
|
@@ -1904,12 +2013,12 @@ function generateConfigModule(ctx) {
|
|
|
1904
2013
|
].join("\n");
|
|
1905
2014
|
}
|
|
1906
2015
|
/**
|
|
1907
|
-
* Detect
|
|
2016
|
+
* Detect a user convention file at both root and src/.
|
|
1908
2017
|
*
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
2018
|
+
* Root takes precedence over src/, extension priority: .ts > .js > .mjs.
|
|
2019
|
+
* Used for instrumentation.ts, timber.cache.ts, and similar file conventions.
|
|
1911
2020
|
*/
|
|
1912
|
-
function
|
|
2021
|
+
function detectConventionFile(root, basename) {
|
|
1913
2022
|
const extensions = [
|
|
1914
2023
|
".ts",
|
|
1915
2024
|
".js",
|
|
@@ -1917,11 +2026,25 @@ function detectInstrumentationFile(root) {
|
|
|
1917
2026
|
];
|
|
1918
2027
|
const dirs = [root, resolve(root, "src")];
|
|
1919
2028
|
for (const dir of dirs) for (const ext of extensions) {
|
|
1920
|
-
const candidate = resolve(dir,
|
|
2029
|
+
const candidate = resolve(dir, `${basename}${ext}`);
|
|
1921
2030
|
if (existsSync(candidate)) return candidate;
|
|
1922
2031
|
}
|
|
1923
2032
|
return null;
|
|
1924
2033
|
}
|
|
2034
|
+
function detectInstrumentationFile(root) {
|
|
2035
|
+
return detectConventionFile(root, "instrumentation");
|
|
2036
|
+
}
|
|
2037
|
+
/**
|
|
2038
|
+
* Detect the user's timber.cache file.
|
|
2039
|
+
*
|
|
2040
|
+
* This file holds runtime cache instances (cacheHandler, cdnPurge)
|
|
2041
|
+
* that cannot be JSON-serialized. Separating them from timber.config.ts
|
|
2042
|
+
* prevents the RSC build from pulling build-time deps (adapters, MDX,
|
|
2043
|
+
* rollup) into the runtime bundle. See TIM-1211.
|
|
2044
|
+
*/
|
|
2045
|
+
function detectCacheFile(root) {
|
|
2046
|
+
return detectConventionFile(root, "timber.cache");
|
|
2047
|
+
}
|
|
1925
2048
|
/**
|
|
1926
2049
|
* Generate the virtual:timber-instrumentation module source.
|
|
1927
2050
|
*
|
|
@@ -1951,50 +2074,37 @@ function generateInstrumentationModule(instrumentationPath) {
|
|
|
1951
2074
|
].join("\n");
|
|
1952
2075
|
}
|
|
1953
2076
|
/**
|
|
1954
|
-
* Detect the user's timber.config file at the project root.
|
|
1955
|
-
* Returns the absolute path or null if not found.
|
|
1956
|
-
*/
|
|
1957
|
-
function detectConfigFile(root) {
|
|
1958
|
-
for (const name of [
|
|
1959
|
-
"timber.config.ts",
|
|
1960
|
-
"timber.config.js",
|
|
1961
|
-
"timber.config.mjs"
|
|
1962
|
-
]) {
|
|
1963
|
-
const candidate = resolve(root, name);
|
|
1964
|
-
if (existsSync(candidate)) return candidate;
|
|
1965
|
-
}
|
|
1966
|
-
return null;
|
|
1967
|
-
}
|
|
1968
|
-
/**
|
|
1969
2077
|
* Generate the virtual:timber-cache-handler module source.
|
|
1970
2078
|
*
|
|
1971
|
-
* When the user's
|
|
1972
|
-
* dynamically imports
|
|
1973
|
-
*
|
|
1974
|
-
*
|
|
1975
|
-
*
|
|
2079
|
+
* When the user's timber.cache.ts exists, generates a module that
|
|
2080
|
+
* dynamically imports it and extracts cacheHandler and cdnPurge.
|
|
2081
|
+
* These are class instances (e.g. RedisCacheHandler) that cannot be
|
|
2082
|
+
* JSON-serialized into virtual:timber-config, so they must be loaded
|
|
2083
|
+
* at runtime via dynamic import.
|
|
2084
|
+
*
|
|
2085
|
+
* timber.cache.ts exports: default cacheHandler, named cdnPurge.
|
|
2086
|
+
*
|
|
2087
|
+
* See TIM-1211, TIM-599.
|
|
1976
2088
|
*/
|
|
1977
|
-
function generateCacheHandlerModule(
|
|
1978
|
-
if (
|
|
2089
|
+
function generateCacheHandlerModule(cachePath) {
|
|
2090
|
+
if (cachePath) return [
|
|
1979
2091
|
"// Auto-generated cache handler loader — do not edit.",
|
|
1980
2092
|
"// Generated by timber-entries plugin.",
|
|
1981
2093
|
"",
|
|
1982
2094
|
`export default async function loadCacheHandler() {`,
|
|
1983
|
-
` const mod = await import(${JSON.stringify(
|
|
1984
|
-
`
|
|
1985
|
-
` return config.cacheHandler ?? null;`,
|
|
2095
|
+
` const mod = await import(${JSON.stringify(cachePath)});`,
|
|
2096
|
+
` return mod.default ?? null;`,
|
|
1986
2097
|
`}`,
|
|
1987
2098
|
"",
|
|
1988
2099
|
`export async function loadCdnPurgeHandler() {`,
|
|
1989
|
-
` const mod = await import(${JSON.stringify(
|
|
1990
|
-
`
|
|
1991
|
-
` return config.cdnPurge ?? null;`,
|
|
2100
|
+
` const mod = await import(${JSON.stringify(cachePath)});`,
|
|
2101
|
+
` return mod.cdnPurge ?? null;`,
|
|
1992
2102
|
`}`
|
|
1993
2103
|
].join("\n");
|
|
1994
2104
|
return [
|
|
1995
2105
|
"// Auto-generated cache handler loader — do not edit.",
|
|
1996
2106
|
"// Generated by timber-entries plugin.",
|
|
1997
|
-
"// No
|
|
2107
|
+
"// No timber.cache.ts found.",
|
|
1998
2108
|
"",
|
|
1999
2109
|
`export default async function loadCacheHandler() {`,
|
|
2000
2110
|
` return null;`,
|
|
@@ -2042,7 +2152,7 @@ function timberEntries(ctx) {
|
|
|
2042
2152
|
load(id) {
|
|
2043
2153
|
if (id === RESOLVED_CONFIG_ID) return generateConfigModule(ctx);
|
|
2044
2154
|
if (id === RESOLVED_INSTRUMENTATION_ID) return generateInstrumentationModule(detectInstrumentationFile(ctx.root));
|
|
2045
|
-
if (id === RESOLVED_CACHE_HANDLER_ID) return generateCacheHandlerModule(
|
|
2155
|
+
if (id === RESOLVED_CACHE_HANDLER_ID) return generateCacheHandlerModule(detectCacheFile(ctx.root));
|
|
2046
2156
|
return null;
|
|
2047
2157
|
}
|
|
2048
2158
|
};
|
|
@@ -4610,6 +4720,30 @@ var FontPipeline = class {
|
|
|
4610
4720
|
const familyKey = family.toLowerCase();
|
|
4611
4721
|
for (const [id, entry] of this._entries) if (entry.importer === importer && entry.family.toLowerCase() === familyKey) this._entries.delete(id);
|
|
4612
4722
|
}
|
|
4723
|
+
/** True if at least one entry is registered under `importer`. */
|
|
4724
|
+
hasImporter(importer) {
|
|
4725
|
+
for (const entry of this._entries.values()) if (entry.importer === importer) return true;
|
|
4726
|
+
return false;
|
|
4727
|
+
}
|
|
4728
|
+
/**
|
|
4729
|
+
* Drop **every** registry entry that originated from `importer`,
|
|
4730
|
+
* regardless of family.
|
|
4731
|
+
*
|
|
4732
|
+
* Called at the start of the transform hook before re-registering the
|
|
4733
|
+
* current extraction set. This handles the cases `pruneFor` misses:
|
|
4734
|
+
*
|
|
4735
|
+
* - **Family rename** (Inter → Roboto): the old family's entries are
|
|
4736
|
+
* cleared because we prune by importer, not by (importer, family).
|
|
4737
|
+
* - **Font call deleted**: the old entries are cleared even though no
|
|
4738
|
+
* new call provides a family to prune against.
|
|
4739
|
+
*
|
|
4740
|
+
* Other importers' fonts are untouched.
|
|
4741
|
+
*
|
|
4742
|
+
* See TIM-1087.
|
|
4743
|
+
*/
|
|
4744
|
+
pruneImporter(importer) {
|
|
4745
|
+
for (const [id, entry] of this._entries) if (entry.importer === importer) this._entries.delete(id);
|
|
4746
|
+
}
|
|
4613
4747
|
/** Drop every registered font. Used by tests and rebuild flows. */
|
|
4614
4748
|
clear() {
|
|
4615
4749
|
this._entries.clear();
|
|
@@ -5788,6 +5922,7 @@ function runFontsTransform(code, id, pipeline, emitError) {
|
|
|
5788
5922
|
}
|
|
5789
5923
|
if (analysis.callSites.length === 0 && analysis.bindings.length === 0) return null;
|
|
5790
5924
|
const s = new MagicString(code);
|
|
5925
|
+
const pending = [];
|
|
5791
5926
|
for (const site of analysis.callSites) if (site.binding.provider === "google") {
|
|
5792
5927
|
const family = site.binding.importedName.replace(/_/g, " ");
|
|
5793
5928
|
const config = extractGoogleFontConfigFromNode(site.configNode);
|
|
@@ -5796,27 +5931,32 @@ function runFontsTransform(code, id, pipeline, emitError) {
|
|
|
5796
5931
|
const className = familyToClassName(family);
|
|
5797
5932
|
const fontStack = buildFontStack(family);
|
|
5798
5933
|
const display = config.display ?? "swap";
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5934
|
+
pending.push({
|
|
5935
|
+
site,
|
|
5936
|
+
extracted: {
|
|
5937
|
+
id: fontId,
|
|
5938
|
+
family,
|
|
5939
|
+
provider: "google",
|
|
5940
|
+
weights: normalizeWeightArray(config.weight),
|
|
5941
|
+
styles: normalizeStyleArray(config.style),
|
|
5942
|
+
subsets: config.subsets ?? ["latin"],
|
|
5943
|
+
display,
|
|
5944
|
+
variable: config.variable,
|
|
5945
|
+
className,
|
|
5946
|
+
fontFamily: fontStack,
|
|
5947
|
+
importer: id
|
|
5948
|
+
}
|
|
5949
|
+
});
|
|
5815
5950
|
} else {
|
|
5816
5951
|
const config = extractLocalFontConfigFromNode(site.configNode);
|
|
5817
5952
|
if (!config) emitError(`Could not statically analyze local font config at line ${site.line}, column ${site.column}. Ensure src is a string or array of { path, weight?, style? } objects.`);
|
|
5818
|
-
|
|
5819
|
-
|
|
5953
|
+
pending.push({
|
|
5954
|
+
site,
|
|
5955
|
+
extracted: processLocalFont(config, id)
|
|
5956
|
+
});
|
|
5957
|
+
}
|
|
5958
|
+
pipeline.pruneImporter(id);
|
|
5959
|
+
for (const { extracted, site } of pending) {
|
|
5820
5960
|
pipeline.register(extracted);
|
|
5821
5961
|
s.overwrite(site.replaceStart, site.replaceEnd, buildFontResultLiteral(extracted));
|
|
5822
5962
|
}
|
|
@@ -5986,6 +6126,25 @@ function writeFontManifest(pipeline, buildManifest, base = "/") {
|
|
|
5986
6126
|
//#endregion
|
|
5987
6127
|
//#region src/plugins/fonts.ts
|
|
5988
6128
|
/**
|
|
6129
|
+
* timber-fonts — Vite sub-plugin shell for build-time font processing.
|
|
6130
|
+
*
|
|
6131
|
+
* This file is intentionally thin: it wires the Vite plugin hooks to the
|
|
6132
|
+
* pipeline + per-concern modules under `../fonts/`. All real logic lives
|
|
6133
|
+
* in:
|
|
6134
|
+
*
|
|
6135
|
+
* - `../fonts/pipeline.ts` — `FontPipeline` (state, mutators)
|
|
6136
|
+
* - `../fonts/transform.ts` — transform-hook logic, parsing helpers
|
|
6137
|
+
* - `../fonts/dev-middleware.ts` — dev font binary server
|
|
6138
|
+
* - `../fonts/virtual-modules.ts` — virtual module ID constants + source
|
|
6139
|
+
*
|
|
6140
|
+
* The public exports below are the stable API that the test suite and
|
|
6141
|
+
* other parts of the framework import. Anything new should be added to
|
|
6142
|
+
* one of the per-concern modules and re-exported here only if external
|
|
6143
|
+
* consumers need it.
|
|
6144
|
+
*
|
|
6145
|
+
* Design doc: 24-fonts.md
|
|
6146
|
+
*/
|
|
6147
|
+
/**
|
|
5989
6148
|
* Create the timber-fonts Vite plugin.
|
|
5990
6149
|
*/
|
|
5991
6150
|
function timberFonts(ctx) {
|
|
@@ -6119,6 +6278,27 @@ function timberFonts(ctx) {
|
|
|
6119
6278
|
*/
|
|
6120
6279
|
buildStart() {},
|
|
6121
6280
|
/**
|
|
6281
|
+
* Prune stale font entries when a module that previously imported fonts
|
|
6282
|
+
* no longer contains any font import. The transform hook's `filter.code`
|
|
6283
|
+
* skips such modules, so without this hook the old entries would persist
|
|
6284
|
+
* until dev restart. See TIM-1087.
|
|
6285
|
+
*/
|
|
6286
|
+
async hotUpdate({ type, file, read }) {
|
|
6287
|
+
if (!pipeline.hasImporter(file)) return;
|
|
6288
|
+
if (type === "delete") {
|
|
6289
|
+
pipeline.pruneImporter(file);
|
|
6290
|
+
return;
|
|
6291
|
+
}
|
|
6292
|
+
const code = await read();
|
|
6293
|
+
if (code.includes("@timber/fonts/") || code.includes("@timber-js/app/fonts/") || code.includes("next/font/")) return;
|
|
6294
|
+
try {
|
|
6295
|
+
parseAst(code, { lang: "tsx" });
|
|
6296
|
+
} catch {
|
|
6297
|
+
return;
|
|
6298
|
+
}
|
|
6299
|
+
pipeline.pruneImporter(file);
|
|
6300
|
+
},
|
|
6301
|
+
/**
|
|
6122
6302
|
* Scan source files for font function calls and extract static config.
|
|
6123
6303
|
*
|
|
6124
6304
|
* Delegates to `runFontsTransform`. Errors raised through the
|
|
@@ -7383,14 +7563,14 @@ function generateStableCallsiteId(relativeModulePath, kind, statementText, callS
|
|
|
7383
7563
|
//#region src/plugins/cache.ts
|
|
7384
7564
|
/**
|
|
7385
7565
|
* timber-cache-transform — Vite plugin that injects stable callsite IDs
|
|
7386
|
-
* into `cache()` calls imported from `@timber-js/app/cache`.
|
|
7566
|
+
* into `cache.data()` calls imported from `@timber-js/app/cache`.
|
|
7387
7567
|
*
|
|
7388
7568
|
* Detected callsite forms (TIM-1054):
|
|
7389
|
-
* - named imports, including aliases: `import { cache as c }` + `c(fn, opts)`
|
|
7390
|
-
* - namespace imports: `import * as ns` + `ns.cache(fn, opts)`
|
|
7569
|
+
* - named imports, including aliases: `import { cache as c }` + `c.data(fn, opts)`
|
|
7570
|
+
* - namespace imports: `import * as ns` + `ns.cache.data(fn, opts)`
|
|
7391
7571
|
* Forms a per-module transform cannot trace — local variable aliases
|
|
7392
7572
|
* (`const c = cache`), user re-export wrapper modules, computed access
|
|
7393
|
-
* (`
|
|
7573
|
+
* (`cache['data']`) — fall back to the runtime fnId derived from
|
|
7394
7574
|
* fnv1a(fn.toString()) (see cache/timber-cache.ts), which is deterministic
|
|
7395
7575
|
* across instances and cold starts of the same build. Without either
|
|
7396
7576
|
* mechanism, an ordinal counter would collide across instances with a shared
|
|
@@ -7398,8 +7578,8 @@ function generateStableCallsiteId(relativeModulePath, kind, statementText, callS
|
|
|
7398
7578
|
* data to another.
|
|
7399
7579
|
*
|
|
7400
7580
|
* The plugin transforms:
|
|
7401
|
-
* cache(fn, opts)
|
|
7402
|
-
* ns.cache(fn, opts)
|
|
7581
|
+
* cache.data(fn, opts) → cache.data(fn, opts, "stable-id")
|
|
7582
|
+
* ns.cache.data(fn, opts) → ns.cache.data(fn, opts, "stable-id")
|
|
7403
7583
|
*
|
|
7404
7584
|
* where stable-id =
|
|
7405
7585
|
* fnv1a(relPath + ":cache:" + fnv1a(stmtText) + ":" + fnv1a(callSpanText) + ":" + occurrence)
|
|
@@ -7417,9 +7597,9 @@ function generateStableCallsiteId(relativeModulePath, kind, statementText, callS
|
|
|
7417
7597
|
* unrelated edits, while a changed call gets a fresh ID — which is exactly
|
|
7418
7598
|
* when invalidation is wanted.
|
|
7419
7599
|
*
|
|
7420
|
-
* The enclosing-statement text is included so that byte-identical cache()
|
|
7600
|
+
* The enclosing-statement text is included so that byte-identical cache.data()
|
|
7421
7601
|
* calls in different scopes — e.g. two factory helpers that each `return
|
|
7422
|
-
* cache(fn, { ttl: 60 })` — get distinct, insertion-stable IDs: their
|
|
7602
|
+
* cache.data(fn, { ttl: 60 })` — get distinct, insertion-stable IDs: their
|
|
7423
7603
|
* enclosing declarations differ by name/body even when the call spans are
|
|
7424
7604
|
* identical. Without it, a source-order occurrence counter alone would shift
|
|
7425
7605
|
* when an identical span is inserted above, reassigning IDs across deploys.
|
|
@@ -7436,32 +7616,40 @@ function generateStableCallsiteId(relativeModulePath, kind, statementText, callS
|
|
|
7436
7616
|
* See TIM-997, TIM-1053, design/06-caching.md §"Cache Key Security".
|
|
7437
7617
|
*/
|
|
7438
7618
|
/**
|
|
7439
|
-
* Is this callee a
|
|
7440
|
-
*
|
|
7441
|
-
*
|
|
7619
|
+
* Is this callee a `cache.data(...)` call? Matches two forms:
|
|
7620
|
+
* - Named import: `cache.data(fn, opts)` where `cache` is the imported binding
|
|
7621
|
+
* - Namespace import: `ns.cache.data(fn, opts)` where `ns` is the namespace
|
|
7622
|
+
* Computed access (`cache['data']`) is deliberately not matched; it falls back
|
|
7442
7623
|
* to the runtime fnId derivation.
|
|
7443
7624
|
*/
|
|
7444
|
-
function
|
|
7445
|
-
if (callee.type
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7625
|
+
function isCacheDataCallee(callee, bindings) {
|
|
7626
|
+
if (callee.type !== "MemberExpression") return false;
|
|
7627
|
+
const member = callee;
|
|
7628
|
+
if (member.computed) return false;
|
|
7629
|
+
if (member.property.type !== "Identifier" || member.property.name !== "data") return false;
|
|
7630
|
+
const obj = member.object;
|
|
7631
|
+
if (obj.type === "Identifier") return bindings.named.has(obj.name);
|
|
7632
|
+
if (obj.type === "MemberExpression") {
|
|
7633
|
+
const outer = obj;
|
|
7634
|
+
return !outer.computed && outer.object.type === "Identifier" && bindings.namespaces.has(outer.object.name) && outer.property.type === "Identifier" && outer.property.name === "cache";
|
|
7449
7635
|
}
|
|
7450
7636
|
return false;
|
|
7451
7637
|
}
|
|
7452
7638
|
/**
|
|
7453
|
-
* Extract the local binding name that a cache callee resolves to — the
|
|
7454
|
-
* identifier for named imports, or the namespace object for `ns.cache()`.
|
|
7639
|
+
* Extract the local binding name that a cache.data callee resolves to — the
|
|
7640
|
+
* `cache` identifier for named imports, or the namespace object for `ns.cache.data()`.
|
|
7455
7641
|
*/
|
|
7456
7642
|
function calleeBindingName(callee, bindings) {
|
|
7457
|
-
if (callee.type
|
|
7458
|
-
|
|
7643
|
+
if (callee.type !== "MemberExpression") return null;
|
|
7644
|
+
const obj = callee.object;
|
|
7645
|
+
if (obj.type === "Identifier") {
|
|
7646
|
+
const name = obj.name;
|
|
7459
7647
|
return bindings.named.has(name) ? name : null;
|
|
7460
7648
|
}
|
|
7461
|
-
if (
|
|
7462
|
-
const
|
|
7463
|
-
if (
|
|
7464
|
-
const name =
|
|
7649
|
+
if (obj.type === "MemberExpression") {
|
|
7650
|
+
const outer = obj;
|
|
7651
|
+
if (outer.object.type === "Identifier") {
|
|
7652
|
+
const name = outer.object.name;
|
|
7465
7653
|
return bindings.namespaces.has(name) ? name : null;
|
|
7466
7654
|
}
|
|
7467
7655
|
}
|
|
@@ -7499,7 +7687,7 @@ function timberCacheTransform(ctx) {
|
|
|
7499
7687
|
const callSites = [];
|
|
7500
7688
|
for (const stmt of program.body) {
|
|
7501
7689
|
const calls = [];
|
|
7502
|
-
findCallSites(stmt, (call) => call.arguments.length === 2 &&
|
|
7690
|
+
findCallSites(stmt, (call) => call.arguments.length === 2 && isCacheDataCallee(call.callee, cacheBindings) && !isCalleeShadowed(call), calls);
|
|
7503
7691
|
for (const call of calls) callSites.push({
|
|
7504
7692
|
call,
|
|
7505
7693
|
stmt
|
|
@@ -8650,6 +8838,7 @@ function timberAdapterBuild(ctx) {
|
|
|
8650
8838
|
}
|
|
8651
8839
|
if (ctx.clientJavascript.disabled) await stripJsFromRscAssetsManifests(buildDir);
|
|
8652
8840
|
const adapterConfig = {
|
|
8841
|
+
root: ctx.root,
|
|
8653
8842
|
output: ctx.config.output ?? "server",
|
|
8654
8843
|
clientJavascriptDisabled: ctx.clientJavascript.disabled,
|
|
8655
8844
|
manifestInit
|
|
@@ -8660,6 +8849,84 @@ function timberAdapterBuild(ctx) {
|
|
|
8660
8849
|
};
|
|
8661
8850
|
}
|
|
8662
8851
|
/**
|
|
8852
|
+
* Parse the RSC assets manifest at `manifestPath`. Tries JSON.parse first
|
|
8853
|
+
* (fast path for plain-string manifests); falls back to a dynamic import()
|
|
8854
|
+
* for manifests that contain runtime expressions from renderBuiltUrl.
|
|
8855
|
+
* Returns null if both paths fail.
|
|
8856
|
+
*/
|
|
8857
|
+
async function parseRscManifest(manifestPath, content) {
|
|
8858
|
+
const jsonStr = content.replace(/^export default\s*/, "").replace(/;?\s*$/, "");
|
|
8859
|
+
try {
|
|
8860
|
+
return JSON.parse(jsonStr);
|
|
8861
|
+
} catch {}
|
|
8862
|
+
try {
|
|
8863
|
+
return (await import(`${pathToFileURL(manifestPath).href}?t=${Date.now()}`)).default ?? null;
|
|
8864
|
+
} catch (err) {
|
|
8865
|
+
swallow(err, `failed to evaluate RSC assets manifest: ${manifestPath}`, { level: "warn" });
|
|
8866
|
+
return null;
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8869
|
+
/**
|
|
8870
|
+
* Replace all `"js": [...]` array contents with `"js": []` using balanced
|
|
8871
|
+
* bracket matching. Handles nested brackets from runtime expressions like
|
|
8872
|
+
* `__rbu("path/to/[slug]/file.js")`.
|
|
8873
|
+
*/
|
|
8874
|
+
function clearJsArrays(src) {
|
|
8875
|
+
const pattern = /"js"\s*:\s*\[/g;
|
|
8876
|
+
let result = "";
|
|
8877
|
+
let lastEnd = 0;
|
|
8878
|
+
let match;
|
|
8879
|
+
while ((match = pattern.exec(src)) !== null) {
|
|
8880
|
+
const arrayStart = match.index + match[0].length;
|
|
8881
|
+
let depth = 1;
|
|
8882
|
+
let i = arrayStart;
|
|
8883
|
+
while (i < src.length && depth > 0) {
|
|
8884
|
+
if (src[i] === "[") depth++;
|
|
8885
|
+
else if (src[i] === "]") depth--;
|
|
8886
|
+
i++;
|
|
8887
|
+
}
|
|
8888
|
+
result += src.slice(lastEnd, match.index) + "\"js\": []";
|
|
8889
|
+
lastEnd = i;
|
|
8890
|
+
}
|
|
8891
|
+
result += src.slice(lastEnd);
|
|
8892
|
+
return result;
|
|
8893
|
+
}
|
|
8894
|
+
/**
|
|
8895
|
+
* Replace `"bootstrapScriptContent": <value>` with an empty string. The
|
|
8896
|
+
* value may be a JS expression (e.g., concatenation with runtime helpers),
|
|
8897
|
+
* not a simple string literal. Scans forward from the colon, tracking
|
|
8898
|
+
* balanced parens and brackets, and stops at the next unbalanced `,` or `}`.
|
|
8899
|
+
*/
|
|
8900
|
+
function clearBootstrapScriptContent(src) {
|
|
8901
|
+
const idx = src.indexOf("\"bootstrapScriptContent\"");
|
|
8902
|
+
if (idx === -1) return src;
|
|
8903
|
+
const colonIdx = src.indexOf(":", idx + 24);
|
|
8904
|
+
if (colonIdx === -1) return src;
|
|
8905
|
+
let valStart = colonIdx + 1;
|
|
8906
|
+
while (valStart < src.length && /\s/.test(src[valStart])) valStart++;
|
|
8907
|
+
let i = valStart;
|
|
8908
|
+
let depth = 0;
|
|
8909
|
+
let inString = false;
|
|
8910
|
+
let stringChar = "";
|
|
8911
|
+
while (i < src.length) {
|
|
8912
|
+
const ch = src[i];
|
|
8913
|
+
if (inString) {
|
|
8914
|
+
if (ch === "\\") {
|
|
8915
|
+
i += 2;
|
|
8916
|
+
continue;
|
|
8917
|
+
}
|
|
8918
|
+
if (ch === stringChar) inString = false;
|
|
8919
|
+
} else if (ch === "\"" || ch === "'") {
|
|
8920
|
+
inString = true;
|
|
8921
|
+
stringChar = ch;
|
|
8922
|
+
} else if (ch === "(" || ch === "[") depth++;
|
|
8923
|
+
else if (ch === ")" || ch === "]") depth--;
|
|
8924
|
+
else if (depth === 0 && (ch === "," || ch === "}" || ch === "\n")) break;
|
|
8925
|
+
i++;
|
|
8926
|
+
}
|
|
8927
|
+
return src.slice(0, idx) + "\"bootstrapScriptContent\": \"\"" + src.slice(i);
|
|
8928
|
+
}
|
|
8929
|
+
/**
|
|
8663
8930
|
* Strip JS references from the RSC plugin's assets manifest files.
|
|
8664
8931
|
*
|
|
8665
8932
|
* The RSC plugin writes `__vite_rsc_assets_manifest.js` to rsc/ and ssr/
|
|
@@ -8684,13 +8951,8 @@ async function mergeRscAssetsManifest(buildDir, root, buildManifest, routeClient
|
|
|
8684
8951
|
} catch {
|
|
8685
8952
|
return;
|
|
8686
8953
|
}
|
|
8687
|
-
const
|
|
8688
|
-
|
|
8689
|
-
try {
|
|
8690
|
-
manifest = JSON.parse(jsonStr);
|
|
8691
|
-
} catch {
|
|
8692
|
-
return;
|
|
8693
|
-
}
|
|
8954
|
+
const manifest = await parseRscManifest(manifestPath, content);
|
|
8955
|
+
if (!manifest) return;
|
|
8694
8956
|
const serverResources = manifest.serverResources;
|
|
8695
8957
|
if (serverResources) {
|
|
8696
8958
|
for (const [relativePath, deps] of Object.entries(serverResources)) if (deps.css && deps.css.length > 0) {
|
|
@@ -8721,16 +8983,23 @@ async function stripJsFromRscAssetsManifests(buildDir) {
|
|
|
8721
8983
|
continue;
|
|
8722
8984
|
}
|
|
8723
8985
|
const jsonStr = content.replace(/^export default\s*/, "").replace(/;?\s*$/, "");
|
|
8724
|
-
let
|
|
8986
|
+
let isStrictJson = true;
|
|
8987
|
+
let manifest = null;
|
|
8725
8988
|
try {
|
|
8726
8989
|
manifest = JSON.parse(jsonStr);
|
|
8727
|
-
} catch
|
|
8728
|
-
|
|
8990
|
+
} catch {
|
|
8991
|
+
isStrictJson = false;
|
|
8992
|
+
}
|
|
8993
|
+
if (!isStrictJson) {
|
|
8994
|
+
let rewritten = content;
|
|
8995
|
+
rewritten = clearJsArrays(rewritten);
|
|
8996
|
+
rewritten = clearBootstrapScriptContent(rewritten);
|
|
8997
|
+
await writeFile(path, rewritten);
|
|
8729
8998
|
continue;
|
|
8730
8999
|
}
|
|
8731
|
-
const deps = manifest
|
|
9000
|
+
const deps = manifest?.clientReferenceDeps;
|
|
8732
9001
|
if (deps) for (const entry of Object.values(deps)) entry.js = [];
|
|
8733
|
-
manifest.bootstrapScriptContent = "";
|
|
9002
|
+
if (manifest) manifest.bootstrapScriptContent = "";
|
|
8734
9003
|
await writeFile(path, `export default ${JSON.stringify(manifest, null, 2)};\n`);
|
|
8735
9004
|
}
|
|
8736
9005
|
}
|