Skip to content

Commit c20f07a

Browse files
committed
Sync use.js
1 parent 6eb462b commit c20f07a

2 files changed

Lines changed: 167 additions & 12 deletions

File tree

use.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ const makeUse = async (options) => {
522522
if (builtinPath) {
523523
return baseUse(builtinPath);
524524
}
525-
525+
526526
// If not a built-in module, use the configured resolver
527527
const modulePath = await specifierResolver(moduleSpecifier, pathResolver);
528528
return baseUse(modulePath);

use.js

Lines changed: 166 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
26114
const 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

313416
const 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

Comments
 (0)