@timber-js/app 0.2.0-alpha.8 → 0.2.0-alpha.9

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.
@@ -56,6 +56,13 @@ export declare function parseGoogleFontImports(source: string): string[];
56
56
  * e.g. { Inter: 'Inter', JetBrains_Mono: 'JetBrains Mono' }
57
57
  */
58
58
  export declare function parseGoogleFontFamilies(source: string): Map<string, string>;
59
+ /**
60
+ * Generate CSS for a single extracted font.
61
+ *
62
+ * Includes @font-face rules (for local fonts), fallback @font-face,
63
+ * and the scoped class rule.
64
+ */
65
+ export declare function generateFontCss(font: ExtractedFont): string;
59
66
  /**
60
67
  * Generate the CSS output for all extracted fonts.
61
68
  *
@@ -1 +1 @@
1
- {"version":3,"file":"fonts.d.ts","sourceRoot":"","sources":["../../src/plugins/fonts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAGlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAqBxE;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AA4CtD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAE7E;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAE5F;AAUD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAkB/D;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAqB3E;AAwED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAwBjE;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKtE;AAqED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAuUtD"}
1
+ {"version":3,"file":"fonts.d.ts","sourceRoot":"","sources":["../../src/plugins/fonts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAGlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AA4BxE;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AA4CtD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAE7E;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAE5F;AAUD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAkB/D;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAqB3E;AAwED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAmB3D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAMjE;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKtE;AAqED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAkUtD"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AAgFA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAE/F;AAiaD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;8BArQ3C,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AAuQhD,wBAAiE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AAkFA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAE/F;AAiaD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;8BArQ3C,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AAuQhD,wBAAiE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.8",
3
+ "version": "0.2.0-alpha.9",
4
4
  "description": "Vite-native React framework for Cloudflare Workers — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
package/src/index.ts CHANGED
@@ -223,8 +223,7 @@ export interface PluginContext {
223
223
  buildManifest: BuildManifest | null;
224
224
  /** Startup timer for profiling cold start phases (active in dev, no-op in prod) */
225
225
  timer: StartupTimer;
226
- /** URL path to font CSS file, set by timber-fonts when fonts are registered */
227
- fontCssUrl?: string;
226
+
228
227
  }
229
228
 
230
229
  /**
@@ -112,7 +112,7 @@ function generateConfigModule(ctx: PluginContext): string {
112
112
  topLoader: ctx.config.topLoader,
113
113
  responseCache: ctx.config.responseCache,
114
114
  debug: ctx.config.debug ?? false,
115
- fontCssUrl: ctx.fontCssUrl ?? null,
115
+
116
116
  };
117
117
 
118
118
  return [
@@ -36,8 +36,15 @@ const VIRTUAL_LOCAL = '@timber/fonts/local';
36
36
  const RESOLVED_GOOGLE = '\0@timber/fonts/google';
37
37
  const RESOLVED_LOCAL = '\0@timber/fonts/local';
38
38
 
39
- /** URL path where font CSS is served (dev middleware and prod asset). */
40
- const FONT_CSS_PATH = '/_timber/fonts/fonts.css';
39
+ /**
40
+ * Virtual module that exports the combined font CSS string.
41
+ *
42
+ * The RSC entry imports this module and inlines it as a <style> tag.
43
+ * Unlike a config-based approach, this module is loaded lazily (on first
44
+ * request), so it always has up-to-date font data from the registry.
45
+ */
46
+ const VIRTUAL_FONT_CSS = 'virtual:timber-font-css';
47
+ const RESOLVED_FONT_CSS = '\0virtual:timber-font-css';
41
48
 
42
49
  /**
43
50
  * Registry of fonts extracted during transform.
@@ -247,6 +254,33 @@ function generateLocalVirtualModule(): string {
247
254
  ].join('\n');
248
255
  }
249
256
 
257
+ /**
258
+ * Generate CSS for a single extracted font.
259
+ *
260
+ * Includes @font-face rules (for local fonts), fallback @font-face,
261
+ * and the scoped class rule.
262
+ */
263
+ export function generateFontCss(font: ExtractedFont): string {
264
+ const cssParts: string[] = [];
265
+
266
+ if (font.provider === 'local' && font.localSources) {
267
+ const faces = generateLocalFontFaces(font.family, font.localSources, font.display);
268
+ const faceCss = generateFontFaces(faces);
269
+ if (faceCss) cssParts.push(faceCss);
270
+ }
271
+
272
+ const fallbackCss = generateFallbackCss(font.family);
273
+ if (fallbackCss) cssParts.push(fallbackCss);
274
+
275
+ if (font.variable) {
276
+ cssParts.push(generateVariableClass(font.className, font.variable, font.fontFamily));
277
+ } else {
278
+ cssParts.push(generateFontFamilyClass(font.className, font.fontFamily));
279
+ }
280
+
281
+ return cssParts.join('\n\n');
282
+ }
283
+
250
284
  /**
251
285
  * Generate the CSS output for all extracted fonts.
252
286
  *
@@ -255,27 +289,9 @@ function generateLocalVirtualModule(): string {
255
289
  */
256
290
  export function generateAllFontCss(registry: FontRegistry): string {
257
291
  const cssParts: string[] = [];
258
-
259
292
  for (const font of registry.values()) {
260
- // Generate @font-face rules for local fonts
261
- if (font.provider === 'local' && font.localSources) {
262
- const faces = generateLocalFontFaces(font.family, font.localSources, font.display);
263
- const faceCss = generateFontFaces(faces);
264
- if (faceCss) cssParts.push(faceCss);
265
- }
266
-
267
- // Generate fallback @font-face if metrics are available
268
- const fallbackCss = generateFallbackCss(font.family);
269
- if (fallbackCss) cssParts.push(fallbackCss);
270
-
271
- // Generate scoped class
272
- if (font.variable) {
273
- cssParts.push(generateVariableClass(font.className, font.variable, font.fontFamily));
274
- } else {
275
- cssParts.push(generateFontFamilyClass(font.className, font.fontFamily));
276
- }
293
+ cssParts.push(generateFontCss(font));
277
294
  }
278
-
279
295
  return cssParts.join('\n\n');
280
296
  }
