@timber-js/app 0.2.0-alpha.147 → 0.2.0-alpha.149
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/_chunks/{actions-OIZ8siQu.js → actions-a0RczDnB.js} +8 -34
- package/dist/_chunks/{actions-OIZ8siQu.js.map → actions-a0RczDnB.js.map} +1 -1
- package/dist/_chunks/cache-api-Co-yI-gP.js +424 -0
- package/dist/_chunks/cache-api-Co-yI-gP.js.map +1 -0
- package/dist/_chunks/{metadata-routes-BjepjefT.js → canonicalize-P41GR6tY.js} +65 -2
- package/dist/_chunks/canonicalize-P41GR6tY.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-CQaxupHv.js → cli-schema-sync-DJDdyEae.js} +111 -260
- package/dist/_chunks/cli-schema-sync-DJDdyEae.js.map +1 -0
- package/dist/_chunks/cli-schema-sync-D_IhHEUW.js +2 -0
- package/dist/_chunks/{define-DMVhsipD.js → define-Bssfp6ot.js} +3 -33
- package/dist/_chunks/define-Bssfp6ot.js.map +1 -0
- package/dist/_chunks/{define-C3RvHQPK.js → define-CFmvb4Bt.js} +15 -7
- package/dist/_chunks/define-CFmvb4Bt.js.map +1 -0
- package/dist/_chunks/{href-validation-DyZIzfMJ.js → href-validation-FiA_rToW.js} +2 -1
- package/dist/_chunks/{href-validation-DyZIzfMJ.js.map → href-validation-FiA_rToW.js.map} +1 -1
- package/dist/_chunks/{tracing-M_zKubhk.js → logger-CM0clugc.js} +394 -107
- package/dist/_chunks/logger-CM0clugc.js.map +1 -0
- package/dist/_chunks/navigation-root-bUDZLw74.js.map +1 -1
- package/dist/_chunks/plugin-context-DurqFBuY.js +2 -0
- package/dist/_chunks/plugin-context-GfDWsdd2.js +268 -0
- package/dist/_chunks/plugin-context-GfDWsdd2.js.map +1 -0
- package/dist/_chunks/{resolve-schema-Dj8tQloi.js → resolve-schema-3iUvBV5T.js} +3 -30
- package/dist/_chunks/resolve-schema-3iUvBV5T.js.map +1 -0
- package/dist/_chunks/{schema-bridge-DK_89h8O.js → schema-bridge-BY3QLBL7.js} +35 -2
- package/dist/_chunks/schema-bridge-BY3QLBL7.js.map +1 -0
- package/dist/_chunks/tree-match-D2l830j2.js +102 -0
- package/dist/_chunks/tree-match-D2l830j2.js.map +1 -0
- package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +1 -1
- package/dist/_chunks/{walkers-Ck4Y45XN.js → walkers-DvWDj453.js} +107 -82
- package/dist/_chunks/walkers-DvWDj453.js.map +1 -0
- package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +36 -14
- package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
- package/dist/adapters/compress-module.d.ts +5 -4
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/nitro.d.ts +1 -3
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +14 -134
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/cache-api.d.ts.map +1 -1
- package/dist/cache/index.d.ts +3 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +20 -235
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/invalidation-epoch.d.ts +43 -0
- package/dist/cache/invalidation-epoch.d.ts.map +1 -0
- package/dist/cache/redis-handler.d.ts +13 -0
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts +17 -9
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cli-schema-sync.d.ts +21 -1
- package/dist/cli-schema-sync.d.ts.map +1 -1
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -31
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-entry/hmr.d.ts.map +1 -1
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/history.d.ts +10 -17
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +30 -33
- package/dist/client/internal.js.map +1 -1
- package/dist/client/navigation-api.d.ts +9 -2
- package/dist/client/navigation-api.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +0 -11
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +0 -5
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/use-search-params.d.ts +10 -5
- package/dist/client/use-search-params.d.ts.map +1 -1
- package/dist/codec.js +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +2 -4
- package/dist/cookies/index.js.map +1 -1
- package/dist/dev-tools/dev-page-shell.d.ts +1 -0
- package/dist/dev-tools/dev-page-shell.d.ts.map +1 -1
- package/dist/dev-tools/hydration-overlay.d.ts +11 -0
- package/dist/dev-tools/hydration-overlay.d.ts.map +1 -0
- package/dist/dev-tools/index.d.ts +1 -2
- package/dist/dev-tools/index.d.ts.map +1 -1
- package/dist/fonts/ast-walk.d.ts +68 -0
- package/dist/fonts/ast-walk.d.ts.map +1 -0
- package/dist/fonts/ast.d.ts +23 -11
- package/dist/fonts/ast.d.ts.map +1 -1
- package/dist/fonts/bundle.d.ts +3 -1
- package/dist/fonts/bundle.d.ts.map +1 -1
- package/dist/fonts/dev-middleware.d.ts +1 -1
- package/dist/fonts/dev-middleware.d.ts.map +1 -1
- package/dist/fonts/fallbacks.d.ts +0 -7
- package/dist/fonts/fallbacks.d.ts.map +1 -1
- package/dist/fonts/google.d.ts +4 -2
- package/dist/fonts/google.d.ts.map +1 -1
- package/dist/fonts/local.d.ts +3 -3
- package/dist/fonts/local.d.ts.map +1 -1
- package/dist/fonts/paths.d.ts +43 -0
- package/dist/fonts/paths.d.ts.map +1 -0
- package/dist/fonts/pipeline.d.ts +7 -21
- 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 +1 -18
- package/dist/fonts/virtual-modules.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +610 -406
- package/dist/index.js.map +1 -1
- package/dist/params/index.js +1 -1
- package/dist/params/resolve-schema.d.ts.map +1 -1
- package/dist/plugin-context.d.ts +28 -5
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/cache.d.ts +13 -5
- package/dist/plugins/cache.d.ts.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +1 -2
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/file-cache.d.ts +8 -0
- package/dist/routing/file-cache.d.ts.map +1 -0
- package/dist/routing/index.js +2 -2
- package/dist/routing/interception.d.ts +0 -5
- package/dist/routing/interception.d.ts.map +1 -1
- package/dist/routing/manifest-codegen.d.ts +32 -0
- package/dist/routing/manifest-codegen.d.ts.map +1 -0
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/schema-bridge.d.ts +15 -0
- package/dist/schema-bridge.d.ts.map +1 -1
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.js +7 -3
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/wrappers.d.ts.map +1 -1
- package/dist/segment-params/define.d.ts.map +1 -1
- package/dist/segment-params/index.js +1 -1
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/actions.d.ts +0 -14
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +0 -29
- package/dist/server/build-manifest.d.ts.map +1 -1
- package/dist/server/canonicalize.d.ts +10 -0
- package/dist/server/canonicalize.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts +8 -0
- package/dist/server/html-injector-core.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/internal.d.ts +1 -5
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +12 -171
- package/dist/server/internal.js.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +0 -11
- package/dist/server/node-stream-transforms.d.ts.map +1 -1
- package/dist/server/render-utils.d.ts +25 -0
- package/dist/server/render-utils.d.ts.map +1 -0
- package/dist/server/route-element-builder.d.ts +11 -25
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +2 -18
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/shared/href-validation.d.ts.map +1 -1
- package/dist/shims/image.d.ts +15 -15
- package/package.json +7 -8
- package/src/adapters/cloudflare-kv-cache.ts +56 -19
- package/src/adapters/compress-module.ts +27 -118
- package/src/adapters/nitro.ts +3 -41
- package/src/adapters/raw-imports.d.ts +5 -0
- package/src/cache/cache-api.ts +9 -1
- package/src/cache/index.ts +5 -2
- package/src/cache/invalidation-epoch.ts +93 -0
- package/src/cache/redis-handler.ts +33 -2
- package/src/cache/timber-cache.ts +109 -53
- package/src/cli-schema-sync.ts +123 -30
- package/src/cli.ts +33 -37
- package/src/client/browser-entry/hmr.ts +0 -11
- package/src/client/browser-entry/hydrate.ts +39 -5
- package/src/client/browser-entry/post-hydration.ts +6 -15
- package/src/client/browser-entry/router-init.ts +98 -3
- package/src/client/history.ts +10 -24
- package/src/client/navigation-api.ts +14 -9
- package/src/client/navigation-context.ts +1 -21
- package/src/client/navigation-root.tsx +0 -8
- package/src/client/router.ts +18 -8
- package/src/client/rsc-fetch.ts +7 -2
- package/src/client/use-search-params.ts +10 -5
- package/src/config-validation.ts +33 -25
- package/src/cookies/define-cookie.ts +2 -18
- package/src/dev-tools/dev-page-shell.ts +6 -2
- package/src/dev-tools/hydration-overlay.ts +158 -0
- package/src/dev-tools/index.ts +3 -16
- package/src/fonts/ast-walk.ts +235 -0
- package/src/fonts/ast.ts +91 -249
- package/src/fonts/bundle.ts +16 -9
- package/src/fonts/dev-middleware.ts +16 -5
- package/src/fonts/fallbacks.ts +0 -23
- package/src/fonts/google.ts +7 -3
- package/src/fonts/local.ts +7 -6
- package/src/fonts/paths.ts +58 -0
- package/src/fonts/pipeline.ts +27 -48
- package/src/fonts/transform.ts +27 -0
- package/src/fonts/virtual-modules.ts +1 -57
- package/src/index.ts +5 -11
- package/src/params/resolve-schema.ts +2 -34
- package/src/plugin-context.ts +95 -17
- package/src/plugins/cache.ts +64 -17
- package/src/plugins/dev-server.ts +61 -9
- package/src/plugins/fonts.ts +21 -6
- package/src/plugins/routing.ts +5 -348
- package/src/plugins/server-bundle.ts +1 -0
- package/src/routing/codegen.ts +3 -2
- package/src/routing/convention-lint.ts +4 -3
- package/src/routing/file-cache.ts +23 -0
- package/src/routing/interception.ts +2 -21
- package/src/routing/manifest-codegen.ts +290 -0
- package/src/routing/scanner.ts +5 -17
- package/src/routing/schema-validation.ts +2 -2
- package/src/schema-bridge.ts +63 -2
- package/src/search-params/define.ts +34 -2
- package/src/search-params/wrappers.ts +21 -3
- package/src/segment-params/define.ts +2 -62
- package/src/server/access-gate.tsx +8 -0
- package/src/server/actions.ts +8 -31
- package/src/server/build-manifest.ts +0 -56
- package/src/server/canonicalize.ts +6 -3
- package/src/server/deny-renderer.ts +6 -0
- package/src/server/html-injector-core.ts +19 -0
- package/src/server/html-injectors.ts +14 -4
- package/src/server/internal.ts +1 -18
- package/src/server/node-stream-transforms.ts +5 -72
- package/src/server/render-utils.ts +31 -0
- package/src/server/route-element-builder.ts +77 -47
- package/src/server/rsc-entry/error-renderer.ts +1 -0
- package/src/server/rsc-entry/helpers.ts +6 -34
- package/src/server/rsc-entry/index.ts +11 -0
- package/src/server/rsc-entry/render-route.ts +7 -9
- package/src/server/rsc-entry/rsc-payload.ts +2 -1
- package/src/server/rsc-entry/ssr-renderer.ts +2 -2
- package/src/server/ssr-render.ts +1 -11
- package/src/shared/href-validation.ts +8 -0
- package/dist/_chunks/cli-schema-sync-CBiMEtqa.js +0 -2
- package/dist/_chunks/cli-schema-sync-CQaxupHv.js.map +0 -1
- package/dist/_chunks/define-C3RvHQPK.js.map +0 -1
- package/dist/_chunks/define-DMVhsipD.js.map +0 -1
- package/dist/_chunks/handler-store-BVDgyTh7.js +0 -78
- package/dist/_chunks/handler-store-BVDgyTh7.js.map +0 -1
- package/dist/_chunks/logger-Dt-jQaBO.js +0 -291
- package/dist/_chunks/logger-Dt-jQaBO.js.map +0 -1
- package/dist/_chunks/metadata-routes-BjepjefT.js.map +0 -1
- package/dist/_chunks/resolve-schema-Dj8tQloi.js.map +0 -1
- package/dist/_chunks/schema-bridge-DK_89h8O.js.map +0 -1
- package/dist/_chunks/tracing-M_zKubhk.js.map +0 -1
- package/dist/_chunks/walkers-Ck4Y45XN.js.map +0 -1
- package/dist/_chunks/warnings-CNPXaypM.js +0 -125
- package/dist/_chunks/warnings-CNPXaypM.js.map +0 -1
- package/dist/client/link-pending-store.d.ts +0 -65
- package/dist/client/link-pending-store.d.ts.map +0 -1
- package/dist/client/nav-link-store.d.ts +0 -27
- package/dist/client/nav-link-store.d.ts.map +0 -1
- package/dist/dev-tools/warnings.d.ts +0 -109
- package/dist/dev-tools/warnings.d.ts.map +0 -1
- package/dist/server/flush.d.ts +0 -74
- package/dist/server/flush.d.ts.map +0 -1
- package/src/client/link-pending-store.ts +0 -116
- package/src/client/nav-link-store.ts +0 -37
- package/src/dev-tools/warnings.ts +0 -238
- package/src/server/flush.ts +0 -183
package/src/fonts/ast.ts
CHANGED
|
@@ -19,13 +19,17 @@
|
|
|
19
19
|
import { parseAst as viteParseAst } from 'vite';
|
|
20
20
|
import type { GoogleFontConfig } from './types.js';
|
|
21
21
|
import type { LocalFontConfig, LocalFontSrc } from './types.js';
|
|
22
|
+
import type { AstNode, IdentifierNode, PositionedNode, FlaggedUsage } from './ast-walk.js';
|
|
23
|
+
import {
|
|
24
|
+
offsetToLineColumn,
|
|
25
|
+
walkForDynamicCalls,
|
|
26
|
+
walkForNestedFontCalls,
|
|
27
|
+
walkForUnconsumedReferences,
|
|
28
|
+
} from './ast-walk.js';
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
export type { FlaggedUsage } from './ast-walk.js';
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
type: string;
|
|
27
|
-
[key: string]: unknown;
|
|
28
|
-
}
|
|
32
|
+
// ── AST node types (font-config-specific, on top of ast-walk.ts's base) ──────
|
|
29
33
|
|
|
30
34
|
interface LiteralNode extends AstNode {
|
|
31
35
|
type: 'Literal';
|
|
@@ -48,26 +52,6 @@ interface PropertyNode extends AstNode {
|
|
|
48
52
|
value: AstNode;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
interface IdentifierNode extends AstNode {
|
|
52
|
-
type: 'Identifier';
|
|
53
|
-
name: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ── Offset → line/column helper ──────────────────────────────────────────────
|
|
57
|
-
|
|
58
|
-
/** Convert a 0-based byte offset into a 1-based line and 0-based column. */
|
|
59
|
-
function offsetToLineColumn(source: string, offset: number): { line: number; column: number } {
|
|
60
|
-
let line = 1;
|
|
61
|
-
let lastNewline = -1;
|
|
62
|
-
for (let i = 0; i < offset; i++) {
|
|
63
|
-
if (source[i] === '\n') {
|
|
64
|
-
line++;
|
|
65
|
-
lastNewline = i;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return { line, column: offset - lastNewline - 1 };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
55
|
// ── Core AST extraction helpers ──────────────────────────────────────────────
|
|
72
56
|
|
|
73
57
|
/**
|
|
@@ -166,63 +150,7 @@ function objectProperties(node: ObjectExpressionNode): Map<string, AstNode> {
|
|
|
166
150
|
export function extractFontConfigAst(callSource: string): GoogleFontConfig | null {
|
|
167
151
|
const node = parseExpression(callSource);
|
|
168
152
|
if (!node || node.type !== 'ObjectExpression') return null;
|
|
169
|
-
|
|
170
|
-
const props = objectProperties(node as ObjectExpressionNode);
|
|
171
|
-
const config: GoogleFontConfig = {};
|
|
172
|
-
|
|
173
|
-
// subsets: string[]
|
|
174
|
-
const subsetsNode = props.get('subsets');
|
|
175
|
-
if (subsetsNode) {
|
|
176
|
-
const arr = extractStringArray(subsetsNode);
|
|
177
|
-
if (arr) config.subsets = arr;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// weight: string | string[]
|
|
181
|
-
const weightNode = props.get('weight');
|
|
182
|
-
if (weightNode) {
|
|
183
|
-
const arr = extractStringArray(weightNode);
|
|
184
|
-
if (arr) {
|
|
185
|
-
config.weight = arr;
|
|
186
|
-
} else {
|
|
187
|
-
const s = extractString(weightNode);
|
|
188
|
-
if (s !== undefined) config.weight = s;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// display: string
|
|
193
|
-
const displayNode = props.get('display');
|
|
194
|
-
if (displayNode) {
|
|
195
|
-
const s = extractString(displayNode);
|
|
196
|
-
if (s !== undefined) config.display = s as GoogleFontConfig['display'];
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// variable: string
|
|
200
|
-
const variableNode = props.get('variable');
|
|
201
|
-
if (variableNode) {
|
|
202
|
-
const s = extractString(variableNode);
|
|
203
|
-
if (s !== undefined) config.variable = s;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// style: string | string[]
|
|
207
|
-
const styleNode = props.get('style');
|
|
208
|
-
if (styleNode) {
|
|
209
|
-
const arr = extractStringArray(styleNode);
|
|
210
|
-
if (arr) {
|
|
211
|
-
config.style = arr;
|
|
212
|
-
} else {
|
|
213
|
-
const s = extractString(styleNode);
|
|
214
|
-
if (s !== undefined) config.style = s;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// preload: boolean
|
|
219
|
-
const preloadNode = props.get('preload');
|
|
220
|
-
if (preloadNode) {
|
|
221
|
-
const b = extractBoolean(preloadNode);
|
|
222
|
-
if (b !== undefined) config.preload = b;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return config;
|
|
153
|
+
return extractGoogleConfig(node as ObjectExpressionNode);
|
|
226
154
|
}
|
|
227
155
|
|
|
228
156
|
// ── Local font config extraction ─────────────────────────────────────────────
|
|
@@ -265,47 +193,7 @@ function extractLocalFontSrcEntry(node: AstNode): LocalFontSrc | null {
|
|
|
265
193
|
export function extractLocalFontConfigAst(callSource: string): LocalFontConfig | null {
|
|
266
194
|
const node = parseExpression(callSource);
|
|
267
195
|
if (!node || node.type !== 'ObjectExpression') return null;
|
|
268
|
-
|
|
269
|
-
const props = objectProperties(node as ObjectExpressionNode);
|
|
270
|
-
|
|
271
|
-
// display: string
|
|
272
|
-
const displayNode = props.get('display');
|
|
273
|
-
const display = displayNode
|
|
274
|
-
? (extractString(displayNode) as LocalFontConfig['display'])
|
|
275
|
-
: undefined;
|
|
276
|
-
|
|
277
|
-
// variable: string
|
|
278
|
-
const variableNode = props.get('variable');
|
|
279
|
-
const variable = variableNode ? extractString(variableNode) : undefined;
|
|
280
|
-
|
|
281
|
-
// family: string
|
|
282
|
-
const familyNode = props.get('family');
|
|
283
|
-
const family = familyNode ? extractString(familyNode) : undefined;
|
|
284
|
-
|
|
285
|
-
// src: string | LocalFontSrc[]
|
|
286
|
-
const srcNode = props.get('src');
|
|
287
|
-
if (!srcNode) return null;
|
|
288
|
-
|
|
289
|
-
// String form: src: './fonts/MyFont.woff2'
|
|
290
|
-
const srcString = extractString(srcNode);
|
|
291
|
-
if (srcString !== undefined) {
|
|
292
|
-
return { src: srcString, display, variable, family };
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Array form: src: [{ path: '...', weight: '...' }, ...]
|
|
296
|
-
if (srcNode.type === 'ArrayExpression') {
|
|
297
|
-
const arr = srcNode as ArrayExpressionNode;
|
|
298
|
-
const entries: LocalFontSrc[] = [];
|
|
299
|
-
for (const elem of arr.elements) {
|
|
300
|
-
const entry = extractLocalFontSrcEntry(elem);
|
|
301
|
-
if (!entry) return null;
|
|
302
|
-
entries.push(entry);
|
|
303
|
-
}
|
|
304
|
-
if (entries.length === 0) return null;
|
|
305
|
-
return { src: entries, display, variable, family };
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return null;
|
|
196
|
+
return extractLocalConfig(node as ObjectExpressionNode);
|
|
309
197
|
}
|
|
310
198
|
|
|
311
199
|
// ── Dynamic call detection ───────────────────────────────────────────────────
|
|
@@ -329,51 +217,7 @@ export function detectDynamicFontCallAst(source: string, importedNames: string[]
|
|
|
329
217
|
}
|
|
330
218
|
|
|
331
219
|
const nameSet = new Set(importedNames);
|
|
332
|
-
|
|
333
|
-
return result;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Recursively walk the AST looking for CallExpression nodes where
|
|
338
|
-
* the callee is one of the imported font names and the first argument
|
|
339
|
-
* is not an ObjectExpression (i.e. it's dynamic).
|
|
340
|
-
*/
|
|
341
|
-
function walkForDynamicCalls(node: AstNode, names: Set<string>, source: string): string | null {
|
|
342
|
-
if (!node || typeof node !== 'object') return null;
|
|
343
|
-
|
|
344
|
-
if (node.type === 'CallExpression') {
|
|
345
|
-
const callee = node.callee as AstNode;
|
|
346
|
-
if (callee.type === 'Identifier' && names.has((callee as IdentifierNode).name)) {
|
|
347
|
-
const args = node.arguments as AstNode[];
|
|
348
|
-
if (args.length > 0 && args[0].type !== 'ObjectExpression') {
|
|
349
|
-
// Extract the argument source text
|
|
350
|
-
const argStart = (args[0] as AstNode & { start: number }).start;
|
|
351
|
-
const argEnd = (args[0] as AstNode & { end: number }).end;
|
|
352
|
-
const argText = source.slice(argStart, argEnd);
|
|
353
|
-
const calleeName = (callee as IdentifierNode).name;
|
|
354
|
-
return `${calleeName}(${argText})`;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Walk all child properties
|
|
360
|
-
for (const key of Object.keys(node)) {
|
|
361
|
-
if (key === 'type') continue;
|
|
362
|
-
const child = node[key];
|
|
363
|
-
if (Array.isArray(child)) {
|
|
364
|
-
for (const item of child) {
|
|
365
|
-
if (item && typeof item === 'object' && (item as AstNode).type) {
|
|
366
|
-
const result = walkForDynamicCalls(item as AstNode, names, source);
|
|
367
|
-
if (result) return result;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
} else if (child && typeof child === 'object' && (child as AstNode).type) {
|
|
371
|
-
const result = walkForDynamicCalls(child as AstNode, names, source);
|
|
372
|
-
if (result) return result;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return null;
|
|
220
|
+
return walkForDynamicCalls(ast as unknown as AstNode, nameSet, source);
|
|
377
221
|
}
|
|
378
222
|
|
|
379
223
|
// ── AST-node-level config extraction ─────────────────────────────────────────
|
|
@@ -554,7 +398,12 @@ export interface FontCallSite {
|
|
|
554
398
|
export interface FontAnalysis {
|
|
555
399
|
bindings: FontBinding[];
|
|
556
400
|
callSites: FontCallSite[];
|
|
557
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* Import nodes that should be removed (deduplicated). Only imports that
|
|
403
|
+
* produced at least one font binding are listed — removing an import
|
|
404
|
+
* whose bindings the transform doesn't rewrite (e.g. a namespace
|
|
405
|
+
* import) would leave dangling references behind.
|
|
406
|
+
*/
|
|
558
407
|
importNodesToRemove: Array<{ start: number; end: number }>;
|
|
559
408
|
/** First dynamic call expression text, or `null`. */
|
|
560
409
|
dynamicCall: string | null;
|
|
@@ -562,12 +411,21 @@ export interface FontAnalysis {
|
|
|
562
411
|
* Font calls found in nested scopes (functions, if-blocks, etc.).
|
|
563
412
|
* These are errors — font calls must be module-level.
|
|
564
413
|
*/
|
|
565
|
-
nestedCalls:
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
414
|
+
nestedCalls: FlaggedUsage[];
|
|
415
|
+
/**
|
|
416
|
+
* Namespace imports of font modules (`import * as fonts from ...`).
|
|
417
|
+
* These are errors — the transform cannot rewrite `fonts.Inter(...)`
|
|
418
|
+
* member calls, so the fonts would silently never load.
|
|
419
|
+
*/
|
|
420
|
+
namespaceImports: FlaggedUsage[];
|
|
421
|
+
/**
|
|
422
|
+
* References to font bindings that were not consumed by a recognized
|
|
423
|
+
* call site (`export default Inter({...})`, object-literal nesting,
|
|
424
|
+
* member access on the call result, aliasing, re-exports). These are
|
|
425
|
+
* errors — the import is removed, so any leftover reference would be a
|
|
426
|
+
* silent runtime ReferenceError.
|
|
427
|
+
*/
|
|
428
|
+
unsupportedUsages: FlaggedUsage[];
|
|
571
429
|
}
|
|
572
430
|
|
|
573
431
|
interface ImportDeclarationNode extends PositionedNode {
|
|
@@ -661,6 +519,7 @@ export function findFontCallSites(source: string): FontAnalysis | null {
|
|
|
661
519
|
// Step 1: Find all font import declarations and build the bindings list.
|
|
662
520
|
const bindings: FontBinding[] = [];
|
|
663
521
|
const importNodesToRemove: Array<{ start: number; end: number }> = [];
|
|
522
|
+
const namespaceImports: FlaggedUsage[] = [];
|
|
664
523
|
|
|
665
524
|
for (const stmt of program.body) {
|
|
666
525
|
if (!isImportDeclaration(stmt)) continue;
|
|
@@ -672,8 +531,10 @@ export function findFontCallSites(source: string): FontAnalysis | null {
|
|
|
672
531
|
else if (LOCAL_FONT_SOURCES.has(specSource)) provider = 'local';
|
|
673
532
|
if (!provider) continue;
|
|
674
533
|
|
|
534
|
+
let producedBinding = false;
|
|
675
535
|
for (const spec of importDecl.specifiers) {
|
|
676
536
|
if (spec.type === 'ImportSpecifier' && provider === 'google') {
|
|
537
|
+
producedBinding = true;
|
|
677
538
|
bindings.push({
|
|
678
539
|
localName: spec.local.name,
|
|
679
540
|
importedName: spec.imported?.name ?? spec.local.name,
|
|
@@ -681,27 +542,39 @@ export function findFontCallSites(source: string): FontAnalysis | null {
|
|
|
681
542
|
importNode: importDecl,
|
|
682
543
|
});
|
|
683
544
|
} else if (spec.type === 'ImportDefaultSpecifier' && provider === 'local') {
|
|
545
|
+
producedBinding = true;
|
|
684
546
|
bindings.push({
|
|
685
547
|
localName: spec.local.name,
|
|
686
548
|
importedName: 'default',
|
|
687
549
|
provider,
|
|
688
550
|
importNode: importDecl,
|
|
689
551
|
});
|
|
552
|
+
} else if (spec.type === 'ImportNamespaceSpecifier') {
|
|
553
|
+
// `import * as fonts from ...` cannot be rewritten — the transform
|
|
554
|
+
// only matches direct identifier calls, so `fonts.Inter(...)` would
|
|
555
|
+
// silently fall through to the runtime fallback module and never
|
|
556
|
+
// load a real font. Collected as a build error.
|
|
557
|
+
const pos = offsetToLineColumn(source, spec.start);
|
|
558
|
+
namespaceImports.push({ name: spec.local.name, line: pos.line, column: pos.column });
|
|
690
559
|
}
|
|
691
|
-
// ImportNamespaceSpecifier (`import * as fonts from ...`) is not
|
|
692
|
-
// currently supported — the old regex didn't handle it either.
|
|
693
|
-
// If we add it later, we'd need to also handle `fonts.Inter(...)`
|
|
694
|
-
// member expressions in the call-site walk.
|
|
695
560
|
}
|
|
696
561
|
|
|
697
|
-
|
|
562
|
+
// Only remove imports whose bindings the transform actually rewrites.
|
|
563
|
+
// Removing an import that produced no binding (namespace imports,
|
|
564
|
+
// default imports of the google module) would orphan its references.
|
|
565
|
+
if (producedBinding) {
|
|
566
|
+
importNodesToRemove.push({ start: stmt.start, end: stmt.end });
|
|
567
|
+
}
|
|
698
568
|
}
|
|
699
569
|
|
|
700
|
-
if (bindings.length === 0) return null;
|
|
570
|
+
if (bindings.length === 0 && namespaceImports.length === 0) return null;
|
|
701
571
|
|
|
702
572
|
// Step 2: Walk the AST for CallExpression nodes whose callee is a font binding.
|
|
703
573
|
const bindingsByName = new Map(bindings.map((b) => [b.localName, b]));
|
|
704
574
|
const callSites: FontCallSite[] = [];
|
|
575
|
+
// Start offsets of recognized (and dynamic-flagged) CallExpressions, so
|
|
576
|
+
// the unsupported-reference walk in step 4 can skip them.
|
|
577
|
+
const consumedCallStarts = new Set<number>();
|
|
705
578
|
let dynamicCall: string | null = null;
|
|
706
579
|
|
|
707
580
|
for (const stmt of program.body) {
|
|
@@ -720,6 +593,7 @@ export function findFontCallSites(source: string): FontAnalysis | null {
|
|
|
720
593
|
if (!site) continue;
|
|
721
594
|
|
|
722
595
|
if (site === 'dynamic') {
|
|
596
|
+
consumedCallStarts.add(call.start);
|
|
723
597
|
if (!dynamicCall && isIdentifier(call.callee)) {
|
|
724
598
|
const argStart = call.arguments[0].start;
|
|
725
599
|
const argEnd = call.arguments[0].end;
|
|
@@ -731,6 +605,7 @@ export function findFontCallSites(source: string): FontAnalysis | null {
|
|
|
731
605
|
const assignedName = isIdentifier(declarator.id) ? declarator.id.name : null;
|
|
732
606
|
|
|
733
607
|
const { line, column } = offsetToLineColumn(source, call.start);
|
|
608
|
+
consumedCallStarts.add(call.start);
|
|
734
609
|
callSites.push({
|
|
735
610
|
binding: site.binding,
|
|
736
611
|
assignedName,
|
|
@@ -750,6 +625,7 @@ export function findFontCallSites(source: string): FontAnalysis | null {
|
|
|
750
625
|
const site = matchCallExpression(call, bindingsByName, source);
|
|
751
626
|
if (site && site !== 'dynamic') {
|
|
752
627
|
const { line, column } = offsetToLineColumn(source, call.start);
|
|
628
|
+
consumedCallStarts.add(call.start);
|
|
753
629
|
callSites.push({
|
|
754
630
|
binding: site.binding,
|
|
755
631
|
assignedName: null,
|
|
@@ -759,10 +635,13 @@ export function findFontCallSites(source: string): FontAnalysis | null {
|
|
|
759
635
|
line,
|
|
760
636
|
column,
|
|
761
637
|
});
|
|
762
|
-
} else if (site === 'dynamic'
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
638
|
+
} else if (site === 'dynamic') {
|
|
639
|
+
consumedCallStarts.add(call.start);
|
|
640
|
+
if (!dynamicCall && isIdentifier(call.callee)) {
|
|
641
|
+
const argStart = call.arguments[0].start;
|
|
642
|
+
const argEnd = call.arguments[0].end;
|
|
643
|
+
dynamicCall = `${call.callee.name}(${source.slice(argStart, argEnd)})`;
|
|
644
|
+
}
|
|
766
645
|
}
|
|
767
646
|
}
|
|
768
647
|
}
|
|
@@ -771,75 +650,38 @@ export function findFontCallSites(source: string): FontAnalysis | null {
|
|
|
771
650
|
// Step 3: Walk nested scopes to find font calls that aren't module-level.
|
|
772
651
|
// Font calls must be at module scope (same constraint as Next.js).
|
|
773
652
|
// If the import is removed but a nested call remains, it's a ReferenceError.
|
|
774
|
-
const nestedCalls:
|
|
653
|
+
const nestedCalls: FlaggedUsage[] = [];
|
|
775
654
|
for (const stmt of program.body) {
|
|
776
655
|
if (stmt.type === 'ImportDeclaration') continue;
|
|
777
656
|
walkForNestedFontCalls(stmt as AstNode, bindingsByName, source, nestedCalls, false);
|
|
778
657
|
}
|
|
779
658
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
out.push({ name: (callee as IdentifierNode).name, line: pos.line, column: pos.column });
|
|
807
|
-
return; // Don't recurse into this call's children
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
// These node types introduce a nested scope — any font call inside is an error.
|
|
812
|
-
const scopeTypes = new Set([
|
|
813
|
-
'FunctionDeclaration',
|
|
814
|
-
'FunctionExpression',
|
|
815
|
-
'ArrowFunctionExpression',
|
|
816
|
-
'IfStatement',
|
|
817
|
-
'ForStatement',
|
|
818
|
-
'ForInStatement',
|
|
819
|
-
'ForOfStatement',
|
|
820
|
-
'WhileStatement',
|
|
821
|
-
'DoWhileStatement',
|
|
822
|
-
'SwitchStatement',
|
|
823
|
-
'TryStatement',
|
|
824
|
-
'BlockStatement',
|
|
825
|
-
'ClassDeclaration',
|
|
826
|
-
'ClassExpression',
|
|
827
|
-
]);
|
|
828
|
-
const enterNested = isNested || scopeTypes.has(node.type);
|
|
829
|
-
|
|
830
|
-
for (const key of Object.keys(node)) {
|
|
831
|
-
if (key === 'type') continue;
|
|
832
|
-
const child = node[key];
|
|
833
|
-
if (Array.isArray(child)) {
|
|
834
|
-
for (const item of child) {
|
|
835
|
-
if (item && typeof item === 'object' && (item as AstNode).type) {
|
|
836
|
-
walkForNestedFontCalls(item as AstNode, bindings, source, out, enterNested);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
} else if (child && typeof child === 'object' && (child as AstNode).type) {
|
|
840
|
-
walkForNestedFontCalls(child as AstNode, bindings, source, out, enterNested);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
659
|
+
// Step 4: Walk for references to font bindings that were not consumed by
|
|
660
|
+
// a recognized call site. The transform removes the import declarations,
|
|
661
|
+
// so any leftover reference (`export default Inter({...})`, object-literal
|
|
662
|
+
// nesting, `Inter({...}).className`, `const f = Inter`, `export { Inter }`)
|
|
663
|
+
// would become a silent runtime ReferenceError. Collected as build errors.
|
|
664
|
+
const unsupportedUsages: FlaggedUsage[] = [];
|
|
665
|
+
for (const stmt of program.body) {
|
|
666
|
+
if (stmt.type === 'ImportDeclaration') continue;
|
|
667
|
+
walkForUnconsumedReferences(
|
|
668
|
+
stmt as AstNode,
|
|
669
|
+
bindingsByName,
|
|
670
|
+
consumedCallStarts,
|
|
671
|
+
source,
|
|
672
|
+
unsupportedUsages
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
return {
|
|
677
|
+
bindings,
|
|
678
|
+
callSites,
|
|
679
|
+
importNodesToRemove,
|
|
680
|
+
dynamicCall,
|
|
681
|
+
nestedCalls,
|
|
682
|
+
namespaceImports,
|
|
683
|
+
unsupportedUsages,
|
|
684
|
+
};
|
|
843
685
|
}
|
|
844
686
|
|
|
845
687
|
/**
|
package/src/fonts/bundle.ts
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { readFileSync, existsSync } from 'node:fs';
|
|
13
|
-
import { resolve, normalize
|
|
13
|
+
import { resolve, normalize } from 'node:path';
|
|
14
14
|
import type { ManifestFontEntry, BuildManifest } from '../server/build-manifest.js';
|
|
15
|
-
import {
|
|
15
|
+
import { fontOutputBasename, fontUrlPrefix, FONT_OUTPUT_DIR } from './paths.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Minimal shape of the asset descriptor accepted by Rollup's `emitFile`.
|
|
@@ -63,7 +63,7 @@ export function emitFontAssets(
|
|
|
63
63
|
for (const cf of pipeline.uniqueCachedFiles()) {
|
|
64
64
|
emitFile({
|
|
65
65
|
type: 'asset',
|
|
66
|
-
fileName:
|
|
66
|
+
fileName: `${FONT_OUTPUT_DIR}/${cf.hashedFilename}`,
|
|
67
67
|
source: cf.data,
|
|
68
68
|
});
|
|
69
69
|
}
|
|
@@ -77,11 +77,11 @@ export function emitFontAssets(
|
|
|
77
77
|
warn(`Local font file not found: ${absolutePath}`);
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
|
-
const filename = src.hashedBasename ?? src.path
|
|
80
|
+
const filename = src.hashedBasename ?? fontOutputBasename(src.path);
|
|
81
81
|
const data = readFileSync(absolutePath);
|
|
82
82
|
emitFile({
|
|
83
83
|
type: 'asset',
|
|
84
|
-
fileName:
|
|
84
|
+
fileName: `${FONT_OUTPUT_DIR}/${filename}`,
|
|
85
85
|
source: data,
|
|
86
86
|
});
|
|
87
87
|
}
|
|
@@ -95,8 +95,15 @@ export function emitFontAssets(
|
|
|
95
95
|
* `buildManifest.css` keys work after TIM-1014.
|
|
96
96
|
*
|
|
97
97
|
* All font binaries use content-hashed filenames for immutable caching.
|
|
98
|
+
* Hrefs are prefixed with Vite's resolved `base` so they match the URLs
|
|
99
|
+
* the platform serves the emitted binaries under.
|
|
98
100
|
*/
|
|
99
|
-
export function writeFontManifest(
|
|
101
|
+
export function writeFontManifest(
|
|
102
|
+
pipeline: FontPipeline,
|
|
103
|
+
buildManifest: BuildManifest,
|
|
104
|
+
base = '/'
|
|
105
|
+
): void {
|
|
106
|
+
const urlPrefix = fontUrlPrefix(base);
|
|
100
107
|
const fontsByImporter = new Map<string, ManifestFontEntry[]>();
|
|
101
108
|
|
|
102
109
|
for (const entry of pipeline.entries()) {
|
|
@@ -104,10 +111,10 @@ export function writeFontManifest(pipeline: FontPipeline, buildManifest: BuildMa
|
|
|
104
111
|
|
|
105
112
|
if (entry.provider === 'local' && entry.localSources) {
|
|
106
113
|
for (const src of entry.localSources) {
|
|
107
|
-
const filename = src.hashedBasename ?? src.path
|
|
114
|
+
const filename = src.hashedBasename ?? fontOutputBasename(src.path);
|
|
108
115
|
const format = inferFontFormat(src.path);
|
|
109
116
|
manifestEntries.push({
|
|
110
|
-
href:
|
|
117
|
+
href: `${urlPrefix}/${filename}`,
|
|
111
118
|
format,
|
|
112
119
|
crossOrigin: 'anonymous',
|
|
113
120
|
});
|
|
@@ -115,7 +122,7 @@ export function writeFontManifest(pipeline: FontPipeline, buildManifest: BuildMa
|
|
|
115
122
|
} else if (entry.cachedFiles) {
|
|
116
123
|
for (const cf of entry.cachedFiles) {
|
|
117
124
|
manifestEntries.push({
|
|
118
|
-
href:
|
|
125
|
+
href: `${urlPrefix}/${cf.hashedFilename}`,
|
|
119
126
|
format: 'woff2',
|
|
120
127
|
crossOrigin: 'anonymous',
|
|
121
128
|
});
|
|
@@ -14,6 +14,7 @@ import type { ViteDevServer } from 'vite';
|
|
|
14
14
|
import { readFileSync, existsSync } from 'node:fs';
|
|
15
15
|
import { resolve, normalize } from 'node:path';
|
|
16
16
|
import type { FontPipeline } from './pipeline.js';
|
|
17
|
+
import { fontOutputBasename, fontUrlPrefix } from './paths.js';
|
|
17
18
|
|
|
18
19
|
const FONT_MIME_TYPES: Record<string, string> = {
|
|
19
20
|
woff2: 'font/woff2',
|
|
@@ -30,12 +31,17 @@ const FONT_MIME_TYPES: Record<string, string> = {
|
|
|
30
31
|
* captured by reference so that fonts registered after `configureServer`
|
|
31
32
|
* runs (during transform) are still resolvable.
|
|
32
33
|
*/
|
|
33
|
-
export function installFontDevMiddleware(
|
|
34
|
+
export function installFontDevMiddleware(
|
|
35
|
+
server: ViteDevServer,
|
|
36
|
+
pipeline: FontPipeline,
|
|
37
|
+
base = '/'
|
|
38
|
+
): void {
|
|
39
|
+
const urlPrefix = `${fontUrlPrefix(base)}/`;
|
|
34
40
|
server.middlewares.use((req, res, next) => {
|
|
35
41
|
const url = req.url;
|
|
36
|
-
if (!url || !url.startsWith(
|
|
42
|
+
if (!url || !url.startsWith(urlPrefix)) return next();
|
|
37
43
|
|
|
38
|
-
const requestedFilename = url.slice(
|
|
44
|
+
const requestedFilename = url.slice(urlPrefix.length);
|
|
39
45
|
// Reject path traversal attempts and any subdirectory access. We only
|
|
40
46
|
// serve flat basenames out of the registry.
|
|
41
47
|
if (requestedFilename.includes('..') || requestedFilename.includes('/')) {
|
|
@@ -50,7 +56,7 @@ export function installFontDevMiddleware(server: ViteDevServer, pipeline: FontPi
|
|
|
50
56
|
for (const font of pipeline.fonts()) {
|
|
51
57
|
if (font.provider !== 'local' || !font.localSources) continue;
|
|
52
58
|
for (const src of font.localSources) {
|
|
53
|
-
const basename = src.path
|
|
59
|
+
const basename = fontOutputBasename(src.path);
|
|
54
60
|
if (basename !== requestedFilename && src.hashedBasename !== requestedFilename) continue;
|
|
55
61
|
|
|
56
62
|
const absolutePath = normalize(resolve(src.path));
|
|
@@ -62,7 +68,12 @@ export function installFontDevMiddleware(server: ViteDevServer, pipeline: FontPi
|
|
|
62
68
|
const data = readFileSync(absolutePath);
|
|
63
69
|
const ext = absolutePath.split('.').pop()?.toLowerCase();
|
|
64
70
|
res.setHeader('Content-Type', FONT_MIME_TYPES[ext ?? ''] ?? 'application/octet-stream');
|
|
65
|
-
|
|
71
|
+
// Dev URLs are raw (unhashed) basenames — `immutable` is only
|
|
72
|
+
// justified by content hashing (design/24-fonts.md). `no-cache`
|
|
73
|
+
// keeps responses revalidatable so edited fonts update without a
|
|
74
|
+
// hard refresh. Production binaries are content-hashed and served
|
|
75
|
+
// immutable by the platform, not this middleware.
|
|
76
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
66
77
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
67
78
|
res.end(data);
|
|
68
79
|
return;
|
package/src/fonts/fallbacks.ts
CHANGED
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
* Design doc: 24-fonts.md §"Step 4: Size-Adjusted Fallbacks"
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { FontFaceDescriptor } from './types.js';
|
|
12
|
-
|
|
13
11
|
/**
|
|
14
12
|
* Font metrics for size-adjusted fallback calculation.
|
|
15
13
|
*
|
|
@@ -169,27 +167,6 @@ export function getGenericFamily(family: string): string {
|
|
|
169
167
|
return 'sans-serif';
|
|
170
168
|
}
|
|
171
169
|
|
|
172
|
-
/**
|
|
173
|
-
* Generate a size-adjusted fallback @font-face for a given font family.
|
|
174
|
-
*
|
|
175
|
-
* Returns null if no metrics are available (unknown font — no fallback generated).
|
|
176
|
-
*/
|
|
177
|
-
export function generateFallbackFontFace(family: string): FontFaceDescriptor | null {
|
|
178
|
-
const metrics = FALLBACK_METRICS[family.toLowerCase()];
|
|
179
|
-
if (!metrics) return null;
|
|
180
|
-
|
|
181
|
-
const fallbackFamily = `${family} Fallback`;
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
family: fallbackFamily,
|
|
185
|
-
src: `local('${metrics.fallbackFont}')`,
|
|
186
|
-
// Encode the metrics into a CSS descriptor string.
|
|
187
|
-
// We abuse the 'style' field to carry the override properties
|
|
188
|
-
// since FontFaceDescriptor doesn't have dedicated fields.
|
|
189
|
-
// The generateFallbackCss function handles this specially.
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
170
|
/**
|
|
194
171
|
* Generate the full CSS for a size-adjusted fallback font.
|
|
195
172
|
*
|
package/src/fonts/google.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { createHash } from 'node:crypto';
|
|
|
19
19
|
import { readFile, writeFile, mkdir, stat } from 'node:fs/promises';
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
import type { ExtractedFont, FontFaceDescriptor } from './types.js';
|
|
22
|
+
import { fontUrlPrefix } from './paths.js';
|
|
22
23
|
|
|
23
24
|
/** Google Fonts CSS API v2 base URL. */
|
|
24
25
|
const GOOGLE_FONTS_API = 'https://fonts.googleapis.com/css2';
|
|
@@ -281,15 +282,18 @@ async function isCacheHit(metaPath: string, dataPath: string): Promise<boolean>
|
|
|
281
282
|
* Generate @font-face descriptors for cached (production) Google Fonts.
|
|
282
283
|
*
|
|
283
284
|
* Each CachedFont gets a FontFaceDescriptor pointing to the
|
|
284
|
-
* content-hashed URL under
|
|
285
|
+
* content-hashed URL under `<base>_timber/fonts/`. The `urlPrefix` comes
|
|
286
|
+
* from `fontUrlPrefix(viteBase)` so apps deployed under a non-root base
|
|
287
|
+
* resolve fonts correctly.
|
|
285
288
|
*/
|
|
286
289
|
export function generateProductionFontFaces(
|
|
287
290
|
cachedFonts: CachedFont[],
|
|
288
|
-
display: string
|
|
291
|
+
display: string,
|
|
292
|
+
urlPrefix = fontUrlPrefix()
|
|
289
293
|
): FontFaceDescriptor[] {
|
|
290
294
|
return cachedFonts.map((cf) => ({
|
|
291
295
|
family: cf.face.family,
|
|
292
|
-
src: `url('
|
|
296
|
+
src: `url('${urlPrefix}/${cf.hashedFilename}') format('woff2')`,
|
|
293
297
|
weight: cf.face.weight,
|
|
294
298
|
style: cf.face.style,
|
|
295
299
|
display,
|