You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We've discussed this briefly on Slack, but I thought an issue would be easier to keep track of.
I'm not sure what it would entail, so I'll just list out (i.e. do a brain dump of) the features I think would be needed.
Should file be interpreted as CJS or ESM
Currently, resolve will only ever return a single string, which is the absolute path to the resolved file. I think in addition to this, we'd need to know if the file should be interpreted as CJS or ESM
Related, should resolve taken an option what the caller would prefer? Should it be a static option in, or some callback?
Again, should the caller be able to define which they want? EDIT: Ish, via conditions
Support promise for async resolution
This is admittedly not a requirement, but it matches the Node APIs making it cleaner to use resolve when implementing custom loaders or using the VM APIs.
Support URLs. Mostly because import() supports it. It probably makes more sense for resolve to return URLs than an absolute path for ESM?
Preserve query strings
Makes sense, but just adding it here as a point
There is also a flagged API in Node for this, import.meta.resolve: nodejs/node#31032. Not sure if we should care too much about it, though?
I think that covers it, but you know way more about this subject than I do, so feel free to either close this to open up your own, or edit this OP as you see fit 👍
For background, Jest uses resolve as the default implementation in jest-resolve, although the user can swap it out. I'm currently working on support for ESM natively in Jest, and while we have a version today that sorta works, it's not a compliant implementation. Most of the (known) issues are due to resolution logic. I'd be happy to help implement support here in resolve.
Some tools/bundlers use resolve('pkgname/package.json') in order to find file locations of package.json files of project dependencies to then extract meta information from these package.json files (like a browser field for file overrides as supported by webpack, rollup etc.). These might break if exports support is added to the resolve package in the same way that only those paths explicitly mentioned in pkg.exports will be resolved.
This came up in react-native-community/cli#1168 where dependencies that don't explicitly export package.json might now be broken. I initially wanted to suggest to use resolve() there instead, but only to realize that resolve() does in fact just not yet have the same problem as require.resolve().
I'm not aware of other problematic tools right now, it's just something we should keep in mind.
The plan is that you'll be able to explicitly request resolve use pre-exports resolution, so i think it's save to use resolve for this use case for the time being.
@SimenB thanks for this suggestion! I took the liberty to copy it over to nodejs/node#33460 (comment) which might be a better place to continue this discussion.
This definitely looks like an elegant solution, but of course it doesn't answer the question yet if the current behavior of node is desired at all 😉 .
@SimenB the edge cases very much matter here; we need a way that works in all cases to identify the directory that a package resolves to, and with an "exports" that doesn't expose package.json, or ./, and also has . resolve to a subdirectory, we can't.
I also don't think that's an edge case - that's "every package that uses exports and babel", which is likely to be the 99% case.
If a library adds an "exports" that does not include the "package.json", isn't that a breaking change and conscious decision from the library? It's the same as if the library adds a "files" or .npmignore which ignores some of the files and it breaks some 3rd party usage.
I'm not sure how extended "exports" is already, but might be only a handful of libraries that use "exports" but does not include "package.json" there. All of the examples linked above have in fact already been fixed in the libraries:
@franciscop yes, it is. Adding "exports" is almost always a breaking change, unless you preserve every possible entry point (use npx ls-exports in your package and you can figure out if you're achieving that or not)
Just want to point out that there are use cases for a synchronous implementation.
Mine is: I'm transpiling ESM to CommonJS using Babel, replacing import statements with require(). I'd like to make resolution of import paths compliant with NodeJS. As Babel plugins must execute synchronously, this would require synchronous resolution.
I understand providing both sync and async implementations may be too complicated to be viable, but just wanted to flag that some use cases do exist.
node doesn’t have synchronous ESM resolution, because not every ESM specifier is possible to synchronously resolve. I wish this wasn’t the case, but babel needs a way for plugins to run asynchronously anyways, so hopefully this will help encourage that.
I thought Node's ESM loader has to be async due to top level await, but I wasn't aware of a reason why resolution of specifiers can't be synchronous as it's all static analysis. Am I wrong?
For my use case I'm OK not to support top level await. I'm aware that's not a perfect ESM to CJS translation, but it's close enough.
For filesystem specifiers, it can be. However, node wants to preserve the ability to later add, for example, https imports - and thus, so too must resolve.
I am highly disinclined to use generators; i find them horrific to maintain. I’d prefer to adapt the current code to support “exports” rather than try a rewrite (something that is virtually always a disaster).
Additionally, using generators or iterators would mean we couldn’t support down to node 0.4, which is absolutely critical for this package.
Reacted by Christopher Svanefalk and Stefan Toubia
OK, that makes sense re 0.4. I still think there's value in creating a radically simpler more modern package though, even if there's a need to maintain something that has deep-rooted legacy limitations.
I have found that generators can be used to simplify code like this greatly, particularly when you use them as strategies that control the transitions of a state machine. If you design the state machine API correctly it essentially becomes a de-facto plugin API because you can always use higher-order strategies to extend any existing functionality.
An immediate gain is that you end up with normal top-down control, and thus useful stack traces! And of course you can provide sync and async variants with a single implementation. And it becomes possible to create a visual state machine, devtools for resolvers essentially. This isn't a big deal when you have one resolver, but what we actually need is a huge family of related resolvers, so that it rapidly becomes useful to be able to explain at any given moment what a resolver is doing and WHY.
@ljharb As @IanVS stated I don't want to add to the pressure and I know you are a busy guy doing a lot of things for free. As you've stated so many other things would be fixed (like eslint-plugin-import) if resolve supports ESM resolution. Given the many problems with build tooling around the big push towards ESM (especially when it comes to consuming ESM-only packages) it would be really great to finally fix this issue. Just trying to be a bit of a squeaky wheel without being too annoying...
Reacted by Jordan Harband, Forbidden Era, Rob Lensen, Zbyszek Tenerowicz and Mikael Härsjö
Activity
ctavan commentedon May 18, 2020
Some tools/bundlers use
resolve('pkgname/package.json')in order to find file locations ofpackage.jsonfiles of project dependencies to then extract meta information from thesepackage.jsonfiles (like abrowserfield for file overrides as supported by webpack, rollup etc.). These might break ifexportssupport is added to theresolvepackage in the same way that only those paths explicitly mentioned inpkg.exportswill be resolved.This came up in react-native-community/cli#1168 where dependencies that don't explicitly export
package.jsonmight now be broken. I initially wanted to suggest to useresolve()there instead, but only to realize thatresolve()does in fact just not yet have the same problem asrequire.resolve().I'm not aware of other problematic tools right now, it's just something we should keep in mind.
See also: nodejs/node#33460
ljharb commentedon May 18, 2020
The plan is that you'll be able to explicitly request
resolveuse pre-exports resolution, so i think it's save to useresolvefor this use case for the time being.SimenB commentedon May 19, 2020
For the "I need to load
package.json" use case, why not do something like this:https://github.com/sindresorhus/pkg-up
Could trivially be packaged up into a
pkg-of-moduleand published to npm if people want.I guess it could be wrong if people have nested
package.jsons, but that seems like an edge case we shouldn't care about 😀ctavan commentedon May 19, 2020
@SimenB thanks for this suggestion! I took the liberty to copy it over to nodejs/node#33460 (comment) which might be a better place to continue this discussion.
This definitely looks like an elegant solution, but of course it doesn't answer the question yet if the current behavior of node is desired at all 😉 .
ljharb commentedon May 19, 2020
@SimenB the edge cases very much matter here; we need a way that works in all cases to identify the directory that a package resolves to, and with an "exports" that doesn't expose
package.json, or./, and also has.resolve to a subdirectory, we can't.I also don't think that's an edge case - that's "every package that uses exports and babel", which is likely to be the 99% case.
SimenB commentedon May 19, 2020
I don't think I've ever seen a nested package.json. Regardless, this isn't the correct issue to discuss it
ljharb commentedon May 19, 2020
"type": "module"will make it more common, I suspect, but you're right :-)NMinhNguyen commentedon May 30, 2020
@SimenB does this count as a nested
package.jsonor did you have something else in mind?SimenB commentedon Jun 5, 2020
There you go, real usage. 😀 Still, it's off topic for this issue
franciscop commentedon Jun 12, 2020
If a library adds an "exports" that does not include the "package.json", isn't that a breaking change and conscious decision from the library? It's the same as if the library adds a
"files"or.npmignorewhich ignores some of the files and it breaks some 3rd party usage.I'm not sure how extended
"exports"is already, but might be only a handful of libraries that use"exports"but does not include"package.json"there. All of the examples linked above have in fact already been fixed in the libraries:package.jsoni18next-http-backendis fixedi18next-locize-backendis fixedljharb commentedon Jun 12, 2020
@franciscop yes, it is. Adding "exports" is almost always a breaking change, unless you preserve every possible entry point (use
npx ls-exportsin your package and you can figure out if you're achieving that or not)overlookmotel commentedon Jan 7, 2021
I noticed @ljharb's comment on Rollup repo mentioning that ESM resolution API is intended to be async only.
Just want to point out that there are use cases for a synchronous implementation.
Mine is: I'm transpiling ESM to CommonJS using Babel, replacing
importstatements withrequire(). I'd like to make resolution of import paths compliant with NodeJS. As Babel plugins must execute synchronously, this would require synchronous resolution.I understand providing both sync and async implementations may be too complicated to be viable, but just wanted to flag that some use cases do exist.
ljharb commentedon Jan 7, 2021
node doesn’t have synchronous ESM resolution, because not every ESM specifier is possible to synchronously resolve. I wish this wasn’t the case, but babel needs a way for plugins to run asynchronously anyways, so hopefully this will help encourage that.
overlookmotel commentedon Jan 7, 2021
I thought Node's ESM loader has to be async due to top level await, but I wasn't aware of a reason why resolution of specifiers can't be synchronous as it's all static analysis. Am I wrong?
For my use case I'm OK not to support top level await. I'm aware that's not a perfect ESM to CJS translation, but it's close enough.
ljharb commentedon Jan 7, 2021
For filesystem specifiers, it can be. However, node wants to preserve the ability to later add, for example, https imports - and thus, so too must resolve.
13 remaining items
ljharb commentedon Jan 21, 2023
I am highly disinclined to use generators; i find them horrific to maintain. I’d prefer to adapt the current code to support “exports” rather than try a rewrite (something that is virtually always a disaster).
Additionally, using generators or iterators would mean we couldn’t support down to node 0.4, which is absolutely critical for this package.
conartist6 commentedon Jan 21, 2023
OK, that makes sense re 0.4. I still think there's value in creating a radically simpler more modern package though, even if there's a need to maintain something that has deep-rooted legacy limitations.
I have found that generators can be used to simplify code like this greatly, particularly when you use them as strategies that control the transitions of a state machine. If you design the state machine API correctly it essentially becomes a de-facto plugin API because you can always use higher-order strategies to extend any existing functionality.
An immediate gain is that you end up with normal top-down control, and thus useful stack traces! And of course you can provide sync and async variants with a single implementation. And it becomes possible to create a visual state machine, devtools for resolvers essentially. This isn't a big deal when you have one resolver, but what we actually need is a huge family of related resolvers, so that it rapidly becomes useful to be able to explain at any given moment what a resolver is doing and WHY.
dlong500 commentedon Feb 7, 2023
@ljharb As @IanVS stated I don't want to add to the pressure and I know you are a busy guy doing a lot of things for free. As you've stated so many other things would be fixed (like eslint-plugin-import) if resolve supports ESM resolution. Given the many problems with build tooling around the big push towards ESM (especially when it comes to consuming ESM-only packages) it would be really great to finally fix this issue. Just trying to be a bit of a squeaky wheel without being too annoying...
dword-design commentedon Mar 3, 2023
Would be great to have this implemented since it blocks eslint-import-resolver-node from resolving packages that do not configure the
mainfield 😀.Add "main" to package.json to fix eslint issues
benmccann commentedon Jun 14, 2023
@SimenB I wonder if the most expedient solution would be to make
resolve.exportsorenhanced-resolvethe default?SimenB commentedon Jun 15, 2023
Jest uses
resolve.exportsto deal withexportsandimportsalreadyLinusBF commentedon Jun 27, 2023
conartist6 commentedon Jun 27, 2023
conartist6 commentedon Jun 27, 2023
ljharb commentedon Jun 27, 2023
robertsLando commentedon Nov 14, 2025
@ljharb just for info, is there any work in progress about this?
ljharb commentedon Nov 15, 2025
@robertsLando lots of work in progress, but no timeline yet.
Add missing `main` field to `package.json`
Add missing `main` field to `package.json` (#216)