1
1
'use strict'
2
2
3
3
const getEsmExports = require ( './get-esm-exports.js' )
4
- const { parse : getCjsExports } = require ( 'cjs-module-lexer' )
5
- const fs = require ( 'fs' )
4
+ const { parse : parseCjs } = require ( 'cjs-module-lexer' )
5
+ const { readFileSync } = require ( 'fs' )
6
+ const { builtinModules } = require ( 'module' )
6
7
const { fileURLToPath, pathToFileURL } = require ( 'url' )
8
+ const { dirname } = require ( 'path' )
7
9
8
10
function addDefault ( arr ) {
9
- return Array . from ( new Set ( [ 'default' , ...arr ] ) )
11
+ return new Set ( [ 'default' , ...arr ] )
12
+ }
13
+
14
+ // Cached exports for Node built-in modules
15
+ const BUILT_INS = new Map ( )
16
+
17
+ function getExportsForNodeBuiltIn ( name ) {
18
+ let exports = BUILT_INS . get ( )
19
+
20
+ if ( ! exports ) {
21
+ exports = new Set ( addDefault ( Object . keys ( require ( name ) ) ) )
22
+ BUILT_INS . set ( name , exports )
23
+ }
24
+
25
+ return exports
10
26
}
11
27
12
28
const urlsBeingProcessed = new Set ( ) // Guard against circular imports.
13
29
14
- async function getFullCjsExports ( url , context , parentLoad , source ) {
30
+ async function getCjsExports ( url , context , parentLoad , source ) {
15
31
if ( urlsBeingProcessed . has ( url ) ) {
16
32
return [ ]
17
33
}
18
34
urlsBeingProcessed . add ( url )
19
35
20
- const ex = getCjsExports ( source )
21
- const full = Array . from ( new Set ( [
22
- ...addDefault ( ex . exports ) ,
23
- ...( await Promise . all ( ex . reexports . map ( re => getExports (
24
- ( / ^ ( ..? ( $ | \/ | \\ ) ) / ) . test ( re )
25
- ? pathToFileURL ( require . resolve ( fileURLToPath ( new URL ( re , url ) ) ) ) . toString ( )
26
- : pathToFileURL ( require . resolve ( re ) ) . toString ( ) ,
27
- context ,
28
- parentLoad
29
- ) ) ) ) . flat ( )
30
- ] ) )
31
-
32
- urlsBeingProcessed . delete ( url )
33
- return full
36
+ try {
37
+ const result = parseCjs ( source )
38
+ const full = addDefault ( result . exports )
39
+
40
+ await Promise . all ( result . reexports . map ( async re => {
41
+ if ( re . startsWith ( 'node:' ) || builtinModules . includes ( re ) ) {
42
+ for ( const each of getExportsForNodeBuiltIn ( re ) ) {
43
+ full . add ( each )
44
+ }
45
+ } else {
46
+ // Resolve the re-exported module relative to the current module.
47
+ const newUrl = pathToFileURL ( require . resolve ( re , { paths : [ dirname ( fileURLToPath ( url ) ) ] } ) ) . href
48
+ for ( const each of await getExports ( newUrl , context , parentLoad ) ) {
49
+ full . add ( each )
50
+ }
51
+ }
52
+ } ) )
53
+
54
+ return full
55
+ } finally {
56
+ urlsBeingProcessed . delete ( url )
57
+ }
34
58
}
35
59
36
60
/**
@@ -45,7 +69,7 @@ async function getFullCjsExports (url, context, parentLoad, source) {
45
69
* @param {Function } parentLoad Next hook function in the loaders API
46
70
* hook chain.
47
71
*
48
- * @returns {Promise<string[] > } An array of identifiers exported by the module.
72
+ * @returns {Promise<Set< string> > } An array of identifiers exported by the module.
49
73
* Please see {@link getEsmExports} for caveats on special identifiers that may
50
74
* be included in the result set.
51
75
*/
@@ -57,23 +81,23 @@ async function getExports (url, context, parentLoad) {
57
81
let source = parentCtx . source
58
82
const format = parentCtx . format
59
83
60
- // TODO support non-node/file urls somehow?
61
- if ( format === 'builtin' ) {
62
- // Builtins don't give us the source property, so we're stuck
63
- // just requiring it to get the exports.
64
- return addDefault ( Object . keys ( require ( url ) ) )
65
- }
66
-
67
84
if ( ! source ) {
68
- // Sometimes source is retrieved by parentLoad, sometimes it isn't.
69
- source = fs . readFileSync ( fileURLToPath ( url ) , 'utf8' )
85
+ if ( format === 'builtin' ) {
86
+ // Builtins don't give us the source property, so we're stuck
87
+ // just requiring it to get the exports.
88
+ return getExportsForNodeBuiltIn ( url )
89
+ }
90
+
91
+ // Sometimes source is retrieved by parentLoad, CommonJs isn't.
92
+ source = readFileSync ( fileURLToPath ( url ) , 'utf8' )
70
93
}
71
94
72
95
if ( format === 'module' ) {
73
96
return getEsmExports ( source )
74
97
}
98
+
75
99
if ( format === 'commonjs' ) {
76
- return getFullCjsExports ( url , context , parentLoad , source )
100
+ return getCjsExports ( url , context , parentLoad , source )
77
101
}
78
102
79
103
// At this point our `format` is either undefined or not known by us. Fall
@@ -84,7 +108,7 @@ async function getExports (url, context, parentLoad) {
84
108
// isn't set at first and yet we have an ESM module with no exports.
85
109
// I couldn't construct an example that would do this, so maybe it's
86
110
// impossible?
87
- return getFullCjsExports ( url , context , parentLoad , source )
111
+ return getCjsExports ( url , context , parentLoad , source )
88
112
}
89
113
}
90
114
0 commit comments