Skip to content

perf: startup and cache micro-optimizations#436

Open
Divkix wants to merge 2 commits intocloudflare:mainfrom
Divkix:perf/startup-and-cache-micro-optimizations
Open

perf: startup and cache micro-optimizations#436
Divkix wants to merge 2 commits intocloudflare:mainfrom
Divkix:perf/startup-and-cache-micro-optimizations

Conversation

@Divkix
Copy link
Contributor

@Divkix Divkix commented Mar 11, 2026

Closes #437

Summary

  • Cache hasMdxFiles() result per root directory — avoids redundant recursive filesystem walks when config() fires per Vite environment (RSC/SSR/Client = 3+ times)
  • Cache resolvePostcssStringPlugins() result per project root — skips repeated existsSync checks across 17 PostCSS config file candidates on each config() invocation
  • Replace byte-by-byte base64 encode/decode in KV cache handler with Buffer APIs for significantly faster serialization
  • Pass pre-normalized URL to nodeToWebRequest() in App Router prod server — eliminates redundant normalizePath call in the RSC handler for pathological URLs

Test plan

  • 9 new tests in tests/startup-cache.test.ts — MDX scan caching (5) and PostCSS config caching (4)
  • 3 new edge-case tests in tests/kv-cache-handler.test.ts — 1 MiB buffer, null bytes, all 256 byte values
  • 5 new tests in tests/node-to-web-request.test.tsurlOverride parameter behavior
  • All 33 kv-cache-handler tests pass
  • Typecheck (tsgo --noEmit) clean
  • Lint (oxlint) clean
  • Format (oxfmt --check) clean

…ization

- Cache hasMdxFiles() result per root directory to avoid redundant
  recursive filesystem walks when config() fires per Vite environment
- Cache resolvePostcssStringPlugins() result per project root to skip
  repeated existsSync checks across 17 PostCSS config file candidates
- Replace byte-by-byte base64 encode/decode in KV cache handler with
  Buffer APIs for significantly faster serialization
- Pass pre-normalized URL to nodeToWebRequest() in App Router prod
  server to eliminate redundant path normalization in the RSC handler
Copilot AI review requested due to automatic review settings March 11, 2026 02:26
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 11, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@436

commit: 34c85a6

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets startup-time performance by caching expensive filesystem operations during repeated Vite config() invocations, and by reducing overhead in request normalization and KV cache serialization.

Changes:

  • Add module-level caches for MDX directory scanning and PostCSS string-plugin resolution.
  • Optimize KV cache ArrayBuffer base64 serialization/deserialization using Buffer.
  • Allow passing a pre-normalized URL into nodeToWebRequest() and add coverage for the new behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/vinext/src/index.ts Adds per-root caches for MDX scanning and PostCSS plugin resolution; exports internals for tests.
packages/vinext/src/cloudflare/kv-cache-handler.ts Switches base64 encode/decode to Buffer and adds input validation before decoding.
packages/vinext/src/server/prod-server.ts Adds urlOverride support to nodeToWebRequest() and passes a pre-normalized URL from the App Router server path.
tests/startup-cache.test.ts New tests covering MDX scan caching and PostCSS resolution caching behaviors.
tests/node-to-web-request.test.ts New tests covering nodeToWebRequest() urlOverride semantics.
tests/kv-cache-handler.test.ts Adds edge-case ArrayBuffer base64 roundtrip tests (large buffers, null bytes, all byte values).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +84 to +86
/** Matches a valid base64 string (standard alphabet with optional padding). */
const BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BASE64_RE only checks the alphabet/padding characters but doesn’t enforce base64 structural validity (e.g., length multiple of 4 / disallowing length % 4 === 1). Because Buffer.from(str, "base64") is permissive, some malformed inputs made of valid characters can decode without throwing, which would bypass the “corrupted entry => cache miss” logic. Consider tightening validation (e.g., a stricter regex or explicit length/padding checks) so invalid base64 reliably triggers the safeBase64ToArrayBuffer() null path.

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +81

// Second call — should use cache, no filesystem reads
const result2 = hasMdxFiles(tmpDir, appDir, null);
expect(result2).toBe(true);
expect(readdirSpy).not.toHaveBeenCalled();

readdirSpy.mockRestore();
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spy is manually restored at the end of the test, but if an assertion throws before reaching mockRestore(), the spy can leak into later tests. Consider wrapping the spy in a try/finally (or adding an afterEach(() => vi.restoreAllMocks()) for the suite) to guarantee cleanup on failures.

Suggested change
// Second call — should use cache, no filesystem reads
const result2 = hasMdxFiles(tmpDir, appDir, null);
expect(result2).toBe(true);
expect(readdirSpy).not.toHaveBeenCalled();
readdirSpy.mockRestore();
try {
// Second call — should use cache, no filesystem reads
const result2 = hasMdxFiles(tmpDir, appDir, null);
expect(result2).toBe(true);
expect(readdirSpy).not.toHaveBeenCalled();
} finally {
readdirSpy.mockRestore();
}

Copilot uses AI. Check for mistakes.
Comment on lines +191 to +193
// Spy on existsSync after first call
const existsSpy = vi.spyOn(fs, "existsSync");

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the MDX caching test, this spy is restored only on the happy path. If an assertion fails before mockRestore(), the spy remains active and may affect subsequent tests. Consider using try/finally around the spy or an afterEach(() => vi.restoreAllMocks()) cleanup.

Copilot uses AI. Check for mistakes.
Comment on lines +216 to +222

// Second call — should use cache
const result2 = await resolvePostcssStringPlugins(dir);
expect(result2).toBeUndefined();
expect(existsSpy).not.toHaveBeenCalled();

existsSpy.mockRestore();
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This spy cleanup has the same failure-path leak risk as the other existsSync spy above (mockRestore() won’t run if an assertion throws first). Using try/finally around the spy (or afterEach restoreAllMocks) would make the test isolation more robust.

Suggested change
// Second call — should use cache
const result2 = await resolvePostcssStringPlugins(dir);
expect(result2).toBeUndefined();
expect(existsSpy).not.toHaveBeenCalled();
existsSpy.mockRestore();
try {
// Second call — should use cache
const result2 = await resolvePostcssStringPlugins(dir);
expect(result2).toBeUndefined();
expect(existsSpy).not.toHaveBeenCalled();
} finally {
existsSpy.mockRestore();
}

Copilot uses AI. Check for mistakes.
Add length % 4 check to base64ToArrayBuffer to reject structurally
invalid base64 strings that pass the character-set regex but produce
empty/truncated buffers via Buffer.from(). Replace manual mockRestore()
calls with afterEach(vi.restoreAllMocks) to prevent spy leaks on
assertion failure.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: cache startup filesystem scans, optimize base64 and path normalization

2 participants