Skip to content

chore: fully specify config-graph imports for native ESM resolution - #16

Merged
JakeGinnivan merged 1 commit into
mainfrom
jakeginnivan/esm-extensionless-imports-nodenext-resolution
Sep 2, 2026
Merged

JakeGinnivan merged 1 commit into
mainfrom
jakeginnivan/esm-extensionless-imports-nodenext-resolution

Conversation

@JakeGinnivan

Copy link
Copy Markdown
Contributor

Why

Vite 8 prints this on every dev and build:

Your Vite config uses features that are unsupported by configLoader: 'native', which is planned to become the default in a future major version of Vite

Nothing is broken today — we're on the default bundle loader, which resolves extensionless imports fine — but the warning is noisy and the underlying incompatibility is real. Rather than silence it with VITE_CONFIG_NATIVE_IGNORE_WARNING, this fixes the imports so the config graph resolves as genuine Node ESM.

The warning only scans the config module graph — vite.config.ts and what it transitively imports at config-load time: vite.config.ts → conference-stub/build-manifest.ts → manifest.ts → config/years-index.ts → the year files → venues.

conference-stub is the seed /new-conference copies for each new fork, so fixing it here stops every new conference inheriting the warning.

What changed

  • .ts on relative imports across the config graph (conference-stub, themes, theme-builder, vite.config.ts). Matches the existing convention in website/e2e/, which Node already runs directly.
  • @ddd/conference-config → module/moduleResolution: NodeNext. It's an extracted library, so resolving it as real Node ESM rather than through a bundler's lenient lookup keeps it honest. Bundlers understand the fully specified form too.
  • allowImportingTsExtensions on the conference-stub tsconfig (already noEmit, so nothing is rewritten on the way out). Deliberately not added where it isn't required — nodenext accepts .ts specifiers under noEmit on its own.
  • Widened the theme-builder module-boundary allowlist to accept an optional .ts. It anchored on the bare specifier (.../theme-builder$), so adding the extension would otherwise resurface the boundary error in conference-stub's themes. This was caught the hard way downstream — it turned the fork's CI red.

One case the warning cannot report

Vite's compat check only scans relative imports, so this stayed invisible until the config was actually run under the native loader:

Cannot find module '.../safe-routes/vite'
Did you mean to import "safe-routes/vite.js"?

safe-routes ships no exports field — just main plus a bare vite.js. Native Node ESM needs the explicit extension for that subpath. Now imports safe-routes/vite.js. Had we only suppressed the warning, this would have surfaced as a hard build failure on a future Vite major.

Verification

Check Result
tsc -b --force exit 0, no diagnostics
nx run-many --target=test --all 328 passed / 33 files
nx run-many --target=lint --all 0 errors across all 3 projects (2 pre-existing warnings in generated worker-configuration.d.ts)
vite build ✓ 3.42s + 818ms, zero warnings
vite build --configLoader native ✓ exit 0
Native ESM import of stub + lib graphs both load

The --configLoader native row is the point: this is compatible with the future default, not just quiet about it today.

Relationship to the fork

This is the core half of dddwa/dddperth#151, which landed the same change for the fork's conference/ layer. The fork carries its own copy of the eslint allowlist (one level deeper, core/website/...), fixed there separately. Forks will pick this up on their next /pull-upstream.

https://claude.ai/code/session_012ZUK9WJ1TGNQzBSNwwrBx1

Vite 8 warns that the config module graph uses features unsupported by
`configLoader: 'native'`, which is planned to become the default. Nothing
is broken — the default `bundle` loader resolves these fine — but the
warning fires on every dev and build invocation, and the underlying
incompatibility is real.

Rather than suppress it via VITE_CONFIG_NATIVE_IGNORE_WARNING, fix the
imports so the config graph resolves as genuine Node ESM:

- Add explicit `.ts` to relative imports across the config graph
  (conference-stub, themes, theme-builder, vite.config.ts). Matches the
  existing convention in website/e2e/, which Node already runs directly.
- Move @ddd/conference-config to module/moduleResolution NodeNext. It's an
  extracted library, so resolving it as real Node ESM rather than through a
  bundler's lenient lookup keeps it honest; bundlers understand the fully
  specified form too.
- Add allowImportingTsExtensions to the conference-stub tsconfig (already
  noEmit, so nothing is rewritten on the way out).
- Widen the theme-builder module-boundary allowlist to accept an optional
  `.ts`. It anchored on the bare specifier, so adding the extension would
  otherwise resurface the boundary error in conference-stub's themes.

Also fixes one case the warning cannot report: `safe-routes/vite` is a bare
specifier, and Vite's compat check only scans relative imports. The package
ships no `exports` field, so native Node ESM cannot resolve the
extensionless subpath and the build fails outright under the native loader.
Import `safe-routes/vite.js` instead.

conference-stub is the seed /new-conference copies for each new fork, so
fixing it here keeps new conferences from inheriting the warning.

Verified: tsc -b --force clean; 328 tests pass; lint 0 errors across all 3
projects; vite build emits no warnings; `vite build --configLoader native`
now succeeds; and the stub and lib graphs each import cleanly under native
Node ESM.

Claude-Session: https://claude.ai/code/session_012ZUK9WJ1TGNQzBSNwwrBx1
@JakeGinnivan
JakeGinnivan merged commit 790acd0 into main Sep 2, 2026
3 checks passed
@JakeGinnivan
JakeGinnivan deleted the jakeginnivan/esm-extensionless-imports-nodenext-resolution branch September 2, 2026 03:28
JakeGinnivan added a commit that referenced this pull request Sep 8, 2026
`setup` fans out to four generators with no ordering, so nx runs them
concurrently — and all four write into `website/`. `react-router typegen`
clears and rewrites `.react-router/types/` as it goes, so its rmdir can hit a
directory another generator has just repopulated:

    ENOTEMPTY: directory not empty, rmdir
    '.../website/.react-router/types/app/routes/+types'

That kills the visual-regression job before it takes a single screenshot. It
hit three PRs in a row (#16, #17, #18), each going green on a plain re-run,
which is what a race looks like rather than a real failure.

`parallelism: false` on the four setup targets serialises them. Verified by
running `nx setup` from a clean tree and again over existing generated output
— the repeat run is the case CI was failing on.

This is a stopgap: the targets are still unordered relative to each other, and
the workflow additionally runs `nx setup website` explicitly even though
`e2e-visual` already dependsOn it, so setup runs twice per job. Worth tidying
properly, but serialising fixes the flake now.

Claude-Session: https://claude.ai/code/session_019eNk5wET3eWEqRFFtLihTV
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant