Skip to content

Fix next.config.ts loading regression (exports is not defined in ES module scope) - #1051

Merged
theoephraim merged 3 commits into
mainfrom
fix-nextjs-config-ts-esm-require
Aug 31, 2026
Merged

Fix next.config.ts loading regression (exports is not defined in ES module scope)#1051
theoephraim merged 3 commits into
mainfrom
fix-nextjs-config-ts-esm-require

Conversation

@theoephraim

@theoephraim theoephraim commented Aug 31, 2026

Copy link
Copy Markdown
Member

Fixes a regression from the tsdown migration (#1021): using varlockNextConfigPlugin in a next.config.ts file fails with

Failed to load next.config.ts
ReferenceError: exports is not defined in ES module scope

Root cause

Next's next.config.ts loader transpiles the config and everything it requires through a require.extensions hook that converts ESM to CJS via SWC, then calls mod._compile. For .mjs files, Node's Module._compile routes to loadESMFromCJS, so the SWC-produced CJS code gets evaluated as ESM and the injected exports references blow up.

Pre-tsdown this never triggered because varlock's dist was extensionless .js. Post-tsdown, plugin.cjs does require('varlock/env') etc., and those entry points became ESM-only .mjs files. The trigger is varlock 1.17.1, independent of the integration version (verified all 4 published combos). The same requires also break outright on Node 22.0-22.11, where require(esm) is behind a flag. createRequire in the user's config does not work around it: the extension hooks are process-global.

Fix

  • varlock now ships real CJS builds of its runtime entry points (env, patch-console, patch-server-response, encrypt-env, exec-sync-varlock), exposed via the require condition. Requiring them from CJS stays CJS end to end, which is safe through Next's hook and on Node without require(esm). This also covers the expo integration's CJS outputs, which had the same latent exposure. Multiple instances coexisting with the ESM builds is already handled: env and redaction state live on globalThis by design, and the patches guard via markers on the patched globals.
  • The nextjs plugin keeps requiring the installed varlock (no bundling), so its runtime glue can never version-skew against the CLI. next-env-compat stays bundled (required for Vercel serverless tracing, [BUG]: Cannot find module 'varlock', on Vercel #584) but only carries runtime glue; env loading always execs the installed CLI.
  • Convert import { type SerializedEnvGraph } from 'varlock' to import type. The old form left a side-effect require("varlock") in the output that pulled the entire varlock index at runtime, and was inlining ~475KB of never-executed loader machinery into next-env-compat.cjs (516KB -> 41KB).

Tests

New framework-test dev scenario loads the app through a real next.config.ts (runs on Next 15, 16, and canary), with new deleteFiles harness support to drop the skeleton next.config.mjs that would otherwise shadow it. Verified the scenario fails against the pre-fix build and passes with the fix. Full Next 16 suite (both bundlers), expo suite, and varlock unit tests all pass.

Also documents in AGENTS.md that single-package builds must go through turbo (bunx turbo run build from the package dir), since bun run --filter <pkg> build skips workspace dependency builds. That gap is what led the automated review to a false build-failure finding.

…ies from CJS plugin build

Next's TS config loader re-transpiles required .mjs files to CJS via SWC,
but Node evaluates .mjs files as ESM, so the transpiled code hits
'exports is not defined in ES module scope'. Bundle varlock into the
build-time entry points (like next-env-compat already does) so the CJS
output never requires an .mjs file. Also fixes require(esm) failures on
Node 22.0-22.11.

Convert { type X } imports to import type so rolldown drops the
side-effect require('varlock'), which was also inlining the entire
varlock index into next-env-compat (516KB -> 41KB).

Add a framework-test dev scenario covering next.config.ts (with new
deleteFiles harness support to drop the skeleton next.config.mjs).
@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

bumpy-frog

The changes in this PR will be included in the next version bump.

minor Minor releases

  • @varlock/native-helper-darwin 1.17.1 → 1.18.0
  • @varlock/native-helper-linux-arm64 1.17.1 → 1.18.0
  • @varlock/native-helper-linux-x64 1.17.1 → 1.18.0
  • @varlock/native-helper-win32-x64 1.17.1 → 1.18.0
  • varlock 1.17.1 → 1.18.0

patch Patch releases

  • @varlock/nextjs-integration 1.2.1 → 1.2.2

Bump files in this PR

Click here if you want to add another bump file to this PR


This comment is maintained by bumpy.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes covering the CommonJS build boundary, type-only import cleanup, and Next.js regression coverage.

  • CJS build boundary: Bundles varlock runtime imports into the build-time integration entries so plugin.cjs no longer loads ESM-only runtime files through Next's TypeScript config hook.
  • Type-only imports: Removes unintended runtime imports of the main varlock entry and avoids bundling unrelated code into next-env-compat.cjs.
  • Regression coverage: Adds deleteFiles scenario isolation and exercises a real next.config.ts through Next.js 15, 16, and canary dev startup and page rendering.

Pullfrog  | View workflow run | Using azure/gpt-5.6-sol𝕏

…hem into the nextjs plugin

Bundling froze the plugin's copy of varlock runtime code at the
integration's build time, so it could diverge from the installed varlock
version. Instead, varlock now emits real .cjs builds of env,
patch-console, patch-server-response, crypto, and exec-sync-varlock,
exposed via the require condition. The nextjs plugin goes back to
requiring the installed varlock, which is now hook-safe and works on
Node without require(esm).

This also covers the expo integration's CJS outputs, which had the same
latent require(esm) exposure. next-env-compat stays bundled (required
for Vercel serverless tracing, #584) but only carries runtime glue.
@github-actions

Copy link
Copy Markdown
Contributor

📦 Bundle size

⚠️ grows the bundle by 161.3 KB (+3.9%)

Metric main This PR Δ
Total dist 4128.2 KB 4289.5 KB +161.3 KB (+3.9%)
JS 1596.7 KB 1645.4 KB +48.7 KB (+3.0%)
Sourcemaps 2446.2 KB 2548.4 KB +102.2 KB (+4.2%)
Type defs 85.2 KB 95.7 KB +10.5 KB (+12.3%)
Other 0.0 KB 0.0 KB

dist/ only; native binaries are versioned separately and not counted here.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The new CommonJS declaration build fails, so the varlock package cannot currently be built or released.

Reviewed changes since the prior Pullfrog review, covering the switch from integration-side bundling to package-level CommonJS runtime exports.

  • Added CommonJS runtime builds: Added .cjs builds and conditional require exports for the five runtime entry points consumed by CommonJS integrations.
  • Restored external runtime loading: Removed varlock bundling from the Next.js build-time entries so the plugin uses the installed package version.
  • Expanded release scope: Added a varlock minor bump and updated the changeset to describe compatibility with Next's TypeScript config loader and older Node 22 releases.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using azure/gpt-5.6-sol𝕏

Comment thread packages/varlock/tsdown.config.ts
@pkg-pr-new

pkg-pr-new Bot commented Aug 31, 2026

Copy link
Copy Markdown

Open in StackBlitz

varlock

npm i https://pkg.pr.new/varlock@1051

@varlock/nextjs-integration

npm i https://pkg.pr.new/@varlock/nextjs-integration@1051

@varlock/native-helper-darwin

npm i https://pkg.pr.new/@varlock/native-helper-darwin@1051

@varlock/native-helper-linux-arm64

npm i https://pkg.pr.new/@varlock/native-helper-linux-arm64@1051

@varlock/native-helper-linux-x64

npm i https://pkg.pr.new/@varlock/native-helper-linux-x64@1051

@varlock/native-helper-win32-x64

npm i https://pkg.pr.new/@varlock/native-helper-win32-x64@1051

commit: c900bd1

bun run --filter <pkg> build does not build workspace dependencies, and
some builds need their dist output (varlock's d.ts bundling inlines
@env-spec/utils's emitted declarations). This tripped up the automated
PR review, which ran the varlock build in isolation and attributed the
pre-existing dependency requirement to this PR's changes.
@theoephraim
theoephraim merged commit 04dc94b into main Aug 31, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant