Commit Graph

5 Commits

Author SHA1 Message Date
Nathan Nguyen 9bab92f012 test(deploy): cover generated worker package exports (#1807)
Generated worker entries can import vinext subpaths that must resolve from the published package. Missing package exports break consumers even when the generated template itself looks correct.

Add deploy coverage that extracts vinext subpath imports from generated App and Pages worker entries and verifies each one is covered by packages/vinext/package.json exports.

Tighten the prefer-shared-utils lint rule so copied shared helpers in repo tests are reported too. Test files mask template literal fixture payloads, while source files still scan generated-source templates.
2026-06-07 21:58:41 +01:00
James Anderson 0b7721dba6 perf(build): minify server build environments by default (#1777)
* fix(build): minify server build environments by default

Vite only minifies the client environment; non-client (rsc/ssr and the
Cloudflare worker) environments default build.minify to false, so the
deployed worker and SSR renderer ship unminified. Add a build-only
configEnvironment default that sets minify: true for server environments,
yielding to any explicit user/plugin minify setting (including false).

* fix(build): patch route classification before minify; build fixtures unminified for output introspection

Minifying server environments by default exposed two problems:

1. Real bug: build-time route classification ran in `generateBundle`, but
   rolldown's minifier renames the top-level `__VINEXT_CLASS` function and
   mangles its `routeIdx` parameter before that hook runs, so the stub regex
   never matched and the patch silently no-op'd — every route fell back to the
   Layer 3 runtime probe in production. Move the injection to a `renderChunk`
   hook with `order: "pre"` so it runs before minification; the patched body is
   then minified along with the rest of the chunk (the runtime calls it by
   reference, not by name).

2. Test artifacts: three build tests introspect emitted server chunks for
   readable identifiers (registerConfiguredCacheAdapters, __VINEXT_CLASS) or
   depend on unminified chunking (MDX frontmatter leak check). These are
   test-only concerns — the runtime behavior is unchanged. Build their fixtures
   with `build.minify: false` (a user-overridable default) so the assertions
   stay deterministic.

* fix(build): drop now-unused RouteClassificationChunk export (knip)

The renderChunk refactor removed the external consumer of this type; keep it
as a local type so knip passes.

* test(build): guard minified classification path; fix stale generateBundle refs

Re-add real regression coverage that the route-classification injector runs
before minification (the production default). The prior fix made three tests
pass by building with minify:false, which is exactly the config where the old
post-minify generateBundle code also passed — so the tests no longer guarded
the bug.

- classification integration: add a production-default (minify on) suite that
  asserts minify-robust signals (injected switch dispatch shape + the
  module-graph reason literal, which is build-time-only). Verified it FAILS when
  the injector is reverted to a post-minify generateBundle hook.
- cache adapters: assert the minify-safe LOCAL_ADAPTER_MARKER under the default
  minified build instead of grepping the renamed registerConfiguredCacheAdapters
  symbol.
- tsconfig path alias (#659): run minified; the old not.toContain('title:
  "Second Post"') was a quote-escaping artifact (frontmatter renders as MDX
  content identically in both builds, not a minify leak) — replaced with a real
  parse-error/raw-YAML guard.
- update stale generateBundle references to renderChunk in injector error
  strings, app-rsc-entry stubs, and index.ts comments; note the per-chunk patch
  assumption near the renderChunk hook.

* test(build): run classification tests against minified output (no minify:false)

Previously the dispatch-logic suites built the fixture with minify:false so they
could regex-extract and eval the readable __VINEXT_CLASS function — but that is
the one config where the original post-minify-hook bug also passes, so it masked
the regression. Rewrite the extractors to be name-agnostic: derive the (mangled)
dispatch function name from its property-keyed call site (__buildTimeClassifications/
__buildTimeReasons — property keys survive minification) and eval the body
(string-literal values like "static" also survive). All suites now build with
vinext's production defaults (minify on). Verified by reproducing the bug
(disabling the patch): the minified tests fail with "was not patched".

* refactor(build): rename buildGenerateBundleReplacement; fix stale generateBundle doc refs

Address review nits: the classification dispatch builder still carried the old
hook name (it now runs in renderChunk, not generateBundle). Rename it to
buildClassificationReplacement (parallel to buildReasonsReplacement) and fix two
stale 'generateBundle' doc comments (route-classification-manifest.ts:203,
layout-classification.ts:130) to say renderChunk.
2026-06-06 22:55:04 +01:00
James Anderson 3f287ff5ab chore: remove useless code assertion tests and snaps (#1023)
* chore: remove useless code assertion tests and snaps

* .
2026-05-02 19:54:31 +00:00
Nathan Nguyen 34ffb948c8 chore(debug): classification reasons sidecar behind VINEXT_DEBUG_CLASSIFICATION [6/6] (#843)
* chore(debug): add classification reasons sidecar behind VINEXT_DEBUG_CLASSIFICATION

Operators tracing why a layout was flagged static or dynamic can opt in
with VINEXT_DEBUG_CLASSIFICATION=1 at build time. When active, the
plugin patches a second stub (__VINEXT_CLASS_REASONS) with a dispatch
table that returns per-layout ClassificationReason structures. The
runtime probe loop emits a debug line per layout when
debugClassification is wired, and the hot path remains allocation-free
when debug is off because the stub returns null.

The reasons machinery (types, report.ts tagged results, and
buildReasonsReplacement) already shipped in the previous PR; this PR
just enables the emission path and wires the debug consumer into the
runtime probe.

* test(debug): cover runtime-probe error path and Layer 2 reasons dispatch

The runtime-probe catch branch in `probeAppPageLayouts` emits
`{ layer: "runtime-probe", outcome: "dynamic", error: "..." }` when the
isolated dynamic scope throws. That emission had no coverage, so a
regression that stopped including the `error` field, or that skipped the
debug callback entirely on the error path, would not have tripped any
test.

The reasons sidecar integration test also only asserted the Layer 1
(`segment-config`) reason on the nested `force-dynamic` layout. Layer 2
(`module-graph`) on the pure-JSX root layout was exercised in the build
but never read, so a regression that stopped serializing the `result`
field or the `module-graph` discriminator for Layer 2 would slip through.

Extends the existing debug-on describe block to read reasons for layout
index 0 on `/force-dyn` and assert the `module-graph` + `result: "static"`
pair, keeping the structural `ReasonShape` so the test still survives
the #863 codegen reshape.

Also removes a pre-existing `as` on the narrowed Map return in favour of
a proper `in`-narrowing type predicate.
2026-04-21 10:29:10 +01:00
Nathan Nguyen bc03bf1c71 feat(build): wire build-time layout classification into RSC entry (#842)
* feat(build): wire build-time layout classification into the generated RSC entry

Introduce a Rollup generateBundle hook that patches the __VINEXT_CLASS
stub in the generated RSC entry with a real dispatch table built from
Layer 1 segment-config analysis and Layer 2 module-graph classification.
The runtime probe loop in app-page-execution.ts consults this table and
skips the dynamic-isolation probe for layouts we proved static or
dynamic at build time.

Add route-classification-manifest.ts as the codegen glue between the
classifier and the entry template, and flow buildTimeClassifications
through renderAppPageLifecycle so the runtime probe can honor the
build-time decision. Fail loudly if generateBundle sees __VINEXT_CLASS
referenced without the recognized stub body, so generator and plugin
cannot silently drift.

* refactor(build): address Copilot review comments on PR 842

Three targeted cleanups from code review:

- Replace inline canonicalize() with tryRealpathSync(p) ?? p, which is
  already imported and does the same thing (realpathSync.native + fallback)

- Memoize readFileSync calls in collectRouteClassificationManifest so
  shared layouts (especially the root layout) are read from disk only once
  per build rather than once per route

- Cache classifyLayoutByModuleGraph results by canonicalized module ID in
  generateBundle so shared layouts are BFS-traversed only once per build
  rather than once per (routeIdx, layoutIdx) pair

* refactor: address review comments on build classification PR

- Use layer2.keys() instead of destructuring with unused _value
- Add comment to stub regex explaining intentional semicolon tolerance
- Add comment to __buildTimeClassifications clarifying module-load evaluation
- Add coupling comment to integration test regex explaining non-greedy assumption

* test: update entry-templates snapshots for module-load comment

* refactor: address second round of review comments

- Expand stubRe comment to note __VINEXT_CLASS name-retention assumption
- Null out rscClassificationManifest after generateBundle consumes it
- Fix buildGenerateBundleReplacement docstring to reflect type-level guarantee

* refactor: address third round of review comments

- Remove routeIdx from generated route objects (dead runtime property,
  only needed as codegen loop variable for __VINEXT_CLASS(N) call)
- Null target.map after patching target.code in generateBundle
- Add Infinity-before-JSON.stringify comment in serializeReasonExpression
- Fix integration test stubRe to tolerate optional semicolon like the plugin
- Update extractRouteIndexByPattern to anchor on __VINEXT_CLASS(N) instead
  of the removed routeIdx property
- Update snapshots

* refactor: address subagent review findings

- Mark classifyAllRouteLayouts as @internal test-only (production code
  calls classifyLayoutByModuleGraph directly via the manifest pipeline)
- Document unreachable runtime-probe/no-classifier arms in
  serializeReasonExpression with explanation of why narrowing is deferred
- Add manifest lifecycle invariant comment to load hook explaining the
  1:1 correspondence between manifest layout indices and codegen routes
- Add test for layer1/layer1Reasons mismatch guard in mergeLayersForRoute

* refactor: hoist build-time classification helpers and rename layer1 binding

`canonicalize` and `dynamicShimPaths` were constructed inside the RSC
generateBundle hook, so they were rebuilt on every invocation even though
shimsDir is fixed for the plugin's lifetime and tryRealpathSync is
deterministic against the package's own shim files. The cost is small
because generateBundle runs once per build, but the hook body grew long
enough that hoisting the closure and the shim-path Set up next to the
shimsDir definition makes the bundle hook read more like the request-time
behaviour it is wiring up.

Separately, mergeLayersForRoute destructured Layer 1 entries as
[layoutIdx, value] and then wrote { kind: value, reason }. Since the
binding is always the Layer1Class kind, renaming it to `kind` lets the
merge use object-shorthand and removes the value-vs-kind shadowing.

No behaviour change; same realpath calls, same merge result, same
generated dispatch table. Targeted unit and integration suites still pass.

* Preserve module-graph reasons in classification manifest

* Share module-graph static reason type
2026-04-20 10:49:50 +01:00