281
297
 
@@ -372,20 +388,32 @@ export function timberFonts(ctx: PluginContext): Plugin {
372
388
  name: 'timber-fonts',
373
389
 
374
390
  /**
375
- * Resolve `@timber/fonts/google` and `@timber/fonts/local` to virtual modules.
391
+ * Resolve `@timber/fonts/google`, `@timber/fonts/local`,
392
+ * and `virtual:timber-font-css` virtual modules.
376
393
  */
377
394
  resolveId(id: string) {
378
395
  if (id === VIRTUAL_GOOGLE) return RESOLVED_GOOGLE;
379
396
  if (id === VIRTUAL_LOCAL) return RESOLVED_LOCAL;
397
+ if (id === VIRTUAL_FONT_CSS) return RESOLVED_FONT_CSS;
380
398
  return null;
381
399
  },
382
400
 
383
401
  /**
384
402
  * Return generated source for font virtual modules.
403
+ *
404
+ * `virtual:timber-font-css` exports the combined @font-face CSS
405
+ * as a string. The RSC entry imports it and inlines a <style> tag.
406
+ * Because this is loaded lazily (on first request), the font
407
+ * registry is always populated by the time it's needed.
385
408
  */
386
409
  load(id: string) {
387
410
  if (id === RESOLVED_GOOGLE) return generateGoogleVirtualModule(registry);
388
411
  if (id === RESOLVED_LOCAL) return generateLocalVirtualModule();
412
+
413
+ if (id === RESOLVED_FONT_CSS) {
414
+ const css = generateAllFontCss(registry);
415
+ return `export default ${JSON.stringify(css)};`;
416
+ }
389
417
  return null;
390
418
  },
391
419
 
@@ -412,14 +440,8 @@ export function timberFonts(ctx: PluginContext): Plugin {
412
440
  return;
413
441
  }
414
442
 
415
- // Serve generated font CSS
416
- if (requestedFilename === 'fonts.css') {
417
- const css = generateAllFontCss(registry);
418
- res.setHeader('Content-Type', 'text/css');
419
- res.setHeader('Cache-Control', 'no-cache');
420
- res.end(css);
421
- return;
422
- }
443
+ // Font CSS is now injected via Vite's CSS pipeline (virtual:timber-font-css modules).
444
+ // This middleware only serves font binary files (woff2, etc.).
423
445
 
424
446
  // Find the matching font file in the registry
425
447
  for (const font of registry.values()) {
@@ -578,10 +600,6 @@ export function timberFonts(ctx: PluginContext): Plugin {
578
600
  }
579
601
 
580
602
  if (transformedCode !== code) {
581
- // Mark that fonts are in use so the RSC entry injects a <link> tag.
582
- if (registry.size > 0 && !ctx.fontCssUrl) {
583
- ctx.fontCssUrl = FONT_CSS_PATH;
584
- }
585
603
  return { code: transformedCode, map: null };
586
604
  }
587
605
 
@@ -627,15 +645,8 @@ export function timberFonts(ctx: PluginContext): Plugin {
627
645
  }
628
646
  }
629
647
 
630
- // Emit the combined font CSS as an asset
631
- const fontCss = generateAllFontCss(registry);
632
- if (fontCss) {
633
- this.emitFile({
634
- type: 'asset',
635
- fileName: '_timber/fonts/fonts.css',
636
- source: fontCss,
637
- });
638
- }
648
+ // Font CSS is emitted by Vite's CSS pipeline via virtual:timber-font-css modules.
649
+ // We only need to emit font binary files and update the build manifest here.
639
650
 
640
651
  if (!ctx.buildManifest) return;
641
652
 
@@ -23,6 +23,8 @@ import config from 'virtual:timber-config';
23
23
  import buildManifest from 'virtual:timber-build-manifest';
24
24
  // @ts-expect-error — virtual module provided by timber-entries plugin
25
25
  import loadUserInstrumentation from 'virtual:timber-instrumentation';
26
+ // @ts-expect-error — virtual module provided by timber-fonts plugin
27
+ import fontCss from 'virtual:timber-font-css';
26
28
 
27
29
  import type { FormRerender } from '#/server/action-handler.js';
28
30
  import { handleActionRequest, isActionRequest } from '#/server/action-handler.js';
@@ -386,11 +388,11 @@ async function renderRoute(
386
388
  headHtml += buildCssLinkTags(cssUrls);
387
389
  }
388
390
 
389
- // Inject font CSS stylesheetpure CSS, no JS needed.
390
- // The URL is set by the timber-fonts plugin when fonts are registered.
391
- const fontCssUrl = (config as { fontCssUrl?: string | null }).fontCssUrl;
392
- if (fontCssUrl) {
393
- headHtml += `<link rel="stylesheet" href="${fontCssUrl}">`;
391
+ // Inline font CSS as a <style> tag @font-face rules and scoped classes.
392
+ // The virtual:timber-font-css module is loaded lazily, so the font registry
393
+ // is always populated by the time we get here (no timing issues).
394
+ if (fontCss) {
395
+ headHtml += `<style data-timber-fonts>${fontCss}</style>`;
394
396
  }
395
397
 
396
398
  const fontEntries = collectRouteFonts(segments, typedManifest);