vinext 0.0.37 → 0.0.38

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.
Files changed (86) hide show
  1. package/dist/cache.d.ts +2 -0
  2. package/dist/cache.js +2 -0
  3. package/dist/cli.js +6 -22
  4. package/dist/cli.js.map +1 -1
  5. package/dist/client/empty-module.d.ts +1 -0
  6. package/dist/client/empty-module.js +1 -0
  7. package/dist/client/entry.js +2 -0
  8. package/dist/client/entry.js.map +1 -1
  9. package/dist/client/instrumentation-client-state.d.ts +10 -0
  10. package/dist/client/instrumentation-client-state.js +19 -0
  11. package/dist/client/instrumentation-client-state.js.map +1 -0
  12. package/dist/client/instrumentation-client.d.ts +8 -0
  13. package/dist/client/instrumentation-client.js +8 -0
  14. package/dist/client/instrumentation-client.js.map +1 -0
  15. package/dist/cloudflare/tpr.js +1 -3
  16. package/dist/cloudflare/tpr.js.map +1 -1
  17. package/dist/deploy.js +9 -3
  18. package/dist/deploy.js.map +1 -1
  19. package/dist/entries/app-rsc-entry.js +27 -22
  20. package/dist/entries/app-rsc-entry.js.map +1 -1
  21. package/dist/entries/pages-client-entry.js +2 -0
  22. package/dist/entries/pages-client-entry.js.map +1 -1
  23. package/dist/entries/pages-server-entry.js +41 -260
  24. package/dist/entries/pages-server-entry.js.map +1 -1
  25. package/dist/entries/runtime-entry-module.d.ts +13 -1
  26. package/dist/entries/runtime-entry-module.js +18 -4
  27. package/dist/entries/runtime-entry-module.js.map +1 -1
  28. package/dist/index.d.ts +13 -11
  29. package/dist/index.js +101 -696
  30. package/dist/index.js.map +1 -1
  31. package/dist/plugins/fix-use-server-closure-collision.d.ts +29 -0
  32. package/dist/plugins/fix-use-server-closure-collision.js +204 -0
  33. package/dist/plugins/fix-use-server-closure-collision.js.map +1 -0
  34. package/dist/plugins/fonts.d.ts +39 -0
  35. package/dist/plugins/fonts.js +432 -0
  36. package/dist/plugins/fonts.js.map +1 -0
  37. package/dist/plugins/instrumentation-client.d.ts +7 -0
  38. package/dist/plugins/instrumentation-client.js +29 -0
  39. package/dist/plugins/instrumentation-client.js.map +1 -0
  40. package/dist/plugins/og-assets.d.ts +26 -0
  41. package/dist/plugins/og-assets.js +118 -0
  42. package/dist/plugins/og-assets.js.map +1 -0
  43. package/dist/server/api-handler.js +6 -23
  44. package/dist/server/api-handler.js.map +1 -1
  45. package/dist/server/app-browser-entry.js +4 -0
  46. package/dist/server/app-browser-entry.js.map +1 -1
  47. package/dist/server/dev-server.js +3 -1
  48. package/dist/server/dev-server.js.map +1 -1
  49. package/dist/server/instrumentation.d.ts +5 -1
  50. package/dist/server/instrumentation.js +15 -6
  51. package/dist/server/instrumentation.js.map +1 -1
  52. package/dist/server/middleware.d.ts +2 -0
  53. package/dist/server/middleware.js +14 -7
  54. package/dist/server/middleware.js.map +1 -1
  55. package/dist/server/pages-api-route.d.ts +23 -0
  56. package/dist/server/pages-api-route.js +40 -0
  57. package/dist/server/pages-api-route.js.map +1 -0
  58. package/dist/server/pages-media-type.d.ts +16 -0
  59. package/dist/server/pages-media-type.js +25 -0
  60. package/dist/server/pages-media-type.js.map +1 -0
  61. package/dist/server/pages-node-compat.d.ts +45 -0
  62. package/dist/server/pages-node-compat.js +148 -0
  63. package/dist/server/pages-node-compat.js.map +1 -0
  64. package/dist/server/prod-server.js +1 -0
  65. package/dist/server/prod-server.js.map +1 -1
  66. package/dist/shims/cache-for-request.d.ts +58 -0
  67. package/dist/shims/cache-for-request.js +74 -0
  68. package/dist/shims/cache-for-request.js.map +1 -0
  69. package/dist/shims/dynamic.js +25 -10
  70. package/dist/shims/dynamic.js.map +1 -1
  71. package/dist/shims/headers.js +1 -1
  72. package/dist/shims/image.js +24 -8
  73. package/dist/shims/image.js.map +1 -1
  74. package/dist/shims/layout-segment-context.js +9 -3
  75. package/dist/shims/layout-segment-context.js.map +1 -1
  76. package/dist/shims/link.js +2 -0
  77. package/dist/shims/link.js.map +1 -1
  78. package/dist/shims/navigation.js +2 -0
  79. package/dist/shims/navigation.js.map +1 -1
  80. package/dist/shims/server.d.ts +1 -0
  81. package/dist/shims/server.js +3 -0
  82. package/dist/shims/server.js.map +1 -1
  83. package/dist/shims/unified-request-context.d.ts +2 -0
  84. package/dist/shims/unified-request-context.js +1 -0
  85. package/dist/shims/unified-request-context.js.map +1 -1
  86. package/package.json +8 -4
