mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
v0.0.33
377 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4241f5d31b |
fix: skip page RSC ISR caching for dynamic requests (#629)
* fix: skip page RSC ISR caching for dynamic requests * test: align page ISR assertions with helper extractionv0.0.33 |
||
|
|
8366217c84 | refactor: extract app page cache writes (#628) | ||
|
|
fc39dbb7d3 |
refactor: extract app page response helpers (#627)
* refactor: extract app page response helpers * test: refresh app page response snapshots * docs: clarify app page response miss policy * test: document app page response policy edges |
||
|
|
32a346a1e3 |
refactor: extract app page cache helpers (#626)
* refactor: extract app page cache helpers * chore: tighten app page cache extraction * test: refresh app page cache snapshots |
||
|
|
fa03a06bea |
feat: optimize barrel imports for RSC-incompatible packages (#138)
* feat: add vinext:optimize-imports plugin for barrel import rewriting
Barrel imports like `import { Slot } from "radix-ui"` cause RSC crashes
because Vite eagerly evaluates all re-exported sub-packages, some of which
call React.createContext() — unavailable in the react-server condition.
This plugin rewrites barrel imports into direct sub-module imports on the
server, matching Next.js's optimizePackageImports behavior:
- Parses barrel entry files to build export maps (namespace, named,
default re-exports, and import-then-export patterns)
- resolveId hook handles pnpm strict hoisting via Vite's own resolver
- Only runs on server environments; client uses Vite's dep optimizer
- Lazy-initialized Set<string> for O(1) package lookups
- Respects experimental.optimizePackageImports from next.config
- Includes Next.js default package list plus radix-ui
Closes cloudflare/vinext#100
Closes cloudflare/vinext#137
* fix: add missing packages to DEFAULT_OPTIMIZE_PACKAGES
Add the full effect ecosystem (17 packages) and all react-icons/*
sub-packages (32 packages) to match Next.js's built-in defaults.
* fix: address bonk review — entryPathCache, buildStart init, Windows path fix, trailing semicolons, fixture-based transform tests
* fix: correct bySource grouping for mixed namespace+named imports, clear barrelCaches on rebuild
- Key bySource map on `${source}::${isNamespace}` instead of just source
so that a namespace re-export and a named re-export from the same sub-module
produce two separate import statements rather than one corrupted one.
Caught by the new 'produces separate statements for namespace and named
imports from the same source' test.
- Clear barrelCaches.exportMapCache and barrelCaches.subpkgOrigin in buildStart
alongside entryPathCache so stale barrel analysis doesn't survive rebuilds.
- Add regression test for the mixed namespace+named grouping bug.
* fix: resolve relative barrel re-export paths to absolute, emit default imports for default re-exports
Barrels like lodash-es and lucide-react use relative re-export paths
(e.g. `export { chunk } from './chunk.js'`). Writing those verbatim caused
Vite to resolve './chunk.js' against the importing user file rather than
the barrel package directory.
Fix: resolve any relative source path against the barrel entry's directory
before emitting the replacement import statement, producing an absolute
filesystem path that Vite resolves correctly regardless of where the
importing file lives.
Also fix: `export { default as X }` re-exports now produce `import X from`
(a default import) instead of `import { default as X }` (invalid syntax
that only works via ASI / bundler leniency).
Tests updated to assert absolute paths and added a dedicated test for
default re-export handling.
* refactor: extract barrel optimization to plugins/optimize-imports.ts, add Windows path fix and react-server TODO
- Move all barrel types, helpers, DEFAULT_OPTIMIZE_PACKAGES, and the
vinext:optimize-imports IIFE from index.ts into a new
packages/vinext/src/plugins/optimize-imports.ts file with a factory
function createOptimizeImportsPlugin(getNextConfig, getRoot)
- Forward-slash normalize resolved absolute paths so emitted import
statements use '/' separators on Windows (path.sep join)
- Add TODO comment in resolveExportsValue noting that the 'react-server'
condition should be added in a future pass
- Add test for ImportDefaultSpecifier: import MyFoo from 'pkg' rewrites
to import MyFoo from '<abs>/sub' (22 tests total, all passing)
* fix(optimize-imports): address sixth-pass review comments
- Fix astName() to throw on unexpected nodes instead of String(value)
- Normalize all resolvePackageEntry return values to forward slashes (Windows fix)
- Fix originalName falsy guard: && → !== undefined
- Pre-build quotedPackages in buildStart to avoid per-file template literal allocations
- Add first-wins comment on subpkgOrigin registration
- Add clarifying comment to lucide-react test fixture
* fix(optimize-imports): remove EnvironmentPluginContext cast, expand subpkgOrigin comment
Vite augments rolldown's MinimalPluginContext with `environment: Environment`
via declaration merging, so both resolveId and transform have `this.environment`
typed without any cast. Drop the EnvironmentPluginContext interface and the
`as unknown as EnvironmentPluginContext` cast in the transform handler, using
direct `this.environment` access consistently with resolveId.
Also expand the subpkgOrigin first-wins comment to make explicit why first-wins
is safe: the sub-package specifier resolves to the same path regardless of which
barrel's importer context is used.
* fix(optimize-imports): address seventh-pass review nits
- Fix test imports: vitest → vite-plus/test, Plugin from vite → vite-plus,
following the convention used by 82 of 85 test files in this repo
- Add TODO comment for extensionless absolute paths produced when barrel
sources lack file extensions (Vite resolves these correctly in practice)
- Expand subpkgOrigin first-wins comment to explicitly note the nested
node_modules edge case and why it is out of scope for the default list
* fix(optimize-imports): address james-elicx review comments
- Support wildcard re-exports (export * from './sub') by recursively
parsing sub-modules and merging their exports into the barrel map
- Add circular-reference guard to prevent infinite loops in recursive
export map building
- Refactor buildBarrelExportMap into buildExportMapFromFile helper
with shared recursive logic
- Use switch statements for spec.type handling instead of if-else chains
- Remove non-null assertions and type assertions; use optional chaining
and typeof guards throughout
- Extract resolvePackageInfo helper to separate concerns in
resolvePackageEntry
- Tests now import directly from optimize-imports.ts instead of via
the full vinext() plugin array
- Remove _buildBarrelExportMap re-export from index.ts (no longer needed
since the plugin has its own file)
- Fix type assertion for optimizePackageImports in next-config.ts using
a proper type-guard filter
- Apply README nit: 'is always optimized' → 'are always optimized'
- Add tests for wildcard export resolution, no-overwrite semantics,
and circular re-export safety
* fix(optimize-imports): address ask-bonk review comments
- Resolve relative sources to absolute paths inside buildExportMapFromFile
so nested wildcard re-exports (e.g. antd-style components/) resolve against
the correct sub-module directory instead of the barrel root
- Add react-server condition to resolveExportsValue (before node/import) for
RSC-compatible entry points in packages like react and react-dom
- Add NOTE comment in resolvePackageEntry documenting that only exports["."]
is checked, not subpath exports
- Eliminate double readFile call in buildBarrelExportMap by passing the
already-read content to buildExportMapFromFile via new initialContent param
- Update subpkgOrigin guard to check for absolute paths (not startsWith ".")
since relative sources are now stored as absolute paths in the export map
- Add test for nested subdirectory wildcard path resolution correctness
- Update existing unit tests to expect absolute paths in export map entries
* fix(optimize-imports): only prefer react-server condition in RSC environment
SSR renders with the full React runtime and must not resolve react-server
export condition entries. Thread preferReactServer (env.name === 'rsc')
through resolveExportsValue and resolvePackageEntry, and key entryPathCache
by environment prefix to keep RSC and SSR barrel entries separate.
* fix(optimize-imports): address review nits — TODO comment, type annotation, test names, wildcard dir test
- Add TODO comment on allResolved bail-out suggesting debug logging
- Remove redundant ': string[]' annotation on optimizePackageImports (inferred)
- Rename namespace re-export test to use generic X/barrel names instead of Slot/lucide-react
- Update syntax-error test to match new empty-map-on-parse-error behavior (not null)
- Add test for directory-style 'export * from "./components"' where components/index.js exists
* fix(optimize-imports): address review nits — wildcard extensions, double-read, readFile hoisting, subpkg registration guard
- Add /index.mjs, /index.tsx, and .tsx to wildcard candidate list for ESM-first and TypeScript-first packages
- Pass candidateContent to buildExportMapFromFile in wildcard loop to avoid reading each sub-module file twice
- Add comment clarifying the no-op resolveEntry callback pattern
- Hoist readFileSafe closure outside the import-declaration for-loop (one allocation per transform, not per barrel import)
- Gate subpkgOrigin registration loop with registeredBarrels Set so 50 files importing the same barrel don't each iterate the full export map
* fix(optimize-imports): remove redundant double-cache, extract PluginCtx type alias
- Remove the redundant exportMapCache.set() in buildBarrelExportMap — buildExportMapFromFile
already stores the result under the same key; the extra set was a no-op but misleading
- Extract PluginCtx type alias for the environment cast so all three hook sites share one
definition and the verbose inline cast doesn't repeat across resolveId and transform
* fix(optimize-imports): hoist readFileSafe to module level, add namespace import test
- Move readFileSafe from handler scope to module level since it captures
nothing from the per-file closure; avoids a per-call function allocation
- Add test verifying import * as Pkg from 'barrel' is left unchanged
(ImportNamespaceSpecifier sets allResolved = false — can't optimize a
full namespace import to a sub-module)
* fix(optimize-imports): make barrel resolution async, add debug logging, update tests
* fix(optimize-imports): address review feedback — extensions, env-keyed subpkgOrigin, debug gate, graceful astName
* fix: scope registeredBarrels key to env to prevent RSC registration blocking SSR
When RSC and SSR resolve the same barrel entry path (common — most packages
have no react-server export condition), the bare barrelEntry key in
registeredBarrels caused RSC's registration to suppress SSR's inner loop
entirely. SSR's subpkgOrigin map was left empty, making resolveId depend
solely on the cross-env fallback — fragile coupling that could silently break.
Change the registeredBarrels key from barrelEntry to `${envKey}:barrelEntry`
so each environment runs the inner loop and populates its own subpkgOrigin
map independently. Matches the existing entryPathCache key pattern.
Add a regression test covering the shared-barrel-entry scenario.
* fix: handle inline export declarations in barrel sub-modules (date-fns)
date-fns barrels use `export * from "./formatDistanceToNow.js"` where the
sub-module directly declares `export function formatDistanceToNow(...) {}`.
buildExportMapFromFile only handled ExportNamedDeclaration with a source
(re-exports) or specifiers (local re-bindings) — it silently skipped inline
declarations (export function/class/const/let/var), leaving the name absent
from the export map and causing the plugin to log "skipping: could not
resolve specifier" and leave the barrel import unchanged.
Handle the declaration branch: FunctionDeclaration and ClassDeclaration
contribute their id.name; VariableDeclaration iterates all declarators.
The source for inline declaration entries is the sub-module file itself.
Also extend AstBodyNode.declaration typing from `unknown` to the minimal
shape covering all three declaration kinds.
Add three unit tests covering export function, export const (multiple
declarators), and export class; all 38 tests pass.
* refactor: remove DEBUG-gated logging from optimize-imports plugin
* refactor: always emit skip log without DEBUG env gating
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: James <james@eli.cx>
|
||
|
|
f57276a754 |
refactor: extract app route handler cache helpers (#625)
* refactor: extract app route handler cache helpers * chore: remove dead app route cache import |
||
|
|
4af18c72f5 | refactor: extract app route handler execution helpers (#624) | ||
|
|
2ae1eaaa1a | refactor: extract app route handler policy helpers (#622) | ||
|
|
6936601a90 | chore(deps): bump nitro for h3 vulnerability (#621) | ||
|
|
69259bc002 | refactor: extract app route handler response helpers (#619) | ||
|
|
30c86e015a | refactor: extract app route handler request runtime (#618) | ||
|
|
a6d2c3a09c |
Refactor App Router virtual entries into typed runtime modules (#616)
* refactor: move app router SSR/browser runtime into typed modules * style: format typed app runtime modules * refactor: address runtime entry review follow-ups |
||
|
|
47fec0c871 |
chore: upgrade vitest to v4 and enable agent reporter (#612)
* chore: bump pnpm to 10.32.1 and enable vitest agent reporter * chore: upgrade GitHub Actions to Node 24 compatible versions * perf: re-land unit/integration split and add CI sharding - Restore unit/integration vitest project split (lost in vp migration) - vitest 4 supports fileParallelism per-project (was global-only in v3), so unit tests now run in parallel (~11s) while integration stays serial - Add 3-shard matrix for integration tests in CI, cutting wall time from ~4.5min to ~1.5min - Move kv-cache-handler to integration to avoid flakiness under parallelism * chore: remove unused deploy-preview slash command workflow * ci: add sentinel job for stable branch protection rule |
||
|
|
95905ec7ba |
fix: serve public directory files after middleware in production (#602)
* fix: serve public directory files after middleware in production Static file serving in the production server ran before middleware for all files, including those from the public directory. This meant middleware could not intercept or redirect requests to public files like /admin/report.pdf or /internal/data.json. Split static serving into two phases in both Pages Router and App Router production servers: - Before middleware: serve only hashed build assets from /assets/ (Vite output, always public, no protection needed) - After middleware: serve all other static files (public directory content) so middleware can run first For Pages Router, middleware response headers (Set-Cookie, security headers, etc.) are passed through to static file responses via the extraHeaders parameter. For App Router, public directory files fall through to the RSC handler, which runs middleware internally before serving them. * apply config headers before serving public directory static files Step 6 (next.config.js headers) now runs before step 5b (public directory static files), so headers configured via next.config.js headers rules are included in responses for public directory files, matching Next.js behavior. --------- Co-authored-by: James <james@eli.cx>v0.0.32 |
||
|
|
2ef52ad66b |
fix: use page extensions for middleware files (#595)
* fix: use page extensions for middleware files * same thing for instrumentation |
||
|
|
75a03115bb |
fix: scope getStaticProps revalidate parsing to the exported function (#592)
* fix: scope getStaticProps revalidate parsing * fix: handle early returns in getStaticProps revalidate parsing * fix: scope getStaticProps parsing to its declaration * fix: handle destructured getStaticProps params * fix: ignore nested helper returns in getStaticProps * fix: avoid re-export false positives in getStaticProps parsing * fix: harden getStaticProps revalidate parsing * chore: polish getStaticProps parser follow-up |
||
|
|
3af644f5e9 |
docs: strengthen Next.js parity verification in agent guidelines (#604)
Add mandatory Next.js behavior verification for all bug fixes and security work, not just feature development. We repeatedly shipped fixes that diverged from Next.js because this step was skipped (config header execution order, javascript: URI blocking, ISR cache key semantics). Changes: - Add local Next.js clone instructions (.nextjs-ref, gitignored) for fast ripgrep searches of source and test suite - Add 4-step verification checklist to Fixing Bugs section - Require documenting what Next.js does in PRs and issue closures - Update Looking at Next.js Source with clone instructions - Add .nextjs-ref/ to .gitignore |
||
|
|
9c2fd26990 | docs: update Vite 8 guidance (#596) | ||
|
|
e73a8616e7 |
fix: validate attribute names in next/head SSR serializer (#597)
The head.ts SSR serializer was interpolating attribute names (the key variable) directly into HTML without validation. Only attribute values were escaped via escapeAttr(). This could produce malformed HTML when attribute names contain characters like quotes, angle brackets, or slashes. Add isSafeAttrName() that validates attribute names against a strict regex (alphanumeric, hyphens, colons, dots) and blocks inline event handler attributes (on*). Applied in both SSR and client-side syncClientHead() paths. Also adds comprehensive unit tests for the validation function. |
||
|
|
c796d8b771 |
fix: strip Set-Cookie headers from fetch cache entries (#598)
When storing fetch responses in the cache, all response headers were included without filtering. This meant Set-Cookie headers from the original response would be replayed to subsequent requests served from cache, which is incorrect since Set-Cookie is per-response and should not persist across different requests. Both the primary cache write path and the stale-while-revalidate background refresh path now skip Set-Cookie when collecting response headers for the cache entry. Adds a test verifying that the original response retains Set-Cookie but the cached response does not. |
||
|
|
70e7553ae0 |
fix: throw errors when next config file loading fails (#593)
* fix: throw errors when next config parse fails * add unit test |
||
|
|
ea08183af2 |
fix: respect pageExtensions in prerender and exclude _ prefixed api files (#591)
* fix: respect pageExtensions in prerender and exclude _ prefixed api files - cli.ts: hoist resolveNextConfig so pageExtensions is passed to printBuildReport - run-prerender.ts: pass config.pageExtensions to pagesRouter/apiRouter (previously defaulted to ["tsx","ts","jsx","js"] regardless of next.config.js) - pages-router.ts (scanApiRoutes): add _ prefix exclude, matching pages/ behaviour - report.ts: tighten printBuildReport signature (pageExtensions required, not optional) - tests: add coverage for _ prefix exclusion in pages/api and pageExtensions filtering * PR #591 reviewed: 1 bug found, rest LGTM Co-authored-by: james-elicx <james-elicx@users.noreply.github.com> * remove files * fix: pass pageExtensions to appRouter in prerender; harden afterEach cache cleanup - run-prerender.ts: appRouter(appDir) was missing config.pageExtensions, same class of bug as the pagesRouter/apiRouter fix — App Router prerender would scan with default extensions regardless of next.config.js - build-report.test.ts: afterEach now invalidates both app and pages route caches so a test failure can't leak cached routes into subsequent tests --------- Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com> Co-authored-by: james-elicx <james-elicx@users.noreply.github.com> |
||
|
|
7f30707e98 |
feat(check): flag __dirname/__filename and suggest ESM path APIs (#590)
* feat(check): flag __dirname/__filename and suggest ESM path APIs Closes #583 Scan server source files for free uses of the CJS globals __dirname and __filename and report them as a partial compatibility issue in the vinext check output. Uses a single-pass alternation regex that skips string literals, template literals, and comments in one pass, so identifiers inside those tokens are never matched. * refactor(check): merge ViewTransition and __dirname scans into one file loop * Regex misses `__dirname` in template expressions. Co-authored-by: james-elicx <james-elicx@users.noreply.github.com> * fix(check): expose __dirname inside template expressions to identifier match The previous template literal skip branch consumed `${...}` bodies as literal content, so __dirname in e.g. `${__dirname}/views` was silently skipped. Change the template branch to stop at ${ boundaries by excluding $ from the non-interpolation character class: [^`\\$]. A bare $ not followed by { is still allowed via |$(?!\{). This means the ${...} body is never consumed by the skip branch and __dirname inside template expressions is correctly caught by the identifier branch. Adds two new tests: one verifying plain template content (no interpolation) is still ignored, one verifying __dirname inside ${} is detected. * fix(check): use unsupported status for __dirname/__filename; show files in issues section There is no vinext shim for __dirname/__filename in user app code — it is a hard runtime failure, so partial was wrong. Changed to unsupported. Also fixed formatReport to list affected files under each unsupported item in the 'Issues to address' section. Previously files were only shown in the per-section detail view (imports), leaving the summary section without the context needed to actually fix the issue. Adds a test verifying the file path appears in the formatted report. * remove files --------- Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com> Co-authored-by: james-elicx <james-elicx@users.noreply.github.com> |
||
|
|
f78dd2b39f |
chore: switch back to setup-vp@v1 (#587)
* chore: switch back to setup-vp@v1 * try commit hash * back to v1 |
||
|
|
337b65dfb7 |
feat: support next/dist/* internal imports and auto-generate .js shim aliases (#586)
* feat: add bare next/dist/shared/lib/router-context alias and strengthen test * feat: add next/dist/shared/lib/router-context to IMPORT_SUPPORT check map * fix: remove bare router-context alias, only .shared-runtime exists in Next.js * feat: add all shimmed next/dist/* paths to IMPORT_SUPPORT check map * refactor: auto-generate .js alias variants via flatMap in nextShimMap * limit .js map to next/ |
||
|
|
a86b67dff4 |
feat: full next/compat/router support and fix vinext check scanner for shimmed modules (#584)
* feat: full next/compat/router support and fix check scanner for shimmed modules - Export NextRouter interface from shims/router.ts so compat-router.ts can use it - Add NextRouter | null return type to useRouter() in shims/compat-router.ts - Add next/compat/router, next/form, next/web-vitals, next/constants to IMPORT_SUPPORT in check.ts so vinext check no longer flags them as unsupported - Add integration fixture pages and components for next/compat/router in both pages-basic and app-basic fixtures - Add integration tests in tests/pages-router.test.ts and tests/nextjs-compat/hooks.test.ts - Add unit tests in tests/check.test.ts covering all four newly-registered imports * test: update entry-templates snapshots for compat-router-test fixture page * remove type assertion * fix: add declare module for next/compat/router to fixture next-shims.d.ts files * fix: add declare module for next/compat/router to internal next-shims.d.ts * pin to vp setup 1.0.0 |
||
|
|
746dc5b522 |
feat!: fumadocs example, mark clientReferenceDedup experimental (#577)
* chore: init fumadocs * vinext init * fix dev * tweaks * tweaks * tweaks * prefer exports * tweaks * try new approach * update * tweaks * Update * exclude deps * update * revert * tweaks * tweaks * tweaks * tweaks * pre include @unpic/react * clientReferenceDedup * tweaks |
||
|
|
8bf8a020c2 |
fix: add basePath and locale properties to NextURL (#516)
* fix: add basePath and locale properties to NextURL NextURL was missing basePath, locale, defaultLocale, and locales properties that Next.js documents and real middleware depends on. - Add NextURLConfig interface for passing basePath and i18n config - Extract locale from pathname on construction (case-insensitive) - Reconstruct full URL (with locale + basePath) in href/toString() - Validate locale setter against configured locales - Thread config through NextRequest → NextURL in all middleware paths: dev middleware runner, App Router RSC entry, Pages Router prod entry * fix: address review feedback on NextURL - Fix basePath setter: setting to "" no longer produces "/" (URL corruption) - Fix href setter: re-strip basePath before locale re-analysis - Extract _stripBasePath() to avoid duplicated stripping logic - Simplify NextRequest constructor type (remove misleading intersection) - Validate locale setter against configured locales (throw TypeError) - Remove dead `|| "/"` fallback in _analyzeLocale - Add tests for basePath clear and href setter with basePath * fix: address second round of review feedback - Strip nextConfig from init before passing to Request super() - Defensive copy of i18n.locales on construction - Add test for NextRequest with Request object input (real callsite pattern) - Silence unused parameter diagnostic in test * fix: address third round of review feedback on NextURL - Fix trailing slash bug: _formatPathname produced /app/ instead of /app at root pathname - Fix locale getter to return "" instead of undefined when no i18n config, matching Next.js's behavior - Eliminate URL allocation in href getter via string slicing - Consolidate _stripBasePath into single condition/assignment - Remove unnecessary ternary guard in _analyzeLocale - Use defensive locale copy consistently in constructor - Add regression tests for trailing slash (basePath-only and basePath + default locale), basePath input stripping, pathname setter contract, and href with port/search/hash * fix: locale setter resets to defaultLocale on falsy value when i18n configured Addresses 4th round of review feedback on PR #516: - Bug: locale setter now falls back to defaultLocale instead of allowing undefined/empty when i18n is configured (prevents invalid no-locale state) - Add tests for searchParams mutations reflected in href with basePath/locale - Add nextUrl.href round-trip assertions to NextRequest integration tests * Update packages/vinext/src/shims/server.ts Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> --------- Co-authored-by: James Anderson <james@eli.cx> Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> |
||
|
|
0b43cad172 |
chore(deps): bump next from 16.1.6 to 16.1.7 (#579)
* chore(deps): bump next from 16.1.6 to 16.1.7 Bumps [next](https://github.com/vercel/next.js) from 16.1.6 to 16.1.7. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v16.1.6...v16.1.7) --- updated-dependencies: - dependency-name: next dependency-version: 16.1.7 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * add benchmarks --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: James <james@eli.cx> |
||
|
|
9cec92beaa |
refactor: migrate ssrLoadModule to moduleRunner.import (#570)
* refactor: migrate ssrLoadModule to moduleRunner.import Replace all server.ssrLoadModule() calls in dev-server.ts and api-handler.ts with runner.import() via the ModuleImporter interface, following the Vite Module Runner migration guide. Remove ssrFixStacktrace calls which are not needed with Module Runner APIs. Update call sites in index.ts to pass the existing getPagesRunner() lazy factory as the runner argument. Update tests to use ModuleImporter-shaped mocks instead of the old ViteDevServer mock. * refactor: address bonk review comments - Fix misleading stack trace comments: ssrFixStacktrace is not applicable to ModuleRunner (not that it rewrites automatically) - Update ALS-ARCHITECTURE.md to reference runner.import() instead of server.ssrLoadModule() - Add importModule() typed helper to instrumentation.ts to centralise the Record<string, any> cast, removing 12 inline eslint-disable comments - Remove dead ssrLoadModule mock from pages-router ISR test (createSSRHandler no longer calls server.ssrLoadModule) |
||
|
|
a6d6688663 |
fix: pass --no-git-checks to pnpm publish (#576)
The version bump step modifies package.json without committing, leaving the working tree dirty. pnpm publish enforces git-checks by default and refuses to publish. Pass --no-git-checks before the -- separator so pnpm skips the check while --provenance still reaches npm.v0.0.31 |
||
|
|
2a8d51233a |
fix: rewrite Flight HL stylesheet hints during client-side navigation (#560)
* fix: rewrite Flight HL "stylesheet" hints at the stream source React Flight emits HL hints with "stylesheet" for CSS, but the HTML spec requires "style" for <link rel="preload">. The previous fix only covered SSR-embedded Flight data, leaving client-side navigation, server actions, and HMR with invalid preload hints. Wrap renderToReadableStream in app-rsc-entry.ts with a line-buffered TransformStream that rewrites hints at the source — one fix that covers all consumers. Remove the now-redundant fixFlightHints from the SSR embed transform. Closes #558 * chore: retrigger CI --------- Co-authored-by: James <james@eli.cx> |
||
|
|
05bae291cc | pass through provenance flag (#575) | ||
|
|
ea73e5b97d |
fix: implement prefix-based invalidation for revalidatePath layout type (#544)
* fix: implement prefix-based invalidation for revalidatePath with type layout
revalidatePath(path, "layout") now invalidates the layout path AND all
child pages beneath it, matching Next.js behavior. Previously the type
parameter was declared but completely ignored.
Adds collectTagsByPathPrefix to the CacheHandler interface (optional) and
implements it on MemoryCacheHandler. The prefix match is path-segment-aware
so /dashboard matches /dashboard/settings but not /dashboard-admin.
* fix: address review feedback for revalidatePath layout type
- Update next-shims.d.ts with collectTagsByPathPrefix on CacheHandler and MemoryCacheHandler
- Add console.warn when layout invalidation degrades on handlers without prefix support
- Normalize trailing slashes in revalidatePath (public API safety)
* refactor: replace collectTagsByPathPrefix with revalidateByPathPrefix
The caller (revalidatePath) should declare WHAT it wants — "invalidate
everything under this prefix" — not HOW to do it by collecting tags and
passing them back. revalidateByPathPrefix lets each backend handle
prefix invalidation optimally (e.g., KV can do bulk prefix operations
natively instead of O(n) iteration in the caller).
* fix: address PR review feedback — remove dead code, harden tag matching, add tests
- Remove unreachable `prefix === "_N_T_/"` branch from `_isPathChildOf`;
`revalidateByPathPrefix` always passes plain normalized paths, never
`_N_T_`-prefixed ones. Add explanatory comment that the `/` root case
works because custom tags never start with `/`.
- Guard against non-path custom tags in `revalidateByPathPrefix`: after
stripping `_N_T_` prefix, skip entries where rawPath doesn't start
with `/` to avoid false matches on user-defined tags.
- Add JSDoc to `CacheHandler.revalidateByPathPrefix` clarifying that when
present it fully replaces the `revalidateTag` call path — implementors
are solely responsible for handling all stored path-based tags.
- Add trailing-slash normalization tests: `revalidatePath("/dashboard/", "layout")`
and `revalidatePath("/about/", "page")` must behave identically to
their non-trailing-slash equivalents (normalization already present in
`revalidatePath`; tests confirm the invariant).
* refactor: replace O(n) prefix scanning with O(1) layout tag invalidation
Rearchitect revalidatePath(path, "layout") to match Next.js's
getDerivedTags / getImplicitTags approach:
- __pageCacheTags now generates layout hierarchy tags at render time
(e.g., /dashboard/settings carries _N_T_/layout, _N_T_/dashboard/layout,
_N_T_/dashboard/settings/layout)
- revalidatePath("/dashboard", "layout") invalidates a single tag
(_N_T_/dashboard/layout) instead of scanning all cache entries
- Remove revalidateByPathPrefix from CacheHandler interface — no new
interface methods needed, works with existing revalidateTag
- Remove _isPathChildOf helper and MemoryCacheHandler.revalidateByPathPrefix
This makes layout invalidation O(1) instead of O(n), works with all
cache backends (KV, Redis, etc.) without special support, and matches
how Next.js achieves this internally.
* fix: implement distinct page-type invalidation for revalidatePath
Address PR review feedback:
- revalidatePath(path, "page") now invalidates _N_T_<path>/page (leaf
page tag), distinct from omitting type which invalidates the broader
path tags. Matches Next.js getDerivedTags behavior.
- __pageCacheTags now generates _N_T_<path>/page leaf tag at render time.
- Simplify trailing-slash ternary to explicit root-path check.
- Add test proving "page" type and no-type take distinct code paths.
- Add "keep in sync" comment on test helper.
* fix: double-slash in root page cache tag breaks revalidatePath("/", "page")
__pageCacheTags("/") generated "_N_T_//page" (double-slash) while
revalidatePath("/", "page") targeted "_N_T_/page" (single-slash),
causing silent invalidation failure for root page entries.
Add root-path guard in __pageCacheTags and the test helper
deriveImplicitTags. Add test for revalidatePath("/", "page").
Rename misleading test name and add Pages Router doc comment.
* refactor: simplify revalidatePath and reduce comment noise
- Collapse layout/page branches into one: tagBase + "/" + type
- Extract tagBase computation to eliminate repeated root-path ternary
- Trim redundant inline comments — JSDoc already explains the approach
- Trim verbose test helper docstring
- Update snapshots
* refactor: simplify tag construction — reuse stem, eliminate tagBase ternary
- revalidatePath: 3 lines of logic, single tag expression, no branching
- __pageCacheTags: reuse `built` variable for page tag (same stem invariant)
- deriveImplicitTags test helper: keep in sync with source
- Update snapshots
|
||
|
|
1d92846d70 |
fix: support @voidzero-dev/vite-plus-core as vite alias (#559)
* fix: support @voidzero-dev/vite-plus-core as vite alias * update * Update * Tweaks |
||
|
|
499ee6a979 |
refactor: use vinext startProdServer HTTP for prerendering (#574)
* refactor: remove wrangler from prerender, use startProdServer HTTP for all builds All builds — plain Node and Cloudflare Workers — now prerender via HTTP against a locally-spawned startProdServer instance. Removes findWranglerConfig, loadWrangler, WranglerWorker, isWorkersBuild branching, and all related fields from PrerenderAppOptions. Fixes generateStaticParamsMap not being accessible from CF bundles by falling through to the RSC handler for prerender endpoints. * . * fix: redirect:manual for prerender fetch, remove wrangler peer dep - Add redirect:manual to renderPage fetch in prerenderPages so that getStaticProps redirect responses are captured instead of followed. Fixes the 'escapes meta refresh URL to prevent HTML injection' test. - Remove wrangler from devDependencies and peerDependencies now that all prerender logic goes through the HTTP prod server path. * refactor: extract readPrerenderSecret to shared utility, fix server-manifest secret rotation - Move readPrerenderSecret to src/build/server-manifest.ts so both build-time code (prerender.ts) and runtime code (prod-server.ts) can import it without creating a circular dependency - Fix vinext:server-manifest plugin calling randomBytes() independently in each Vite environment (rsc + ssr), causing the second env to overwrite the first with a different secret; generate the secret once at plugin creation time via an IIFE closure - Replace the two duplicate manifest-read blocks in prod-server.ts with calls to the shared readPrerenderSecret helper * fix: warn on non-OK response from pages-static-paths prerender endpoint A 403 (stale secret) or 500 from the endpoint was silently treated the same as 'no paths', causing dynamic routes to be skipped without any indication of why. Now logs a warning with the HTTP status code so a stale-secret scenario is visible in the build output. * fix: guard static-params fetch against non-OK responses in prerenderApp A 403 (stale secret) or 500 from /__vinext/prerender/static-params was passed directly to JSON.parse, which throws on a non-JSON body like 'Forbidden'. The rejected Promise was then cached in staticParamsCache, poisoning all subsequent generateStaticParams lookups for that route. - Check res.ok before JSON.parse; return null and warn on non-OK status - Evict the cache entry on rejection so a transient error does not permanently block the route |
||
|
|
6a68fd1c94 |
fix: harden origin validation and proxy request handling (#571)
* Treat `Origin: "null"` as an explicit cross-origin value Also strip Transfer-Encoding * Further hardening * Add Vite HMR websocket token regression tests Verify that websocket upgrades with an Origin header are rejected when the HMR token is missing or invalid, and accepted when the token matches the value exposed by @vite/client. * Add clarifying comment and share a single dev server |
||
|
|
69a0cff2d5 | fix: add assets.directory to generated wrangler.jsonc (#569) | ||
|
|
6d261846d5 |
refactor: migrate wrangler unstable_dev to unstable_startWorker (#568)
* fix: migrate wrangler unstable_dev to unstable_startWorker
Replace the deprecated wrangler unstable_dev API with unstable_startWorker
across the prerender pipeline. Key changes:
- Use unstable_startWorker({ entrypoint, config, bindings }) instead of
unstable_dev(script, { local, vars, experimental, logLevel })
- Replace Unstable_DevWorker type with the Worker type returned by
unstable_startWorker
- Replace .stop() cleanup calls with .dispose()
- Pass VINEXT_PRERENDER via bindings: { type: 'plain_text', value: '1' }
instead of vars
- Update all comments and test descriptions to reference the new API
- Update entry-template snapshots to reflect updated comment text
* fix: silence wrangler logs during prerender via dev.logLevel: none
* fix: update stale JSDoc comments referencing unstable_dev
|
||
|
|
75eb3c1e8d |
feat: production prerender pipeline (#553)
* feat: production prerender pipeline (no dev server)
Add prerenderPages() and prerenderApp() that load from production bundles
exclusively — no ViteDevServer dependency. runPrerender() replaces
runPrerenderWithDevServer() (kept as a shim for cli.ts/deploy.ts callers).
Key changes:
- packages/vinext/src/build/prerender.ts — new: prerenderPages/prerenderApp
- packages/vinext/src/build/run-prerender.ts — new: runPrerender + shim
- packages/vinext/src/build/static-export.ts — delegates to prerender layer
- packages/vinext/src/index.ts — adds rscOutDir/ssrOutDir/clientOutDir/disableAppRouter options
- packages/vinext/src/entries/app-rsc-entry.ts — exports generateStaticParamsMap
- packages/vinext/src/entries/pages-server-entry.ts — exports pageRoutes
- packages/vinext/src/shims/cache.ts — adds NoOpCacheHandler
- packages/vinext/src/server/prod-server.ts — serves prerendered .html files
- packages/vinext/src/build/report.ts — integrates PrerenderResult for build report
- tests/helpers.ts — buildPagesFixture/buildAppFixture helpers (isolated outDirs)
- tests/prerender.test.ts — 36 new tests
- tests/static-export.test.ts/app-router.test.ts/pages-router.test.ts — updated
All 487 tests pass. Typecheck, lint, fmt clean.
* fix: build Pages Router SSR bundle for hybrid app+pages projects
For hybrid projects that have both app/ and pages/ directories, the
App Router multi-env build (createBuilder/buildApp) does not produce
a Pages Router SSR bundle. The prerender phase then fails with 'bundle
not found at dist/server/entry.js'.
Fix: after buildApp() completes, detect pages/ presence and run a
separate standalone Vite SSR build with vinext({ disableAppRouter: true })
so the plugin's multi-env environments config does not override the SSR
input/entryFileNames. The build is run with emptyOutDir: false to preserve
RSC artefacts from the App Router build.
* Clean up run-prerender: remove deprecated shim, fix progress arithmetic and typos
* regen snaps
* refactor: clean up prerender pipeline, cache shim, and report utilities
- Extract loadBundle() helper used by both prerenderPages and prerenderApp
- Save/restore CacheHandler around prerender calls (isolation fix)
- Rename isExplicitlyDynamic -> isConfiguredDynamic for clarity
- Export getRscOutputPath and findDir for cross-module reuse
- Import findDir in run-prerender.ts; remove duplicated inline logic
- Fix single-row table corner character in formatBuildReport
- Remove duplicate JSDoc block before revalidateTag in cache shim
- Add SetCtx interface; eliminate (ctx as any) casts in MemoryCacheHandler
- Replace string sentinel in unstable_cache with structural CacheResultWrapper
- Use completedUrls += 1 consistently in Pages Router phase
* fix: remove unused appDir from PrerenderAppOptions and all callers
prerenderApp() never reads appDir — the bundle is loaded via rscBundlePath
and route scanning is done before the call. Remove the field from
PrerenderAppOptions and AppStaticExportOptions, and drop the now-unused
argument from all callers in run-prerender.ts, static-export.ts, and tests.
* refactor: miscellaneous cleanups across prerender, cache, cli, and deploy
* refactor: document gaps, re-detect project after installDeps, fix Vite resolution in deploy.ts
* refactor(prerender): unify onProgress to single call site; document pageCount semantics
Extract renderUrl() inner function from prerenderApp's concurrency loop so
that onProgress is called exactly once per URL at the outer loop level, rather
than being duplicated inside two early-return branches. Eliminates the risk
of accidentally omitting the callback when adding future early exits.
Add a comment in toStaticExportResult clarifying when pageCount and
files.length can diverge (currently they stay in sync).
* build(prerender): write non-export prerender output to dist/server/prerendered-routes/
On Cloudflare Workers, wrangler.jsonc uses not_found_handling: "none" so
every request hits the worker first. Files in dist/client/ are never
auto-served for page requests — they are uploaded but remain inert.
Writing prerendered HTML/RSC to dist/server/prerendered-routes/ keeps
them co-located with server artifacts and away from the static assets
directory.
This also prevents a future issue: when KV pre-population is implemented,
ISR route files must not be in dist/client/ or they would be served as
stale static files forever (bypassing revalidation).
output: 'export' builds are unaffected — static-export.ts passes its
own outDir explicitly, and runPrerender still writes to dist/client/
when mode === 'export'.
* .
* build: suppress IMPORT_IS_UNDEFINED warnings for generateStaticParams
Dynamic route pages that don't export generateStaticParams produce noisy
IMPORT_IS_UNDEFINED warnings because the virtual RSC entry unconditionally
emits mod?.generateStaticParams for every dynamic route. The optional
chaining guards the access safely at runtime; suppress the build-time noise
in the existing onwarn handler alongside the MODULE_LEVEL_DIRECTIVE filter.
* .
* .
* .
* fix conflict regression
* regen snaps
* update cli comment
* add todo for rsc double req
* encode uri components
* encode uri components
* fix: inherit user plugins in hybrid Pages Router secondary build
The secondary `vinext build` step for hybrid (App Router + Pages Router)
projects was constructed with `configFile: false` and only `vinext({ disableAppRouter: true })`,
dropping all user-supplied plugins including `cloudflare()`. This caused a
false-positive "Missing @cloudflare/vite-plugin" error on any hybrid project
with a `wrangler.jsonc` present.
Fix by inheriting the resolved plugin list from the already-completed App
Router builder, filtering out the `vinext:*` sub-plugins and re-injecting
`vinext({ disableAppRouter: true })` in their place. `configFile: false` is
retained to prevent the user's `environments` block from overriding the SSR
input and `entryFileNames`.
* fix: filter vite:react and vite-tsconfig-paths from inherited hybrid build plugins
vinext auto-registers both @vitejs/plugin-react (vite:react*) and
vite-tsconfig-paths when disableAppRouter is false, so inheriting the
resolved plugin list from the App Router builder caused duplicates.
Filter them out alongside the vinext:* sub-plugins.
* fix: filter rsc:* and vite-rsc-* plugins from inherited hybrid build plugins
@vitejs/plugin-rsc registers resolveId for App Router virtual modules
(virtual:vinext-app-ssr-entry etc.) but the corresponding load hooks live
in vinext:* — which we already strip. With rsc:* present but vinext:* absent
the virtual module resolves but can't load, crashing with PLUGIN_ERROR.
Filter rsc:* and vite-rsc-load-module-dev-proxy alongside the other
auto-registered plugin families.
* fix: skip missing-Cloudflare-plugin guard for hybrid Pages Router secondary build
The secondary build uses configFile:false + vinext({disableAppRouter:true})
so it never loads cloudflare() — by design, it's a plain SSR Rollup bundle
with no Workers config. The configResolved guard was incorrectly treating
this as a misconfigured user build.
Revert the inherited-plugins approach (cloudflare() reconstructs the full
multi-env environments block from its own plugin config hook, independent
of vite.config, causing the App SSR virtual entry to be resolved but not
loadable). Instead, gate the guard on !options.disableAppRouter, which is
exclusively set by this internal invocation.
* fix: inherit user transform plugins in hybrid Pages Router secondary build
The secondary SSR build for hybrid projects (app/ + pages/) was introduced
on this branch and always used configFile:false with only
vinext({disableAppRouter:true}), silently dropping any user transform
plugins from vite.config.ts (SVG loaders, CSS-in-JS, etc.).
Fix by loading the raw user config via loadConfigFromFile (before any
plugin config() hooks fire, so cloudflare() hasn't yet injected its
multi-env environments block) and forwarding all plugins except the
families that vinext auto-registers or that would break the plain SSR
build: vinext:*, vite:react*, rsc:*, vite-tsconfig-paths,
vite-rsc-load-module-dev-proxy, and vite-plugin-cloudflare*.
* fix: reset ANSI styling before prerender and route report output
Vite's logger leaves the terminal in a styled state after build output.
Write an ANSI reset before the prerender label and route report so they
always print at full brightness regardless of preceding Vite output.
* fix: skip instrumentation register() during prerender to prevent process hang
Instrumentation modules like @vercel/otel register OpenTelemetry SDK
exporters with background timers that keep the Node process alive
indefinitely. During prerender these side-effects cause the process to
hang after rendering completes, preventing the route report from printing.
Set VINEXT_PRERENDER=1 in prerenderApp/prerenderPages and gate
__ensureInstrumentation() on that env var so instrumentation is skipped
during prerender builds.
* feat: production prerender pipeline for Cloudflare Workers (App Router + Pages Router)
- Add wrangler devDep to packages/vinext; add `@cloudflare/workers-types` to catalog
- Fix prerender.ts: typed wrangler import, Unstable_DevWorker, undici Response casts,
URL-string extraction for dev.fetch() (no Request object), staticParamsMap Proxy
- Remove process.env.VINEXT_PRERENDER gate from /__vinext/prerender/static-params endpoint
- Add cf-app-basic pages/ fixture (index, about, posts/[slug], api/ping)
- Update buildCloudflareAppFixture to also run buildPagesFixture for Pages Router bundle
- Add CF prerender tests: shared beforeAll, nested App Router + Pages Router describes (46/46)
* .
* feat: CF Workers hybrid build Pages Router prerender support
* regen snaps
* fix(prerender): detect CF Workers build via @cloudflare/vite-plugin in node_modules
Replace wrangler.json file-presence check with node_modules detection,
consistent with how deploy.ts detects CF projects. The old check looked
in dist/server/ which never has a wrangler.json, so isWorkersBuild was
always false for CF projects, causing only the 404 to be prerendered.
The generated dist/server/wrangler.json (from @cloudflare/vite-plugin)
is now used as the config path for unstable_dev, with the project-root
wrangler.jsonc as a fallback. This ensures assets.directory is present,
which wrangler 4+ requires.
* fix(prerender): address bonk review comments
- runWithConcurrency: early return on empty items instead of spawning a
spurious worker via the '|| 1' fallback
- staticParamsMap Proxy: flip has() trap to return false so the typeof-fn
check works for routes without generateStaticParams on CF Workers builds;
also handle null return from the proxy fn (no generateStaticParams) in
the parent-params expansion path
- loadWrangler(): extract shared helper with a two-candidate fallback
(wrangler-dist/cli.js → index.js) used by both prerenderApp and
runPrerender, replacing duplicated path resolution + existence checks
- VINEXT_PRERENDER process.env mutation: add explanatory comments
documenting why the global mutation is intentional and safe for the
sequential-call contract
* refactor(prerender): extract findWranglerConfig, pass CF detection through, cache static-params
- Extract findWranglerConfig(serverDir, projectRoot) helper from both prerender.ts and
run-prerender.ts, eliminating the duplicated 4-candidate wrangler.json search
- Add isWorkersBuild and wranglerConfigPath optional fields to PrerenderAppOptions so
runPrerender can pass its already-computed values into prerenderApp, avoiding a
redundant findInNodeModules walk + 4-candidate fs.existsSync loop on every build
- Add a per-build staticParamsCache (Map keyed on pattern+parentParams) inside the CF
Proxy to dedup repeated /__vinext/prerender/static-params round-trips for deeply
nested dynamic routes
- Add clarifying comment on renderUrl's runWithHeadersContext wrapper explaining it is
a no-op for the CF Workers path (rscHandler is an HTTP proxy; ALS context never
crosses the isolate boundary) but kept for shape-compatibility across both modes
* fix(prerender): address remaining bonk review comments
- Security: gate /__vinext/prerender/* endpoints behind VINEXT_PRERENDER=1 check
to prevent exposure in normal deployments (process.env works for both Node and
CF Workers via Miniflare's var injection into process.env)
- Bug: buildUrlFromParams now throws a clear error when a required param is
missing instead of silently producing 'undefined' in the URL
- Design: add TODO comments for layout-level generateStaticParams limitation in
both resolveParentParams() and generateStaticParamsMap
- Correctness: prerenderPages() now uses runtime module exports (getServerSideProps/
getStaticProps) to classify page type on Node builds instead of static file
analysis; CF builds continue to use classifyPagesRoute() as fallback
* fix(prerender): address latest bonk review comments
- Add shape validation for parentParams on the /__vinext/prerender/static-params
endpoint: JSON.parse result is guarded to ensure user generateStaticParams always
receives a plain object, never a primitive, array, or null
- Add .gitignore for tests/fixtures/cf-app-basic/dist/ to prevent accidental commits
of build output if test cleanup fails (CF fixture builds to source tree, unlike
other fixtures that use tmpdirs)
- Add comment on nextConfigOverride shallow merge in run-prerender.ts to make the
limitation explicit for future maintainers
* regen snaps
* fix: build CF fixture in tmpdir instead of source tree
Use createIsolatedFixture in buildCloudflareAppFixture so the CF Vite
build output goes to a tmpdir rather than tests/fixtures/cf-app-basic/dist/.
Adds an optional nodeModulesDir param to createIsolatedFixture so callers
with fixture-scoped deps (like @cloudflare/vite-plugin) can point the
symlink at the fixture's own node_modules instead of the workspace root.
Removes the stopgap .gitignore and the afterAll that deleted dist/ from
the source tree.
* fix: cache-bust prod-server import() to prevent stale module reuse in tests
startProdServer() used a bare file:// URL for its dynamic import() of the RSC
entry bundle. Node's module cache keyed on that URL, so when two test describe
blocks rebuild to the same output path the second invocation always got the
cached module from the first build. The stale module had __instrumentationInitialized
already set to true and globalThis.__VINEXT_onRequestErrorHandler__ pointing at the
first build's instrumentation instance, whose capturedErrors array lived in a
different module instance than the one the production route handler was reading.
Fix: append ?t=<mtime> to the import URL, matching the pattern used by prerender.ts
loadBundle(). Same mtime means same content (cache hit, no-op); new mtime means
a fresh build and gets a fresh module. Applied to both startAppRouterServer and
startPagesRouterServer.
Also removes debug console.log calls from the instrumentation production test.
* fix: use globalThis for instrumentation test state to survive sequential prod builds
In Vitest, the 'App Router Production build' and 'App Router Production
server' describes run in the same process. The first build's preview server
imports dist/server/index.js uncached, setting
globalThis.__VINEXT_onRequestErrorHandler__ to onRequestError_v1 from the
first module instance. The second build is loaded cache-busted (by mtime),
producing a fresh module instance (v2). After v2 sets the handler, v1's
__ensureInstrumentation can re-fire and overwrite it, causing errors from
v2 to be recorded in capturedErrors_v1 (a different array) while the GET
route reads capturedErrors_v2 (empty).
Fix: store capturedErrors and registerCalled on globalThis (same pattern
as the middleware counter) so all module instances write to and read from
the same shared state regardless of which build instance is active.
Also removes debug logging and restores the afterAll dist cleanup that was
commented out during investigation.
* add process.exit(0) at end of build
|
||
|
|
25f6e0fc87 |
chore: enable typeAware and typeCheck, use vp check (#551)
* chore: enable typeAware and typeCheck, use vp check
* Build
* Try cache false
* Revert "Try cache false"
This reverts commit
|
||
|
|
2f42e05dae |
fix: merge top-level optimizeDeps with per-environment config (#539)
* fix: merge top-level optimizeDeps with per-environment config When vinext's config() hook builds per-environment optimizeDeps, it now merges any exclude/include entries from config.optimizeDeps (populated by earlier plugins like @lingui/vite-plugin) instead of overwriting them. This ensures packages excluded by other plugins remain excluded across all three environments (rsc, ssr, client). Fixes #538 https://claude.ai/code/session_01T7kpjyEcsZTUG6wHscx3nb * fix: merge top-level optimizeDeps with per-environment config When vinext's config() hook builds per-environment optimizeDeps, it now merges any exclude/include entries from config.optimizeDeps (populated by earlier plugins like @lingui/vite-plugin) instead of overwriting them. This ensures packages excluded by other plugins remain excluded across all three environments (rsc, ssr, client). Add test verifying top-level optimizeDeps entries are preserved in all per-environment configs after vinext's config hook runs. Fixes #538 https://claude.ai/code/session_01T7kpjyEcsZTUG6wHscx3nb * docs: add PR description for optimizeDeps merge fix https://claude.ai/code/session_01T7kpjyEcsZTUG6wHscx3nb * chore: remove pr-description.md https://claude.ai/code/session_01T7kpjyEcsZTUG6wHscx3nb * fix: merge optimizeDeps for Pages Router + improve test coverage Address PR review feedback: - Move incomingExclude/incomingInclude capture above the hasAppDir branch so Pages Router builds also merge entries from earlier plugins (e.g. @lingui/vite-plugin) instead of overwriting them. - Add @vercel/og assertion to confirm vinext's own entries survive merging. - Add Set-based deduplication assertions to all environments. - Test overlapping entries (incoming "vinext" + vinext's own "vinext") to validate the Set dedup logic. https://claude.ai/code/session_01T7kpjyEcsZTUG6wHscx3nb * fix: preserve optimizeDeps.include for Pages Router + fix formatting Address upstream PR review feedback: - Spread incomingInclude into top-level optimizeDeps so Pages Router builds preserve include entries from upstream plugins (e.g. @lingui/vite-plugin) - Run pnpm run fmt to fix formatting in test file https://claude.ai/code/session_01T7kpjyEcsZTUG6wHscx3nb --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f3c02b84a0 |
test: add production coverage for global-error compat (#556)
* Add production coverage for global-error compat * Address global-error review feedback * docs: update tracker notes for tests #3 and #4 to reflect production preview coverage |
||
|
|
847260ccde |
fix: align next/headers cookie path semantics (#555)
* Align next/headers cookie path semantics * Address cookie path review feedback |
||
|
|
e720372e8e |
fix: preserve named has captures in config destinations (#557)
* fix: preserve named has captures in config destinations * fix: align locale-static has capture precedence |
||
|
|
ad368d9c53 |
fix: use waitUntil in after() for Cloudflare Workers (#543)
* fix: use waitUntil in after() to prevent task loss on Cloudflare Workers
after() was scheduling tasks as fire-and-forget microtasks, which are
silently dropped when the Cloudflare Workers runtime terminates after
the response stream completes.
Now uses getRequestExecutionContext()?.waitUntil() to keep tasks alive
past the response, falling back to fire-and-forget when no execution
context exists (e.g. Node.js dev server).
Also adds throwIfInsideCacheScope() guard matching Next.js behavior —
after() must not be called inside "use cache" scopes.
* fix: make after() synchronous with static imports per review feedback
after() was async with dynamic imports, which broke the API contract:
- Next.js after() returns void (fire-and-forget), so making it async
causes unhandled rejections when throwIfInsideCacheScope throws
- await import() deferred waitUntil registration to a later microtask,
risking task loss if the Worker tears down in between
Changed to static top-level imports and synchronous function signature.
Updated tests to assert synchronous throw behavior and remove setTimeout
hacks that were only needed due to the async implementation.
* fix: inline cache-scope check in after() to avoid headers.ts static import
The static `import { throwIfInsideCacheScope } from "./headers.js"` pulled
headers.ts into the module graph for every consumer of next/server. headers.ts
contains the literal string "use cache" which triggers Vite's cache-transform
handler (`code.includes("use cache")`). Pages Router fixtures without
@vitejs/plugin-rsc then fail at dev-server startup.
Inline the cache-scope check using the same Symbol.for ALS keys, avoiding the
problematic import chain. This mirrors how connection() already avoids eagerly
importing headers.ts (via dynamic import), but keeps after() synchronous.
* address review: fix cache-scope error message and add unstable_cache test for after()
* fix: use AST directive check before throwing missing-RSC error in use-cache transform
The fast-path code.includes("use cache") check in the vinext:use-cache transform
handler was firing on files that contain the string only in error messages or
comments (after TS constant folding), not as actual directives. This caused Pages
Router fixtures without @vitejs/plugin-rsc to throw at startup when server.ts
(which uses "use cache" in its cache-scope error message) was transformed.
Fix: parse the AST first and confirm an actual file-level or function-level
"use cache" directive exists before throwing the missing-RSC error.
Also address second-round review comments:
- server.ts: add TODO about silent waitUntil fallback vs Next.js throw behavior
- server.ts: document timing divergence (concurrent vs post-response) on task execution
- shims.test.ts: drop unnecessary async from runWithExecutionContext callback
- shims.test.ts: bump fire-and-forget fallback timeout 10ms -> 50ms for CI robustness
* fix: tighten AST walker and clarify after() TODO per review
- Add MethodDefinition to the explicit node-type check in astHasInlineCache
(was handled by generic key traversal but should be listed explicitly)
- Fix array heuristic in astHasInlineCache to use .some() instead of checking
child[0], which could miss arrays starting with null
- Remove node.consequent fallback from nodeHasInlineCacheDirective — consequent
is a SwitchCase property and a 'use cache' directive there would not be a
valid function-level directive
- Clarify the after() TODO comment: the dev server always returns null from
getRequestExecutionContext(), so the fire-and-forget fallback is correct there;
the warning concern is production-only (Workers with missing runWithExecutionContext)
* address bonk review: clarify comments and improve test determinism
- Add inline comment explaining why `?? node.value` is needed for MethodDefinition nodes in the AST walker
- Expand TODO on the after() fire-and-forget fallback to note the warn should be gated to production (NODE_ENV or Workers runtime check) and use a module-level _warned flag
- Replace 50ms setTimeout in the fire-and-forget test with two await Promise.resolve() ticks, which is deterministic since after() wraps tasks in Promise.resolve().then(task)
* test: make error-swallowing after() test deterministic
Replace 10ms setTimeout with two microtask awaits in the pre-existing
error-swallowing test, matching the same pattern used in the fire-and-forget
test. after() wraps function tasks in Promise.resolve().then(task) so two
await Promise.resolve() ticks are sufficient and deterministic.
* address bonk review: clarify MethodDefinition unwrapping, unhandledrejection safety note, and ALS test isolation comment
* fix: unwrap BlockStatement in nodeHasInlineCacheDirective to restore inline use-cache detection
---------
Co-authored-by: James <james@eli.cx>
|
||
|
|
abb1aa617c |
fix: eliminate double middleware execution in hybrid app+pages dev mode (#541)
* fix: eliminate double middleware execution in hybrid app+pages dev mode
In hybrid app+pages dev setups, middleware ran twice per App Router
request — once in the connect handler (SSR env) and again in the RSC
entry (RSC env). This caused double side effects for auth checks,
logging, analytics, and any middleware state mutations.
The connect handler now forwards its middleware results to the RSC entry
via an x-vinext-mw-ctx request header containing the response headers,
rewrite URL, and rewrite status. The RSC entry reconstructs _mwCtx from
the forwarded data instead of re-executing the middleware function.
Key details:
- Response headers are deferred (not applied to res) in the connect
handler when hasAppDir is true, avoiding duplicates. They're applied
to res only for Pages routes handled directly by the connect handler.
- The RSC plugin constructs its Request from the original HTTP request,
not from req.url, so middleware rewrites must be forwarded explicitly
(the connect handler's req.url rewrite is invisible to the RSC entry).
- Pure App Router (no pages/) and pure Pages Router (no app/) setups
are unaffected — they already use a single middleware execution path.
* test: update entry template snapshot for middleware forwarding
* fix: address code review findings for middleware forwarding
- Guard x-vinext-mw-ctx check with NODE_ENV !== "production" so the
forwarding header cannot be exploited in production builds
- Replace bare catch {} with error logging and fallback to running
middleware normally when the forwarded context is malformed
- Filter x-middleware-next and x-middleware-rewrite from forwarded
header entries to match the RSC entry's inline filtering behavior
- Apply deferred middleware response headers before all three
proxyExternalRewriteNode call sites (beforeFiles, afterFiles,
fallback external rewrites)
* test: update entry template snapshot for review fixes
* fix: extract deferred headers helper, clarify flag placement per review
- Extract applyDeferredMwHeaders() helper to deduplicate 6 identical
inline blocks in the connect handler
- Add comment clarifying that __mwCtxApplied flag placement inside try
block is intentional — falls back to re-running middleware on error
|
||
|
|
39791bf3cc |
feat: implement revalidateByPathPrefix on KVCacheHandler (#552)
* feat: implement revalidateByPathPrefix on KVCacheHandler Uses KV list metadata to discover entry tags without extra get() calls. set() now stores tags in KV metadata, and revalidateByPathPrefix reads them back via kv.list() — making prefix invalidation O(list_pages) instead of O(entries × get). Entries written before metadata support are gracefully skipped. * fix: guard against KV metadata 1024-byte limit Cloudflare KV limits metadata to 1024 bytes per key. If an entry's tags exceed this budget when JSON-serialized, omit metadata entirely and fall back gracefully — prefix invalidation skips entries without metadata, and exact-path invalidation via revalidateTag still works. * test: add regression test for KV metadata 1024-byte overflow |
||
|
|
9aa1748ea6 |
fix(ssr): preload client reference modules before first SSR render (#257)
On cold start, client reference modules ('use client' components) are loaded
lazily via async import() through @vitejs/plugin-rsc's memoized
__vite_rsc_client_require__. The memoize cache is cold, so the function returns
an unresolved Promise. Without <Suspense> wrapping the root shell, React SSR
rejects and the server returns 500. Subsequent requests work because the cache
is warm from the first (failed) attempt.
The fix imports virtual:vite-rsc/client-references in the generated SSR entry
and awaits all client reference modules via Promise.all before
renderToReadableStream runs. This warms the memoize cache so all calls during
rendering resolve synchronously.
Changes addressing reviewer feedback:
- Add regression test replicating the memoize + cold-cache pattern (thanks to
@NathanDrake2406 for contributing the test via PR #546 comment — greatly
- Replace silent .catch(() => {}) with dev-mode console.warn per review
- Add _clientRefsPreloaded flag to skip preloading after first request
- Remove unnecessary optional chaining on .default and .catch
Closes #256
Co-authored-by: NathanDrake2406 <NathanDrake2406@users.noreply.github.com>
|
||
|
|
cd08cbaa6e | fix: add ResponseCookies.has() shim (#554) |