@@ -9,14 +9,18 @@ import {
99import * as path from "@std/path" ;
1010import * as babel from "@babel/core" ;
1111import { httpAbsolute } from "./patches/http_absolute.ts" ;
12- import { JSX_REG } from "../utils.ts" ;
12+ import { JS_REG , JSX_REG } from "../utils.ts" ;
1313import { builtinModules } from "node:module" ;
1414
1515// @ts -ignore Workaround for https://github.com/denoland/deno/issues/30850
1616const { default : babelReact } = await import ( "@babel/preset-react" ) ;
1717
1818const BUILTINS = new Set ( builtinModules ) ;
1919
20+ interface DenoState {
21+ type : RequestedModuleType ;
22+ }
23+
2024export function deno ( ) : Plugin {
2125 let ssrLoader : Loader ;
2226 let browserLoader : Loader ;
@@ -81,21 +85,6 @@ export function deno(): Plugin {
8185 id = `${ url . origin } ${ id } ` ;
8286 }
8387
84- // Apply resolve.alias before Deno resolution so that
85- // react -> preact/compat works even in externalized packages.
86- // Vite normalizes alias config to { find, replacement }[] format.
87- const aliases = this . environment ?. config ?. resolve ?. alias ;
88- if ( aliases ) {
89- const list = Array . isArray ( aliases ) ? aliases : [ ] ;
90- for ( const alias of list ) {
91- const find = alias . find ;
92- if ( typeof find === "string" ? find === id : find ?. test ?.( id ) ) {
93- id = typeof alias . replacement === "string" ? alias . replacement : id ;
94- break ;
95- }
96- }
97- }
98-
9988 // We still want to allow other plugins to participate in
10089 // resolution, with us being in front due to `enforce: "pre"`.
10190 // But we still want to ignore everything `vite:resolve` does
@@ -166,13 +155,14 @@ export function deno(): Plugin {
166155 resolved = path . fromFileUrl ( resolved ) ;
167156 }
168157
169- // For file:// resolved modules (npm packages in node_modules,
170- // local files), let Vite handle loading natively. This allows
171- // Vite to externalize CJS packages in SSR mode (Node.js handles
172- // them with native require()) and avoids needing a custom CJS
173- // transform. Only \0deno:: virtual modules (jsr:, non-default
174- // types) need Fresh's custom load hook.
175- return { id : resolved } ;
158+ return {
159+ id : resolved ,
160+ meta : {
161+ deno : {
162+ type,
163+ } ,
164+ } ,
165+ } ;
176166 } catch {
177167 // ignore
178168 }
@@ -182,80 +172,6 @@ export function deno(): Plugin {
182172 ? ssrLoader
183173 : browserLoader ;
184174
185- // In dev mode, CJS files need to be wrapped in an ESM shim:
186- // - SSR: module runner evaluates as ESM, needs module/exports/require
187- // - Client: browser evaluates as ESM, needs module/exports
188- // In build mode, Rollup's @rollup/plugin-commonjs handles CJS.
189- if (
190- isDev &&
191- ! id . startsWith ( "\0" ) &&
192- id . includes ( "node_modules" ) &&
193- / \. ( c ? j s | c j s ) $ / . test ( id )
194- ) {
195- try {
196- const code = await Deno . readTextFile ( id ) ;
197- // Quick heuristic: if file has CJS patterns and no ESM
198- if (
199- ! code . includes ( "export " ) &&
200- ! code . includes ( "import " ) &&
201- ( code . includes ( "module.exports" ) ||
202- code . includes ( "exports." ) ||
203- code . includes ( "require(" ) )
204- ) {
205- const isServer = this . environment . config . consumer === "server" ;
206-
207- if ( isServer ) {
208- // SSR: use Node.js createRequire for full CJS compat
209- const wrapped = `
210- import { createRequire as __cjs_createRequire } from "node:module";
211- import { fileURLToPath as __cjs_fileURLToPath } from "node:url";
212- import { dirname as __cjs_dirname } from "node:path";
213- var __filename = __cjs_fileURLToPath(import.meta.url);
214- var __dirname = __cjs_dirname(__filename);
215- var require = __cjs_createRequire(import.meta.url);
216- var module = { exports: {} };
217- var exports = module.exports;
218-
219- ${ code }
220-
221- export default module.exports;
222- ` ;
223- return { code : wrapped } ;
224- }
225-
226- // Client: convert require() calls to ESM imports so
227- // browsers can load them. Hoist static require() calls
228- // to import statements at the top.
229- const imports : string [ ] = [ ] ;
230- let idx = 0 ;
231- const transformed = code . replace (
232- / \b r e q u i r e \( [ " ' ] ( [ ^ " ' ] + ) [ " ' ] \) / g,
233- ( _match : string , spec : string ) => {
234- const varName = `__cjs_import_${ idx ++ } ` ;
235- imports . push (
236- `import ${ varName } from ${ JSON . stringify ( spec ) } ;` ,
237- ) ;
238- return `(${ varName } .default ?? ${ varName } )` ;
239- } ,
240- ) ;
241-
242- const wrapped = `${ imports . join ( "\n" ) }
243- var module = { exports: {} };
244- var exports = module.exports;
245- var __filename = "";
246- var __dirname = "";
247-
248- ${ transformed }
249-
250- export default module.exports;
251- ` ;
252- return { code : wrapped } ;
253- }
254- } catch {
255- // Fall through to default loading
256- }
257- }
258-
259175 if ( isDenoSpecifier ( id ) ) {
260176 const { type, specifier } = parseDenoSpecifier ( id ) ;
261177
@@ -281,6 +197,49 @@ export default module.exports;
281197 code,
282198 } ;
283199 }
200+
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+ } ;
284243 } ,
285244 transform : {
286245 filter : {
0 commit comments