-
Notifications
You must be signed in to change notification settings - Fork 44
Simplifying package.json of backward-compatible packages (type: "module") #432
Comments
Packages don’t just have one entry point; they have infinite - as such, a map of exports is always needed. Using only the directory doesn’t allow for selective exposing/hiding of files within the same directory. |
I'm not asking to do anything (or remove) support for |
@sheerun you do not need to use type module if you don't want to. An alternative configuration to do the same thing would be the following: {
"main": "./cjs-main.js",
"exports": {
"require": "./cjs-main.js",
"default": "./esm-main.mjs"
}
} Note that CommonJS supports |
@guybedford Why can't I write this as follows:
Which can be sugared to follows?
|
This isn’t correct. A package can have multiple
And then all The |
Because |
@GeoffreyBooth But I don't use |
That’s not how And before you ask why can’t this not be the case, why does The next version of Node includes a new section in the docs with recommended approaches for publishing what I call dual packages, packages meant to be used by both CommonJS and ES module consumers: https://github.com/nodejs/node/blob/master/doc/api/esm.md#dual-commonjses-module-packages |
Could you update this guide to tell how to create Dual Package without using |
You can’t have a dual package without two extensions, or, without using multiple package.jsons. (Also as soon as node is released with unflagged modules, there will be many examples to point to) |
I hope that unflagged modules will allow to publish single package.json and just tell that some subfolder (e.g. |
There's nothing wrong with the approach of putting a An important property of the system though is that it is possible to determine the module format of a file in this way without having to rely on assuming how the consumer got to the file. This is an important property for a module registry as the registry is a file-pathed namespace. |
So {
"main": "index.js",
"exports": {
"require": "index.js",
"default": "src/index.js"
}
} and {
"type": "module"
} ? |
Yes exactly. |
Could you just show how it should look like if both packages need to support subpath imports? i.e. what should be
|
For “first”, two of the many possibilities: you could have |
A good tutorial article online is https://2ality.com/2019/10/hybrid-npm-packages.html, and as @ljharb mentions there will surely be more to come in the near future. We don’t plan for the docs to show examples for every use case, as we’re trying to keep them as concise as possible. The section on dual packages is already quite long. I can try to answer your question here though. First, I wouldn’t recommend publishing a dual package at all until either Second, if your package already has a public API like
The root {
"name": "lodash",
"type": "commonjs", // Not necessary, but encouraged
"main": "./index.js",
"exports": {
".": {
"require": "./index.js",
"default": "./es/index.js"
},
"./first": {
"require": "./first.js",
"default": "./es/first.js"
},
// and so on for every public export And {
"type": "module"
}
Unfortunately it’s just too complicated to have one |
Would following be OK?
with following files:
|
In your version, ES module consumers would need to type |
Unless |
It would, but there is still considerable opposition to changing that default within this group. Many members of the group want to encourage public packages to have Also few packages have as many public exports as |
Then it means |
but you're also encouraging using package.json mappings, which browsers definitely don't support. I don't understand this dichotomy. |
Not necessarily, because that is in consumer code, outside of the package itself. Think of it this way: to use a package in a browser context, you need to pull in an entry point somehow, e.g.: import _ from 'https://somewhere.com/lodash/es/index.js';
// or
import first from 'https://somewhere.com/lodash/es/first.js'; The URL you use in a browser is separate from anything defined in But then within that export * as first from './first.js'; This will work in browsers, whereas There’s also something called import maps coming to browsers (in Chrome already) that provides a browser equivalent to |
Import maps are already shipping behind a flag in chrome and will be
unflagged in the not too distant future.
Anything defined in an export map will be able to be used to statically
generate an import map 🎉
…On Sat, Nov 16, 2019, 1:24 PM Geoffrey Booth ***@***.***> wrote:
Then it means import first from 'lodash/first.js' is better for these
group members?
Not necessarily, because that is in consumer code, outside of the package
itself. Think of it this way: to use a package in a browser context, you
need to pull in an entry point somehow, e.g.:
import _ from 'https://somewhere.com/lodash/es/index.js';
// or
import first from 'https://somewhere.com/lodash/es/first.js';
The URL you use in a browser is separate from anything defined in
package.json.
But then *within* that index.js or first.js, it’s more compatible to have
relative references that browsers can resolve. So for example within
index.js, there might be:
export * as first from './first.js';
This will work in browsers, whereas export * as first from './first' will
not (unless you have special mappings set up by your webserver).
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#432?email_source=notifications&email_token=AADZYV725AUDILG4XGILBADQUBQHPA5CNFSM4JOE2K62YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEEH24II#issuecomment-554675745>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AADZYV5PQLFB545ZZVRAPMTQUBQHPANCNFSM4JOE2K6Q>
.
|
@MylesBorins I'm just trying to figure out what our goals here are. Do we want modules to be usable unmodified in browsers or are we okay with tooling being needed? |
I fail to see where is issue making Using explicit extensions in consumer code seems like antipattern because such code won't survive or allow refactors in packages themselves (for example: changing .js extension to .mjs or vice versa, or moving utils.js to utils/index.js if it becomes complex enough). My experience tells me that using node modules in browser environment without tooling can never be performant and is usable only in development, I don't believe this can be solved with modules. |
Can we move this to another thread this is off topic.
But tldr there is a huge difference between tooling that can generate meta
data at install time vs transpilation
…On Sat, Nov 16, 2019, 2:09 PM Gus Caplan ***@***.***> wrote:
@MylesBorins <https://github.com/MylesBorins> I'm just trying to figure
out what our goals here are. Do we want modules to be usable unmodified in
browsers or are we okay with tooling being needed?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#432?email_source=notifications&email_token=AADZYV2DXE2YEQGOFHQUPLTQUBVSHA5CNFSM4JOE2K62YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEEH3VFA#issuecomment-554678932>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AADZYV6QEISHNHAYBG6LP2DQUBVSHANCNFSM4JOE2K6Q>
.
|
Import maps cannot generate file extensions they can allow you to convert a
specifier into a URL.
If you wanted to write code in a repo with 0 file extensions for any import
you would need an entry for every file, which is fairly verbose.
If we turned on the resolution algorithm by default we would lose the
static nature of the current implementation. We would have no way to
determine the full path without multiple pings to the filesystem.
This is actually a non trivial performance issue with nodes cjs algorithm
…On Sat, Nov 16, 2019, 2:11 PM Myles Borins ***@***.***> wrote:
Can we move this to another thread this is off topic.
But tldr there is a huge difference between tooling that can generate meta
data at install time vs transpilation
On Sat, Nov 16, 2019, 2:09 PM Gus Caplan ***@***.***> wrote:
> @MylesBorins <https://github.com/MylesBorins> I'm just trying to figure
> out what our goals here are. Do we want modules to be usable unmodified in
> browsers or are we okay with tooling being needed?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <#432?email_source=notifications&email_token=AADZYV2DXE2YEQGOFHQUPLTQUBVSHA5CNFSM4JOE2K62YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEEH3VFA#issuecomment-554678932>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AADZYV6QEISHNHAYBG6LP2DQUBVSHANCNFSM4JOE2K6Q>
> .
>
|
Isn't the root issue with Node's resolution algoritm? That -- what is cheap and easy ona local FS -- becomes a perf deal breaker in browsers? |
It isn't necessarily cheap on the file system either when you scale up to
millions of entry points
…On Sat, Nov 16, 2019, 3:39 PM Evan Plaice ***@***.***> wrote:
Isn't the root issue with Node's resolution algoritm? That -- what is
cheap and easy ona local FS -- becomes a perf deal breaker in browsers?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#432?email_source=notifications&email_token=AADZYV5XOEONMY3Y7ZLJ3FLQUCADZA5CNFSM4JOE2K62YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEEH5EHA#issuecomment-554684956>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AADZYV2XGP7VKEHUA7GLBITQUCADZANCNFSM4JOE2K6Q>
.
|
Import maps are a more-or-less direct equivalent to
This shouldn’t be the case for too much longer, if it isn’t already a thing of the past. Technologies like HTTP/2 are designed to make the need to bundle JavaScript no longer necessary from a network performance perspective; the idea behind encouraging public packages to be browser-compatible by default is to lessen (or eliminate) the need for bundling for compatibility reasons as well.
That is the benefit of filenames without extensions, yes. The question is whether that benefit is more important than pushing public package authors toward a future where universal JavaScript is the default, rather than something achieved only through tooling. That’s a subjective call that different people will feel differently about. And it’s not quite a zero-sum game. A tool like a Babel plugin could certainly rewrite specifiers such as This has been a good discussion and a good review of the implementation as it is currently designed, but aside from answering any other questions you may have, is there any specific feedback you’d like to see addressed? Obviously we can’t make everyone happy with every decision we’ve made, but I hope we’ve at least explained them such that you understand why things came out the way they did. I also hope that those design decisions can be ones that you at least can work with, even if they’re not quite what you might have chosen had it been entirely up to you. |
Performant web apps require much more than simple response multiplexing. They involve tree shaking, minifying, ordering resources in proper order, deduplicating imports, adding polyfills for legacy or mobile browsers, pre-rendering, small image inlining, among others. Processing and bundling on production needs to happen even if it happens on-the-fly.
If processing needs to happen anyway for production application for the reasons above, either statically or in cached-on-the-fly way, all import paths can be inlined, so even if in code there is
With this approach ultimately the only reason to build project for node would be to apply node resolution logic which is very weird. And yes, Babel can rewrite
I'd just like to express my concern that unflagging new modules without making
If performance is concern paths can be rewritten either when bundling (on-the-fly or not) for browser, installing packages with npm/yarn or publishing packages to registry. |
I can suggest one more alternative that seems good for both sides: So let's say there's following
This way there's both no node resolution algorithm and it's possible to not use |
It seems that most of this discussion is rendered irrelevant because |
@sheerun the modules implementation is experimental; there remains the possibility that things like the default specifier resolution could change. |
As asked by @GeoffreyBooth in #323 I'm opening new issue for this discussion
The
"type": "module"
solution for publishing MJS modules had an issue. Specifically was not possible to publish package that cold be simultaneously consumed in following ways (without publishing separate package likelodash-es
):const lodash = require('lodash')
in old node (for projects without upgraded node)const lodash = require('lodash')
in new node (for projects without upgraded code)import lodash from 'lodash'
in new node (for projects with upgraded node and code)According to interesting comment by @jkrems the current experimental solution for this is to run node with
--experimental-conditional-exports
and publishlodash
with following package.jsonNeedless to say this seems quite complicated package.json what should probably be the default for all migrated packages.
Additionally it requires lodash to rename all its current CJS-compatible files to
.cjs
extension, while the current standard practice is to put all modern code to one directory (e.g.src
), and all compiled code to other (e.g.lib
). Additionally"type": "module"
I'm opening this issue to suggest that this issue needs to be solved before these features go stable, discuss this issue, and suggest one solution myself. Ideal solution would:
The solution I suggest is to:
main
to mean CJS compatible entrypoint without ability to change its meaning with"type": "module"
. This ensures 1. and 2. can be always handled properly.import
instead ofrequire
, make node use already implemented "conditional-exports" logic and look atexports
field of package.json + ignoremain
fieldWithout
"type": "module"
there is no need for conditional conditional-exports because they are currently used to override back what it changes. Even worse, using conditional-expressions in any form for CJS modules makes these changes not backward compatible, because old nodes would not know how to interpret this.The final package.json would be as follows (thanks to already implemented exports sugar):
which looks like much better alternative. Additionally if
lodash
would like to support importing subpaths likeimport first from 'lodash/first'
, the package.json is equally simple:The text was updated successfully, but these errors were encountered: