Skip to content

refactor: remove CJS and env var Babel transforms, let Vite handle natively#3767

Merged
bartlomieju merged 9 commits into
mainfrom
refactor/replace-babel-env-vars-with-vite-define
Apr 25, 2026
Merged

refactor: remove CJS and env var Babel transforms, let Vite handle natively#3767
bartlomieju merged 9 commits into
mainfrom
refactor/replace-babel-env-vars-with-vite-define

Conversation

@bartlomieju
Copy link
Copy Markdown
Contributor

@bartlomieju bartlomieju commented Apr 9, 2026

Summary

  • Replace Babel env var transforms with Vite's native defineprocess.env.FRESH_PUBLIC_* and import.meta.env.FRESH_PUBLIC_* now use Vite's built-in define config. Deno.env.get() calls use a lightweight regex-based transform (~10 lines vs 78-line Babel plugin).

  • Remove the 960-line CJS→ESM Babel transform — Instead of transforming CJS to ESM via Babel, let Vite handle CJS packages natively:

    • Build mode: Rollup's @rollup/plugin-commonjs handles CJS (always has, but was bypassed by deno.ts loading through @deno/loader)
    • Dev mode: A ~30-line CJS shim wraps node_modules CJS files with module/exports/require so Vite's SSR module runner can evaluate them
    • deno.ts no longer attaches meta.deno to file:// resolved paths — Vite loads them from disk directly
  • Remove noDiscovery: true — Vite's dependency optimizer can now pre-bundle CJS packages for the client

  • Fix React compat aliasing — Apply resolve.alias (react → preact/compat) in deno.ts before @deno/loader resolution, so the alias works even when packages are non-external in SSR

Net: ~2,050 lines deleted, ~150 lines added.

Fixes #3619, fixes #3653, fixes #3505, fixes #3478, fixes #3449.

bartlomieju and others added 4 commits April 9, 2026 17:52
Remove the 78-line Babel `inlineEnvVarsPlugin` and replace it with
Vite's built-in `define` configuration for `process.env.FRESH_PUBLIC_*`
and `import.meta.env.FRESH_PUBLIC_*` patterns. A lightweight regex-based
Vite plugin handles `Deno.env.get()` calls which can't use `define`.

Env file loading moved from `configResolved` to `config` so define
entries are available during Vite's config resolution phase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of transforming CJS to ESM via a 960-line Babel plugin, let
Vite handle CJS packages natively by:

1. Removing `meta.deno` from file:// resolved paths in deno.ts so
   Vite loads npm packages from disk instead of through @deno/loader
2. Removing `noExternal: true` so Vite externalizes npm packages in
   SSR dev mode (Node.js handles CJS natively via require())
3. Removing `noDiscovery: true` so Vite's dependency optimizer can
   pre-bundle CJS packages for the client
4. Applying resolve.alias before Deno resolution so react -> preact/compat
   works even when packages are externalized

Also converts local .cjs test fixtures to ESM since they no longer
go through the CJS transform.

Deletes ~1,800 lines. Eliminates the #1 source of npm compat bugs
(#3619, #3653, #3505, #3478, #3449).

Known regressions (2 tests): radix-ui and remote island need
investigation for duplicate preact instances with externalization.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…l for radix-ui

Apply Vite's resolve.alias config (e.g. react -> preact/compat) in
deno.ts before calling @deno/loader, so the alias works even when
packages are externalized in SSR mode.

Also add noExternal for @radix-ui packages in the SSR environment
since they depend on React compat aliases being applied.

WIP: radix test still failing — alias format from Vite config needs
further investigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add lightweight CJS shim in deno.ts load hook for dev mode: wraps
  CJS files in node_modules with module/exports/require so Vite's
  SSR module runner can evaluate them. Only ~30 lines vs the old
  960-line Babel CJS transform. Only runs in dev mode — build mode
  uses Rollup's @rollup/plugin-commonjs natively.

- Restore ssr.noExternal: true so resolve.alias (react -> preact/compat)
  is applied consistently in SSR. Without it, Node.js require() bypasses
  aliases and loads real react@19.1.1.

- Apply resolve.alias in deno.ts resolveId before @deno/loader runs,
  so aliased specifiers (react, react-dom) resolve to preact/compat
  through the Deno resolution pipeline.

- Remove environment-level noExternal (was duplicated).

Test results: 35/36 dev tests pass, 31/31 build tests pass.
The 1 failing test (remote island) is pre-existing on main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bartlomieju bartlomieju changed the title refactor: replace Babel env var transforms with Vite native define refactor: remove CJS and env var Babel transforms, let Vite handle natively Apr 9, 2026
bartlomieju and others added 5 commits April 9, 2026 20:43
Instead of disabling the dependency optimizer entirely (noDiscovery),
exclude only preact ecosystem packages from optimization. This allows
CJS packages like mime-db to be pre-bundled for the browser while
preventing duplicate preact instances when remote (JSR) islands
resolve deps to /@fs/ paths.

Also extends the CJS shim to work in both SSR (with createRequire)
and client (with stub require) environments.

All dev server tests pass (35/36 — 1 pre-existing flaky failure
on remote island that also fails on main). All 31 build tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The dependency optimizer causes duplicate preact instances when remote
(JSR) islands resolve deps to /@fs/ paths while the optimizer bundles
to /.vite/deps/. Restore noDiscovery: true to prevent this.

For CJS packages used in client-side islands (like mime-db), convert
require() calls to ESM import statements via regex so browsers can
load them. The SSR shim continues to use Node.js createRequire.

All tests pass: 36/36 dev, 31/31 build, 15/15 patches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The `links` field referenced a sibling directory that only exists on the
author's machine, causing `deno install` to fail in CI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bartlomieju bartlomieju merged commit b9a314b into main Apr 25, 2026
9 checks passed
@bartlomieju bartlomieju deleted the refactor/replace-babel-env-vars-with-vite-define branch April 25, 2026 12:40
bartlomieju added a commit that referenced this pull request Apr 28, 2026
…3793)

Closes #3794

Fixes a regression from #3767 where CJS npm packages fail with `exports
is not defined` in dev mode.

- The old heuristic (`code.includes("export ")`) was fooled by the word
"export" appearing in comments of CJS files — e.g. `@opentelemetry/api`
has `// TODO: Remove ProxyTracerProvider export in the next major
version.` which caused the CJS shim to be skipped entirely
- Replaces the naive string check with two-layer detection:
1. **Package.json `type` field** (Node.js semantics): `.cjs` → always
CJS, `.js` → CJS unless nearest `package.json` has `"type": "module"`.
Results cached per directory.
2. **ESM statement regex fallback**: for dual CJS/ESM packages that ship
ESM in `.js` without `"type": "module"` (e.g. `@opentelemetry/api`,
`preact-render-to-string`), detects actual ESM syntax including minified
forms like `import{` and `export*from`
- Adds a test importing from a real CJS npm package (`qs`) in
`node_modules` — the existing "CJS test" fixtures were local ESM files
that never exercised the shim path
bartlomieju added a commit that referenced this pull request Apr 28, 2026
## Summary

Reverts two commits that broke CJS package handling in dev mode:

- Reverts #3793 (CJS detection regex fix)
- Reverts #3767 (removal of Babel CJS→ESM transform)

The simplified CJS shim introduced in #3767 has two fundamental issues:
1. **Detection**: the heuristic (`code.includes("export ")`) is fooled
by comments containing "export"
2. **Named exports**: the shim only provides `export default
module.exports`, so `import { trace } from "@opentelemetry/api"` returns
`undefined` (#3797)

The Babel-based transform handled both correctly. Bringing it back until
we can replace it with a proper solution using `deno_ast`'s CJS parser.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant