Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support exports syntax in package.json #187

Closed
matthiasg opened this issue Jun 19, 2020 · 18 comments
Closed

Support exports syntax in package.json #187

matthiasg opened this issue Jun 19, 2020 · 18 comments

Comments

@matthiasg
Copy link

More modern ESM node modules are supposed to use exports syntax to control visibility of exports.

https://nodejs.org/api/esm.html#esm_main_entry_point_export

In case this is something that could be done an issue should track any progress

@evanw
Copy link
Owner

evanw commented Jun 19, 2020

I've been following along with discussions about this on the issue trackers for other bundlers. As far as I know the exports syntax doesn't interact well with other bundler features that are already widely in use, namely the module and browser fields in package.json. Here is an example thread: webpack/webpack#11014. I think it's premature to add support for this before other bundlers figure this out and the community settles on a way to work around these issues.

@matthiasg
Copy link
Author

I think that is a prudent approach, but should at least be documented in a section about the module resolution strategy. Node 14 is advocating its use export takes precendence over main which will lead to some surprises.

@developit
Copy link

developit commented Jan 7, 2021

Just wanted to re-up this issue to get it back on your radar. The "exports" field is now supported in every major bundler except Parcel and ESbuild. Some examples to look at:

Webpack
Rollup
Vite
Snowpack
WMR

@evanw
Copy link
Owner

evanw commented Jan 8, 2021

Thanks for the links. However, it looks like decisions on how this stuff is supposed to work are still settling. See for example this long Twitter thread. Big +1 from me to what Devon is advocating for. I'd like to wait until y'all figure it out first so I don't end up implementing something that doesn't follow the community conventions.

@developit
Copy link

@evanw Shouldn't it be safe to implement Node's algorithm at this point though? It has been shipped as stable for 3 major versions, which makes it pretty locked in at this point. The additional community-proposed fields can certainly be subject to change, but that may always be the case, and they're only ever going to be a layer atop the base 3 fields.

@Tobbe
Copy link

Tobbe commented Jan 15, 2021

How about using the resolve.exports package by @lukeed?

@lukeed
Copy link
Contributor

lukeed commented Jan 15, 2021

