@@ -3,7 +3,9 @@ import { readdirSync } from "node:fs";
3
3
import { readFile } from "node:fs/promises" ;
4
4
import path from "node:path" ;
5
5
import globToRegExp from "glob-to-regexp" ;
6
+ import { exports as resolveExports } from "resolve.exports" ;
6
7
import { logger } from "../logger" ;
8
+ import { BUILD_CONDITIONS } from "./bundle" ;
7
9
import {
8
10
findAdditionalModules ,
9
11
findAdditionalModuleWatchDirs ,
@@ -64,6 +66,23 @@ export const noopModuleCollector: ModuleCollector = {
64
66
} ,
65
67
} ;
66
68
69
+ // Extracts a package name from a string that may be a file path
70
+ // or a package name. Returns null if the string is not a valid
71
+ // Handles `wrangler`, `wrangler/example`, `wrangler/example.wasm`,
72
+ // `@cloudflare/wrangler`, `@cloudflare/wrangler/example`, etc.
73
+ export function extractPackageName ( packagePath : string ) {
74
+ if ( packagePath . startsWith ( "." ) ) return null ;
75
+
76
+ const match = packagePath . match ( / ^ ( @ [ ^ / ] + \/ ) ? ( [ ^ / ] + ) / ) ;
77
+
78
+ if ( match ) {
79
+ const scoped = match [ 1 ] || "" ;
80
+ const packageName = match [ 2 ] ;
81
+ return `${ scoped } ${ packageName } ` ;
82
+ }
83
+ return null ;
84
+ }
85
+
67
86
export function createModuleCollector ( props : {
68
87
entry : Entry ;
69
88
findAdditionalModules : boolean ;
@@ -237,7 +256,7 @@ export function createModuleCollector(props: {
237
256
// take the file and massage it to a
238
257
// transportable/manageable format
239
258
240
- const filePath = path . join ( args . resolveDir , args . path ) ;
259
+ let filePath = path . join ( args . resolveDir , args . path ) ;
241
260
242
261
// If this was a found additional module, mark it as external.
243
262
// Note, there's no need to watch the file here as we already
@@ -251,6 +270,51 @@ export function createModuleCollector(props: {
251
270
// it to `esbuild` to bundle it.
252
271
if ( isJavaScriptModuleRule ( rule ) ) return ;
253
272
273
+ // Check if this file is possibly from an npm package
274
+ // and if so, validate the import against the package.json exports
275
+ // and resolve the file path to the correct file.
276
+ if ( args . path . includes ( "/" ) && ! args . path . startsWith ( "." ) ) {
277
+ // get npm package name from string, taking into account scoped packages
278
+ const packageName = extractPackageName ( args . path ) ;
279
+ if ( ! packageName ) {
280
+ throw new Error (
281
+ `Unable to extract npm package name from ${ args . path } `
282
+ ) ;
283
+ }
284
+ const packageJsonPath = path . join (
285
+ process . cwd ( ) ,
286
+ "node_modules" ,
287
+ packageName ,
288
+ "package.json"
289
+ ) ;
290
+ // Try and read the npm package's package.json
291
+ // and then resolve the import against the package's exports
292
+ // and then finally override filePath if we find a match.
293
+ try {
294
+ const packageJson = JSON . parse (
295
+ await readFile ( packageJsonPath , "utf8" )
296
+ ) ;
297
+ const testResolved = resolveExports (
298
+ packageJson ,
299
+ args . path . replace ( `${ packageName } /` , "" ) ,
300
+ {
301
+ conditions : BUILD_CONDITIONS ,
302
+ }
303
+ ) ;
304
+ if ( testResolved ) {
305
+ filePath = path . join (
306
+ process . cwd ( ) ,
307
+ "node_modules" ,
308
+ packageName ,
309
+ testResolved [ 0 ]
310
+ ) ;
311
+ }
312
+ } catch ( e ) {
313
+ // We tried, now it'll just fall-through to the previous behaviour
314
+ // and ENOENT if the absolute file path doesn't exist.
315
+ }
316
+ }
317
+
254
318
const fileContent = await readFile ( filePath ) ;
255
319
const fileHash = crypto
256
320
. createHash ( "sha1" )
0 commit comments