@@ -116,6 +116,38 @@ function isBareSpecifier (specifier) {
116116 }
117117}
118118
119+ function isBareSpecifierOrFileUrl ( input ) {
120+ // Relative and absolute paths
121+ if (
122+ input . startsWith ( '.' ) ||
123+ input . startsWith ( '/' ) ) {
124+ return false
125+ }
126+
127+ try {
128+ // eslint-disable-next-line no-new
129+ const url = new URL ( input )
130+ return url . protocol === 'file:'
131+ } catch ( err ) {
132+ // Anything that fails parsing is a bare specifier
133+ return true
134+ }
135+ }
136+
137+ function ensureArrayWithBareSpecifiersAndFileUrls ( array , type ) {
138+ if ( ! Array . isArray ( array ) ) {
139+ return undefined
140+ }
141+
142+ const invalid = array . filter ( s => ! isBareSpecifierOrFileUrl ( s ) )
143+
144+ if ( invalid . length ) {
145+ throw new Error ( `'${ type } ' option only supports bare specifiers and file URLs. Invalid entries: ${ inspect ( invalid ) } ` )
146+ }
147+
148+ return array
149+ }
150+
119151function emitWarning ( err ) {
120152 // Unfortunately, process.emitWarning does not output the full error
121153 // with error.cause like console.warn does so we need to inspect it when
@@ -217,6 +249,14 @@ function addIitm (url) {
217249function createHook ( meta ) {
218250 let cachedResolve
219251 const iitmURL = new URL ( 'lib/register.js' , meta . url ) . toString ( )
252+ let includeModules , excludeModules
253+
254+ async function initialize ( data ) {
255+ if ( data ) {
256+ includeModules = ensureArrayWithBareSpecifiersAndFileUrls ( data . include , 'include' )
257+ excludeModules = ensureArrayWithBareSpecifiersAndFileUrls ( data . exclude , 'exclude' )
258+ }
259+ }
220260
221261 async function resolve ( specifier , context , parentResolve ) {
222262 cachedResolve = parentResolve
@@ -234,39 +274,52 @@ function createHook (meta) {
234274 if ( isWin && parentURL . indexOf ( 'file:node' ) === 0 ) {
235275 context . parentURL = ''
236276 }
237- const url = await parentResolve ( newSpecifier , context , parentResolve )
238- if ( parentURL === '' && ! EXTENSION_RE . test ( url . url ) ) {
239- entrypoint = url . url
240- return { url : url . url , format : 'commonjs' }
277+ const result = await parentResolve ( newSpecifier , context , parentResolve )
278+ if ( parentURL === '' && ! EXTENSION_RE . test ( result . url ) ) {
279+ entrypoint = result . url
280+ return { url : result . url , format : 'commonjs' }
281+ }
282+
283+ // For included/excluded modules, we check the specifier to match libraries
284+ // that are loaded with bare specifiers from node_modules.
285+ //
286+ // For non-bare specifier imports, we only support matching file URL strings
287+ // because using relative paths would be very error prone!
288+ if ( includeModules && ! includeModules . some ( lib => lib === specifier || lib === result . url . url ) ) {
289+ return result
290+ }
291+
292+ if ( excludeModules && excludeModules . some ( lib => lib === specifier || lib === result . url . url ) ) {
293+ return result
241294 }
242295
243296 if ( isIitm ( parentURL , meta ) || hasIitm ( parentURL ) ) {
244- return url
297+ return result
245298 }
246299
247300 // Node.js v21 renames importAssertions to importAttributes
248301 if (
249302 ( context . importAssertions && context . importAssertions . type === 'json' ) ||
250303 ( context . importAttributes && context . importAttributes . type === 'json' )
251304 ) {
252- return url
305+ return result
253306 }
254307
255308 // If the file is referencing itself, we need to skip adding the iitm search params
256- if ( url . url === parentURL ) {
309+ if ( result . url === parentURL ) {
257310 return {
258- url : url . url ,
311+ url : result . url ,
259312 shortCircuit : true ,
260- format : url . format
313+ format : result . format
261314 }
262315 }
263316
264- specifiers . set ( url . url , specifier )
317+ specifiers . set ( result . url , specifier )
265318
266319 return {
267- url : addIitm ( url . url ) ,
320+ url : addIitm ( result . url ) ,
268321 shortCircuit : true ,
269- format : url . format
322+ format : result . format
270323 }
271324 }
272325
@@ -337,9 +390,10 @@ register(${JSON.stringify(realUrl)}, _, set, ${JSON.stringify(specifiers.get(rea
337390 }
338391
339392 if ( NODE_MAJOR >= 17 || ( NODE_MAJOR === 16 && NODE_MINOR >= 12 ) ) {
340- return { load, resolve }
393+ return { initialize , load, resolve }
341394 } else {
342395 return {
396+ initialize,
343397 load,
344398 resolve,
345399 getSource,
0 commit comments