Thanks! Yes that package follows the Node.js implementation and supports all exports map variants. It comes with shortcuts to toggle on/off require-vs-import and browser-vs-node, but you can use any custom conditions you want too (like webpack's production-vs-development).

The nice thing about exports is that the package author determines the condition priority. When resolving, you just have to know which conditions you care about.

This makes it able to support all existing bundlers & whatever conditions they may choose to invent. Vite is already using it, wmr and yarn are WIP, and Rollup has stated that they're open to swapping too.

@airhorns
Copy link

In the meantime, we could probably write a plugin that used resolve.exports for now, right? You'd take a major performance hit because each resolution would have to jump back into JS land but that seems like an ok fix until things are stable enough to meet Evan's threshold?

@evanw
Copy link
Owner

evanw commented Feb 25, 2021

In the meantime, we could probably write a plugin that used resolve.exports for now, right?

If you're looking at writing a plugin, you may find this recently-released feature helpful for the import vs. require distinction: #879.

that seems like an ok fix until things are stable enough to meet Evan's threshold?

Right now my main reason for not doing this right now (at least a node-compatible version) is that doing this is a breaking change. This change is on my list of potential things to bundle together in the next upcoming breaking change release.

@matthiasg
Copy link
Author

@evanw appreciate your great work ! You do not have to make this a breaking change though if you hide it behind a feature flag. Do you think that would add too much complexity ?

rvagg added a commit to rvagg/ipjs that referenced this issue Mar 5, 2021
esbuild still doesn't support exports evanw/esbuild#187
it's uncertain as yet what impact this might have on other bundlers and
loaders, hence this is a BREAKING CHANGE.
rvagg added a commit to rvagg/ipjs that referenced this issue Mar 5, 2021
esbuild still doesn't support exports evanw/esbuild#187
it's uncertain as yet what impact this might have on other bundlers and
loaders, hence this is a BREAKING CHANGE.
mikeal pushed a commit to mikeal/ipjs that referenced this issue Mar 6, 2021
* fix!: don't delete "main" in package.json

esbuild still doesn't support exports evanw/esbuild#187
it's uncertain as yet what impact this might have on other bundlers and
loaders, hence this is a BREAKING CHANGE.

* fixup! fix!: don't delete "main" in package.json
@evanw evanw mentioned this issue Mar 6, 2021
10 tasks
@evanw evanw closed this as completed in 2e89fcc Mar 9, 2021
@evanw
Copy link
Owner

evanw commented Mar 10, 2021

This feature has been released in version 0.9.0 by the way. Please try it out and let me know if anything seems off. I tried to follow node's specification as close as I could but I don't use this feature myself so it's possible that something might not be working exactly right on the first try.

jayfreestone added a commit to jayfreestone/priority-plus that referenced this issue Mar 31, 2021
The module landscape is a mess. Currently the UMD build is preferred by
Webpack and esbuild, since the `browser` field takes precedence over the
`module` field.

However, conditional exports (yet another field!) now allow for a
separate approach. Setting the first type to `import` results in both
bundlers favouring it over the UMD build. I'm leaving Browser around as
a legacy field for now.

See:

- webpack/webpack#4674
- evanw/esbuild#187
- https://webpack.js.org/guides/package-exports/#providing-different-versions-depending-on-target-environment
- https://nodejs.org/api/packages.html#packages_exports_sugar
@Lcfvs
Copy link

Lcfvs commented Jun 6, 2024

Hi @evanw,

I think I have an unsupported case, into the project of my clients:

  "exports": {
    "./*": "./src/*"
  },

@evanw
Copy link
Owner

evanw commented Jun 6, 2024

@Lcfvs
Copy link

Lcfvs commented Jun 10, 2024

Hi @evanw,

Thanks a lot for your reply & sorry for the delay, I was investigating... but even if it works within your playground... the bundling fails for my client project files.

Since, as you said, you don't really use the exports yourself, I tried to make a very minimal sample for you, available here: https://github.com/Lcfvs/esbuild-attempt

After the download:

cd client
npm i
npm run build

Produces:
image

EDIT:
I tested the multi-level with your playground, which works, but with the real files, it seems to always break after the first one.

@evanw
Copy link
Owner

evanw commented Jun 10, 2024

The failing import is in the components-generics package, not the client package. So you also need to do this to get it to build:

cd components-generics
npm i

That doesn't seem like a problem with esbuild to me.

@Lcfvs
Copy link

Lcfvs commented Jun 10, 2024

Hum, since components-generics is a client dependency... there is no reasons to go into the components-generics to install it distincly... just like any remote dependency (like the https://www.npmjs.com hosted ones), it isn't?

Moreover, it's a distinct behavior from rollup on that point.

@evanw
Copy link
Owner

evanw commented Jun 10, 2024

The problem you're describing also happens when you run that code in node:

$ node src/js/index.js 
node:internal/modules/esm/resolve:854
  throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
        ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@owner/front' imported from ./esbuild-attempt/components-generics/src/js/generics.js

Since esbuild implements node's path resolution algorithm, this problem also happens in esbuild. So what esbuild is doing is the expected behavior. Node invented the exports field in package.json and has specified it very precisely. If Rollup is behaving differently then Rollup might be the one that's not following the specification correctly.

You can see what's happening if you pass logLevel: 'debug' to esbuild:

● [DEBUG] Resolving import "@owner/front/js/contracts/contract.js" in directory "./esbuild-attempt/components-generics/src/js" of type "import-statement"

  Checking for package alias matches
    Failed to find any package alias matches
  Read 1 entry for directory "./esbuild-attempt/components-generics/src/js"
  No "browser" map found in directory "./esbuild-attempt/components-generics/src/js"
  Searching for "@owner/front/js/contracts/contract.js" in "node_modules" directories starting from "./esbuild-attempt/components-generics/src/js"
    Parsed package name "@owner/front" and package subpath "./js/contracts/contract.js"

The import path @owner/front/js/contracts/contract.js is evaluated in ./esbuild-attempt/components-generics/src/js, but there are no node_modules directories in any parent directory of that path unless you run npm i in components-generics.

@Lcfvs
Copy link

Lcfvs commented Jun 10, 2024

Oh, I see... thanks a lot and sorry for that, then 🫣

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment