Skip to content

Refactor: Extract template string code generation from index.ts and app-dev-server.ts into separate modules #253

@southpolesteve

Description

@southpolesteve

Problem

index.ts (3,528 lines) and app-dev-server.ts (2,734 lines) are the two largest files in the codebase. Both files mix Vite plugin logic with massive template string blocks that generate runtime JavaScript modules. This creates several problems:

  1. AI agents can't hold the entire file in context. These files exceed typical context windows, leading to partial reads and missed interactions between sections.
  2. Template string edits are error-prone. The generated code uses escaped characters (\\n, \\.), var instead of const/let, and mixes interpolated expressions with static code. AI agents routinely break escaping or confuse plugin code with generated runtime code.
  3. No way to test generated code in isolation. The template strings are embedded in plugin functions, so the only way to verify generated output is to run the full dev/build pipeline.

What needs to be extracted

From index.ts

Template Lines Span Function Generates
Pages Router prod server entry 806-1549 ~743 lines generateServerEntry() Complete production server module: SSR, ISR, API routes, i18n, asset injection, middleware, route matching
Middleware runtime 655-801 ~147 lines middlewareExportCode variable Middleware matching and execution, ReDoS protection (embedded inside the server entry)
Pages Router client entry 1576-1634 ~58 lines generateClientEntry() Client hydration: page loaders, _app wrapping, hydrateRoot()

The server entry template (743 lines) is the primary target. It contains the entire Pages Router production request lifecycle as a template string.

From app-dev-server.ts

Template Lines Span Function Generates
RSC entry 201-2060 ~1,860 lines generateRscEntry() The entire App Router request handler: route matching, middleware, CSRF, config redirects/rewrites/headers, metadata routes, server actions, layout tree construction, RSC streaming, caching
SSR entry 2070-2425 ~356 lines generateSsrEntry() RSC-to-HTML rendering, progressive streaming, font/head injection
Browser entry 2436-2733 ~298 lines generateBrowserEntry() Client hydration, RSC navigation, server actions, prefetch cache, HMR

The RSC entry template (1,860 lines) is the largest single template string in the codebase. It contains at least 15 logically distinct subsystems inlined as generated code.

Proposed approach

1. Create an entries/ directory

packages/vinext/src/entries/
  pages-server-entry.ts      # From index.ts generateServerEntry()
  pages-client-entry.ts      # From index.ts generateClientEntry()
  app-rsc-entry.ts           # From app-dev-server.ts generateRscEntry()
  app-ssr-entry.ts           # From app-dev-server.ts generateSsrEntry()
  app-browser-entry.ts       # From app-dev-server.ts generateBrowserEntry()

Each file exports a function that takes the same parameters the current generator functions receive (route table, config, feature flags) and returns the generated code string.

2. Add snapshot tests

For each extracted template function, add a test that:

  • Calls the function with representative inputs (a few routes, middleware present/absent, basePath set/unset, i18n enabled/disabled)
  • Snapshots the generated output
  • When an AI edits a template, the snapshot diff shows exactly what changed in the generated code

This is the key safety improvement. Currently there's no way to see what the generated code looks like without running the full pipeline.

3. Reduce index.ts and app-dev-server.ts to orchestration

After extraction, index.ts should be the Vite plugin shell (resolveId, load, configureServer, config hooks) that calls into the entry generators. app-dev-server.ts should be a thin module that calls the three App Router entry generators.

Non-goals

  • Don't eliminate the template string pattern entirely. Code generation via template strings is the right approach for virtual modules that need dynamic imports based on the scanned route table. The goal is to isolate the templates, not replace them.
  • Don't refactor the generated code's internal logic. That's a separate concern (see the shared request handler issue). This issue is purely about file organization.

Verification

  • pnpm test (all Vitest tests pass)
  • pnpm run test:e2e (all Playwright E2E tests pass)
  • pnpm run typecheck
  • pnpm run lint
  • Manually verify that index.ts is under 1,000 lines and app-dev-server.ts is under 500 lines after extraction

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions