Skip to content

Commit dd3339b

Browse files
bartlomiejuclaude
andcommitted
refactor: remove CJS→ESM Babel transform, let Vite handle CJS natively
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>
1 parent 15767c7 commit dd3339b

11 files changed

Lines changed: 25 additions & 1879 deletions

File tree

packages/plugin-vite/demo/fixtures/commonjs_mod.cjs

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const value = "ok";

packages/plugin-vite/demo/fixtures/maxmind.cjs

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import assert from "node:assert";
2+
assert(true);

packages/plugin-vite/demo/routes/tests/commonjs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { value } from "../../fixtures/commonjs_mod.cjs";
1+
import { value } from "../../fixtures/commonjs_mod.js";
22

33
export default function Page() {
44
return <h1>{value}</h1>;

packages/plugin-vite/demo/routes/tests/maxmind.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as maxmind from "../../fixtures/maxmind.cjs";
1+
import * as maxmind from "../../fixtures/maxmind.js";
22

33
export default function Page() {
44
// deno-lint-ignore no-console

packages/plugin-vite/src/mod.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,6 @@ export function fresh(config?: FreshViteConfig): Plugin[] {
140140
"react-dom": "preact/compat",
141141
react: "preact/compat",
142142
},
143-
// Disallow externals, because it leads to duplicate
144-
// modules with `preact` vs `npm:preact@*` in the server
145-
// environment.
146-
noExternal: true,
147-
},
148-
optimizeDeps: {
149-
// Optimize deps somehow leads to duplicate modules or them
150-
// being placed in the wrong chunks...
151-
noDiscovery: true,
152143
},
153144

154145
publicDir: pathWithRoot(fConfig.staticDir[0], config.root),
@@ -213,14 +204,6 @@ export function fresh(config?: FreshViteConfig): Plugin[] {
213204
return;
214205
}
215206

216-
// Ignore commonjs optional exports
217-
if (
218-
warning.code === "MISSING_EXPORT" &&
219-
warning.message.includes("__require")
220-
) {
221-
return;
222-
}
223-
224207
// Ignore this warnings
225208
if (warning.code === "THIS_IS_UNDEFINED") {
226209
return;

packages/plugin-vite/src/plugins/deno.ts

Lines changed: 20 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,14 @@ import {
99
import * as path from "@std/path";
1010
import * as babel from "@babel/core";
1111
import { httpAbsolute } from "./patches/http_absolute.ts";
12-
import { JS_REG, JSX_REG } from "../utils.ts";
12+
import { JSX_REG } from "../utils.ts";
1313
import { builtinModules } from "node:module";
1414

1515
// @ts-ignore Workaround for https://github.com/denoland/deno/issues/30850
1616
const { default: babelReact } = await import("@babel/preset-react");
1717

1818
const BUILTINS = new Set(builtinModules);
1919

20-
interface DenoState {
21-
type: RequestedModuleType;
22-
}
23-
2420
export function deno(): Plugin {
2521
let ssrLoader: Loader;
2622
let browserLoader: Loader;
@@ -85,6 +81,18 @@ export function deno(): Plugin {
8581
id = `${url.origin}${id}`;
8682
}
8783

84+
// Apply resolve.alias before Deno resolution so that
85+
// react -> preact/compat works even in externalized packages.
86+
const aliases = this.environment?.config?.resolve?.alias;
87+
if (Array.isArray(aliases)) {
88+
for (const alias of aliases) {
89+
if (typeof alias.find === "string" && alias.find === id) {
90+
id = alias.replacement;
91+
break;
92+
}
93+
}
94+
}
95+
8896
// We still want to allow other plugins to participate in
8997
// resolution, with us being in front due to `enforce: "pre"`.
9098
// But we still want to ignore everything `vite:resolve` does
@@ -155,14 +163,13 @@ export function deno(): Plugin {
155163
resolved = path.fromFileUrl(resolved);
156164
}
157165

158-
return {
159-
id: resolved,
160-
meta: {
161-
deno: {
162-
type,
163-
},
164-
},
165-
};
166+
// For file:// resolved modules (npm packages in node_modules,
167+
// local files), let Vite handle loading natively. This allows
168+
// Vite to externalize CJS packages in SSR mode (Node.js handles
169+
// them with native require()) and avoids needing a custom CJS
170+
// transform. Only \0deno:: virtual modules (jsr:, non-default
171+
// types) need Fresh's custom load hook.
172+
return { id: resolved };
166173
} catch {
167174
// ignore
168175
}
@@ -198,48 +205,6 @@ export function deno(): Plugin {
198205
};
199206
}
200207

201-
if (id.startsWith("\0")) {
202-
id = id.slice(1);
203-
}
204-
205-
const meta = this.getModuleInfo(id)?.meta.deno as
206-
| DenoState
207-
| undefined
208-
| null;
209-
210-
if (meta === null || meta === undefined) return;
211-
212-
// Skip for non-js files like `.css`
213-
if (
214-
meta.type === RequestedModuleType.Default &&
215-
!JS_REG.test(id)
216-
) {
217-
return;
218-
}
219-
220-
const url = path.toFileUrl(id);
221-
222-
const result = await loader.load(url.href, meta.type);
223-
if (result.kind === "external") {
224-
return null;
225-
}
226-
227-
const code = new TextDecoder().decode(result.code);
228-
229-
const maybeJsx = babelTransform({
230-
ssr: this.environment.config.consumer === "server",
231-
media: result.mediaType,
232-
id,
233-
code,
234-
isDev,
235-
});
236-
if (maybeJsx) {
237-
return maybeJsx;
238-
}
239-
240-
return {
241-
code,
242-
};
243208
},
244209
transform: {
245210
filter: {

packages/plugin-vite/src/plugins/patches.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { Plugin } from "vite";
22
import * as babel from "@babel/core";
3-
import { cjsPlugin } from "./patches/commonjs.ts";
43
import { jsxComments } from "./patches/jsx_comment.ts";
54
import { removePolyfills } from "./patches/remove_polyfills.ts";
65
import { JS_REG, JSX_REG } from "../utils.ts";
@@ -40,7 +39,6 @@ export function patches(): Plugin {
4039

4140
const plugins: babel.PluginItem[] = [
4241
codeEvalPlugin(this.environment.config.consumer, env),
43-
cjsPlugin,
4442
removePolyfills,
4543
jsxComments,
4644
];

0 commit comments

Comments
 (0)