@@ -21,9 +21,19 @@ const updateImportMap = (entry, entryPath) => {
2121 entryPath = `${ entryPath } .js` ;
2222 }
2323
24- importMap [ entry ] = entryPath ;
24+ // handle WIn v Unix-style path separators and force to /
25+ importMap [ entry . replace ( / \\ / g, '/' ) ] = entryPath . replace ( / \\ / g, '/' ) ;
2526} ;
2627
28+ // handle ESM paths that have varying levels of nesting, e.g. export * from '../../something.js'
29+ // https://github.com/ProjectEvergreen/greenwood/issues/820
30+ async function resolveRelativeSpecifier ( specifier , modulePath , dependency ) {
31+ const absoluteNodeModulesLocation = await getNodeModulesLocationForPackage ( dependency ) ;
32+
33+ // handle WIn v Unix-style path separators and force to /
34+ return `${ dependency } ${ path . join ( path . dirname ( modulePath ) , specifier ) . replace ( / \\ / g, '/' ) . replace ( absoluteNodeModulesLocation . replace ( / \\ / g, '/' , '' ) , '' ) } ` ;
35+ }
36+
2737const getPackageEntryPath = async ( packageJson ) => {
2838 let entry = packageJson . exports
2939 ? Object . keys ( packageJson . exports ) // first favor export maps first
@@ -41,58 +51,64 @@ const getPackageEntryPath = async (packageJson) => {
4151 return entry ;
4252} ;
4353
44- const walkModule = async ( module , dependency ) => {
45- walk . simple ( acorn . parse ( module , {
54+ const walkModule = async ( modulePath , dependency ) => {
55+ const moduleContents = fs . readFileSync ( modulePath , 'utf-8' ) ;
56+
57+ walk . simple ( acorn . parse ( moduleContents , {
4658 ecmaVersion : '2020' ,
4759 sourceType : 'module'
4860 } ) , {
4961 async ImportDeclaration ( node ) {
5062 let { value : sourceValue } = node . source ;
5163 const absoluteNodeModulesLocation = await getNodeModulesLocationForPackage ( dependency ) ;
64+ const isBarePath = sourceValue . indexOf ( 'http' ) !== 0 && sourceValue . charAt ( 0 ) !== '.' && sourceValue . charAt ( 0 ) !== path . sep ;
65+ const hasExtension = path . extname ( sourceValue ) !== '' ;
5266
53- if ( path . extname ( sourceValue ) === '' && sourceValue . indexOf ( 'http' ) !== 0 && sourceValue . indexOf ( './' ) < 0 ) {
67+ if ( isBarePath && ! hasExtension ) {
5468 if ( ! importMap [ sourceValue ] ) {
55- // found a _new_ bare import for ${sourceValue}
56- // we should add this to the importMap and walk its package.json for more transitive deps
5769 updateImportMap ( sourceValue , `/node_modules/${ sourceValue } ` ) ;
5870 }
5971
6072 await walkPackageJson ( path . join ( absoluteNodeModulesLocation , 'package.json' ) ) ;
61- } else if ( sourceValue . indexOf ( './' ) < 0 ) {
62- // adding a relative import
73+ } else if ( isBarePath ) {
6374 updateImportMap ( sourceValue , `/node_modules/${ sourceValue } ` ) ;
6475 } else {
6576 // walk this module for all its dependencies
66- sourceValue = sourceValue . indexOf ( '.js' ) < 0
77+ sourceValue = ! hasExtension
6778 ? `${ sourceValue } .js`
6879 : sourceValue ;
6980
7081 if ( fs . existsSync ( path . join ( absoluteNodeModulesLocation , sourceValue ) ) ) {
71- const moduleContents = fs . readFileSync ( path . join ( absoluteNodeModulesLocation , sourceValue ) ) ;
72- await walkModule ( moduleContents , dependency ) ;
73- updateImportMap ( `${ dependency } /${ sourceValue . replace ( './' , '' ) } ` , `/node_modules/${ dependency } /${ sourceValue . replace ( './' , '' ) } ` ) ;
82+ const entry = `/node_modules/${ await resolveRelativeSpecifier ( sourceValue , modulePath , dependency ) } ` ;
83+ await walkModule ( path . join ( absoluteNodeModulesLocation , sourceValue ) , dependency ) ;
84+
85+ updateImportMap ( path . join ( dependency , sourceValue ) , entry ) ;
7486 }
7587 }
76-
77- return Promise . resolve ( ) ;
7888 } ,
79- ExportNamedDeclaration ( node ) {
89+ async ExportNamedDeclaration ( node ) {
8090 const sourceValue = node && node . source ? node . source . value : '' ;
8191
8292 if ( sourceValue !== '' && sourceValue . indexOf ( 'http' ) !== 0 ) {
93+ // handle relative specifier
8394 if ( sourceValue . indexOf ( '.' ) === 0 ) {
84- updateImportMap ( `${ dependency } /${ sourceValue . replace ( './' , '' ) } ` , `/node_modules/${ dependency } /${ sourceValue . replace ( './' , '' ) } ` ) ;
95+ const entry = `/node_modules/${ await resolveRelativeSpecifier ( sourceValue , modulePath , dependency ) } ` ;
96+
97+ updateImportMap ( path . join ( dependency , sourceValue ) , entry ) ;
8598 } else {
99+ // handle bare specifier
86100 updateImportMap ( sourceValue , `/node_modules/${ sourceValue } ` ) ;
87101 }
88102 }
89103 } ,
90- ExportAllDeclaration ( node ) {
104+ async ExportAllDeclaration ( node ) {
91105 const sourceValue = node && node . source ? node . source . value : '' ;
92106
93107 if ( sourceValue !== '' && sourceValue . indexOf ( 'http' ) !== 0 ) {
94108 if ( sourceValue . indexOf ( '.' ) === 0 ) {
95- updateImportMap ( `${ dependency } /${ sourceValue . replace ( './' , '' ) } ` , `/node_modules/${ dependency } /${ sourceValue . replace ( './' , '' ) } ` ) ;
109+ const entry = `/node_modules/${ await resolveRelativeSpecifier ( sourceValue , modulePath , dependency ) } ` ;
110+
111+ updateImportMap ( path . join ( dependency , sourceValue ) , entry ) ;
96112 } else {
97113 updateImportMap ( sourceValue , `/node_modules/${ sourceValue } ` ) ;
98114 }
@@ -107,7 +123,7 @@ const walkPackageJson = async (packageJson = {}) => {
107123 // and walk its package.json for its dependencies
108124
109125 for ( const dependency of Object . keys ( packageJson . dependencies || { } ) ) {
110- const dependencyPackageRootPath = path . join ( process . cwd ( ) , './ node_modules' , dependency ) ;
126+ const dependencyPackageRootPath = path . join ( process . cwd ( ) , 'node_modules' , dependency ) ;
111127 const dependencyPackageJsonPath = path . join ( dependencyPackageRootPath , 'package.json' ) ;
112128 const dependencyPackageJson = JSON . parse ( fs . readFileSync ( dependencyPackageJsonPath , 'utf-8' ) ) ;
113129 const entry = await getPackageEntryPath ( dependencyPackageJson ) ;
@@ -137,7 +153,7 @@ const walkPackageJson = async (packageJson = {}) => {
137153 break ;
138154 case 'object' :
139155 const entryTypes = Object . keys ( mapItem ) ;
140-
156+
141157 if ( entryTypes . import ) {
142158 esmPath = entryTypes . import ;
143159 } else if ( entryTypes . require ) {
@@ -165,31 +181,26 @@ const walkPackageJson = async (packageJson = {}) => {
165181
166182 // use the dependency itself as an entry in the importMap
167183 if ( entry === '.' ) {
168- updateImportMap ( dependency , `/node_modules/${ dependency } / ${ packageExport . replace ( './' , '' ) } ` ) ;
184+ updateImportMap ( dependency , `/node_modules/${ path . join ( dependency , packageExport ) } ` ) ;
169185 }
170186 } else if ( exportMapEntry . endsWith && ( exportMapEntry . endsWith ( '.js' ) || exportMapEntry . endsWith ( '.mjs' ) ) && exportMapEntry . indexOf ( '*' ) < 0 ) {
171187 // is probably a file, so _not_ an export array, package.json, or wildcard export
172188 packageExport = exportMapEntry ;
173189 }
174-
190+
175191 if ( packageExport ) {
176- const packageExportLocation = path . join ( absoluteNodeModulesLocation , packageExport . replace ( './' , '' ) ) ;
192+ const packageExportLocation = path . resolve ( absoluteNodeModulesLocation , packageExport ) ;
177193
178- // check all exports of an exportMap entry
179- // to make sure those deps get added to the importMap
180194 if ( packageExport . endsWith ( 'js' ) ) {
181- const moduleContents = fs . readFileSync ( packageExportLocation ) ;
182-
183- await walkModule ( moduleContents , dependency ) ;
184- updateImportMap ( `${ dependency } ${ entry . replace ( '.' , '' ) } ` , `/node_modules/${ dependency } /${ packageExport . replace ( './' , '' ) } ` ) ;
195+ updateImportMap ( path . join ( dependency , entry ) , `/node_modules/${ path . join ( dependency , packageExport ) } ` ) ;
185196 } else if ( fs . lstatSync ( packageExportLocation ) . isDirectory ( ) ) {
186197 fs . readdirSync ( packageExportLocation )
187198 . filter ( file => file . endsWith ( '.js' ) || file . endsWith ( '.mjs' ) )
188199 . forEach ( ( file ) => {
189- updateImportMap ( ` ${ dependency } / ${ packageExport . replace ( './' , '' ) } ${ file } ` , `/node_modules/${ dependency } / ${ packageExport . replace ( './' , '' ) } ${ file } ` ) ;
200+ updateImportMap ( path . join ( dependency , packageExport , file ) , `/node_modules/${ path . join ( dependency , packageExport , file ) } ` ) ;
190201 } ) ;
191202 } else {
192- console . warn ( 'Warning, not able to handle export' , ` ${ dependency } / ${ packageExport } ` ) ;
203+ console . warn ( 'Warning, not able to handle export' , path . join ( dependency , packageExport ) ) ;
193204 }
194205 }
195206 }
@@ -200,10 +211,9 @@ const walkPackageJson = async (packageJson = {}) => {
200211
201212 // sometimes a main file is actually just an empty string... :/
202213 if ( fs . existsSync ( packageEntryPointPath ) ) {
203- const packageEntryModule = fs . readFileSync ( packageEntryPointPath , 'utf-8' ) ;
204-
205- await walkModule ( packageEntryModule , dependency ) ;
206- updateImportMap ( dependency , `/node_modules/${ dependency } /${ entry } ` ) ;
214+ updateImportMap ( dependency , `/node_modules/${ path . join ( dependency , entry ) } ` ) ;
215+
216+ await walkModule ( packageEntryPointPath , dependency ) ;
207217 await walkPackageJson ( dependencyPackageJson ) ;
208218 }
209219 }
0 commit comments