feat(init): onboard MDX and Tailwind v4 apps on first build - #3239
JamesbbBriz wants to merge 40 commits into
Conversation
Migrating a Next.js app that uses MDX pages and Tailwind v4 currently fails twice before the first successful build: - @mdx-js/rollup is required for the documented MDX auto-injection but was never installed by init - Tailwind v4 configured through postcss.config string plugins cannot resolve under Vite; it needs @tailwindcss/vite in the config detectProject now reports hasTailwind (tailwindcss dependency); init installs @mdx-js/rollup / @tailwindcss/vite for detected frameworks and generates vite configs that wire tailwindcss() for both platforms. The next.config webpack-ignore warning now explains that Vite resolves TS/TSX natively, since every Payload-style extensionAlias config trips it today. Output for projects without Tailwind stays byte-identical.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
@vinext/cloudflare
create-vinext-app
@vinext/types
vinext
@cloudflare/workers-response-store
commit: |
Performance benchmarksCompared 0 improved · 0 regressed · 6 within ±1.5%
View detailed results and traces 🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head |
The first pass rewrapped a dozen untouched call sites onto multiple lines. main is format-clean with the single-line forms, so the rewraps were pure review noise.
# Conflicts: # packages/vinext/src/init.ts
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
1 similar comment
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
Posted 1 actionable inline finding. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
Posted 2 actionable inline findings. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
Posted 1 actionable inline finding. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
Posted 1 actionable inline finding. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
Posted 1 actionable inline finding. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
Posted 3 actionable inline findings. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
Posted 3 actionable inline findings. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
Posted 5 actionable inline findings. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
Posted 6 actionable inline findings. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
Posted 2 actionable inline findings. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
Posted 1 actionable inline finding. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
|
@james-elicx Bonk workflow was cancelled. View workflow run · To retry, trigger Bonk again. |
|
/bigbonk review for issues Time limit: 8 minutes. Complete the review and post all findings within that limit. |
| ); | ||
| } | ||
| if (expression?.type === "Identifier" && !seenBindings.has(expression.name)) { | ||
| const initializer = findVisibleConstInitializer(program, scopeTarget, expression.name); |
There was a problem hiding this comment.
[P2] Validate identifier-backed nested arrays before trusting their calls
This follows a nested const array but only the outer plugins array is checked by assertPluginArrayIsStatic. For const nested = [tw()]; nested.pop(); export default { plugins: [nested] }, the scan finds tw() in the initializer and leaves the config unchanged, although the effective plugins array is empty at runtime. Please apply the post-initializer mutation check to every identifier-backed array traversed here (including aliases), or conservatively append Tailwind when its effective contents are not static.
| return; | ||
| } | ||
| } | ||
| forEachAstChild(node, visit); |
There was a problem hiding this comment.
[P2] Do not treat mutations inside unexecuted functions as top-level writes
This recursively scans function bodies, so an unrelated declaration such as const config = { plugins: [] }; function unused() { config.plugins.push(custom()) } export default config makes Tailwind onboarding abort even though unused is never executed and the exported config remains static. Existing Node configs were previously skipped, so rerunning init now rejects this otherwise supported static form. Skip nested function/class bodies when looking for post-initializer execution, and account for a mutation only when the function is actually part of the evaluated config path.
| statement.type === "ExportNamedDeclaration" ? statement.declaration : statement; | ||
| if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") continue; | ||
| for (const declarator of declaration.declarations) { | ||
| if (declarator.id.type !== "Identifier") continue; |
There was a problem hiding this comment.
[P2] Follow destructured aliases of namespace default exports
Restricting aliases to identifier declarators misses import * as tailwind from "@tailwindcss/vite"; const { default: tw } = tailwind; export default { plugins: [tw({ optimize: false })] }. prepareTailwindPlugin knows the namespace binding, but neither this alias collector nor callee resolution associates tw with tailwind.default, so init appends tailwind.default() and registers Tailwind twice. Please recognize immutable { default: local } aliases here as well.
|
Posted 3 actionable inline findings. |
What changed
vinext initnow completes the Vite-side setup for existing Next.js projects that use MDX or Tailwind CSS v4:@mdx-js/rollup, which vinext already auto-injects at runtime@tailwindcss/viteand generated Node and Cloudflare configs registertailwindcss()webpackwarning explains Vite's native TypeScript resolution and directs genuinely custom rules tovite.config.tsTailwind detection prefers v4-specific packages and the exact installed
tailwindcsspackage major through project-aware resolution, including Yarn PnP. That handles comparator, prerelease-only, workspace, git, and tag declarations without maintaining a partial semver parser. Simple explicit v4 declarations remain a fallback when dependencies are not installed; unresolved complex specifiers are not guessed.Next.js currently scaffolds Tailwind v4 with
@tailwindcss/postcss: https://github.com/vercel/next.js/blob/canary/packages/create-next-app/templates/app-tw/ts/postcss.config.mjsFixes #3240.
Validation
vp test run tests/init.test.ts tests/init-cloudflare.test.ts tests/create-vinext-app.test.ts tests/next-config.test.ts tests/deploy.test.ts— 887 passedvp check packages/vinext/src/init.ts packages/vinext/src/init-cloudflare.ts packages/vinext/src/utils/project.ts packages/vinext/src/config/next-config.ts packages/vinext/src/plugins/ast-utils.ts tests/init.test.ts tests/init-cloudflare.test.ts tests/create-vinext-app.test.ts tests/next-config.test.ts tests/deploy.test.ts— cleanvp run vinext#build— passedRegression coverage exercises App and Pages Router init on Node and Cloudflare for Tailwind v3 and v4, MDX dependency onboarding for both router types, installed-major detection across supported dependency specifiers, generated-config syntax/type checking and real Vite loading of the ESM-only Tailwind package, the supported ESM and CommonJS default-import interop matrix, including typed CommonJS require wrappers, type-only imports before runtime imports, typed/parenthesized plugin-call wrappers, nested and nested, statically spread, logical, conditional, and top-level plain and exported variable-backed plugin arrays, computed ESM and CommonJS default access, and unchanged output when Tailwind is absent.