Commit Graph

22 Commits

Author SHA1 Message Date
Yunfei He feb78add12 feat(pages): enforce reactStrictMode by wrapping client root in <StrictMode> (#2433)
* feat(pages): enforce reactStrictMode by wrapping client root in <StrictMode>

`reactStrictMode: true` was recognized but not enforced — the app root was
never wrapped in <React.StrictMode>, so dev-time strict checks (double-invoked
effects/render, deprecation warnings) were silently lost.

Resolve `reactStrictMode` from next.config (preserved as `boolean | null` so
each router applies its own default) and, for the Pages Router, wrap the
client tree in <React.StrictMode> when the value is `true`. The default
matches Next.js: `null`/unset is OFF for the Pages Router
(`reactStrictMode === null ? false` in define-env.ts).

The wrap lives in `wrapWithRouterContext` (next/router) — the single seam every
render path funnels through: the initial hydration entry (production AND the
dev server's inline hydration script) and every client-side navigation
`root.render()` in shims/router.ts. This mirrors Next.js, whose `doRender`
closure wraps in <React.StrictMode> for both the initial hydrate and subsequent
`reactRoot.render()` calls (client/index.tsx). Wrapping only the production
client entry would have been inert — StrictMode does nothing in production, and
the dev server hydrates via a separate template — so the flag is also threaded
into createSSRHandler and the dev hydration script. The wrap is gated on a
client-only `window.__VINEXT_REACT_STRICT_MODE__` flag so the server-rendered
tree is never wrapped (Next.js wraps client-side only); StrictMode renders no
DOM, so SSR markup and hydration are unaffected. The CommitBoundary stays
outside StrictMode so its commit effect is not double-invoked (Next.js keeps
`<Root>` outside <StrictMode> too).

`vinext check` reports reactStrictMode as "partial": enforced for the Pages
Router, but the App Router is not yet wrapped (its root is mounted by the RSC
client runtime, not vinext-owned code, and Next.js defaults App Router strict
mode on).

* test(pages): cover strict mode navigation renders

---------

Co-authored-by: James <james@eli.cx>
2026-06-30 23:06:25 +01:00
James Anderson ee551d1e28 fix(config): match deprecation warning parity (#2252)
* fix(config): match deprecation warning output

* fix(config): dedupe deprecation warnings

* fix(test): preserve lifecycle install diagnostics

* fix(check): report unsupported proxy config options
2026-06-23 20:39:39 +01:00
James Anderson 2181c904dc fix(check): only flag config options used as property keys (#1778)
* fix(check): only flag config options used as property keys

vinext check reported `webpack` as an unsupported config option even
when the project had no custom webpack config. The analyzeConfig
heuristic matched the bare word `webpack` anywhere in next.config (a
comment, or a value substring like `react-server-dom-webpack`),
producing false positives.

Match the option name only when it appears as an actual object property
key (`opt:`, method shorthand `opt(`, assignment `opt =`, or quoted
`"opt":`).

* fix(check): strip comments and string values before option detection

Addresses review: the boundary+follower regex alone only fixed comments
without a trailing :/(/= and still flagged options mentioned in comments
(// webpack: removed) or embedded in string values ("webpack:1234").

Add a single-pass, lexer-grade pre-pass (modeled on hasFreeCjsGlobal)
that removes comments and empties string/template-literal values before
running the per-option regexes. It is regex-literal aware so a quote
inside /['"]/ cannot hijack string state, and quoted property keys
("webpack":) are preserved. Runs in O(n) time so it is safe on large
files, consistent with the #1776 large-file fix.

Adds tests for the reviewer's comment and string-value cases plus
scanner edge cases (regex literals, template interpolation, division).

* fix(check): scope nested option lookup to parent block; escape regex keys

Addresses Bonk re-review findings:

- Dot-notation options (e.g. i18n.domains) were detected by testing the
  parent block and child name as two independent regexes anywhere in the
  file, so a config with both `i18n: {…}` and `images: { domains: [...] }`
  wrongly reported `i18n.domains`. Extract the parent block body (naive
  brace balancing is safe on already-stripped content) and require the
  child to appear inside it.
- Escape interpolated option/key names before building RegExps via a new
  escapeRegExp helper, future-proofing against keys with metacharacters.

Adds tests for both scoping cases.

* fix(check): scan all parent blocks and match quoted parent keys

Closes the two non-blocking findings from Bonk re-review:

- extractBlockBody only inspected the first matching parent block, so a
  child key in a later same-named block (two `experimental: {}` objects)
  was missed. Renamed to extractBlockBodies, iterating every match.
- The dot-notation opener now allows an optional closing quote, so quoted
  parent keys (`"experimental": {}`) are matched, consistent with how the
  per-option regex handles quoted keys.

Adds tests for both cases.

* refactor(check): parse next.config to an AST instead of scanning text

Replace the hand-rolled comment/string scanner + regex heuristics with a
proper AST parse via oxc-parser (the project's own toolchain). analyzeConfig
now reads option keys directly off the exported config object, which is
exact: comments, string values, regex literals, template literals, and
identifier substrings can never be mistaken for a config key.

This deletes stripCommentsAndStrings, extractBlockBodies, and escapeRegExp
(~115 net lines) and also handles cases the text scan couldn't: configs
wrapped in plugin calls (withMDX(config)), variable-referenced configs, and
TS as/satisfies annotations. The dot-notation child lookup is now a true
parent/child relationship from the AST rather than scoped text matching.

Adds oxc-parser as a dependency (already in the install tree via vite-plus).

* refactor(check): use Vite's parseAst instead of adding oxc-parser dep

Vite already re-exports parseAst (its bundled oxc/rolldown parser), so
there's no need to add oxc-parser as a separate dependency. Parse with
{ lang: "ts" } so .ts configs with type annotations / as / satisfies parse
the same as .js. Types come from Vite's exported ESTree namespace.

Drops the oxc-parser dependency and catalog entry added in the previous
commit; vite is already a required peer dependency of vinext.

* fix(check): detect function-form next.config

Bonk re-review caught a regression vs main: function-form configs — a
documented Next.js pattern that vinext supports at runtime
(config/next-config.ts) — reported no options under the AST rewrite
because resolveObject did not look through functions.

Extend resolveObject to unwrap arrow/function configs: a concise arrow
body is the config object directly; a block body resolves the first
top-level return statement's argument. Covers both
`(phase) => ({…})` and `function(phase, { defaultConfig }){ return {…} }`.

Adds tests for both forms.

* fix(check): detect `export default function` config form

resolveObject handled FunctionExpression (module.exports = function(){…})
and arrow forms but not FunctionDeclaration, which is what
`export default function (phase) { return {…} }` parses to. That form
silently reported no options.

Add FunctionDeclaration to the function-form branch and widen the param
type to ESTree.Function. Adds a regression test.

* fix(check): detect options across all branches of multi-phase configs

resolveObject took only the first top-level return of a function body, so
the canonical next/constants multi-phase form — where the phase-specific
config is an early return nested in an if and the default is the trailing
return — only reported the default branch's keys.

Resolve all object literals a config expression can denote and merge their
keys: collect every reachable return arg (descending through if/switch/try
but not into nested functions) and handle ternary branches. Covers the
PHASE_* if/return form and the ternary function form.

Adds tests for both.
2026-06-07 00:43:53 +01:00
James Anderson 77cc46bc43 fix(check): prevent regex stack overflow / hang on very large files (#1776)
* fix(check): prevent regex stack overflow / hang on very large files

The check command had two file-content regexes that fail badly on very
large inputs:

- cjsGlobalScanRegex used a string-body sub-pattern (?:[^"\\]|\\.)*,
  a star over an alternation group that V8 cannot compile into a tight
  loop. It pushes one backtrack frame per character and overflows the
  regex stack ("Maximum call stack size exceeded") on large files such
  as a multi-MB minified bundle or a long/unterminated string literal.
  Replaced with hasFreeCjsGlobal(), a single-pass O(n)-time,
  O(template-nesting)-stack scanner that preserves existing behavior.

- stringPluginRegex (PostCSS string-form plugin detection) used two lazy
  [\s\S]*? quantifiers around a capture group, causing quadratic
  catastrophic backtracking on a config with no closing ]. Replaced with
  a linear anchored regex matching the same configs.

Adds regression tests covering multi-MB unterminated/escape-heavy inputs
and the PostCSS backtracking case, plus behavior tests for the scanner.

* style(check): apply formatter fixes

* fix(check): make __dirname scanner regex-literal aware

Addresses Bonk review feedback. The single-pass scanner had no concept
of regex literals, so an unpaired quote or backtick inside one (e.g.
/'/ or /`/) hijacked the string/template state and could swallow real
code after it, hiding a later __dirname (a false negative the old regex
did not have).

The scanner now tracks whether a / is in expression position (regex
literal) vs after a value (division), and skips regex-literal bodies
honoring escapes and [...] char classes. Also:

- replace magic-number offsets (i+9/i+10) with full-identifier matching
  against a CJS_GLOBALS set
- correct the string-literal comment re: line continuations

Adds tests covering regex literals hiding/​not-hiding __dirname, division
not being mistaken for a regex, and __dirname inside a regex literal.

* fix(check): treat } and postfix ++/-- as value position

Addresses Bonk re-review. After } or a postfix ++/--, the scanner set
prevType to "op", so a following division / was misread as a regex
literal and swallowed the rest of the line, hiding a same-line __dirname
(e.g. "i++ / 2; const d = __dirname;").

Fix: treat } as value-producing (the common { … } / x object-literal
division), and consume ++/-- without changing expression position
(postfix keeps value, prefix keeps op). Biasing ambiguous cases toward
division is safe because a misread division never consumes a following
identifier, whereas a misread regex swallows the line.

Adds tests for division after }/postfix ++/-- and confirms a real regex
after a prefix ++ / keyword still parses.

* docs(check): pin value-position regex limitation; clarify PostCSS comment

Addresses Bonk round-3 review (both notes non-blocking).

- Document the residual division-vs-regex limitation on hasFreeCjsGlobal:
  a value-position regex literal after a block } (e.g.
  "function f(){} /'/.test(x)") is read as division, so a stray quote in
  its body can mask a same-line __dirname. Fully resolving this needs a
  parser; accepted for an advisory check. Added tests pinning the gap and
  confirming the multi-line variant still works.
- Soften the PostCSS comment: the anchored regex matches the same
  string-form configs but intentionally no longer matches the
  require()-form (a false positive in the old regex).
2026-06-06 00:14:11 +01:00
Divanshu Chauhan (divkix) 19dfd5e009 feat(config): add experimental.appShells config plumbing and validation (#1692) 2026-05-31 18:22:15 +01:00
Divanshu Chauhan (divkix) 00f585969d feat: recognize Sparkle experimental flags (varyParams, optimisticRouting, cachedNavigations) (#1655)
* feat(check): recognize Sparkle experimental flags in CONFIG_SUPPORT

* test(check): add tests for Sparkle experimental flags

* feat(config): warn when cachedNavigations is set without cacheComponents

* test: address ask-bonk review on cachedNavigations tests

- Remove redundant mockRestore() calls (afterEach handles it)
- Add negative test cases for cachedNavigations not set / false
- Match existing swcEnvOptions test style (warn.mock.calls.find())
2026-05-29 11:27:42 +01:00
Divanshu Chauhan (divkix) cd1013ea33 feat(config): add experimental.appShells plumbing (#1415)
* feat(config): add experimental.appShells plumbing

Accepts experimental.appShells in next.config and defines
process.env.__NEXT_APP_SHELLS=false at build time, matching
Next.js PR #93997 plumbing scope. Behavorial implementation
is gated on upstream co-flags that vinext does not yet support.

- Adds experimental.appShells to CONFIG_SUPPORT as unsupported
- Sets __NEXT_APP_SHELLS define to false regardless of config value
- Tests for vinext check detection and Vite define injection

Fixes #1405

* review: address PR nits

- Expand experimental.appShells detail to explain why it's unsupported
- Move build-time define tests into their own describe block, so the
  __NEXT_APP_SHELLS define test isn't nested under basePath

* review: drop orphaned server lifecycle from basePath describe

The two remaining resolveNextConfig tests import from source directly
and don't use the dev server.

---------

Co-authored-by: James <james@eli.cx>
2026-05-22 10:26:40 +01:00
Divanshu Chauhan (divkix) 155be501e1 fix: accept experimental.swcEnvOptions config without erroring (#977)
* fix: accept experimental.swcEnvOptions config without erroring (#774)

Next.js added experimental.swcEnvOptions for SWC preset-env polyfill injection.
vinext uses Vite for transforms, so polyfill injection is not applicable, but
the config key should be accepted without errors. Add:
- CONFIG_SUPPORT entry in check.ts (unsupported with detail)
- Warning during config resolution in next-config.ts
- Tests for analyzeConfig detection and dot-notation matching

* refactor: align swcEnvOptions messaging and add test coverage

- Change runtime warning from "not yet supported" to "not applicable"

- Update CONFIG_SUPPORT detail for consistent messaging

- Add test coverage for swcEnvOptions warning in next-config.test.ts

* fix(check): update test assertion for swcEnvOptions message

The CONFIG_SUPPORT message for experimental.swcEnvOptions was updated
to indicate it's not applicable since vinext uses Vite instead of SWC.
This updates the corresponding test assertion to match the new message.

Fixes CI failure in tests/check.test.ts
2026-04-30 08:53:58 +01:00
James Anderson c5da1db4ff refactor: share config support list across checks (#971)
* refactor: share config support list across checks

* fmt
2026-04-29 20:17:07 +00:00
Divanshu Chauhan (divkix) cc0001e5ea fix: add experimental.prefetchInlining to compatibility check (#949)
* fix: add experimental.prefetchInlining to compatibility check (#860)

Next.js flipped experimental.prefetchInlining from false to true by default
in PR #92863. vinext does not implement per-segment prefetch inlining — it
uses unified RSC navigation payloads — so no behavioral change is needed.
This adds the config option to the compatibility scanner so vinext check
correctly reports it as supported.

* fix: mark experimental.prefetchInlining as partial, add test

- Change status from supported to partial (per review feedback —
  option is recognized but underlying behavior is not implemented)
- Add test case in tests/check.test.ts matching existing
  experimental.* config patterns
2026-04-29 18:30:20 +01:00
yyh e81820279e fix: preserve console output for caught app errors in dev (#862)
* fix: preserve console output for caught app errors in dev

* address bonk review comments on PR 862

---------

Co-authored-by: James <james@eli.cx>
2026-04-20 08:20:59 +01:00
Shorebirdmgmt e854f77bd4 Update @clerk/nextjs compatibility: unsupported → partial (#803)
Tested against @clerk/nextjs v7.0.12 with vinext 0.0.40. Runtime-verified
that clerkMiddleware, auth.protect, frontendApiProxy, ClerkProvider, and
client hooks all work through vinext's middleware runtime.

What works:
- clerkMiddleware() — runs correctly, authenticates requests
- auth.protect() — redirects unauthenticated page requests to sign-in
- frontendApiProxy — proxies Clerk Frontend API through the app domain
- ClerkProvider — renders with Clerk JS SDK and publishable key
- x-clerk-auth-* headers — propagated correctly on responses
- Clerk handshake flow — 307 redirect with correct handshake URL

What's still WIP:
- auth() in Server Components (requires next/headers shim)

Closes #800

Co-authored-by: Sam Hill <samhill7738@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 07:18:00 +01:00
James Anderson 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>
2026-03-19 16:23:10 +00:00
James Anderson 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/
2026-03-19 09:19:38 +00:00
James Anderson 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
2026-03-19 08:47:40 +00:00
Stephen Zhou c17d6941be chore: migrate to vite plus (#535)
* chore: migrate to vite plus

* Disable typeAware and typeCheck

* Update CI

* Fix CI

* Fix test

* Clean

* Run test with vp

* Try revert

* react: false In test

* Fix test

* Revert "Try revert"

This reverts commit 009da10473.

* Update

* Update

* Try revert ci changes

* revert

* Run vp migrate

* Disable typeAware and typeCheck for now

* Better resolve for test

* Use vp dev instead of vite

* Update expect

* Fix NormalizeManifestModuleId

* Try increase timeout

* Update to use vp

* Try new check

* Bring back npx vp

* Migrate CI

* Make next-intl resolvable

* Update

* Update

* Update
2026-03-15 10:50:13 +00:00
Stephen Zhou 21558f3092 feat: support @vitejs/plugin-react v6 as peer (#525)
* feat: support @vitejs/plugin-react v6 as peer

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update
2026-03-13 15:35:36 +00:00
Nathan Nguyen 38dc409a43 feat: Add Pages Router i18n domain routing (#471)
* Add Pages Router domain locale routing

* Add coverage for Pages Router domain locale behavior

* Format Pages Router i18n domain routing changes

* Handle basePath in Pages Router i18n domains

* Refactor Pages Router i18n domain fixtures

* Update Pages Router entry snapshot after rebase

* Align domain locale redirects with Next.js

* Clarify Pages Router domain locale helpers

* Fix case-insensitive locale prefixes

* Simplify same-domain locale URL handling

* Cover same-domain locale alias redirects

* Document Pages Router i18n invariants

* Guard preferred locale in domain redirects
2026-03-12 13:49:59 +00:00
James Anderson 764a496ce7 add oxfmt formatter (#380)
* add oxfmt formatter: config, scripts, CI, editor setup, docs

* rebuild lockfile

* fix: add Format to required checks list, remove dead ignore pattern

* run fmt

* add format to agents.md again
2026-03-09 14:56:14 +00:00
Nathan Nguyen 2b51ca7a85 fix: update stale compat scanner entries (#370)
next/font/local variable mode works (className and variable both
supported) — the scanner still said it was broken.

styled-components and @emotion/react work via useServerInsertedHTML —
the scanner still said the hook wasn't implemented.

Updated both entries from partial to supported with accurate details,
and updated corresponding test assertions.
2026-03-09 09:57:06 +00:00
Jared Stowell 49689537d0 Fix and fully implement allowedDevOrigins (#337) 2026-03-08 08:56:15 +00:00
Steve Faulkner 12fea722b6 Initial public release of vinext 2026-02-24 09:29:39 -06:00