@@ -0,0 +1,432 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parseAst } from "vite";
4
+ import MagicString from "magic-string";
5
+ //#region src/plugins/fonts.ts
6
+ const VIRTUAL_GOOGLE_FONTS = "virtual:vinext-google-fonts";
7
+ const RESOLVED_VIRTUAL_GOOGLE_FONTS = "\0" + VIRTUAL_GOOGLE_FONTS;
8
+ const GOOGLE_FONT_UTILITY_EXPORTS = new Set([
9
+ "buildGoogleFontsUrl",
10
+ "getSSRFontLinks",
11
+ "getSSRFontStyles",
12
+ "getSSRFontPreloads",
13
+ "createFontLoader"
14
+ ]);
15
+ /**
16
+ * Safely parse a static JS object literal string into a plain object.
17
+ * Uses Vite's parseAst (Rollup/acorn) so no code is ever evaluated.
18
+ * Returns null if the expression contains anything dynamic (function calls,
19
+ * template literals, identifiers, computed properties, etc.).
20
+ *
21
+ * Supports: string literals, numeric literals, boolean literals,
22
+ * arrays of the above, and nested object literals.
23
+ */
24
+ function parseStaticObjectLiteral(objectStr) {
25
+ let ast;
26
+ try {
27
+ ast = parseAst(`(${objectStr})`);
28
+ } catch {
29
+ return null;
30
+ }
31
+ const body = ast.body;
32
+ if (body.length !== 1 || body[0].type !== "ExpressionStatement") return null;
33
+ const expr = body[0].expression;
34
+ if (expr.type !== "ObjectExpression") return null;
35
+ const result = extractStaticValue(expr);
36
+ return result === void 0 ? null : result;
37
+ }
38
+ /**
39
+ * Recursively extract a static value from an ESTree AST node.
40
+ * Returns undefined (not null) if the node contains any dynamic expression.
41
+ *
42
+ * Uses `any` for the node parameter because Rollup's internal ESTree types
43
+ * (estree.Expression, estree.ObjectExpression, etc.) aren't re-exported by Vite,
44
+ * and the recursive traversal touches many different node shapes.
45
+ */
46
+ function extractStaticValue(node) {
47
+ switch (node.type) {
48
+ case "Literal": return node.value;
49
+ case "UnaryExpression":
50
+ if (node.operator === "-" && node.argument?.type === "Literal" && typeof node.argument.value === "number") return -node.argument.value;
51
+ return;
52
+ case "ArrayExpression": {
53
+ const arr = [];
54
+ for (const elem of node.elements) {
55
+ if (!elem) return void 0;
56
+ const val = extractStaticValue(elem);
57
+ if (val === void 0) return void 0;
58
+ arr.push(val);
59
+ }
60
+ return arr;
61
+ }
62
+ case "ObjectExpression": {
63
+ const obj = {};
64
+ for (const prop of node.properties) {
65
+ if (prop.type !== "Property") return void 0;
66
+ if (prop.computed) return void 0;
67
+ let key;
68
+ if (prop.key.type === "Identifier") key = prop.key.name;
69
+ else if (prop.key.type === "Literal" && typeof prop.key.value === "string") key = prop.key.value;
70
+ else return;
71
+ const val = extractStaticValue(prop.value);
72
+ if (val === void 0) return void 0;
73
+ obj[key] = val;
74
+ }
75
+ return obj;
76
+ }
77
+ default: return;
78
+ }
79
+ }
80
+ function encodeGoogleFontsVirtualId(payload) {
81
+ const params = new URLSearchParams();
82
+ if (payload.hasDefault) params.set("default", "1");
83
+ if (payload.fonts.length > 0) params.set("fonts", payload.fonts.join(","));
84
+ if (payload.utilities.length > 0) params.set("utilities", payload.utilities.join(","));
85
+ return `${VIRTUAL_GOOGLE_FONTS}?${params.toString()}`;
86
+ }
87
+ function parseGoogleFontsVirtualId(id) {
88
+ const cleanId = id.startsWith("\0") ? id.slice(1) : id;
89
+ if (!cleanId.startsWith("virtual:vinext-google-fonts")) return null;
90
+ const queryIndex = cleanId.indexOf("?");
91
+ const params = new URLSearchParams(queryIndex === -1 ? "" : cleanId.slice(queryIndex + 1));
92
+ return {
93
+ hasDefault: params.get("default") === "1",
94
+ fonts: params.get("fonts")?.split(",").map((value) => value.trim()).filter(Boolean) ?? [],
95
+ utilities: params.get("utilities")?.split(",").map((value) => value.trim()).filter(Boolean) ?? []
96
+ };
97
+ }
98
+ function generateGoogleFontsVirtualModule(id, fontGoogleShimPath) {
99
+ const payload = parseGoogleFontsVirtualId(id);
100
+ if (!payload) return null;
101
+ const utilities = Array.from(new Set(payload.utilities));
102
+ const fonts = Array.from(new Set(payload.fonts));
103
+ const lines = [];
104
+ lines.push(`import { createFontLoader } from ${JSON.stringify(fontGoogleShimPath)};`);
105
+ const reExports = [];
106
+ if (payload.hasDefault) reExports.push("default");
107
+ reExports.push(...utilities);
108
+ if (reExports.length > 0) lines.push(`export { ${reExports.join(", ")} } from ${JSON.stringify(fontGoogleShimPath)};`);
109
+ for (const fontName of fonts) {
110
+ const family = fontName.replace(/_/g, " ");
111
+ lines.push(`export const ${fontName} = /*#__PURE__*/ createFontLoader(${JSON.stringify(family)});`);
112
+ }
113
+ lines.push("");
114
+ return lines.join("\n");
115
+ }
116
+ function parseGoogleFontNamedSpecifiers(specifiersStr, forceType = false) {
117
+ return specifiersStr.split(",").map((spec) => spec.trim()).filter(Boolean).map((raw) => {
118
+ const isType = forceType || raw.startsWith("type ");
119
+ const asParts = (isType ? raw.replace(/^type\s+/, "") : raw).split(/\s+as\s+/);
120
+ return {
121
+ imported: asParts[0]?.trim() ?? "",
122
+ local: (asParts[1] || asParts[0] || "").trim(),
123
+ isType,
124
+ raw
125
+ };
126
+ }).filter((spec) => spec.imported.length > 0 && spec.local.length > 0);
127
+ }
128
+ function parseGoogleFontImportClause(clause) {
129
+ const trimmed = clause.trim();
130
+ if (trimmed.startsWith("type ")) {
131
+ const braceStart = trimmed.indexOf("{");
132
+ const braceEnd = trimmed.lastIndexOf("}");
133
+ if (braceStart === -1 || braceEnd === -1) return {
134
+ defaultLocal: null,
135
+ namespaceLocal: null,
136
+ named: []
137
+ };
138
+ return {
139
+ defaultLocal: null,
140
+ namespaceLocal: null,
141
+ named: parseGoogleFontNamedSpecifiers(trimmed.slice(braceStart + 1, braceEnd), true)
142
+ };
143
+ }
144
+ const braceStart = trimmed.indexOf("{");
145
+ const braceEnd = trimmed.lastIndexOf("}");
146
+ if (braceStart !== -1 && braceEnd !== -1) return {
147
+ defaultLocal: trimmed.slice(0, braceStart).trim().replace(/,\s*$/, "").trim() || null,
148
+ namespaceLocal: null,
149
+ named: parseGoogleFontNamedSpecifiers(trimmed.slice(braceStart + 1, braceEnd))
150
+ };
151
+ const commaIndex = trimmed.indexOf(",");
152
+ if (commaIndex !== -1) {
153
+ const defaultLocal = trimmed.slice(0, commaIndex).trim() || null;
154
+ const rest = trimmed.slice(commaIndex + 1).trim();
155
+ if (rest.startsWith("* as ")) return {
156
+ defaultLocal,
157
+ namespaceLocal: rest.slice(5).trim() || null,
158
+ named: []
159
+ };
160
+ }
161
+ if (trimmed.startsWith("* as ")) return {
162
+ defaultLocal: null,
163
+ namespaceLocal: trimmed.slice(5).trim() || null,
164
+ named: []
165
+ };
166
+ return {
167
+ defaultLocal: trimmed || null,
168
+ namespaceLocal: null,
169
+ named: []
170
+ };
171
+ }
172
+ function propertyNameToGoogleFontFamily(prop) {
173
+ return prop.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2");
174
+ }
175
+ /**
176
+ * Fetch Google Fonts CSS, download .woff2 files, cache locally, and return
177
+ * @font-face CSS with local file references.
178
+ *
179
+ * Cache dir structure: .vinext/fonts/<family-hash>/
180
+ * - style.css (the rewritten @font-face CSS)
181
+ * - *.woff2 (downloaded font files)
182
+ */
183
+ async function fetchAndCacheFont(cssUrl, family, cacheDir) {
184
+ const { createHash } = await import("node:crypto");
185
+ const urlHash = createHash("md5").update(cssUrl).digest("hex").slice(0, 12);
186
+ const fontDir = path.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${urlHash}`);
187
+ const cachedCSSPath = path.join(fontDir, "style.css");
188
+ if (fs.existsSync(cachedCSSPath)) return fs.readFileSync(cachedCSSPath, "utf-8");
189
+ const cssResponse = await fetch(cssUrl, { headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" } });
190
+ if (!cssResponse.ok) throw new Error(`Failed to fetch Google Fonts CSS: ${cssResponse.status}`);
191
+ let css = await cssResponse.text();
192
+ const urlRe = /url\((https:\/\/fonts\.gstatic\.com\/[^)]+)\)/g;
193
+ const urls = /* @__PURE__ */ new Map();
194
+ let urlMatch;
195
+ while ((urlMatch = urlRe.exec(css)) !== null) {
196
+ const fontUrl = urlMatch[1];
197
+ if (!urls.has(fontUrl)) {
198
+ const ext = fontUrl.includes(".woff2") ? ".woff2" : fontUrl.includes(".woff") ? ".woff" : ".ttf";
199
+ const fileHash = createHash("md5").update(fontUrl).digest("hex").slice(0, 8);
200
+ urls.set(fontUrl, `${family.toLowerCase().replace(/\s+/g, "-")}-${fileHash}${ext}`);
201
+ }
202
+ }
203
+ fs.mkdirSync(fontDir, { recursive: true });
204
+ for (const [fontUrl, filename] of urls) {
205
+ const filePath = path.join(fontDir, filename);
206
+ if (!fs.existsSync(filePath)) {
207
+ const fontResponse = await fetch(fontUrl);
208
+ if (fontResponse.ok) {
209
+ const buffer = Buffer.from(await fontResponse.arrayBuffer());
210
+ fs.writeFileSync(filePath, buffer);
211
+ }
212
+ }
213
+ css = css.split(fontUrl).join(filePath);
214
+ }
215
+ fs.writeFileSync(cachedCSSPath, css);
216
+ return css;
217
+ }
218
+ /**
219
+ * Create the `vinext:google-fonts` Vite plugin.
220
+ *
221
+ * @param fontGoogleShimPath - Absolute path to the font-google shim module
222
+ * (either `.ts` in source or `.js` in built packages). Resolved by the caller
223
+ * so the plugin file has no dependency on `__dirname`.
224
+ * @param shimsDir - Absolute path to the shims directory. Used to skip shim
225
+ * files from transform (they contain `next/font/google` references that must
226
+ * not be rewritten).
227
+ */
228
+ function createGoogleFontsPlugin(fontGoogleShimPath, shimsDir) {
229
+ let isBuild = false;
230
+ const fontCache = /* @__PURE__ */ new Map();
231
+ let cacheDir = "";
232
+ return {
233
+ name: "vinext:google-fonts",
234
+ enforce: "pre",
235
+ configResolved(config) {
236
+ isBuild = config.command === "build";
237
+ cacheDir = path.join(config.root, ".vinext", "fonts");
238
+ },
239
+ transform: {
240
+ filter: {
241
+ id: { include: /\.(tsx?|jsx?|mjs)$/ },
242
+ code: "next/font/google"
243
+ },
244
+ async handler(code, id) {
245
+ if (id.startsWith("\0")) return null;
246
+ if (!id.match(/\.(tsx?|jsx?|mjs)$/)) return null;
247
+ if (!code.includes("next/font/google")) return null;
248
+ if (id.startsWith(shimsDir)) return null;
249
+ const s = new MagicString(code);
250
+ let hasChanges = false;
251
+ let proxyImportCounter = 0;
252
+ const overwrittenRanges = [];
253
+ const fontLocals = /* @__PURE__ */ new Map();
254
+ const proxyObjectLocals = /* @__PURE__ */ new Set();
255
+ const importRe = /^[ \t]*import\s+([^;]+?)\s+from\s*(["'])next\/font\/google\2\s*;?/gm;
256
+ let importMatch;
257
+ while ((importMatch = importRe.exec(code)) !== null) {
258
+ const [fullMatch, clause] = importMatch;
259
+ const matchStart = importMatch.index;
260
+ const matchEnd = matchStart + fullMatch.length;
261
+ const parsed = parseGoogleFontImportClause(clause);
262
+ const utilityImports = parsed.named.filter((spec) => !spec.isType && GOOGLE_FONT_UTILITY_EXPORTS.has(spec.imported));
263
+ const fontImports = parsed.named.filter((spec) => !spec.isType && !GOOGLE_FONT_UTILITY_EXPORTS.has(spec.imported));
264
+ if (parsed.defaultLocal) proxyObjectLocals.add(parsed.defaultLocal);
265
+ for (const fontImport of fontImports) fontLocals.set(fontImport.local, fontImport.imported);
266
+ if (fontImports.length > 0) {
267
+ const virtualId = encodeGoogleFontsVirtualId({
268
+ hasDefault: Boolean(parsed.defaultLocal),
269
+ fonts: Array.from(new Set(fontImports.map((spec) => spec.imported))),
270
+ utilities: Array.from(new Set(utilityImports.map((spec) => spec.imported)))
271
+ });
272
+ s.overwrite(matchStart, matchEnd, `import ${clause} from ${JSON.stringify(virtualId)};`);
273
+ overwrittenRanges.push([matchStart, matchEnd]);
274
+ hasChanges = true;
275
+ continue;
276
+ }
277
+ if (parsed.namespaceLocal) {
278
+ const proxyImportName = `__vinext_google_fonts_proxy_${proxyImportCounter++}`;
279
+ const replacementLines = [`import ${proxyImportName} from ${JSON.stringify(fontGoogleShimPath)};`];
280
+ if (parsed.defaultLocal) replacementLines.push(`var ${parsed.defaultLocal} = ${proxyImportName};`);
281
+ replacementLines.push(`var ${parsed.namespaceLocal} = ${proxyImportName};`);
282
+ s.overwrite(matchStart, matchEnd, replacementLines.join("\n"));
283
+ overwrittenRanges.push([matchStart, matchEnd]);
284
+ proxyObjectLocals.add(parsed.namespaceLocal);
285
+ hasChanges = true;
286
+ }
287
+ }
288
+ const exportRe = /^[ \t]*export\s*\{([^}]+)\}\s*from\s*(["'])next\/font\/google\2\s*;?/gm;
289
+ let exportMatch;
290
+ while ((exportMatch = exportRe.exec(code)) !== null) {
291
+ const [fullMatch, specifiers] = exportMatch;
292
+ const matchStart = exportMatch.index;
293
+ const matchEnd = matchStart + fullMatch.length;
294
+ const namedExports = parseGoogleFontNamedSpecifiers(specifiers);
295
+ const utilityExports = namedExports.filter((spec) => !spec.isType && GOOGLE_FONT_UTILITY_EXPORTS.has(spec.imported));
296
+ const fontExports = namedExports.filter((spec) => !spec.isType && !GOOGLE_FONT_UTILITY_EXPORTS.has(spec.imported));
297
+ if (fontExports.length === 0) continue;
298
+ const virtualId = encodeGoogleFontsVirtualId({
299
+ hasDefault: false,
300
+ fonts: Array.from(new Set(fontExports.map((spec) => spec.imported))),
301
+ utilities: Array.from(new Set(utilityExports.map((spec) => spec.imported)))
302
+ });
303
+ s.overwrite(matchStart, matchEnd, `export { ${specifiers.trim()} } from ${JSON.stringify(virtualId)};`);
304
+ overwrittenRanges.push([matchStart, matchEnd]);
305
+ hasChanges = true;
306
+ }
307
+ async function injectSelfHostedCss(callStart, callEnd, optionsStr, family, calleeSource) {
308
+ let options = {};
309
+ try {
310
+ const parsed = parseStaticObjectLiteral(optionsStr);
311
+ if (!parsed) return;
312
+ options = parsed;
313
+ } catch {
314
+ return;
315
+ }
316
+ const weights = options.weight ? Array.isArray(options.weight) ? options.weight : [options.weight] : [];
317
+ const styles = options.style ? Array.isArray(options.style) ? options.style : [options.style] : [];
318
+ const display = options.display ?? "swap";
319
+ let spec = family.replace(/\s+/g, "+");
320
+ if (weights.length > 0) if (styles.includes("italic")) {
321
+ const pairs = [];
322
+ for (const w of weights) {
323
+ pairs.push(`0,${w}`);
324
+ pairs.push(`1,${w}`);
325
+ }
326
+ spec += `:ital,wght@${pairs.join(";")}`;
327
+ } else spec += `:wght@${weights.join(";")}`;
328
+ else if (styles.length === 0) spec += `:wght@100..900`;
329
+ const params = new URLSearchParams();
330
+ params.set("family", spec);
331
+ params.set("display", display);
332
+ const cssUrl = `https://fonts.googleapis.com/css2?${params.toString()}`;
333
+ let localCSS = fontCache.get(cssUrl);
334
+ if (!localCSS) try {
335
+ localCSS = await fetchAndCacheFont(cssUrl, family, cacheDir);
336
+ fontCache.set(cssUrl, localCSS);
337
+ } catch {
338
+ return;
339
+ }
340
+ const escapedCSS = JSON.stringify(localCSS);
341
+ const closingBrace = optionsStr.lastIndexOf("}");
342
+ const replacement = `${calleeSource}(${optionsStr.slice(0, closingBrace) + (optionsStr.slice(0, closingBrace).trim().endsWith("{") ? "" : ", ") + `_selfHostedCSS: ${escapedCSS}` + optionsStr.slice(closingBrace)})`;
343
+ s.overwrite(callStart, callEnd, replacement);
344
+ hasChanges = true;
345
+ }
346
+ if (isBuild) {
347
+ const namedCallRe = /\b([A-Za-z_$][A-Za-z0-9_$]*)\s*\(\s*(\{[^}]*\})\s*\)/g;
348
+ let namedCallMatch;
349
+ while ((namedCallMatch = namedCallRe.exec(code)) !== null) {
350
+ const [fullMatch, localName, optionsStr] = namedCallMatch;
351
+ const importedName = fontLocals.get(localName);
352
+ if (!importedName) continue;
353
+ const callStart = namedCallMatch.index;
354
+ const callEnd = callStart + fullMatch.length;
355
+ if (overwrittenRanges.some(([start, end]) => callStart < end && callEnd > start)) continue;
356
+ await injectSelfHostedCss(callStart, callEnd, optionsStr, importedName.replace(/_/g, " "), localName);
357
+ }
358
+ const memberCallRe = /\b([A-Za-z_$][A-Za-z0-9_$]*)\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\(\s*(\{[^}]*\})\s*\)/g;
359
+ let memberCallMatch;
360
+ while ((memberCallMatch = memberCallRe.exec(code)) !== null) {
361
+ const [fullMatch, objectName, propName, optionsStr] = memberCallMatch;
362
+ if (!proxyObjectLocals.has(objectName)) continue;
363
+ const callStart = memberCallMatch.index;
364
+ const callEnd = callStart + fullMatch.length;
365
+ if (overwrittenRanges.some(([start, end]) => callStart < end && callEnd > start)) continue;
366
+ await injectSelfHostedCss(callStart, callEnd, optionsStr, propertyNameToGoogleFontFamily(propName), `${objectName}.${propName}`);
367
+ }
368
+ }
369
+ if (!hasChanges) return null;
370
+ return {
371
+ code: s.toString(),
372
+ map: s.generateMap({ hires: "boundary" })
373
+ };
374
+ }
375
+ }
376
+ };
377
+ }
378
+ /**
379
+ * Create the `vinext:local-fonts` Vite plugin.
380
+ *
381
+ * Rewrites relative font file paths in `next/font/local` calls into Vite
382
+ * asset import references so that both dev (/@fs/...) and prod
383
+ * (/assets/font-xxx.woff2) URLs resolve correctly.
384
+ */
385
+ function createLocalFontsPlugin() {
386
+ return {
387
+ name: "vinext:local-fonts",
388
+ enforce: "pre",
389
+ transform: {
390
+ filter: {
391
+ id: {
392
+ include: /\.(tsx?|jsx?|mjs)$/,
393
+ exclude: /node_modules/
394
+ },
395
+ code: "next/font/local"
396
+ },
397
+ handler(code, id) {
398
+ if (id.includes("node_modules")) return null;
399
+ if (id.startsWith("\0")) return null;
400
+ if (!id.match(/\.(tsx?|jsx?|mjs)$/)) return null;
401
+ if (!code.includes("next/font/local")) return null;
402
+ if (id.includes("font-local")) return null;
403
+ if (!/import\s+\w+\s+from\s*['"]next\/font\/local['"]/.test(code)) return null;
404
+ const s = new MagicString(code);
405
+ let hasChanges = false;
406
+ let fontImportCounter = 0;
407
+ const imports = [];
408
+ const fontPathRe = /((?:path|src)\s*:\s*)(['"])([^'"]+\.(?:woff2?|ttf|otf|eot))\2/g;
409
+ let match;
410
+ while ((match = fontPathRe.exec(code)) !== null) {
411
+ const [fullMatch, prefix, _quote, fontPath] = match;
412
+ const varName = `__vinext_local_font_${fontImportCounter++}`;
413
+ imports.push(`import ${varName} from ${JSON.stringify(fontPath)};`);
414
+ const matchStart = match.index;
415
+ const matchEnd = matchStart + fullMatch.length;
416
+ s.overwrite(matchStart, matchEnd, `${prefix}${varName}`);
417
+ hasChanges = true;
418
+ }
419
+ if (!hasChanges) return null;
420
+ s.prepend(imports.join("\n") + "\n");
421
+ return {
422
+ code: s.toString(),
423
+ map: s.generateMap({ hires: "boundary" })
424
+ };
425
+ }
426
+ }
427
+ };
428
+ }
429
+ //#endregion
430
+ export { GOOGLE_FONT_UTILITY_EXPORTS, RESOLVED_VIRTUAL_GOOGLE_FONTS, VIRTUAL_GOOGLE_FONTS, createGoogleFontsPlugin, createLocalFontsPlugin, generateGoogleFontsVirtualModule, parseStaticObjectLiteral };
431
+
432
+ //# sourceMappingURL=fonts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fonts.js","names":[],"sources":["../../src/plugins/fonts.ts"],"sourcesContent":["/**\n * vinext font plugins\n *\n * Exports two Vite plugins:\n *\n * `createGoogleFontsPlugin` — vinext:google-fonts\n * 1. Rewrites named `next/font/google` imports/exports to tiny virtual modules\n * that export only the requested fonts plus any utility exports. This lets us\n * delete the generated ~1,900-line runtime catalog while keeping ESM import\n * semantics intact.\n * 2. During production builds, fetches Google Fonts CSS + font files, caches\n * them locally under `.vinext/fonts/`, and injects `_selfHostedCSS` into\n * statically analyzable font loader calls so fonts are served from the\n * deployed origin rather than fonts.googleapis.com.\n *\n * `createLocalFontsPlugin` — vinext:local-fonts\n * When a source file calls localFont({ src: \"./font.woff2\" }) or\n * localFont({ src: [{ path: \"./font.woff2\" }] }), the relative paths\n * won't resolve in the browser because the CSS is injected at runtime.\n * This plugin rewrites those path strings into Vite asset import references\n * so that both dev (/@fs/...) and prod (/assets/font-xxx.woff2) URLs are\n * correct.\n */\n\nimport type { Plugin } from \"vite\";\nimport { parseAst } from \"vite\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport MagicString from \"magic-string\";\n\n// ── Virtual module IDs ────────────────────────────────────────────────────────\n\nexport const VIRTUAL_GOOGLE_FONTS = \"virtual:vinext-google-fonts\";\nexport const RESOLVED_VIRTUAL_GOOGLE_FONTS = \"\\0\" + VIRTUAL_GOOGLE_FONTS;\n\n// ── Constants ─────────────────────────────────────────────────────────────────\n\n// IMPORTANT: keep this set in sync with the non-default exports from\n// packages/vinext/src/shims/font-google.ts (and its re-export barrel).\nexport const GOOGLE_FONT_UTILITY_EXPORTS = new Set([\n \"buildGoogleFontsUrl\",\n \"getSSRFontLinks\",\n \"getSSRFontStyles\",\n \"getSSRFontPreloads\",\n \"createFontLoader\",\n]);\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\ntype GoogleFontNamedSpecifier = {\n imported: string;\n local: string;\n isType: boolean;\n raw: string;\n};\n\n// ── Helpers shared with index.ts ──────────────────────────────────────────────\n\n/**\n * Safely parse a static JS object literal string into a plain object.\n * Uses Vite's parseAst (Rollup/acorn) so no code is ever evaluated.\n * Returns null if the expression contains anything dynamic (function calls,\n * template literals, identifiers, computed properties, etc.).\n *\n * Supports: string literals, numeric literals, boolean literals,\n * arrays of the above, and nested object literals.\n */\nexport function parseStaticObjectLiteral(objectStr: string): Record<string, unknown> | null {\n let ast: ReturnType<typeof parseAst>;\n try {\n // Wrap in parens so the parser treats `{…}` as an expression, not a block\n ast = parseAst(`(${objectStr})`);\n } catch {\n return null;\n }\n\n // The AST should be: Program > ExpressionStatement > ObjectExpression\n const body = ast.body;\n if (body.length !== 1 || body[0].type !== \"ExpressionStatement\") return null;\n\n const expr = body[0].expression;\n if (expr.type !== \"ObjectExpression\") return null;\n\n const result = extractStaticValue(expr);\n return result === undefined ? null : (result as Record<string, unknown>);\n}\n\n/**\n * Recursively extract a static value from an ESTree AST node.\n * Returns undefined (not null) if the node contains any dynamic expression.\n *\n * Uses `any` for the node parameter because Rollup's internal ESTree types\n * (estree.Expression, estree.ObjectExpression, etc.) aren't re-exported by Vite,\n * and the recursive traversal touches many different node shapes.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction extractStaticValue(node: any): unknown {\n switch (node.type) {\n case \"Literal\":\n // String, number, boolean, null\n return node.value;\n\n case \"UnaryExpression\":\n // Handle negative numbers: -1, -3.14\n if (\n node.operator === \"-\" &&\n node.argument?.type === \"Literal\" &&\n typeof node.argument.value === \"number\"\n ) {\n return -node.argument.value;\n }\n return undefined;\n\n case \"ArrayExpression\": {\n const arr: unknown[] = [];\n for (const elem of node.elements) {\n if (!elem) return undefined; // sparse array\n const val = extractStaticValue(elem);\n if (val === undefined) return undefined;\n arr.push(val);\n }\n return arr;\n }\n\n case \"ObjectExpression\": {\n const obj: Record<string, unknown> = {};\n for (const prop of node.properties) {\n if (prop.type !== \"Property\") return undefined; // SpreadElement etc.\n if (prop.computed) return undefined; // [expr]: val\n\n // Key can be Identifier (unquoted) or Literal (quoted)\n let key: string;\n if (prop.key.type === \"Identifier\") {\n key = prop.key.name;\n } else if (prop.key.type === \"Literal\" && typeof prop.key.value === \"string\") {\n key = prop.key.value;\n } else {\n return undefined;\n }\n\n const val = extractStaticValue(prop.value);\n if (val === undefined) return undefined;\n obj[key] = val;\n }\n return obj;\n }\n\n default:\n // TemplateLiteral, CallExpression, Identifier, etc. — reject\n return undefined;\n }\n}\n\n// ── Virtual module encoding/decoding ─────────────────────────────────────────\n\nfunction encodeGoogleFontsVirtualId(payload: {\n hasDefault: boolean;\n fonts: string[];\n utilities: string[];\n}): string {\n const params = new URLSearchParams();\n if (payload.hasDefault) params.set(\"default\", \"1\");\n if (payload.fonts.length > 0) params.set(\"fonts\", payload.fonts.join(\",\"));\n if (payload.utilities.length > 0) params.set(\"utilities\", payload.utilities.join(\",\"));\n return `${VIRTUAL_GOOGLE_FONTS}?${params.toString()}`;\n}\n\nfunction parseGoogleFontsVirtualId(id: string): {\n hasDefault: boolean;\n fonts: string[];\n utilities: string[];\n} | null {\n const cleanId = id.startsWith(\"\\0\") ? id.slice(1) : id;\n if (!cleanId.startsWith(VIRTUAL_GOOGLE_FONTS)) return null;\n const queryIndex = cleanId.indexOf(\"?\");\n const params = new URLSearchParams(queryIndex === -1 ? \"\" : cleanId.slice(queryIndex + 1));\n return {\n hasDefault: params.get(\"default\") === \"1\",\n fonts:\n params\n .get(\"fonts\")\n ?.split(\",\")\n .map((value) => value.trim())\n .filter(Boolean) ?? [],\n utilities:\n params\n .get(\"utilities\")\n ?.split(\",\")\n .map((value) => value.trim())\n .filter(Boolean) ?? [],\n };\n}\n\nexport function generateGoogleFontsVirtualModule(\n id: string,\n fontGoogleShimPath: string,\n): string | null {\n const payload = parseGoogleFontsVirtualId(id);\n if (!payload) return null;\n\n const utilities = Array.from(new Set(payload.utilities));\n const fonts = Array.from(new Set(payload.fonts));\n const lines: string[] = [];\n\n lines.push(`import { createFontLoader } from ${JSON.stringify(fontGoogleShimPath)};`);\n\n const reExports: string[] = [];\n if (payload.hasDefault) reExports.push(\"default\");\n reExports.push(...utilities);\n if (reExports.length > 0) {\n lines.push(`export { ${reExports.join(\", \")} } from ${JSON.stringify(fontGoogleShimPath)};`);\n }\n\n for (const fontName of fonts) {\n const family = fontName.replace(/_/g, \" \");\n lines.push(\n `export const ${fontName} = /*#__PURE__*/ createFontLoader(${JSON.stringify(family)});`,\n );\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n// ── Import clause parsers ─────────────────────────────────────────────────────\n\nfunction parseGoogleFontNamedSpecifiers(\n specifiersStr: string,\n forceType = false,\n): GoogleFontNamedSpecifier[] {\n return specifiersStr\n .split(\",\")\n .map((spec) => spec.trim())\n .filter(Boolean)\n .map((raw) => {\n const isType = forceType || raw.startsWith(\"type \");\n const valueSpec = isType ? raw.replace(/^type\\s+/, \"\") : raw;\n const asParts = valueSpec.split(/\\s+as\\s+/);\n const imported = asParts[0]?.trim() ?? \"\";\n const local = (asParts[1] || asParts[0] || \"\").trim();\n return { imported, local, isType, raw };\n })\n .filter((spec) => spec.imported.length > 0 && spec.local.length > 0);\n}\n\nfunction parseGoogleFontImportClause(clause: string): {\n defaultLocal: string | null;\n namespaceLocal: string | null;\n named: GoogleFontNamedSpecifier[];\n} {\n const trimmed = clause.trim();\n\n if (trimmed.startsWith(\"type \")) {\n const braceStart = trimmed.indexOf(\"{\");\n const braceEnd = trimmed.lastIndexOf(\"}\");\n if (braceStart === -1 || braceEnd === -1) {\n return { defaultLocal: null, namespaceLocal: null, named: [] };\n }\n return {\n defaultLocal: null,\n namespaceLocal: null,\n named: parseGoogleFontNamedSpecifiers(trimmed.slice(braceStart + 1, braceEnd), true),\n };\n }\n\n const braceStart = trimmed.indexOf(\"{\");\n const braceEnd = trimmed.lastIndexOf(\"}\");\n if (braceStart !== -1 && braceEnd !== -1) {\n const beforeNamed = trimmed.slice(0, braceStart).trim().replace(/,\\s*$/, \"\").trim();\n return {\n defaultLocal: beforeNamed || null,\n namespaceLocal: null,\n named: parseGoogleFontNamedSpecifiers(trimmed.slice(braceStart + 1, braceEnd)),\n };\n }\n\n const commaIndex = trimmed.indexOf(\",\");\n if (commaIndex !== -1) {\n const defaultLocal = trimmed.slice(0, commaIndex).trim() || null;\n const rest = trimmed.slice(commaIndex + 1).trim();\n if (rest.startsWith(\"* as \")) {\n return {\n defaultLocal,\n namespaceLocal: rest.slice(\"* as \".length).trim() || null,\n named: [],\n };\n }\n }\n\n if (trimmed.startsWith(\"* as \")) {\n return {\n defaultLocal: null,\n namespaceLocal: trimmed.slice(\"* as \".length).trim() || null,\n named: [],\n };\n }\n\n return {\n defaultLocal: trimmed || null,\n namespaceLocal: null,\n named: [],\n };\n}\n\nfunction propertyNameToGoogleFontFamily(prop: string): string {\n return prop.replace(/_/g, \" \").replace(/([a-z])([A-Z])/g, \"$1 $2\");\n}\n\n// ── Font fetching and caching ─────────────────────────────────────────────────\n\n/**\n * Fetch Google Fonts CSS, download .woff2 files, cache locally, and return\n * @font-face CSS with local file references.\n *\n * Cache dir structure: .vinext/fonts/<family-hash>/\n * - style.css (the rewritten @font-face CSS)\n * - *.woff2 (downloaded font files)\n */\nasync function fetchAndCacheFont(\n cssUrl: string,\n family: string,\n cacheDir: string,\n): Promise<string> {\n // Use a hash of the URL for the cache key\n const { createHash } = await import(\"node:crypto\");\n const urlHash = createHash(\"md5\").update(cssUrl).digest(\"hex\").slice(0, 12);\n const fontDir = path.join(cacheDir, `${family.toLowerCase().replace(/\\s+/g, \"-\")}-${urlHash}`);\n\n // Check if already cached\n const cachedCSSPath = path.join(fontDir, \"style.css\");\n if (fs.existsSync(cachedCSSPath)) {\n return fs.readFileSync(cachedCSSPath, \"utf-8\");\n }\n\n // Fetch CSS from Google Fonts (woff2 user-agent gives woff2 URLs)\n const cssResponse = await fetch(cssUrl, {\n headers: {\n \"User-Agent\":\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\",\n },\n });\n if (!cssResponse.ok) {\n throw new Error(`Failed to fetch Google Fonts CSS: ${cssResponse.status}`);\n }\n let css = await cssResponse.text();\n\n // Extract all font file URLs\n const urlRe = /url\\((https:\\/\\/fonts\\.gstatic\\.com\\/[^)]+)\\)/g;\n const urls = new Map<string, string>(); // original URL -> local filename\n let urlMatch;\n while ((urlMatch = urlRe.exec(css)) !== null) {\n const fontUrl = urlMatch[1];\n if (!urls.has(fontUrl)) {\n const ext = fontUrl.includes(\".woff2\")\n ? \".woff2\"\n : fontUrl.includes(\".woff\")\n ? \".woff\"\n : \".ttf\";\n const fileHash = createHash(\"md5\").update(fontUrl).digest(\"hex\").slice(0, 8);\n urls.set(fontUrl, `${family.toLowerCase().replace(/\\s+/g, \"-\")}-${fileHash}${ext}`);\n }\n }\n\n // Download font files\n fs.mkdirSync(fontDir, { recursive: true });\n for (const [fontUrl, filename] of urls) {\n const filePath = path.join(fontDir, filename);\n if (!fs.existsSync(filePath)) {\n const fontResponse = await fetch(fontUrl);\n if (fontResponse.ok) {\n const buffer = Buffer.from(await fontResponse.arrayBuffer());\n fs.writeFileSync(filePath, buffer);\n }\n }\n // Rewrite CSS to use absolute path (Vite will resolve /@fs/ for dev, or asset for build)\n css = css.split(fontUrl).join(filePath);\n }\n\n // Cache the rewritten CSS\n fs.writeFileSync(cachedCSSPath, css);\n return css;\n}\n\n// ── Plugin factories ──────────────────────────────────────────────────────────\n\n/**\n * Create the `vinext:google-fonts` Vite plugin.\n *\n * @param fontGoogleShimPath - Absolute path to the font-google shim module\n * (either `.ts` in source or `.js` in built packages). Resolved by the caller\n * so the plugin file has no dependency on `__dirname`.\n * @param shimsDir - Absolute path to the shims directory. Used to skip shim\n * files from transform (they contain `next/font/google` references that must\n * not be rewritten).\n */\nexport function createGoogleFontsPlugin(fontGoogleShimPath: string, shimsDir: string): Plugin {\n // Vite does not bind `this` to the plugin object when calling hooks, so\n // plugin state must be held in closure variables rather than as properties.\n let isBuild = false;\n const fontCache = new Map<string, string>(); // url -> local @font-face CSS\n let cacheDir = \"\";\n\n return {\n name: \"vinext:google-fonts\",\n enforce: \"pre\",\n\n configResolved(config) {\n isBuild = config.command === \"build\";\n cacheDir = path.join(config.root, \".vinext\", \"fonts\");\n },\n\n transform: {\n // Hook filter: only invoke JS when code contains 'next/font/google'.\n // This still eliminates nearly all Rust-to-JS calls since very few files\n // import from next/font/google.\n filter: {\n id: {\n include: /\\.(tsx?|jsx?|mjs)$/,\n },\n code: \"next/font/google\",\n },\n async handler(code, id) {\n // Defensive guard — duplicates filter logic\n if (id.startsWith(\"\\0\")) return null;\n if (!id.match(/\\.(tsx?|jsx?|mjs)$/)) return null;\n if (!code.includes(\"next/font/google\")) return null;\n if (id.startsWith(shimsDir)) return null;\n\n const s = new MagicString(code);\n let hasChanges = false;\n let proxyImportCounter = 0;\n const overwrittenRanges: Array<[number, number]> = [];\n const fontLocals = new Map<string, string>();\n const proxyObjectLocals = new Set<string>();\n\n const importRe = /^[ \\t]*import\\s+([^;]+?)\\s+from\\s*([\"'])next\\/font\\/google\\2\\s*;?/gm;\n let importMatch;\n while ((importMatch = importRe.exec(code)) !== null) {\n const [fullMatch, clause] = importMatch;\n const matchStart = importMatch.index;\n const matchEnd = matchStart + fullMatch.length;\n const parsed = parseGoogleFontImportClause(clause);\n const utilityImports = parsed.named.filter(\n (spec) => !spec.isType && GOOGLE_FONT_UTILITY_EXPORTS.has(spec.imported),\n );\n const fontImports = parsed.named.filter(\n (spec) => !spec.isType && !GOOGLE_FONT_UTILITY_EXPORTS.has(spec.imported),\n );\n\n if (parsed.defaultLocal) {\n proxyObjectLocals.add(parsed.defaultLocal);\n }\n for (const fontImport of fontImports) {\n fontLocals.set(fontImport.local, fontImport.imported);\n }\n\n if (fontImports.length > 0) {\n const virtualId = encodeGoogleFontsVirtualId({\n hasDefault: Boolean(parsed.defaultLocal),\n fonts: Array.from(new Set(fontImports.map((spec) => spec.imported))),\n utilities: Array.from(new Set(utilityImports.map((spec) => spec.imported))),\n });\n s.overwrite(\n matchStart,\n matchEnd,\n `import ${clause} from ${JSON.stringify(virtualId)};`,\n );\n overwrittenRanges.push([matchStart, matchEnd]);\n hasChanges = true;\n continue;\n }\n\n if (parsed.namespaceLocal) {\n const proxyImportName = `__vinext_google_fonts_proxy_${proxyImportCounter++}`;\n const replacementLines = [\n `import ${proxyImportName} from ${JSON.stringify(fontGoogleShimPath)};`,\n ];\n if (parsed.defaultLocal) {\n replacementLines.push(`var ${parsed.defaultLocal} = ${proxyImportName};`);\n }\n replacementLines.push(`var ${parsed.namespaceLocal} = ${proxyImportName};`);\n s.overwrite(matchStart, matchEnd, replacementLines.join(\"\\n\"));\n overwrittenRanges.push([matchStart, matchEnd]);\n proxyObjectLocals.add(parsed.namespaceLocal);\n hasChanges = true;\n }\n }\n\n const exportRe = /^[ \\t]*export\\s*\\{([^}]+)\\}\\s*from\\s*([\"'])next\\/font\\/google\\2\\s*;?/gm;\n let exportMatch;\n while ((exportMatch = exportRe.exec(code)) !== null) {\n const [fullMatch, specifiers] = exportMatch;\n const matchStart = exportMatch.index;\n const matchEnd = matchStart + fullMatch.length;\n const namedExports = parseGoogleFontNamedSpecifiers(specifiers);\n const utilityExports = namedExports.filter(\n (spec) => !spec.isType && GOOGLE_FONT_UTILITY_EXPORTS.has(spec.imported),\n );\n const fontExports = namedExports.filter(\n (spec) => !spec.isType && !GOOGLE_FONT_UTILITY_EXPORTS.has(spec.imported),\n );\n if (fontExports.length === 0) continue;\n\n const virtualId = encodeGoogleFontsVirtualId({\n hasDefault: false,\n fonts: Array.from(new Set(fontExports.map((spec) => spec.imported))),\n utilities: Array.from(new Set(utilityExports.map((spec) => spec.imported))),\n });\n s.overwrite(\n matchStart,\n matchEnd,\n `export { ${specifiers.trim()} } from ${JSON.stringify(virtualId)};`,\n );\n overwrittenRanges.push([matchStart, matchEnd]);\n hasChanges = true;\n }\n\n async function injectSelfHostedCss(\n callStart: number,\n callEnd: number,\n optionsStr: string,\n family: string,\n calleeSource: string,\n ) {\n // Parse options safely via AST — no eval/new Function\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let options: Record<string, any> = {};\n try {\n const parsed = parseStaticObjectLiteral(optionsStr);\n if (!parsed) return; // Contains dynamic expressions, skip\n options = parsed as Record<string, any>;\n } catch {\n return; // Can't parse options statically, skip\n }\n\n // Build the Google Fonts CSS URL\n const weights = options.weight\n ? Array.isArray(options.weight)\n ? options.weight\n : [options.weight]\n : [];\n const styles = options.style\n ? Array.isArray(options.style)\n ? options.style\n : [options.style]\n : [];\n const display = options.display ?? \"swap\";\n\n let spec = family.replace(/\\s+/g, \"+\");\n if (weights.length > 0) {\n const hasItalic = styles.includes(\"italic\");\n if (hasItalic) {\n const pairs: string[] = [];\n for (const w of weights) {\n pairs.push(`0,${w}`);\n pairs.push(`1,${w}`);\n }\n spec += `:ital,wght@${pairs.join(\";\")}`;\n } else {\n spec += `:wght@${weights.join(\";\")}`;\n }\n } else if (styles.length === 0) {\n // Request full variable weight range when no weight specified.\n // Without this, Google Fonts returns only weight 400.\n spec += `:wght@100..900`;\n }\n const params = new URLSearchParams();\n params.set(\"family\", spec);\n params.set(\"display\", display);\n const cssUrl = `https://fonts.googleapis.com/css2?${params.toString()}`;\n\n // Check cache\n let localCSS = fontCache.get(cssUrl);\n if (!localCSS) {\n try {\n localCSS = await fetchAndCacheFont(cssUrl, family, cacheDir);\n fontCache.set(cssUrl, localCSS);\n } catch {\n // Fetch failed (offline?) — fall back to CDN mode\n return;\n }\n }\n\n // Inject _selfHostedCSS into the options object\n const escapedCSS = JSON.stringify(localCSS);\n const closingBrace = optionsStr.lastIndexOf(\"}\");\n const optionsWithCSS =\n optionsStr.slice(0, closingBrace) +\n (optionsStr.slice(0, closingBrace).trim().endsWith(\"{\") ? \"\" : \", \") +\n `_selfHostedCSS: ${escapedCSS}` +\n optionsStr.slice(closingBrace);\n\n const replacement = `${calleeSource}(${optionsWithCSS})`;\n s.overwrite(callStart, callEnd, replacement);\n hasChanges = true;\n }\n\n if (isBuild) {\n const namedCallRe = /\\b([A-Za-z_$][A-Za-z0-9_$]*)\\s*\\(\\s*(\\{[^}]*\\})\\s*\\)/g;\n let namedCallMatch;\n while ((namedCallMatch = namedCallRe.exec(code)) !== null) {\n const [fullMatch, localName, optionsStr] = namedCallMatch;\n const importedName = fontLocals.get(localName);\n if (!importedName) continue;\n\n const callStart = namedCallMatch.index;\n const callEnd = callStart + fullMatch.length;\n if (overwrittenRanges.some(([start, end]) => callStart < end && callEnd > start)) {\n continue;\n }\n\n await injectSelfHostedCss(\n callStart,\n callEnd,\n optionsStr,\n importedName.replace(/_/g, \" \"),\n localName,\n );\n }\n\n const memberCallRe =\n /\\b([A-Za-z_$][A-Za-z0-9_$]*)\\.([A-Za-z_$][A-Za-z0-9_$]*)\\s*\\(\\s*(\\{[^}]*\\})\\s*\\)/g;\n let memberCallMatch;\n while ((memberCallMatch = memberCallRe.exec(code)) !== null) {\n const [fullMatch, objectName, propName, optionsStr] = memberCallMatch;\n if (!proxyObjectLocals.has(objectName)) continue;\n\n const callStart = memberCallMatch.index;\n const callEnd = callStart + fullMatch.length;\n if (overwrittenRanges.some(([start, end]) => callStart < end && callEnd > start)) {\n continue;\n }\n\n await injectSelfHostedCss(\n callStart,\n callEnd,\n optionsStr,\n propertyNameToGoogleFontFamily(propName),\n `${objectName}.${propName}`,\n );\n }\n }\n\n if (!hasChanges) return null;\n return {\n code: s.toString(),\n map: s.generateMap({ hires: \"boundary\" }),\n };\n },\n },\n } satisfies Plugin;\n}\n\n/**\n * Create the `vinext:local-fonts` Vite plugin.\n *\n * Rewrites relative font file paths in `next/font/local` calls into Vite\n * asset import references so that both dev (/@fs/...) and prod\n * (/assets/font-xxx.woff2) URLs resolve correctly.\n */\nexport function createLocalFontsPlugin(): Plugin {\n return {\n name: \"vinext:local-fonts\",\n enforce: \"pre\",\n\n transform: {\n filter: {\n id: {\n include: /\\.(tsx?|jsx?|mjs)$/,\n exclude: /node_modules/,\n },\n code: \"next/font/local\",\n },\n handler(code, id) {\n // Defensive guards — duplicate filter logic\n if (id.includes(\"node_modules\")) return null;\n if (id.startsWith(\"\\0\")) return null;\n if (!id.match(/\\.(tsx?|jsx?|mjs)$/)) return null;\n if (!code.includes(\"next/font/local\")) return null;\n // Skip vinext's own font-local shim — it contains example paths\n // in comments that would be incorrectly rewritten.\n if (id.includes(\"font-local\")) return null;\n\n // Verify there's actually an import from next/font/local\n const importRe = /import\\s+\\w+\\s+from\\s*['\"]next\\/font\\/local['\"]/;\n if (!importRe.test(code)) return null;\n\n const s = new MagicString(code);\n let hasChanges = false;\n let fontImportCounter = 0;\n const imports: string[] = [];\n\n // Match font file paths in `path: \"...\"` or `src: \"...\"` properties.\n // Captures: (1) property+colon prefix, (2) quote char, (3) the path.\n const fontPathRe = /((?:path|src)\\s*:\\s*)(['\"])([^'\"]+\\.(?:woff2?|ttf|otf|eot))\\2/g;\n\n let match;\n while ((match = fontPathRe.exec(code)) !== null) {\n const [fullMatch, prefix, _quote, fontPath] = match;\n const varName = `__vinext_local_font_${fontImportCounter++}`;\n\n // Add an import for this font file — Vite resolves it as a static\n // asset and returns the correct URL for both dev and prod.\n imports.push(`import ${varName} from ${JSON.stringify(fontPath)};`);\n\n // Replace: path: \"./font.woff2\" -> path: __vinext_local_font_0\n const matchStart = match.index;\n const matchEnd = matchStart + fullMatch.length;\n s.overwrite(matchStart, matchEnd, `${prefix}${varName}`);\n hasChanges = true;\n }\n\n if (!hasChanges) return null;\n\n // Prepend the asset imports at the top of the file\n s.prepend(imports.join(\"\\n\") + \"\\n\");\n\n return {\n code: s.toString(),\n map: s.generateMap({ hires: \"boundary\" }),\n };\n },\n },\n } satisfies Plugin;\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/plugins/instrumentation-client.d.ts
4
+ declare function createInstrumentationClientTransformPlugin(getInstrumentationClientPath: () => string | null): Plugin;
5
+ //#endregion
6
+ export { createInstrumentationClientTransformPlugin };
7
+ //# sourceMappingURL=instrumentation-client.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { normalizePath, parseAst } from "vite";
2
+ import MagicString from "magic-string";
3
+ //#region src/plugins/instrumentation-client.ts
4
+ function createInstrumentationClientTransformPlugin(getInstrumentationClientPath) {
5
+ return {
6
+ name: "vinext:instrumentation-client",
7
+ apply: "serve",
8
+ transform(code, id) {
9
+ const instrumentationClientPath = getInstrumentationClientPath();
10
+ if (!instrumentationClientPath) return null;
11
+ if (normalizePath(id.split("?", 1)[0]) !== normalizePath(instrumentationClientPath)) return null;
12
+ if (code.includes("__vinextInstrumentationClientStart")) return null;
13
+ const ast = parseAst(code);
14
+ let insertPos = 0;
15
+ for (const node of ast.body) if (node.type === "ImportDeclaration") insertPos = node.end;
16
+ const s = new MagicString(code);
17
+ s.appendLeft(insertPos, "\nconst __vinextInstrumentationClientStart = performance.now();\n");
18
+ s.append("\nconst __vinextInstrumentationClientEnd = performance.now();\nconst __vinextInstrumentationClientDuration = __vinextInstrumentationClientEnd - __vinextInstrumentationClientStart;\n// Match Next.js: only report slow client instrumentation during dev.\n// Production should execute the hook without additional timing overhead.\nif (__vinextInstrumentationClientDuration > 16) {\n console.log(`[Client Instrumentation Hook] Slow execution detected: ${__vinextInstrumentationClientDuration.toFixed(0)}ms (Note: Code download overhead is not included in this measurement)`);\n}\n");
19
+ return {
20
+ code: s.toString(),
21
+ map: s.generateMap({ hires: true })
22
+ };
23
+ }
24
+ };
25
+ }
26
+ //#endregion
27
+ export { createInstrumentationClientTransformPlugin };
28
+
29
+ //# sourceMappingURL=instrumentation-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentation-client.js","names":[],"sources":["../../src/plugins/instrumentation-client.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { normalizePath, parseAst } from \"vite\";\nimport MagicString from \"magic-string\";\n\nexport function createInstrumentationClientTransformPlugin(\n getInstrumentationClientPath: () => string | null,\n): Plugin {\n return {\n name: \"vinext:instrumentation-client\",\n apply: \"serve\",\n transform(code, id) {\n const instrumentationClientPath = getInstrumentationClientPath();\n if (!instrumentationClientPath) return null;\n\n const normalizedId = normalizePath(id.split(\"?\", 1)[0]);\n if (normalizedId !== normalizePath(instrumentationClientPath)) return null;\n if (code.includes(\"__vinextInstrumentationClientStart\")) return null;\n\n const ast = parseAst(code);\n let insertPos = 0;\n // When the module has no imports, inject the timer at the top so the\n // measurement still wraps the full module body execution.\n for (const node of ast.body) {\n if (node.type === \"ImportDeclaration\") {\n insertPos = node.end;\n }\n }\n\n const s = new MagicString(code);\n s.appendLeft(insertPos, \"\\nconst __vinextInstrumentationClientStart = performance.now();\\n\");\n s.append(\n \"\\nconst __vinextInstrumentationClientEnd = performance.now();\\n\" +\n \"const __vinextInstrumentationClientDuration = __vinextInstrumentationClientEnd - __vinextInstrumentationClientStart;\\n\" +\n \"// Match Next.js: only report slow client instrumentation during dev.\\n\" +\n \"// Production should execute the hook without additional timing overhead.\\n\" +\n \"if (__vinextInstrumentationClientDuration > 16) {\\n\" +\n \" console.log(`[Client Instrumentation Hook] Slow execution detected: ${__vinextInstrumentationClientDuration.toFixed(0)}ms (Note: Code download overhead is not included in this measurement)`);\\n\" +\n \"}\\n\",\n );\n\n return {\n code: s.toString(),\n map: s.generateMap({ hires: true }),\n };\n },\n };\n}\n"],"mappings":";;;AAIA,SAAgB,2CACd,8BACQ;AACR,QAAO;EACL,MAAM;EACN,OAAO;EACP,UAAU,MAAM,IAAI;GAClB,MAAM,4BAA4B,8BAA8B;AAChE,OAAI,CAAC,0BAA2B,QAAO;AAGvC,OADqB,cAAc,GAAG,MAAM,KAAK,EAAE,CAAC,GAAG,KAClC,cAAc,0BAA0B,CAAE,QAAO;AACtE,OAAI,KAAK,SAAS,qCAAqC,CAAE,QAAO;GAEhE,MAAM,MAAM,SAAS,KAAK;GAC1B,IAAI,YAAY;AAGhB,QAAK,MAAM,QAAQ,IAAI,KACrB,KAAI,KAAK,SAAS,oBAChB,aAAY,KAAK;GAIrB,MAAM,IAAI,IAAI,YAAY,KAAK;AAC/B,KAAE,WAAW,WAAW,oEAAoE;AAC5F,KAAE,OACA,mkBAOD;AAED,UAAO;IACL,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,YAAY,EAAE,OAAO,MAAM,CAAC;IACpC;;EAEJ"}
@@ -0,0 +1,26 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/plugins/og-assets.d.ts
4
+ /**
5
+ * Create the `vinext:og-inline-fetch-assets` Vite plugin.
6
+ *
7
+ * Inlines binary assets that are runtime-fetched via
8
+ * `fetch(new URL("./asset", import.meta.url))` or read via
9
+ * `readFileSync(fileURLToPath(new URL("./asset", import.meta.url)))`.
10
+ * Both patterns are rewritten to inline base64 literals so the code works
11
+ * correctly inside Cloudflare Workers where `import.meta.url` is not a
12
+ * valid file URL.
13
+ */
14
+ declare function createOgInlineFetchAssetsPlugin(): Plugin;
15
+ /**
16
+ * The `vinext:og-assets` Vite plugin.
17
+ *
18
+ * Copies @vercel/og binary assets (e.g. resvg.wasm) to the RSC output
19
+ * directory for production builds. The edge build inlines fonts as base64 via
20
+ * `vinext:og-inline-fetch-assets`; this plugin is a safety net to ensure
21
+ * resvg.wasm exists in the output directory for the Node.js disk-read fallback.
22
+ */
23
+ declare const ogAssetsPlugin: Plugin;
24
+ //#endregion
25
+ export { createOgInlineFetchAssetsPlugin, ogAssetsPlugin };
26
+ //# sourceMappingURL=og-assets.d.ts.map