@timber-js/app 0.2.0-alpha.139 → 0.2.0-alpha.140
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/LICENSE +8 -0
- package/dist/fonts/bundle.d.ts +5 -5
- package/dist/fonts/bundle.d.ts.map +1 -1
- package/dist/fonts/pipeline.d.ts +5 -0
- package/dist/fonts/pipeline.d.ts.map +1 -1
- package/dist/fonts/types.d.ts +2 -0
- package/dist/fonts/types.d.ts.map +1 -1
- package/dist/index.js +111 -33
- package/dist/index.js.map +1 -1
- package/dist/plugins/client-chunks.d.ts +8 -1
- package/dist/plugins/client-chunks.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/package.json +6 -7
- package/src/cli.ts +0 -0
- package/src/fonts/bundle.ts +12 -23
- package/src/fonts/local.ts +2 -2
- package/src/fonts/pipeline.ts +24 -1
- package/src/fonts/types.ts +2 -0
- package/src/index.ts +1 -1
- package/src/plugins/client-chunks.ts +69 -21
- package/src/plugins/fonts.ts +9 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
DONTFUCKINGUSE LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Daniel Saewitz
|
|
4
|
+
|
|
5
|
+
This software may not be used, copied, modified, merged, published,
|
|
6
|
+
distributed, sublicensed, or sold by anyone other than the copyright holder.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
package/dist/fonts/bundle.d.ts
CHANGED
|
@@ -37,12 +37,12 @@ export declare function groupCachedFontsByFamily(cachedFonts: readonly CachedFon
|
|
|
37
37
|
export declare function emitFontAssets(pipeline: FontPipeline, emitFile: EmitFile, warn: EmitWarning): void;
|
|
38
38
|
/**
|
|
39
39
|
* Populate `buildManifest.fonts` with `ManifestFontEntry[]` keyed by the
|
|
40
|
-
* importer
|
|
41
|
-
* `
|
|
40
|
+
* absolute importer path. This matches the runtime route manifest's
|
|
41
|
+
* `filePath` values (which are absolute) and is consistent with how
|
|
42
|
+
* `buildManifest.css` keys work after TIM-1014.
|
|
42
43
|
*
|
|
43
|
-
*
|
|
44
|
-
* `buildStart`. Local fonts use the source basename.
|
|
44
|
+
* All font binaries use content-hashed filenames for immutable caching.
|
|
45
45
|
*/
|
|
46
|
-
export declare function writeFontManifest(pipeline: FontPipeline, buildManifest: BuildManifest
|
|
46
|
+
export declare function writeFontManifest(pipeline: FontPipeline, buildManifest: BuildManifest): void;
|
|
47
47
|
export {};
|
|
48
48
|
//# sourceMappingURL=bundle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../src/fonts/bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAqB,aAAa,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../src/fonts/bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAqB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAGpF;;;;GAIG;AACH,UAAU,YAAY;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;CAC7B;AACD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,CAAC;AAChD,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;AAEzC,kEAAkE;AAClE,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,SAAS,UAAU,EAAE,GACjC,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAS3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,WAAW,GAChB,IAAI,CA4BN;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,GAAG,IAAI,CAgC5F"}
|
package/dist/fonts/pipeline.d.ts
CHANGED
|
@@ -49,6 +49,11 @@ export declare class FontPipeline {
|
|
|
49
49
|
* the map contents are mutable through the methods below.
|
|
50
50
|
*/
|
|
51
51
|
private readonly _entries;
|
|
52
|
+
/** When true, local font sources are content-hashed at registration time. */
|
|
53
|
+
private readonly _hashOnRegister;
|
|
54
|
+
constructor(opts?: {
|
|
55
|
+
hashOnRegister?: boolean;
|
|
56
|
+
});
|
|
52
57
|
/**
|
|
53
58
|
* Per-importer virtual CSS module id registry. Populated by the
|
|
54
59
|
* transform hook when it inlines a font function call, read by the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/fonts/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/fonts/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAEtD,qBAAa,YAAY;IACvB;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;IAEzD,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;gBAE9B,IAAI,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE;IAI/C;;;;;;;;;OASG;IACH,QAAQ,CAAC,QAAQ,sBAA6B;IAI9C,kCAAkC;IAClC,IAAI,IAAI,MAAM;IAId,2EAA2E;IAC3E,OAAO,IAAI,gBAAgB,CAAC,SAAS,CAAC;IAItC,uEAAuE;IACvE,KAAK,IAAI,gBAAgB,CAAC,aAAa,CAAC;IAIxC,2CAA2C;IAC1C,WAAW,IAAI,gBAAgB,CAAC,SAAS,CAAC;IAM3C,kCAAkC;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI3C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAKjC;;;;;OAKG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAOvC;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI;IAoBxC,oEAAoE;IACpE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,EAAE,GAAG,IAAI;IAW9D,8DAA8D;IAC9D,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI;IAW5D;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAShD,mEAAmE;IACnE,KAAK,IAAI,IAAI;IAOb;;;;;OAKG;IACF,iBAAiB,IAAI,gBAAgB,CAAC,UAAU,CAAC;IAYlD;;;;;;;OAOG;IACH,MAAM,IAAI,MAAM;IAQhB;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;CAQ3C;AAoCD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,IAAI,CAON"}
|
package/dist/fonts/types.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface LocalFontSrc {
|
|
|
21
21
|
path: string;
|
|
22
22
|
weight?: string;
|
|
23
23
|
style?: string;
|
|
24
|
+
/** Content-hashed basename for production builds (e.g. `MyFont-Bold-a1b2c3d4.woff2`). */
|
|
25
|
+
hashedBasename?: string;
|
|
24
26
|
}
|
|
25
27
|
/** Configuration passed to `localFont()`. */
|
|
26
28
|
export interface LocalFontConfig {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/fonts/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/fonts/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9B,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,EAAE,EAAE,MAAM,CAAC;IACX,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC7B,+CAA+C;IAC/C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oDAAoD;IACpD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;IAC9B,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,SAAU,SAAQ,aAAa;IAC9C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAE7B;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B"}
|
package/dist/index.js
CHANGED
|
@@ -3927,7 +3927,7 @@ function generateLocalFontFaces(family, sources, display, urlPrefix = "/_timber/
|
|
|
3927
3927
|
const format = inferFontFormat(entry.path);
|
|
3928
3928
|
return {
|
|
3929
3929
|
family,
|
|
3930
|
-
src: `url('${urlPrefix}/${entry.path.split("/").pop() ?? entry.path}') format('${format}')`,
|
|
3930
|
+
src: `url('${urlPrefix}/${entry.hashedBasename ?? entry.path.split("/").pop() ?? entry.path}') format('${format}')`,
|
|
3931
3931
|
weight: entry.weight,
|
|
3932
3932
|
style: entry.style,
|
|
3933
3933
|
display
|
|
@@ -4095,6 +4095,33 @@ function generateLocalVirtualModule() {
|
|
|
4095
4095
|
}
|
|
4096
4096
|
//#endregion
|
|
4097
4097
|
//#region src/fonts/pipeline.ts
|
|
4098
|
+
/**
|
|
4099
|
+
* FontPipeline — owns all per-font state for the timber-fonts plugin.
|
|
4100
|
+
*
|
|
4101
|
+
* Before TIM-829, font state was spread across three parallel mutable maps:
|
|
4102
|
+
*
|
|
4103
|
+
* - `registry: Map<string, ExtractedFont>`
|
|
4104
|
+
* - `googleFontFacesMap: Map<string, FontFaceDescriptor[]>`
|
|
4105
|
+
* - `cachedFonts: CachedFont[]`
|
|
4106
|
+
*
|
|
4107
|
+
* Every plugin hook had to remember which map(s) it owned and which it
|
|
4108
|
+
* read. The TIM-824 bug existed because `transform` could write to
|
|
4109
|
+
* `registry` multiple times for the same `(importer, family)` pair without
|
|
4110
|
+
* a corresponding cleanup of the entries that `buildStart` / `load` would
|
|
4111
|
+
* later use. The fix in TIM-824 added prune calls in `transform` for the
|
|
4112
|
+
* registry, but the parallel maps still had the same hazard latent in
|
|
4113
|
+
* them: any future code path that ran `buildStart`-equivalent logic on
|
|
4114
|
+
* HMR could re-introduce a stale-entry leak in a different map.
|
|
4115
|
+
*
|
|
4116
|
+
* TIM-829 collapses the three maps into a single `Map<string, FontEntry>`
|
|
4117
|
+
* keyed by font ID. Mutation goes through a small set of methods that
|
|
4118
|
+
* maintain the core invariant: **every piece of state belonging to a font
|
|
4119
|
+
* lives on its `FontEntry`, and `pruneFor` removes the entry along with
|
|
4120
|
+
* everything attached to it in one call.** No code path inside the plugin
|
|
4121
|
+
* touches the underlying map directly.
|
|
4122
|
+
*
|
|
4123
|
+
* Design doc: 24-fonts.md
|
|
4124
|
+
*/
|
|
4098
4125
|
var FontPipeline = class {
|
|
4099
4126
|
/**
|
|
4100
4127
|
* The single per-font store. Keyed by `FontEntry.id` (which is itself
|
|
@@ -4105,6 +4132,11 @@ var FontPipeline = class {
|
|
|
4105
4132
|
* the map contents are mutable through the methods below.
|
|
4106
4133
|
*/
|
|
4107
4134
|
_entries = /* @__PURE__ */ new Map();
|
|
4135
|
+
/** When true, local font sources are content-hashed at registration time. */
|
|
4136
|
+
_hashOnRegister;
|
|
4137
|
+
constructor(opts) {
|
|
4138
|
+
this._hashOnRegister = opts?.hashOnRegister ?? false;
|
|
4139
|
+
}
|
|
4108
4140
|
/**
|
|
4109
4141
|
* Per-importer virtual CSS module id registry. Populated by the
|
|
4110
4142
|
* transform hook when it inlines a font function call, read by the
|
|
@@ -4170,7 +4202,16 @@ var FontPipeline = class {
|
|
|
4170
4202
|
* dropped (TIM-824).
|
|
4171
4203
|
*/
|
|
4172
4204
|
register(extracted) {
|
|
4173
|
-
|
|
4205
|
+
const entry = { ...extracted };
|
|
4206
|
+
if (this._hashOnRegister && entry.provider === "local" && entry.localSources) for (const src of entry.localSources) {
|
|
4207
|
+
const absolutePath = normalize(resolve(src.path));
|
|
4208
|
+
if (!existsSync(absolutePath)) continue;
|
|
4209
|
+
const hash = contentHash(readFileSync(absolutePath));
|
|
4210
|
+
const basename = src.path.split("/").pop() ?? src.path;
|
|
4211
|
+
const ext = extname(basename);
|
|
4212
|
+
src.hashedBasename = `${ext ? basename.slice(0, -ext.length) : basename}-${hash}${ext}`;
|
|
4213
|
+
}
|
|
4214
|
+
this._entries.set(entry.id, entry);
|
|
4174
4215
|
}
|
|
4175
4216
|
/** Attach pre-resolved `@font-face` descriptors to a font entry. */
|
|
4176
4217
|
attachFaces(fontId, faces) {
|
|
@@ -5547,11 +5588,11 @@ function emitFontAssets(pipeline, emitFile, warn) {
|
|
|
5547
5588
|
warn(`Local font file not found: ${absolutePath}`);
|
|
5548
5589
|
continue;
|
|
5549
5590
|
}
|
|
5550
|
-
const
|
|
5591
|
+
const filename = src.hashedBasename ?? src.path.split("/").pop() ?? src.path;
|
|
5551
5592
|
const data = readFileSync(absolutePath);
|
|
5552
5593
|
emitFile({
|
|
5553
5594
|
type: "asset",
|
|
5554
|
-
fileName: `_timber/fonts/${
|
|
5595
|
+
fileName: `_timber/fonts/${filename}`,
|
|
5555
5596
|
source: data
|
|
5556
5597
|
});
|
|
5557
5598
|
}
|
|
@@ -5559,18 +5600,18 @@ function emitFontAssets(pipeline, emitFile, warn) {
|
|
|
5559
5600
|
}
|
|
5560
5601
|
/**
|
|
5561
5602
|
* Populate `buildManifest.fonts` with `ManifestFontEntry[]` keyed by the
|
|
5562
|
-
* importer
|
|
5563
|
-
* `
|
|
5603
|
+
* absolute importer path. This matches the runtime route manifest's
|
|
5604
|
+
* `filePath` values (which are absolute) and is consistent with how
|
|
5605
|
+
* `buildManifest.css` keys work after TIM-1014.
|
|
5564
5606
|
*
|
|
5565
|
-
*
|
|
5566
|
-
* `buildStart`. Local fonts use the source basename.
|
|
5607
|
+
* All font binaries use content-hashed filenames for immutable caching.
|
|
5567
5608
|
*/
|
|
5568
|
-
function writeFontManifest(pipeline, buildManifest
|
|
5609
|
+
function writeFontManifest(pipeline, buildManifest) {
|
|
5569
5610
|
const fontsByImporter = /* @__PURE__ */ new Map();
|
|
5570
5611
|
for (const entry of pipeline.entries()) {
|
|
5571
5612
|
const manifestEntries = fontsByImporter.get(entry.importer) ?? [];
|
|
5572
5613
|
if (entry.provider === "local" && entry.localSources) for (const src of entry.localSources) {
|
|
5573
|
-
const filename = src.path.split("/").pop() ?? src.path;
|
|
5614
|
+
const filename = src.hashedBasename ?? src.path.split("/").pop() ?? src.path;
|
|
5574
5615
|
const format = inferFontFormat(src.path);
|
|
5575
5616
|
manifestEntries.push({
|
|
5576
5617
|
href: `/_timber/fonts/${filename}`,
|
|
@@ -5585,10 +5626,7 @@ function writeFontManifest(pipeline, buildManifest, rootDir) {
|
|
|
5585
5626
|
});
|
|
5586
5627
|
fontsByImporter.set(entry.importer, manifestEntries);
|
|
5587
5628
|
}
|
|
5588
|
-
for (const [importer, entries] of fontsByImporter)
|
|
5589
|
-
const relativePath = importer.startsWith(rootDir) ? importer.slice(rootDir.length + 1) : importer;
|
|
5590
|
-
buildManifest.fonts[relativePath] = entries;
|
|
5591
|
-
}
|
|
5629
|
+
for (const [importer, entries] of fontsByImporter) buildManifest.fonts[importer] = entries;
|
|
5592
5630
|
}
|
|
5593
5631
|
//#endregion
|
|
5594
5632
|
//#region src/plugins/fonts.ts
|
|
@@ -5596,7 +5634,7 @@ function writeFontManifest(pipeline, buildManifest, rootDir) {
|
|
|
5596
5634
|
* Create the timber-fonts Vite plugin.
|
|
5597
5635
|
*/
|
|
5598
5636
|
function timberFonts(ctx) {
|
|
5599
|
-
const pipeline = new FontPipeline();
|
|
5637
|
+
const pipeline = new FontPipeline({ hashOnRegister: !ctx.dev });
|
|
5600
5638
|
return {
|
|
5601
5639
|
name: "timber-fonts",
|
|
5602
5640
|
/**
|
|
@@ -5690,8 +5728,10 @@ function timberFonts(ctx) {
|
|
|
5690
5728
|
*
|
|
5691
5729
|
* In dev mode this is a no-op — fonts point to the Google CDN and are
|
|
5692
5730
|
* resolved lazily inside `load()` when the side-effect module runs.
|
|
5693
|
-
*
|
|
5694
|
-
*
|
|
5731
|
+
*
|
|
5732
|
+
* Local font hashing is handled at registration time by the pipeline
|
|
5733
|
+
* (when `hashOnRegister` is set) so hashed filenames are available
|
|
5734
|
+
* before the virtual CSS `load()` hook runs.
|
|
5695
5735
|
*/
|
|
5696
5736
|
async buildStart() {
|
|
5697
5737
|
if (ctx.dev) return;
|
|
@@ -5716,15 +5756,14 @@ function timberFonts(ctx) {
|
|
|
5716
5756
|
/**
|
|
5717
5757
|
* Emit font binaries and build-manifest entries.
|
|
5718
5758
|
*
|
|
5719
|
-
* Google
|
|
5720
|
-
*
|
|
5721
|
-
*
|
|
5722
|
-
* binaries here.
|
|
5759
|
+
* All font binaries (Google and local) use content-hashed filenames
|
|
5760
|
+
* for immutable caching. Font CSS is handled by Vite's CSS pipeline
|
|
5761
|
+
* via virtual modules — we only emit binaries here.
|
|
5723
5762
|
*/
|
|
5724
5763
|
generateBundle() {
|
|
5725
5764
|
emitFontAssets(pipeline, (asset) => this.emitFile(asset), (msg) => this.warn(msg));
|
|
5726
5765
|
if (!ctx.buildManifest) return;
|
|
5727
|
-
writeFontManifest(pipeline, ctx.buildManifest
|
|
5766
|
+
writeFontManifest(pipeline, ctx.buildManifest);
|
|
5728
5767
|
}
|
|
5729
5768
|
};
|
|
5730
5769
|
}
|
|
@@ -6445,6 +6484,36 @@ function timberChunks() {
|
|
|
6445
6484
|
* See design/27-chunking-strategy.md, TIM-440, TIM-499.
|
|
6446
6485
|
*/
|
|
6447
6486
|
/**
|
|
6487
|
+
* Check whether a root-relative path lives under the app directory,
|
|
6488
|
+
* and if so return the first non-group route segment.
|
|
6489
|
+
*
|
|
6490
|
+
* `appPrefix` is the root-relative app directory with a trailing slash
|
|
6491
|
+
* (e.g. "app/" or "src/app/"), computed once from the resolved appDir
|
|
6492
|
+
* and root. Matching uses `startsWith` — no ambiguity.
|
|
6493
|
+
*
|
|
6494
|
+
* Returns:
|
|
6495
|
+
* { inApp: false } — path is not under app/
|
|
6496
|
+
* { inApp: true, segment: null } — root of app/ (e.g. app/layout.tsx)
|
|
6497
|
+
* { inApp: true, segment: "dashboard" } — nested route boundary
|
|
6498
|
+
*/
|
|
6499
|
+
function findAppBoundary(path, appPrefix) {
|
|
6500
|
+
const relPath = path.replace(/\\/g, "/");
|
|
6501
|
+
if (!relPath.startsWith(appPrefix)) return { inApp: false };
|
|
6502
|
+
const segments = relPath.slice(appPrefix.length).split("/");
|
|
6503
|
+
for (const seg of segments) {
|
|
6504
|
+
if (seg.includes(".")) break;
|
|
6505
|
+
if (seg.startsWith("(") && seg.endsWith(")")) continue;
|
|
6506
|
+
return {
|
|
6507
|
+
inApp: true,
|
|
6508
|
+
segment: seg
|
|
6509
|
+
};
|
|
6510
|
+
}
|
|
6511
|
+
return {
|
|
6512
|
+
inApp: true,
|
|
6513
|
+
segment: null
|
|
6514
|
+
};
|
|
6515
|
+
}
|
|
6516
|
+
/**
|
|
6448
6517
|
* Derive a chunk group name for a client reference module.
|
|
6449
6518
|
*
|
|
6450
6519
|
* Groups by the first non-group route segment under appDir so that all
|
|
@@ -6455,22 +6524,31 @@ function timberChunks() {
|
|
|
6455
6524
|
* - `facade:app/layout.tsx` (root layout) → `"client-shared"`
|
|
6456
6525
|
* - `shared:...` (shared across chunks) → `"client-shared"`
|
|
6457
6526
|
*
|
|
6527
|
+
* When a 'use client' module lives outside the app directory (e.g. in
|
|
6528
|
+
* src/posts/ or src/components/), the function falls back to deriving
|
|
6529
|
+
* the layout boundary from the serverChunk's facade path. This prevents
|
|
6530
|
+
* lazily-loaded client dependencies (e.g. via import.meta.glob) from
|
|
6531
|
+
* being hoisted into the shared chunk when they're only used by a
|
|
6532
|
+
* specific route.
|
|
6533
|
+
*
|
|
6458
6534
|
* This balances route-scoped code splitting with HTTP request count:
|
|
6459
6535
|
* fewer chunks than per-serverChunk, but still avoids downloading every
|
|
6460
6536
|
* client component on every page.
|
|
6461
6537
|
*/
|
|
6462
|
-
function clientChunkGroup(meta, appDir) {
|
|
6538
|
+
function clientChunkGroup(meta, appDir, root) {
|
|
6463
6539
|
const { normalizedId, serverChunk } = meta;
|
|
6464
6540
|
if (serverChunk.startsWith("shared:")) return "client-shared";
|
|
6465
|
-
const
|
|
6466
|
-
const
|
|
6467
|
-
|
|
6468
|
-
if (
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
if (
|
|
6473
|
-
return
|
|
6541
|
+
const appPrefix = relative(root, appDir).replace(/\\/g, "/") + "/";
|
|
6542
|
+
const result = findAppBoundary(normalizedId, appPrefix);
|
|
6543
|
+
if (result.inApp && result.segment) return `client-${result.segment}`;
|
|
6544
|
+
if (result.inApp) return "client-shared";
|
|
6545
|
+
if (serverChunk.startsWith("facade:")) {
|
|
6546
|
+
const facadePath = serverChunk.slice(7).replace(/\\/g, "/");
|
|
6547
|
+
const facadeResult = findAppBoundary(facadePath, appPrefix);
|
|
6548
|
+
if (facadeResult.inApp && facadeResult.segment) return `client-${facadeResult.segment}`;
|
|
6549
|
+
if (facadeResult.inApp) return "client-shared";
|
|
6550
|
+
const facadeName = facadePath.replace(/\.[^.]+$/, "").replace(/\//g, "-");
|
|
6551
|
+
if (facadeName) return `client-${facadeName}`;
|
|
6474
6552
|
}
|
|
6475
6553
|
return "client-shared";
|
|
6476
6554
|
}
|
|
@@ -7454,7 +7532,7 @@ function createRscOptions(ctx, encryptionKeyExpr) {
|
|
|
7454
7532
|
ssr: "virtual:timber-ssr-entry",
|
|
7455
7533
|
client: "virtual:timber-browser-entry"
|
|
7456
7534
|
},
|
|
7457
|
-
clientChunks: (meta) => clientChunkGroup(meta, ctx.appDir)
|
|
7535
|
+
clientChunks: (meta) => clientChunkGroup(meta, ctx.appDir, ctx.root)
|
|
7458
7536
|
};
|
|
7459
7537
|
Object.defineProperty(options, "enableActionEncryption", {
|
|
7460
7538
|
get() {
|