From 50a073fca881a719285ac70b9f397e569326f492 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Tue, 10 Mar 2026 19:21:22 -0700 Subject: [PATCH 1/4] perf: convert og-inline-fetch-assets transform to async I/O with per-build cache The `vinext:og-inline-fetch-assets` plugin used synchronous `fs.readFileSync()` in Vite's transform hook, blocking the transform pipeline. This converts both Pattern 1 (fetch inlining) and Pattern 2 (readFileSync inlining) to use `await fs.promises.readFile()` and adds a per-build `Map` cache so repeated reads of the same file (common with shared fonts) hit memory instead of disk. --- packages/vinext/src/index.ts | 37 ++++++---- tests/og-inline.test.ts | 138 +++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 tests/og-inline.test.ts diff --git a/packages/vinext/src/index.ts b/packages/vinext/src/index.ts index da966df5..01ecc292 100644 --- a/packages/vinext/src/index.ts +++ b/packages/vinext/src/index.ts @@ -700,6 +700,9 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { // Shim alias map — populated in config(), used by resolveId() for .js variants let nextShimMap: Record = {}; + // Per-build cache for og-inline-fetch-assets plugin to avoid repeated file reads + const _ogInlineCache = new Map(); + /** * Generate the virtual SSR server entry module. * This is the entry point for `vite build --ssr`. @@ -2835,7 +2838,7 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { { name: "vinext:og-inline-fetch-assets", enforce: "pre", - transform(code, id) { + async transform(code, id) { // Quick bail-out: only process modules that use new URL(..., import.meta.url) if (!code.includes("import.meta.url")) { return null; @@ -2856,12 +2859,16 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { const relPath = match[2]; // e.g. "./noto-sans-v27-latin-regular.ttf" const absPath = path.resolve(moduleDir, relPath); - let fileBase64: string; - try { - fileBase64 = fs.readFileSync(absPath).toString("base64"); - } catch { - // File not found on disk — skip (may be a runtime-only asset) - continue; + let fileBase64 = _ogInlineCache.get(absPath); + if (!fileBase64) { + try { + const buf = await fs.promises.readFile(absPath); + fileBase64 = buf.toString("base64"); + _ogInlineCache.set(absPath, fileBase64); + } catch { + // File not found on disk — skip (may be a runtime-only asset) + continue; + } } // Replace fetch(...).then(...) with an inline IIFE that returns Promise. @@ -2892,12 +2899,16 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { const relPath = match[2]; // e.g. "./noto-sans-v27-latin-regular.ttf" const absPath = path.resolve(moduleDir, relPath); - let fileBase64: string; - try { - fileBase64 = fs.readFileSync(absPath).toString("base64"); - } catch { - // File not found on disk — skip - continue; + let fileBase64 = _ogInlineCache.get(absPath); + if (!fileBase64) { + try { + const buf = await fs.promises.readFile(absPath); + fileBase64 = buf.toString("base64"); + _ogInlineCache.set(absPath, fileBase64); + } catch { + // File not found on disk — skip + continue; + } } // Replace readFileSync(...) with Buffer.from("", "base64"). diff --git a/tests/og-inline.test.ts b/tests/og-inline.test.ts new file mode 100644 index 00000000..c7883a0b --- /dev/null +++ b/tests/og-inline.test.ts @@ -0,0 +1,138 @@ +import { describe, it, expect, vi, beforeAll, afterAll } from "vitest"; +import vinext from "../packages/vinext/src/index.js"; +import type { Plugin } from "vite"; +import fs from "node:fs"; +import fsp from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; + +// ── Helpers ─────────────────────────────────────────────────── + +/** Unwrap a Vite plugin hook that may use the object-with-filter format */ +function unwrapHook(hook: any): Function { + return typeof hook === "function" ? hook : hook?.handler; +} + +/** Extract the vinext:og-inline-fetch-assets plugin from the plugin array */ +function getOgInlinePlugin(): Plugin { + const plugins = vinext() as Plugin[]; + const plugin = plugins.find((p) => p.name === "vinext:og-inline-fetch-assets"); + if (!plugin) throw new Error("vinext:og-inline-fetch-assets plugin not found"); + return plugin; +} + +// ── Test fixture setup ──────────────────────────────────────── + +let tmpDir: string; +let fontPath: string; +const fontContent = Buffer.from("fake-font-data-for-testing"); +const fontBase64 = fontContent.toString("base64"); + +beforeAll(async () => { + tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "og-inline-test-")); + fontPath = path.join(tmpDir, "noto-sans.ttf"); + await fsp.writeFile(fontPath, fontContent); +}); + +afterAll(async () => { + await fsp.rm(tmpDir, { recursive: true, force: true }); +}); + +// ── Tests ───────────────────────────────────────────────────── + +describe("vinext:og-inline-fetch-assets plugin", () => { + it("exists in the plugin array", () => { + const plugin = getOgInlinePlugin(); + expect(plugin.name).toBe("vinext:og-inline-fetch-assets"); + expect(plugin.enforce).toBe("pre"); + }); + + // ── Guard clause ────────────────────────────────────────── + + it("returns null when code has no import.meta.url", async () => { + const plugin = getOgInlinePlugin(); + const transform = unwrapHook(plugin.transform); + const code = `import fs from 'node:fs';\nconst x = 1;`; + const result = await transform.call(plugin, code, "/app/og.tsx"); + expect(result).toBeNull(); + }); + + // ── Pattern 1: fetch ───────────────────────────────────── + + it("transforms fetch(new URL(..., import.meta.url)).then(r => r.arrayBuffer())", async () => { + const plugin = getOgInlinePlugin(); + const transform = unwrapHook(plugin.transform); + const code = `const data = fetch(new URL("./noto-sans.ttf", import.meta.url)).then((res) => res.arrayBuffer());`; + const moduleId = path.join(tmpDir, "og.tsx"); + + const result = await transform.call(plugin, code, moduleId); + expect(result).not.toBeNull(); + expect(result.code).toContain(JSON.stringify(fontBase64)); + expect(result.code).toContain("Promise.resolve(a.buffer)"); + expect(result.code).not.toContain("fetch("); + }); + + // ── Pattern 2: readFileSync ────────────────────────────── + + it("transforms fs.readFileSync(fileURLToPath(new URL(..., import.meta.url)))", async () => { + const plugin = getOgInlinePlugin(); + const transform = unwrapHook(plugin.transform); + const code = `const buf = fs.readFileSync(fileURLToPath(new URL("./noto-sans.ttf", import.meta.url)));`; + const moduleId = path.join(tmpDir, "og.tsx"); + + const result = await transform.call(plugin, code, moduleId); + expect(result).not.toBeNull(); + expect(result.code).toContain(`Buffer.from(${JSON.stringify(fontBase64)},"base64")`); + expect(result.code).not.toContain("readFileSync"); + }); + + // ── File not found ─────────────────────────────────────── + + it("silently skips when the referenced file does not exist", async () => { + const plugin = getOgInlinePlugin(); + const transform = unwrapHook(plugin.transform); + const code = `const data = fetch(new URL("./nonexistent.ttf", import.meta.url)).then((res) => res.arrayBuffer());`; + const moduleId = path.join(tmpDir, "og.tsx"); + + const result = await transform.call(plugin, code, moduleId); + // No file found → no replacement → returns null + expect(result).toBeNull(); + }); + + // ── Async assertion ────────────────────────────────────── + + it("returns a Promise (hook is async)", () => { + const plugin = getOgInlinePlugin(); + const transform = unwrapHook(plugin.transform); + const code = `const data = fetch(new URL("./noto-sans.ttf", import.meta.url)).then((res) => res.arrayBuffer());`; + const moduleId = path.join(tmpDir, "og.tsx"); + + const result = transform.call(plugin, code, moduleId); + expect(result).toBeInstanceOf(Promise); + }); + + // ── Cache hit ──────────────────────────────────────────── + + it("reads the file only once for repeated transforms (cache hit)", async () => { + const readFileSpy = vi.spyOn(fs.promises, "readFile"); + + const plugin = getOgInlinePlugin(); + const transform = unwrapHook(plugin.transform); + const code = `const buf = fs.readFileSync(fileURLToPath(new URL("./noto-sans.ttf", import.meta.url)));`; + const moduleId = path.join(tmpDir, "og.tsx"); + + // First call — should read from disk + await transform.call(plugin, code, moduleId); + + // Second call — should use cache + await transform.call(plugin, code, moduleId); + + // fs.promises.readFile should have been called at most once for this path + const calls = readFileSpy.mock.calls.filter( + (call) => call[0] === path.join(tmpDir, "noto-sans.ttf"), + ); + expect(calls.length).toBe(1); + + readFileSpy.mockRestore(); + }); +}); From 7d3f8ccf5ed90d0b0346b36b67244001e07d9875 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Tue, 10 Mar 2026 21:03:38 -0700 Subject: [PATCH 2/4] fix: use strict undefined check in og-inline cache and restore spies via afterEach Replace falsy `if (!fileBase64)` with `if (fileBase64 === undefined)` in both og-inline patterns to correctly handle hypothetical empty-string cache values. Add `afterEach(() => vi.restoreAllMocks())` to prevent spy leaks on assertion failure, replacing the manual mockRestore() call. --- packages/vinext/src/index.ts | 4 ++-- tests/og-inline.test.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/vinext/src/index.ts b/packages/vinext/src/index.ts index 01ecc292..ab117a17 100644 --- a/packages/vinext/src/index.ts +++ b/packages/vinext/src/index.ts @@ -2860,7 +2860,7 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { const absPath = path.resolve(moduleDir, relPath); let fileBase64 = _ogInlineCache.get(absPath); - if (!fileBase64) { + if (fileBase64 === undefined) { try { const buf = await fs.promises.readFile(absPath); fileBase64 = buf.toString("base64"); @@ -2900,7 +2900,7 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { const absPath = path.resolve(moduleDir, relPath); let fileBase64 = _ogInlineCache.get(absPath); - if (!fileBase64) { + if (fileBase64 === undefined) { try { const buf = await fs.promises.readFile(absPath); fileBase64 = buf.toString("base64"); diff --git a/tests/og-inline.test.ts b/tests/og-inline.test.ts index c7883a0b..33574d08 100644 --- a/tests/og-inline.test.ts +++ b/tests/og-inline.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeAll, afterAll } from "vitest"; +import { describe, it, expect, vi, beforeAll, afterAll, afterEach } from "vitest"; import vinext from "../packages/vinext/src/index.js"; import type { Plugin } from "vite"; import fs from "node:fs"; @@ -41,6 +41,8 @@ afterAll(async () => { // ── Tests ───────────────────────────────────────────────────── describe("vinext:og-inline-fetch-assets plugin", () => { + afterEach(() => vi.restoreAllMocks()); + it("exists in the plugin array", () => { const plugin = getOgInlinePlugin(); expect(plugin.name).toBe("vinext:og-inline-fetch-assets"); @@ -132,7 +134,5 @@ describe("vinext:og-inline-fetch-assets plugin", () => { (call) => call[0] === path.join(tmpDir, "noto-sans.ttf"), ); expect(calls.length).toBe(1); - - readFileSpy.mockRestore(); }); }); From 396795eb72a556168ea555a5386859c67f420bf3 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Wed, 11 Mar 2026 08:43:10 -0700 Subject: [PATCH 3/4] fix: address og-inline review feedback --- packages/vinext/src/index.ts | 26 +++++++++++++---- tests/og-inline.test.ts | 56 ++++++++++++++++++++++++++---------- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/packages/vinext/src/index.ts b/packages/vinext/src/index.ts index ab117a17..ce4190bd 100644 --- a/packages/vinext/src/index.ts +++ b/packages/vinext/src/index.ts @@ -700,8 +700,11 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { // Shim alias map — populated in config(), used by resolveId() for .js variants let nextShimMap: Record = {}; - // Per-build cache for og-inline-fetch-assets plugin to avoid repeated file reads + // Build-only cache for og-inline-fetch-assets to avoid repeated file reads + // during a single production build. Dev mode skips the cache so asset edits + // are picked up without restarting the Vite server. const _ogInlineCache = new Map(); + let _ogInlineIsBuild = false; /** * Generate the virtual SSR server entry module. @@ -2838,12 +2841,21 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { { name: "vinext:og-inline-fetch-assets", enforce: "pre", + configResolved(config) { + _ogInlineIsBuild = config.command === "build"; + }, + buildStart() { + if (_ogInlineIsBuild) { + _ogInlineCache.clear(); + } + }, async transform(code, id) { // Quick bail-out: only process modules that use new URL(..., import.meta.url) if (!code.includes("import.meta.url")) { return null; } + const useCache = _ogInlineIsBuild; const moduleDir = path.dirname(id); let newCode = code; let didReplace = false; @@ -2859,12 +2871,14 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { const relPath = match[2]; // e.g. "./noto-sans-v27-latin-regular.ttf" const absPath = path.resolve(moduleDir, relPath); - let fileBase64 = _ogInlineCache.get(absPath); + let fileBase64 = useCache ? _ogInlineCache.get(absPath) : undefined; if (fileBase64 === undefined) { try { const buf = await fs.promises.readFile(absPath); fileBase64 = buf.toString("base64"); - _ogInlineCache.set(absPath, fileBase64); + if (useCache) { + _ogInlineCache.set(absPath, fileBase64); + } } catch { // File not found on disk — skip (may be a runtime-only asset) continue; @@ -2899,12 +2913,14 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { const relPath = match[2]; // e.g. "./noto-sans-v27-latin-regular.ttf" const absPath = path.resolve(moduleDir, relPath); - let fileBase64 = _ogInlineCache.get(absPath); + let fileBase64 = useCache ? _ogInlineCache.get(absPath) : undefined; if (fileBase64 === undefined) { try { const buf = await fs.promises.readFile(absPath); fileBase64 = buf.toString("base64"); - _ogInlineCache.set(absPath, fileBase64); + if (useCache) { + _ogInlineCache.set(absPath, fileBase64); + } } catch { // File not found on disk — skip continue; diff --git a/tests/og-inline.test.ts b/tests/og-inline.test.ts index 33574d08..35fbd990 100644 --- a/tests/og-inline.test.ts +++ b/tests/og-inline.test.ts @@ -13,25 +13,28 @@ function unwrapHook(hook: any): Function { return typeof hook === "function" ? hook : hook?.handler; } -/** Extract the vinext:og-inline-fetch-assets plugin from the plugin array */ -function getOgInlinePlugin(): Plugin { +/** + * Create a fresh vinext:og-inline-fetch-assets plugin instance. + * Each call gets an independent cache so tests do not share state. + */ +function createOgInlinePlugin(command: "serve" | "build" = "serve"): Plugin { const plugins = vinext() as Plugin[]; const plugin = plugins.find((p) => p.name === "vinext:og-inline-fetch-assets"); if (!plugin) throw new Error("vinext:og-inline-fetch-assets plugin not found"); + const configResolved = unwrapHook(plugin.configResolved); + configResolved?.call(plugin, { command }); return plugin; } // ── Test fixture setup ──────────────────────────────────────── let tmpDir: string; -let fontPath: string; const fontContent = Buffer.from("fake-font-data-for-testing"); const fontBase64 = fontContent.toString("base64"); beforeAll(async () => { tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "og-inline-test-")); - fontPath = path.join(tmpDir, "noto-sans.ttf"); - await fsp.writeFile(fontPath, fontContent); + await fsp.writeFile(path.join(tmpDir, "noto-sans.ttf"), fontContent); }); afterAll(async () => { @@ -44,7 +47,7 @@ describe("vinext:og-inline-fetch-assets plugin", () => { afterEach(() => vi.restoreAllMocks()); it("exists in the plugin array", () => { - const plugin = getOgInlinePlugin(); + const plugin = createOgInlinePlugin(); expect(plugin.name).toBe("vinext:og-inline-fetch-assets"); expect(plugin.enforce).toBe("pre"); }); @@ -52,7 +55,7 @@ describe("vinext:og-inline-fetch-assets plugin", () => { // ── Guard clause ────────────────────────────────────────── it("returns null when code has no import.meta.url", async () => { - const plugin = getOgInlinePlugin(); + const plugin = createOgInlinePlugin(); const transform = unwrapHook(plugin.transform); const code = `import fs from 'node:fs';\nconst x = 1;`; const result = await transform.call(plugin, code, "/app/og.tsx"); @@ -62,7 +65,7 @@ describe("vinext:og-inline-fetch-assets plugin", () => { // ── Pattern 1: fetch ───────────────────────────────────── it("transforms fetch(new URL(..., import.meta.url)).then(r => r.arrayBuffer())", async () => { - const plugin = getOgInlinePlugin(); + const plugin = createOgInlinePlugin(); const transform = unwrapHook(plugin.transform); const code = `const data = fetch(new URL("./noto-sans.ttf", import.meta.url)).then((res) => res.arrayBuffer());`; const moduleId = path.join(tmpDir, "og.tsx"); @@ -77,7 +80,7 @@ describe("vinext:og-inline-fetch-assets plugin", () => { // ── Pattern 2: readFileSync ────────────────────────────── it("transforms fs.readFileSync(fileURLToPath(new URL(..., import.meta.url)))", async () => { - const plugin = getOgInlinePlugin(); + const plugin = createOgInlinePlugin(); const transform = unwrapHook(plugin.transform); const code = `const buf = fs.readFileSync(fileURLToPath(new URL("./noto-sans.ttf", import.meta.url)));`; const moduleId = path.join(tmpDir, "og.tsx"); @@ -91,7 +94,7 @@ describe("vinext:og-inline-fetch-assets plugin", () => { // ── File not found ─────────────────────────────────────── it("silently skips when the referenced file does not exist", async () => { - const plugin = getOgInlinePlugin(); + const plugin = createOgInlinePlugin(); const transform = unwrapHook(plugin.transform); const code = `const data = fetch(new URL("./nonexistent.ttf", import.meta.url)).then((res) => res.arrayBuffer());`; const moduleId = path.join(tmpDir, "og.tsx"); @@ -104,21 +107,22 @@ describe("vinext:og-inline-fetch-assets plugin", () => { // ── Async assertion ────────────────────────────────────── it("returns a Promise (hook is async)", () => { - const plugin = getOgInlinePlugin(); + const plugin = createOgInlinePlugin(); const transform = unwrapHook(plugin.transform); const code = `const data = fetch(new URL("./noto-sans.ttf", import.meta.url)).then((res) => res.arrayBuffer());`; const moduleId = path.join(tmpDir, "og.tsx"); const result = transform.call(plugin, code, moduleId); expect(result).toBeInstanceOf(Promise); + return result; }); - // ── Cache hit ──────────────────────────────────────────── + // ── Build cache hit ────────────────────────────────────── - it("reads the file only once for repeated transforms (cache hit)", async () => { + it("reads the file only once for repeated build transforms (cache hit)", async () => { const readFileSpy = vi.spyOn(fs.promises, "readFile"); - const plugin = getOgInlinePlugin(); + const plugin = createOgInlinePlugin("build"); const transform = unwrapHook(plugin.transform); const code = `const buf = fs.readFileSync(fileURLToPath(new URL("./noto-sans.ttf", import.meta.url)));`; const moduleId = path.join(tmpDir, "og.tsx"); @@ -129,10 +133,32 @@ describe("vinext:og-inline-fetch-assets plugin", () => { // Second call — should use cache await transform.call(plugin, code, moduleId); - // fs.promises.readFile should have been called at most once for this path + // Exactly once: first call reads from disk, second call hits the build cache. const calls = readFileSpy.mock.calls.filter( (call) => call[0] === path.join(tmpDir, "noto-sans.ttf"), ); expect(calls.length).toBe(1); }); + + // ── Dev mode stays fresh ───────────────────────────────── + + it("does not cache asset contents across serve transforms", async () => { + const devFontPath = path.join(tmpDir, "dev-font.ttf"); + const initialFontBase64 = Buffer.from("dev-font-v1").toString("base64"); + const updatedFontBase64 = Buffer.from("dev-font-v2").toString("base64"); + await fsp.writeFile(devFontPath, Buffer.from("dev-font-v1")); + + const plugin = createOgInlinePlugin("serve"); + const transform = unwrapHook(plugin.transform); + const code = `const buf = fs.readFileSync(fileURLToPath(new URL("./dev-font.ttf", import.meta.url)));`; + const moduleId = path.join(tmpDir, "og.tsx"); + + const firstResult = await transform.call(plugin, code, moduleId); + expect(firstResult.code).toContain(`Buffer.from(${JSON.stringify(initialFontBase64)},"base64")`); + + await fsp.writeFile(devFontPath, Buffer.from("dev-font-v2")); + + const secondResult = await transform.call(plugin, code, moduleId); + expect(secondResult.code).toContain(`Buffer.from(${JSON.stringify(updatedFontBase64)},"base64")`); + }); }); From 7950f0d5621ccf7bcd136f8a06cd8ad308b95f33 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Wed, 11 Mar 2026 08:49:45 -0700 Subject: [PATCH 4/4] style: format og-inline test assertions --- tests/og-inline.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/og-inline.test.ts b/tests/og-inline.test.ts index 35fbd990..4aa34ddd 100644 --- a/tests/og-inline.test.ts +++ b/tests/og-inline.test.ts @@ -154,11 +154,15 @@ describe("vinext:og-inline-fetch-assets plugin", () => { const moduleId = path.join(tmpDir, "og.tsx"); const firstResult = await transform.call(plugin, code, moduleId); - expect(firstResult.code).toContain(`Buffer.from(${JSON.stringify(initialFontBase64)},"base64")`); + expect(firstResult.code).toContain( + `Buffer.from(${JSON.stringify(initialFontBase64)},"base64")`, + ); await fsp.writeFile(devFontPath, Buffer.from("dev-font-v2")); const secondResult = await transform.call(plugin, code, moduleId); - expect(secondResult.code).toContain(`Buffer.from(${JSON.stringify(updatedFontBase64)},"base64")`); + expect(secondResult.code).toContain( + `Buffer.from(${JSON.stringify(updatedFontBase64)},"base64")`, + ); }); });