-
Notifications
You must be signed in to change notification settings - Fork 922
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
[esinstall] Package Export keys should be used in-order #2203
Comments
interesting! We rely on rollup for this feature, and just pass that property to Rollup. Here's the PR where that was implemented in Rollup with the erroneous behavior that you're describing: rollup/plugins#540 /cc @LarsDenBakker |
Interesting.. I did actually re-implement the whole thing in rollup/plugins#693 where the implementation follows the spec more literally. Looks like it follows the exports order instead of the conditions order: https://github.com/rollup/plugins/blob/master/packages/node-resolve/src/package/resolvePackageTarget.js#L90 It's merged, but hasn't been released yet. |
Is the code I linked to in I'm also curious if esinstall/snowpack currently support the |
Ah you're right, it is unused. Two things missing:
|
If it's helpful, Evan implemented the same change in Vite just now: vitejs/vite#1418 (comment) |
Also note that that logic you linked to isn't yet released to |
appreciate the link! |
This is correct Node behavior as you said, but one question I have is: from a community standpoint, there are packages where both are used:
Knowing that a package may have both |
@drwpow supporting "browser" is totally up to Snowpack to decide, but implementations should not give precedence to a given field arbitrarily. Instead, module authors are the ones who decide whether the "browser" field should be given precedence over "import", which they can do by changing the order. Here's a package.json that tells the bundler/runtime "use my browser build when bundling for the browser": {
"exports": {
"browser": "./browser-modern.mjs",
"import": "./unknown.mjs",
"default": "./fallback.js"
}
} Here's a package.json that tells the bundler/runtime "use my browser build for {
"exports": {
"browser": {
"import": "./browser-modern.mjs"
},
"import": "./unknown.mjs",
"default": "./fallback.umd.js"
}
} However, some packages ship universal (not browser-specific) {
"exports": {
"import": "./universal-modern.mjs",
"browser": "./browser.umd.js",
"default": "./universal.umd.js"
}
} This last pattern is particularly important given the introduction of package imports, which allow re-mapping specific internal modules within package (such as to apply browser/node variants): {
"exports": {
"import": "./modern.mjs", // includes `import fetch from 'fetch'`
"default": "./fallback.js" // includes `fetch = require('fetch')`
},
"imports": {
"#fetch": {
"browser": "./native-fetch.mjs",
"default": "node-fetch",
}
},
"peerDependencies": {
"node-fetch": "*"
}
} |
Totally fair! Thanks for clarifying that point. I’m on board with that. |
Happy to! I keep meaning to write a legit guide on this, I feel like the ecosystem could really use something. It's these little details that end up making To add a bit of context to the above: I know it's a little concerning to go with Node's solution where the field precedence decision is in the hands of package authors. But consider: the alternative (custom or user-defined precedence) is actually the same as the solution we had before (package main fields). Without this consistency, Package Exports are just a nested version of the existing the pkg.browser / pkg.main / pkg.module fields. |
Trusting package authors, Jason??? 🙀 |
Key ordering is an odd API choice and I worry that authors are not going to realize it and make mistakes. You can also imagine a scenario where runtimes support the same conditions and so you can never target each runtime with different modules. For ex. if Deno ever supported this and you wanted to use different entrypoints for Deno and Node, you couldn't really do that. |
@developit in your original example: {
"exports": {
"import": "./dist/index.mjs",
"browser": "./dist/browser.js",
"default": "./dist/fallback.umd.js"
}
} You want the bundler to include To rephrase, is there a tool that understands export maps and the |
I do agree that it assumes an understanding of export maps that I just haven't seen so far in the ecosystem. For example, I've spent plenty of time looking at export maps and didn't realize this behavior until you brought it up. Definitely a little concerned about this path forward, even if it is The Right Thing to do. |
@matthewp in that example, {
"exports": {
"import": "./dist/index.mjs",
"browser": {
"require": "./dist/browser.umd.js"
},
"default": "./dist/fallback.umd.js"
}
} |
The understanding piece is definitely and important one. For me though, the lightbulb moment was realizing that we already tried custom semantics and know those erode over time as a result of misuse, whereas package-defined semantics can evolve over time. Poor ordering choices get filed as bugs in the offending package, which is something the package author can then address. One of those "distributed problem requires distributed solution" type things. |
@developit Does unpkg support Let's pretend it did support it though, presumably it would also support So it seems like the only reason to have |
btw, I'm not arguing we shouldn't support the ordering, I think we should until there's a more serious problem, just pointing out that |
Unpkg doesn't support the exports field at all, no. I was using it as an example. Another example of this would be when bundling with Webpack or Rollup while targeting UMD. The reason this might seem superfluous is because bundlers are most commonly used to produce browser code - but that association is not actually a generalizable one, it's just a common case. Ultimately, the use-case for being able to give "browser" precedence over "import" is when packages have browser-specific behavior. There isn't actually a reason to assume "browser" implies ESM usage - environments like Electron are examples where one might actually want to provide a CJS version of a browser-specific entry. UMD is another such example: {
"name": "isomorphic-fetch",
"exports": {
"browser": {
"import": "./browser.mjs", // "export default fetch"
"default": "./browser.umd.js", // "(function(g,f){g.fetch=f();...})(this,function(e){e.fetch=function(){...}})"
},
"import": "./polyfill.mjs"
"default": "./polyfill.js"
}
} Another way to consider this that might help clarify the need for author control over ordering is to consider what happens when user-defined Package Export fields are configured. The package author needs to be able to specify that "production" takes precedence over "browser", regardless of what the bundler configuration says. |
I'm not sure if this is related but we upgrade to snowpack 3 recently and the import order of some @material-ui/core css assets changed which broke their menu styling. We haven't managed to get a reproducible demo of the bug but would this be of interest to snowpack / rollup if we could or would this be a material-ui thing? Specifically the order of these two items seems to randomly change in our app (this is the attempt to reproduce so it's got a lot less going on) |
The spec indicates that the ordering of keys in
"exports"
objects defines their precedence, where the first matching key (direct string or via object traversal to matched nested key) is used. Currently,esinstall
uses its configured keys (packageExportLookupFields
) as the precedence ordering: https://github.com/snowpackjs/snowpack/blob/b00d0cf08ce9c04d18a05a0592c4cdb5cf2477c4/esinstall/src/entrypoints.ts#L96-L103This causes the following export map to be interpreted incorrectly:
Using the above package with
packageExportLookupFields:['browser']
will resolve an import statement to./dist/browser.js
rather than the expected./dist/index.mjs
.The text was updated successfully, but these errors were encountered: