cloudflare / cloudflare/vinext

perf: emit pre-compiled regex literals for config patterns at build time

Open
#389 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
8.8k
Forks
406
Avg merge
2d 6h
Merged PRs (30d)
120

Description

## Background

PR #387 fixed the dominant per-request CPU bottleneck in `matchConfigPattern` by adding a module-level `Map` cache so the tokeniser, `isSafeRegex` scan, and `new RegExp()` call run at most once per unique pattern string per isolate lifetime.

That fix is sufficient for long-lived isolates (Cloudflare Workers reuses isolates aggressively for popular routes). The remaining opportunity is to move pattern compilation entirely to **build time** so even the first request after a cold start pays zero compilation cost.

## What this would look like

Redirect/rewrite `source` patterns are already baked into the bundle as JSON-serialized arrays:

```js
// Current output in the generated virtual module
const __configRedirects = [
{ source: "/:locale(en|es|fr|id|ja|ko|pt-br|pt|ro|ta|tr|uk|zh-cn|zh-tw)?/security",
destination: "/security", permanent: true },
// ...87 more rules
];
```

With build-time compilation, the code generator would also emit a parallel compiled array as raw JS regex literals, completely bypassing `matchConfigPattern`'s regex branch at runtime:

```js
// Proposed: emitted alongside __configRedirects
const __compiledRedirects = [
{ re: /^\/(en|es|fr|id|ja|ko|pt-br|pt|ro|ta|tr|uk|zh-cn|zh-tw)?\/security$/,
paramNames: ["locale"], destination: "/security", permanent: true },
// null for simple segment rules that don't use the regex branch
null,
// ...
];
```

`matchRedirect` would accept an optional pre-compiled array and skip `matchConfigPattern` entirely when it's present.

## What needs to change

1. **Extract a `compileConfigPattern(pattern)` function** from `matchConfigPattern` that returns `{ re: RegExp; paramNames: string[] } | null` — the same logic currently cached by `_compiledPatternCache`, made callable from the code generators.

2. **`generateRscEntry` (App Router)** — call `compileConfigPattern` for each redirect/rewrite/header source at code-gen time; emit `__compiledRedirects`, `__compiledRewrites`, `__compiledHeaders` as JS regex literals alongside the existing `__configRedirects` JSON arrays.

3. **`generateServerEntry` (Pages Router)** — same: embed compiled forms into the `vinextConfig` export or as separate module-level constants.

4. **`matchRedirect` / `matchRewrite`** — add an overload (or a new `matchRedirectCompiled`) that accepts the pre-compiled array and skips to `re.exec` directly.

5. **Dev server (`index.ts` connect handler)** — the dev server calls `applyRedirects`/`applyRewrites` which call `matchRedirect`/`matchRewrite` with the live `nextConfig` arrays. Pre-compilation here would mean compiling once at plugin init time (after `resolveNextConfig`) and keeping a compiled copy alongside `nextConfig`. Worth doing for consistency, though the module-level cache in `config-matchers.ts` already covers this path.

6. **Emit regex source correctly** — `re.source` + `re.flags` can be used to reconstruct a regex literal as a string: `` `/${re.source}/${re.flags}` ``. Flags are `""` for all current patterns (no `i`, `g`, etc.), so the output is simply `/${re.source}/`.

## Why the current cache is sufficient for most cases

The module-level `Map` cache (PR #387) means:
- Steady-state (warm isolate): O(1) `Map.get` per rule — identical to reading a module constant
- Cold start / first request: each unique pattern compiled once, amortised over all subsequent requests

Build-time emission only helps the **first request after a cold start** on low-traffic routes where isolates are recycled frequently. For high-traffic routes Workers keeps isolates alive long enough that the cache is always warm.

## When to prioritise this

Worth revisiting if profiling shows measurable p99 cold-start latency from pattern compilation, or if the codebase moves toward a model where isolates are intentionally short-lived (e.g. per-request isolate mode).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.