@@ -10,7 +10,9 @@ type RegisteredModule = {
10
10
// lazily parsed json string
11
11
parsedJson ?: any
12
12
}
13
+ type ModuleResolutions = ( subpath : string ) => string
13
14
const registeredModules = new Map < string , RegisteredModule > ( )
15
+ const memoizedPackageResolvers = new WeakMap < RegisteredModule , ModuleResolutions > ( )
14
16
15
17
const require = createRequire ( import . meta. url )
16
18
@@ -30,6 +32,187 @@ function parseJson(matchedModule: RegisteredModule) {
30
32
}
31
33
}
32
34
35
+ function normalizePackageExports ( exports : any ) {
36
+ if ( typeof exports === 'string' ) {
37
+ return { '.' : exports }
38
+ }
39
+
40
+ if ( Array . isArray ( exports ) ) {
41
+ return Object . fromEntries ( exports . map ( ( entry ) => [ entry , entry ] ) )
42
+ }
43
+
44
+ return exports
45
+ }
46
+
47
+ type Condition = string // 'import', 'require', 'default', 'node-addon' etc
48
+ type SubpathMatcher = string
49
+ type ConditionalTarget = { [ key in Condition ] : string | ConditionalTarget }
50
+ type SubpathTarget = string | ConditionalTarget
51
+ /**
52
+ * @example
53
+ * {
54
+ * ".": "./main.js",
55
+ * "./foo": {
56
+ * "import": "./foo.js",
57
+ * "require": "./foo.cjs"
58
+ * }
59
+ * }
60
+ */
61
+ type NormalizedExports = Record < SubpathMatcher , SubpathTarget | Record < Condition , SubpathTarget > >
62
+
63
+ // https://github.com/nodejs/node/blob/6fd67ec6e3ccbdfcfa0300b9b742040a0607a4bc/lib/internal/modules/esm/resolve.js#L555
64
+ function isConditionalExportsMainSugar ( exports : any ) {
65
+ if ( typeof exports === 'string' || Array . isArray ( exports ) ) {
66
+ return true
67
+ }
68
+ if ( typeof exports !== 'object' || exports === null ) {
69
+ return false
70
+ }
71
+
72
+ // not doing validation at this point, if the package.json was misconfigured
73
+ // we would not get to this point as it would throw when running `next build`
74
+ const keys = Object . keys ( exports )
75
+ return keys . length > 0 && ( keys [ 0 ] === '' || keys [ 0 ] [ 0 ] !== '.' )
76
+ }
77
+
78
+ // https://github.com/nodejs/node/blob/6fd67ec6e3ccbdfcfa0300b9b742040a0607a4bc/lib/internal/modules/esm/resolve.js#L671
79
+ function patternKeyCompare ( a : string , b : string ) {
80
+ const aPatternIndex = a . indexOf ( '*' )
81
+ const bPatternIndex = b . indexOf ( '*' )
82
+ const baseLenA = aPatternIndex === - 1 ? a . length : aPatternIndex + 1
83
+ const baseLenB = bPatternIndex === - 1 ? b . length : bPatternIndex + 1
84
+ if ( baseLenA > baseLenB ) {
85
+ return - 1
86
+ }
87
+ if ( baseLenB > baseLenA ) {
88
+ return 1
89
+ }
90
+ if ( aPatternIndex === - 1 ) {
91
+ return 1
92
+ }
93
+ if ( bPatternIndex === - 1 ) {
94
+ return - 1
95
+ }
96
+ if ( a . length > b . length ) {
97
+ return - 1
98
+ }
99
+ if ( b . length > a . length ) {
100
+ return 1
101
+ }
102
+ return 0
103
+ }
104
+
105
+ function applyWildcardMatch ( target : string , bestMatchSubpath ?: string ) {
106
+ return bestMatchSubpath ? target . replace ( '*' , bestMatchSubpath ) : target
107
+ }
108
+
109
+ // https://github.com/nodejs/node/blob/323f19c18fea06b9234a0c945394447b077fe565/lib/internal/modules/helpers.js#L76
110
+ const conditions = new Set ( [ 'require' , 'node' , 'node-addons' , 'default' ] )
111
+
112
+ // https://github.com/nodejs/node/blob/6fd67ec6e3ccbdfcfa0300b9b742040a0607a4bc/lib/internal/modules/esm/resolve.js#L480
113
+ function matchConditions ( target : SubpathTarget , bestMatchSubpath ?: string ) {
114
+ if ( typeof target === 'string' ) {
115
+ return applyWildcardMatch ( target , bestMatchSubpath )
116
+ }
117
+
118
+ if ( Array . isArray ( target ) && target . length > 0 ) {
119
+ for ( const targetItem of target ) {
120
+ return matchConditions ( targetItem , bestMatchSubpath )
121
+ }
122
+ }
123
+
124
+ if ( typeof target === 'object' && target !== null ) {
125
+ for ( const [ condition , targetValue ] of Object . entries ( target ) ) {
126
+ if ( conditions . has ( condition ) ) {
127
+ return matchConditions ( targetValue , bestMatchSubpath )
128
+ }
129
+ }
130
+ }
131
+
132
+ throw new Error ( 'Invalid package target' )
133
+ }
134
+
135
+ function getPackageResolver ( packageJsonMatchedModule : RegisteredModule ) {
136
+ const memoized = memoizedPackageResolvers . get ( packageJsonMatchedModule )
137
+ if ( memoized ) {
138
+ return memoized
139
+ }
140
+
141
+ // https://nodejs.org/api/packages.html#package-entry-points
142
+
143
+ const pkgJson = parseJson ( packageJsonMatchedModule )
144
+
145
+ let exports : NormalizedExports | null = null
146
+ if ( pkgJson . exports ) {
147
+ // https://github.com/nodejs/node/blob/6fd67ec6e3ccbdfcfa0300b9b742040a0607a4bc/lib/internal/modules/esm/resolve.js#L590
148
+ exports = isConditionalExportsMainSugar ( pkgJson . exports )
149
+ ? { '.' : pkgJson . exports }
150
+ : pkgJson . exports
151
+ }
152
+
153
+ const resolveInPackage : ModuleResolutions = ( subpath : string ) => {
154
+ if ( exports ) {
155
+ const normalizedSubpath = subpath . length === 0 ? '.' : './' + subpath
156
+
157
+ // https://github.com/nodejs/node/blob/6fd67ec6e3ccbdfcfa0300b9b742040a0607a4bc/lib/internal/modules/esm/resolve.js#L594
158
+ // simple case with matching as-is
159
+ if (
160
+ normalizedSubpath in exports &&
161
+ ! normalizedSubpath . includes ( '*' ) &&
162
+ ! normalizedSubpath . endsWith ( '/' )
163
+ ) {
164
+ return matchConditions ( exports [ normalizedSubpath ] )
165
+ }
166
+
167
+ // https://github.com/nodejs/node/blob/6fd67ec6e3ccbdfcfa0300b9b742040a0607a4bc/lib/internal/modules/esm/resolve.js#L610
168
+ let bestMatchKey = ''
169
+ let bestMatchSubpath
170
+ for ( const key of Object . keys ( exports ) ) {
171
+ const patternIndex = key . indexOf ( '*' )
172
+ if ( patternIndex !== - 1 && normalizedSubpath . startsWith ( key . slice ( 0 , patternIndex ) ) ) {
173
+ const patternTrailer = key . slice ( patternIndex + 1 )
174
+ if (
175
+ normalizedSubpath . length > key . length &&
176
+ normalizedSubpath . endsWith ( patternTrailer ) &&
177
+ patternKeyCompare ( bestMatchKey , key ) === 1 &&
178
+ key . lastIndexOf ( '*' ) === patternIndex
179
+ ) {
180
+ bestMatchKey = key
181
+ bestMatchSubpath = normalizedSubpath . slice (
182
+ patternIndex ,
183
+ normalizedSubpath . length - patternTrailer . length ,
184
+ )
185
+ }
186
+ }
187
+ }
188
+
189
+ if ( bestMatchKey && typeof bestMatchSubpath === 'string' ) {
190
+ const matchedTarget = exports [ bestMatchKey ]
191
+ console . log ( {
192
+ matchedTarget,
193
+ bestMatchKey,
194
+ bestMatchSubpath,
195
+ subpath,
196
+ } )
197
+ return matchConditions ( matchedTarget , bestMatchSubpath )
198
+ }
199
+
200
+ // if exports are defined, they are source of truth and any imports not allowed by it will fail
201
+ throw new Error ( `Cannot find module '${ normalizedSubpath } '` )
202
+ }
203
+
204
+ if ( subpath . length === 0 && pkgJson . main ) {
205
+ return pkgJson . main
206
+ }
207
+
208
+ return subpath
209
+ }
210
+
211
+ memoizedPackageResolvers . set ( packageJsonMatchedModule , resolveInPackage )
212
+
213
+ return resolveInPackage
214
+ }
215
+
33
216
function seedCJSModuleCacheAndReturnTarget ( matchedModule : RegisteredModule , parent : Module ) {
34
217
if ( matchedModule . loaded ) {
35
218
return matchedModule . filepath
@@ -101,7 +284,6 @@ export function registerCJSModules(baseUrl: URL, modules: Map<string, string>) {
101
284
102
285
for ( const [ filename , source ] of modules . entries ( ) ) {
103
286
const target = join ( basePath , filename )
104
-
105
287
registeredModules . set ( target , { source, loaded : false , filepath : target } )
106
288
}
107
289
@@ -135,14 +317,10 @@ export function registerCJSModules(baseUrl: URL, modules: Map<string, string>) {
135
317
136
318
let relativeTarget = moduleInPackagePath
137
319
138
- let pkgJson : any = null
139
320
if ( maybePackageJson ) {
140
- pkgJson = parseJson ( maybePackageJson )
321
+ const packageResolver = getPackageResolver ( maybePackageJson )
141
322
142
- // TODO: exports and anything else like that
143
- if ( moduleInPackagePath . length === 0 && pkgJson . main ) {
144
- relativeTarget = pkgJson . main
145
- }
323
+ relativeTarget = packageResolver ( moduleInPackagePath )
146
324
}
147
325
148
326
const potentialPath = join ( nodeModulePaths , packageName , relativeTarget )
0 commit comments