@@ -23,7 +23,110 @@ Please specify a package name, and an optional version (e.g.: 'lodash', 'lodash@
2323 return { packageName, version, modulePath } ;
2424}
2525
26+ // Built-in modules that we support across all environments
27+ // Always use lowercase names for consistency
28+ const supportedBuiltins = {
29+ // Universal modules
30+ 'console' : {
31+ browser : ( ) => ( { default : console , log : console . log , error : console . error , warn : console . warn , info : console . info } ) ,
32+ node : ( ) => import ( 'node:console' ) . then ( m => ( { default : m . Console , ...m } ) )
33+ } ,
34+ 'crypto' : {
35+ browser : ( ) => ( { default : crypto , subtle : crypto . subtle } ) ,
36+ node : ( ) => import ( 'node:crypto' ) . then ( m => ( { default : m , ...m } ) )
37+ } ,
38+ 'url' : {
39+ browser : ( ) => ( { default : URL , URL , URLSearchParams } ) ,
40+ node : ( ) => import ( 'node:url' ) . then ( m => ( { default : m , ...m } ) )
41+ } ,
42+ 'performance' : {
43+ browser : ( ) => ( { default : performance , now : performance . now . bind ( performance ) } ) ,
44+ node : ( ) => import ( 'node:perf_hooks' ) . then ( m => ( { default : m . performance , performance : m . performance , now : m . performance . now . bind ( m . performance ) , ...m } ) )
45+ } ,
46+
47+ // Node.js/Bun only modules
48+ 'fs' : {
49+ browser : null , // Not available in browser
50+ node : ( ) => import ( 'node:fs' ) . then ( m => ( { default : m , ...m } ) )
51+ } ,
52+ 'path' : {
53+ browser : null , // Not available in browser
54+ node : ( ) => import ( 'node:path' ) . then ( m => ( { default : m , ...m } ) )
55+ } ,
56+ 'os' : {
57+ browser : null , // Not available in browser
58+ node : ( ) => import ( 'node:os' ) . then ( m => ( { default : m , ...m } ) )
59+ } ,
60+ 'util' : {
61+ browser : null , // Not available in browser
62+ node : ( ) => import ( 'node:util' ) . then ( m => ( { default : m , ...m } ) )
63+ } ,
64+ 'events' : {
65+ browser : null , // Not available in browser
66+ node : ( ) => import ( 'node:events' ) . then ( m => ( { default : m . EventEmitter , EventEmitter : m . EventEmitter , ...m } ) )
67+ } ,
68+ 'stream' : {
69+ browser : null , // Not available in browser
70+ node : ( ) => import ( 'node:stream' ) . then ( m => ( { default : m . Stream , Stream : m . Stream , ...m } ) )
71+ } ,
72+ 'buffer' : {
73+ browser : null , // Not available in browser (would need polyfill)
74+ node : ( ) => import ( 'node:buffer' ) . then ( m => ( { default : m , Buffer : m . Buffer , ...m } ) )
75+ } ,
76+ 'process' : {
77+ browser : null , // Not available in browser
78+ node : ( ) => ( { default : process , ...process } )
79+ } ,
80+ 'child_process' : {
81+ browser : null ,
82+ node : ( ) => import ( 'node:child_process' ) . then ( m => ( { default : m , ...m } ) )
83+ } ,
84+ 'http' : {
85+ browser : null ,
86+ node : ( ) => import ( 'node:http' ) . then ( m => ( { default : m , ...m } ) )
87+ } ,
88+ 'https' : {
89+ browser : null ,
90+ node : ( ) => import ( 'node:https' ) . then ( m => ( { default : m , ...m } ) )
91+ } ,
92+ 'net' : {
93+ browser : null ,
94+ node : ( ) => import ( 'node:net' ) . then ( m => ( { default : m , ...m } ) )
95+ } ,
96+ 'dns' : {
97+ browser : null ,
98+ node : ( ) => import ( 'node:dns' ) . then ( m => ( { default : m , ...m } ) )
99+ } ,
100+ 'zlib' : {
101+ browser : null ,
102+ node : ( ) => import ( 'node:zlib' ) . then ( m => ( { default : m , ...m } ) )
103+ } ,
104+ 'querystring' : {
105+ browser : null ,
106+ node : ( ) => import ( 'node:querystring' ) . then ( m => ( { default : m , ...m } ) )
107+ } ,
108+ 'assert' : {
109+ browser : null ,
110+ node : ( ) => import ( 'node:assert' ) . then ( m => ( { default : m . default || m , ...m } ) )
111+ }
112+ } ;
113+
26114const resolvers = {
115+ builtin : async ( moduleSpecifier , pathResolver ) => {
116+ const { packageName } = parseModuleSpecifier ( moduleSpecifier ) ;
117+
118+ // Remove 'node:' prefix if present
119+ const moduleName = packageName . startsWith ( 'node:' ) ? packageName . slice ( 5 ) : packageName ;
120+
121+ // Check if we support this built-in module
122+ if ( supportedBuiltins [ moduleName ] ) {
123+ // Return special marker indicating this is a built-in module
124+ return `builtin:${ moduleName } ` ;
125+ }
126+
127+ // Not a supported built-in module
128+ return null ;
129+ } ,
27130 npm : async ( moduleSpecifier , pathResolver ) => {
28131 const path = await import ( 'path' ) ;
29132 const { exec } = await import ( 'child_process' ) ;
@@ -311,28 +414,71 @@ const resolvers = {
311414}
312415
313416const baseUse = async ( modulePath ) => {
417+ // Handle built-in modules
418+ if ( typeof modulePath === 'string' && modulePath . startsWith ( 'builtin:' ) ) {
419+ const moduleName = modulePath . slice ( 8 ) ; // Remove 'builtin:' prefix
420+ const builtinConfig = supportedBuiltins [ moduleName ] ;
421+
422+ if ( ! builtinConfig ) {
423+ throw new Error ( `Built-in module '${ moduleName } ' is not supported.` ) ;
424+ }
425+
426+ // Determine environment
427+ const isBrowser = typeof window !== 'undefined' ;
428+ const environment = isBrowser ? 'browser' : 'node' ;
429+
430+ const moduleFactory = builtinConfig [ environment ] ;
431+ if ( ! moduleFactory ) {
432+ throw new Error ( `Built-in module '${ moduleName } ' is not available in ${ environment } environment.` ) ;
433+ }
434+
435+ try {
436+ // Execute the factory function to get the module
437+ const result = await moduleFactory ( ) ;
438+ return result ;
439+ } catch ( error ) {
440+ throw new Error ( `Failed to load built-in module '${ moduleName } ' in ${ environment } environment.` , { cause : error } ) ;
441+ }
442+ }
443+
314444 // Dynamically import the module
315445 try {
316446 const module = await import ( modulePath ) ;
317- // Check if we should extract the default export
318- // In Bun, CommonJS modules wrapped as ESM have 'default' plus function properties (length, name, prototype)
319- // In Node.js, they typically only have 'default'
447+
448+ // More robust default export handling for cross-environment compatibility
449+ const keys = Object . keys ( module ) ;
450+
451+ // If it's a Module object with a default property, unwrap it
320452 if ( module . default !== undefined ) {
321- // Check if this looks like a CommonJS module wrapped as ESM
322- const keys = Object . keys ( module ) ;
323- const hasOnlyDefaultOrFunctionProps = keys . every ( key =>
324- key === 'default' || key === 'length' || key === 'name' || key === 'prototype'
325- ) ;
453+ // Check if this is likely a CommonJS module with only default export
454+ if ( keys . length === 1 && keys [ 0 ] === 'default' ) {
455+ return module . default ;
456+ }
457+
458+ // Check if default is the main export and other keys are just function/module metadata
459+ const metadataKeys = new Set ( [
460+ 'default' , '__esModule' , 'Symbol(Symbol.toStringTag)' ,
461+ 'length' , 'name' , 'prototype' , 'constructor' ,
462+ 'toString' , 'valueOf' , 'hasOwnProperty' , 'isPrototypeOf' , 'propertyIsEnumerable'
463+ ] ) ;
326464
327- if ( keys . length === 4 && hasOnlyDefaultOrFunctionProps && typeof module . default === 'function' ) {
465+ const nonMetadataKeys = keys . filter ( key => ! metadataKeys . has ( key ) ) ;
466+
467+ // If there are no significant non-metadata keys, return the default
468+ if ( nonMetadataKeys . length === 0 ) {
328469 return module . default ;
329470 }
330471
331- // If only has default export (Node.js style)
332- if ( keys . length === 1 && keys [ 0 ] === 'default' ) {
472+ // Special case: If the module looks like a Module object (has toString that returns '[object Module]')
473+ // and default is a function, prefer the default
474+ if ( typeof module . default === 'function' &&
475+ module . toString &&
476+ module . toString ( ) . includes ( '[object Module]' ) ) {
333477 return module . default ;
334478 }
335479 }
480+
481+ // Return the whole module if it has multiple meaningful exports or no default
336482 return module ;
337483 } catch ( error ) {
338484 throw new Error ( `Failed to import module from '${ modulePath } '.` , { cause : error } ) ;
@@ -356,6 +502,8 @@ const makeUse = async (options) => {
356502 if ( typeof specifierResolver !== 'function' ) {
357503 if ( typeof window !== 'undefined' ) {
358504 specifierResolver = resolvers [ specifierResolver || 'esm' ] ;
505+ } else if ( typeof Bun !== 'undefined' ) {
506+ specifierResolver = resolvers [ specifierResolver || 'bun' ] ;
359507 } else {
360508 specifierResolver = resolvers [ specifierResolver || 'npm' ] ;
361509 }
@@ -384,6 +532,13 @@ const makeUse = async (options) => {
384532 }
385533 }
386534 return async ( moduleSpecifier ) => {
535+ // Always try built-in resolver first
536+ const builtinPath = await resolvers . builtin ( moduleSpecifier , pathResolver ) ;
537+ if ( builtinPath ) {
538+ return baseUse ( builtinPath ) ;
539+ }
540+
541+ // If not a built-in module, use the configured resolver
387542 const modulePath = await specifierResolver ( moduleSpecifier , pathResolver ) ;
388543 return baseUse ( modulePath ) ;
389544 } ;
0 commit comments