Commit Graph

1 Commits

Author SHA1 Message Date
James Anderson 8de3afdf25 fix(config): recognize next.config.mts and interop CJS imports in config loader (#1296)
* fix(config): recognize next.config.mts and interop CJS imports in config

The next.config.ts loader did not recognize next.config.mts and did not
interop CJS imports (.cjs/.cts/.js/.ts files that assign to module.exports)
brought in by the config file. As a result, fixtures from the Next.js
test/e2e/app-dir/next-config-ts-native-mts/ family failed with either
"Cannot find next.config.*" (silent null return) or "module is not
defined" inside Vite's ESM-only module runner.

Two changes:

1) The loader's local CONFIG_FILES list is extended to include
   next.config.mts (matching the canonical list in shims/constants.ts,
   which already mirrors Next.js packages/next/src/shared/lib/constants.ts)
   while keeping .cjs for backwards-compat with existing vinext users.
   deploy.ts and check.ts pick up the same extended list so their config
   scanners stay in sync.

2) A new Vite plugin (cjsImportsInteropPlugin) is installed on the inline
   runnerImport instance. It wraps .cjs/.cts files (and .js/.ts files that
   statically assign to module.exports) so that module.exports becomes the
   ESM default export, mirroring how Next.js's SWC pipeline auto-converts
   CJS imports from inside next.config.ts. The transform is scoped to the
   config-load instance only, so it never affects the user's application
   module graph.

Tests ported from .nextjs-ref/test/e2e/app-dir/next-config-ts-native-mts/
{nested-imports,import-js-extensions-cjs,import-js-extensions-esm,
import-from-node-modules}/.

* docs(config): clarify CJS exports/module.exports semantics in comment

The previous comment said "Named exports off module.exports are also
reflected", which glosses over the asymmetric behaviour that Node CJS
modules exhibit when user code does `module.exports = X` followed by
`exports.foo = Y`: the latter writes to the discarded original object
and does not show up on the final default export. Rewrite the comment to
spell out the aliasing, the reassignment break, and the
`module.exports.foo` workaround.

* refactor(config): reuse vite-plugin-commonjs for next.config sibling imports

The hand-rolled cjsImportsInteropPlugin duplicated logic that vinext
already pulls in via vite-plugin-commonjs (used globally for application
code in packages/vinext/src/index.ts:795). Drop the bespoke plugin and
install commonjs() on the inline runnerImport instance instead.

Two pieces of configuration are needed for parity with the previous
behaviour:

1. resolve.extensions explicitly includes .cjs and .cts. vite-plugin-commonjs
   keys its 'should I transform this id?' check off config.resolve.extensions,
   and Vite's default list (.mjs/.js/.mts/.ts/.jsx/.tsx/.json) omits both
   CJS-only extensions. Without this, .cjs/.cts imports flowed through
   the runner untransformed and still crashed with 'module is not defined'.

2. A filter that:
   - Returns false for the config file itself, so cjsGlobalsInjectorPlugin
     remains the sole producer of the file's module wrapper (otherwise
     both plugins inject 'module = …' and the runner trips on
     'Identifier module has already been declared').
   - Returns true for ids under node_modules, so bare-import packages
     imported by next.config.* (CJS plugin wrappers, etc.) get transformed.
     vite-plugin-commonjs defaults to skipping node_modules.

Net diff: -59 lines, all four next-config-extensions tests still pass,
no regression on tests/next-config.test.ts (108 passing) or the broader
deploy/features/shims/check suites (1637 passing in aggregate).
2026-05-18 12:07:44 +01:00