@timber-js/app 0.2.0-alpha.149 → 0.2.0-alpha.150

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.
@@ -1 +1 @@
1
- {"version":3,"file":"fonts.d.ts","sourceRoot":"","sources":["../../src/plugins/fonts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,MAAM,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA2B1D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAE/B;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAsMtD"}
1
+ {"version":3,"file":"fonts.d.ts","sourceRoot":"","sources":["../../src/plugins/fonts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,MAAM,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA2B1D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAE/B;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAkLtD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.149",
3
+ "version": "0.2.0-alpha.150",
4
4
  "description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
package/src/index.ts CHANGED
@@ -325,14 +325,20 @@ export function timber(config?: TimberUserConfig): PluginOption[] {
325
325
  envPort: process.env.PORT,
326
326
  listen: (p) => holdingRef.listen(p),
327
327
  });
328
- resolvedDevPort = result.port;
329
- resolvedDevPortExplicit = result.explicit;
330
328
  if (!result.bound) {
331
329
  // Holding server failed to bind. Drop the reference so the
332
330
  // dev-server plugin doesn't try to close a server that was
333
- // never listening, and let Vite surface the conflict.
331
+ // never listening.
334
332
  ctx.holdingServer = null;
335
333
  }
334
+ if (result.bound || result.explicit) {
335
+ // When bound, use the probed port with strictPort so Vite
336
+ // doesn't re-bump. When unbound but explicit, still forward
337
+ // the port so Vite surfaces the conflict via strictPort.
338
+ // When unbound and implicit, skip — let Vite auto-bump.
339
+ resolvedDevPort = result.port;
340
+ resolvedDevPortExplicit = result.explicit;
341
+ }
336
342
  } else if (command === 'serve' && isPreview) {
337
343
  // Preview mode: resolve the starting port from config/env/
338
344
  // default but do NOT probe. Vite's preview server handles the
@@ -143,8 +143,11 @@ export function timberFonts(ctx: PluginContext): Plugin {
143
143
  * emitted as a real CSS asset and @vitejs/plugin-rsc's `collectCss`
144
144
  * walks the RSC module graph to find it.
145
145
  *
146
- * In dev mode we also lazily resolve Google `@font-face` descriptors
147
- * from the CDN here, since `buildStart` is a no-op in dev.
146
+ * Google `@font-face` descriptors are resolved lazily here (both
147
+ * dev and production) because `load` runs after `transform` has
148
+ * registered the font. `buildStart` runs before any transforms,
149
+ * so fonts aren't in the pipeline yet when it fires for the first
150
+ * environment.
148
151
  */
149
152
  async load(id: string) {
150
153
  if (id === RESOLVED_GOOGLE) return generateGoogleVirtualModule(pipeline.fonts());
@@ -153,31 +156,32 @@ export function timberFonts(ctx: PluginContext): Plugin {
153
156
  if (pipeline.cssIdMap.matches(id)) {
154
157
  const importer = pipeline.cssIdMap.importerFor(id);
155
158
  if (!importer) {
156
- // Hash is valid but no importer is registered — return an empty
157
- // CSS module rather than `null`. This can happen if Vite's
158
- // module graph invalidates the virtual module between the
159
- // transform that registered the hash and the load that asks
160
- // for it (e.g. after an HMR edit that removes every font from
161
- // the file). An empty stylesheet is a valid, safe no-op.
162
159
  return '';
163
160
  }
164
161
 
165
- if (ctx.dev) {
166
- for (const font of pipeline.googleFonts()) {
167
- if (font.importer !== importer) continue;
168
- if (pipeline.hasFaces(font.id)) continue;
169
- try {
162
+ for (const font of pipeline.googleFonts()) {
163
+ if (font.importer !== importer) continue;
164
+ if (pipeline.hasFaces(font.id)) continue;
165
+ try {
166
+ if (ctx.dev) {
170
167
  const faces = await resolveDevFontFaces(font);
171
168
  pipeline.attachFaces(font.id, faces);
172
- } catch (e) {
173
- // Fail gracefully — fonts fall back to system fonts. Don't
174
- // cache the failure so transient errors (network blips, rate
175
- // limits) are retried on the next request. See TIM-636.
176
- const msg = e instanceof Error ? e.message : String(e);
177
- console.warn(
178
- `[timber-fonts] Failed to resolve Google font "${font.family}": ${msg}. Will retry on next request.`
169
+ } else {
170
+ const cached = await downloadAndCacheFonts([font], ctx.root);
171
+ const byFamily = groupCachedFontsByFamily(cached);
172
+ const familyCached = byFamily.get(font.family.toLowerCase()) ?? [];
173
+ pipeline.attachCachedFiles(font.id, familyCached);
174
+ pipeline.attachFaces(
175
+ font.id,
176
+ generateProductionFontFaces(familyCached, font.display, fontUrlPrefix(resolvedBase))
179
177
  );
180
178
  }
179
+ } catch (e) {
180
+ if (!ctx.dev) throw e;
181
+ const msg = e instanceof Error ? e.message : String(e);
182
+ console.warn(
183
+ `[timber-fonts] Failed to resolve Google font "${font.family}": ${msg}. Will retry on next request.`
184
+ );
181
185
  }
182
186
  }
183
187
 
@@ -196,39 +200,15 @@ export function timberFonts(ctx: PluginContext): Plugin {
196
200
  },
197
201
 
198
202
  /**
199
- * Download and cache Google Fonts during production builds.
200
- *
201
- * In dev mode this is a no-op — fonts point to the Google CDN and are
202
- * resolved lazily inside `load()` when the side-effect module runs.
203
+ * No-op. Font downloading and face attachment happen lazily in `load()`
204
+ * because `buildStart` runs before `transform` — fonts aren't registered
205
+ * in the pipeline yet when this fires for the first environment.
203
206
  *
204
207
  * Local font hashing is handled at registration time by the pipeline
205
208
  * (when `hashOnRegister` is set) so hashed filenames are available
206
209
  * before the virtual CSS `load()` hook runs.
207
210
  */
208
- async buildStart() {
209
- if (ctx.dev) return;
210
-
211
- const googleFonts = [...pipeline.googleFonts()];
212
- if (googleFonts.length === 0) return;
213
-
214
- const cachedFonts = await downloadAndCacheFonts(googleFonts, ctx.root);
215
-
216
- // Build a family→CachedFont[] lookup, then attach the cached files
217
- // and resolved @font-face descriptors to each registered Google
218
- // font. Multiple fonts that share a family hold references to the
219
- // same `CachedFont` objects; the bundle emitter dedupes by
220
- // `hashedFilename` so each binary is written to disk exactly once.
221
- const cachedByFamily = groupCachedFontsByFamily(cachedFonts);
222
- const urlPrefix = fontUrlPrefix(resolvedBase);
223
- for (const font of googleFonts) {
224
- const familyCached = cachedByFamily.get(font.family.toLowerCase()) ?? [];
225
- pipeline.attachCachedFiles(font.id, familyCached);
226
- pipeline.attachFaces(
227
- font.id,
228
- generateProductionFontFaces(familyCached, font.display, urlPrefix)
229
- );
230
- }
231
- },
211
+ buildStart() {},
232
212
 
233
213
  /**
234
214
  * Scan source files for font function calls and extract static config.