-
Notifications
You must be signed in to change notification settings - Fork 43
Proposal: extension map in package.json #283
Comments
Thank you for proposing this. It looks like you proposed this in #160 (comment) and there were objections to using file extensions on both sides of the mapping, because it’s confusing (what does I am also opposed to requiring users to use any secondary extension, whether |
It wasn't that "the group favored", it was that some members of the group favored. What do you mean "secondary extension"? Either way, you can continue that practice with this proposal by adding a package.json inside src, or dist, or both, that alters the extensions mapping to whatever you like. |
@MylesBorins @bmeck there was another thread where myles posted a suggested interface which brad then built upon. I've not been able to find it but if either of you remember what it was i think it would be a good thing to track here as it was similar. |
if I recall my proposal was for loaders. the idea would be that we could give canonical names to our built in loaders An MVP could just be exposing the translators we already have e.g. which is what the object in https://github.com/nodejs/ecmascript-modules/blob/da0667d4b0c4fcd26b595a5af3fafd6d743cd2d1/lib/internal/modules/esm/default_resolve.js#L18-L23 gets mapped to so if we were to expose the built in loaders in a --mode=modules "loaders" {
"js": "@nodejs/loaders/esm",
"mjs": "@nodejs/loaders/esm",
"cjs": "@nodejs/loaders/cjs"
} --mode=cjs or perhaps --mode=legacy 😇 "loaders" {
"js": "@nodejs/loaders/cjs",
"mjs": "@nodejs/loaders/esm",
"cjs": "@nodejs/loaders/cjs",
"json": "@nodejs/loaders/cjs",
"node": "@nodejs/loaders/cjs"
} but you could also extend this in a bunch of different ways "loaders" {
"js": "@standard-things/esm/ng",
"mjs": "./my-custom-loader.mjs",
"ts": "typescript-loader-from-npm",
} We also talked about making it composabe. I just added * as an example below as perhaps a pre processing step for all loader? --mode=modules "loaders" {
"*": ["@nodejs/loaders/legacy-extension-resolution", "@nodejs/loaders/reload"],
"jsx": ["./jsx-2-esm", "@nodejs/loaders/esm"]
} The above api is all somewhat dependent on brads custom loader implementation, which I have not looked at recently. We would also likely have to refactor our internal loaders quite a bit to allow composability, but we could likely get an MVP together that only support a single loader per extension. |
Just to take a step back here - it would really help to understand what the exact use cases / problems / user stories are that this feature solves. Is this purely to support TypeScript in Node.js like Deno? |
@guybedford no, it’s to enable anyone to choose any extensions they want for anything - without that full ability, i don’t think there’s any benefit (in fact i think it’d be harmful) to allow anyone to use anything but the default extensions. Put another way, this mechanism is the only way (modulo sugar/shortcuts) i think you should be able to have ESM in a .js file. |
Why is this a goal? Who is "anyone" here? I would really appreciate it being phrased in terms of a genuine use case. |
"Consistency" is a genuine use case - making "ESM in .js" a special, magical, first-class citizen is confusing, and incredibly short-sighted. People have come up with all kinds of use cases for Basically, with CJS, users have a default extension for each parse goal, but there's a mechanism to override or augment it - if we're taking that ability away from them for ESM, we're saying "we know better than you do how extensions should work" - and in that case, why not force If, instead, we want to say "you, the user, have the ability to use extensions as you see fit, but we're providing defaults", then every extension and parse goal should be equally customizable. |
We're not taking this ability away. The resolver can be intercepted with custom behaviours just like
This is exactly what we're doing. Users can override the resolver if they want to and inject this customization. I guess my primary concern is that when something is in the package.json that means it is now being published to npm with those customizations, and I can install an npm package that I don't necessarily know is making these customizations. Now say I want my http server to serve to the browser, but it won't know what MIME type to use for the module that is now stored at Similarly, as mentioned many times before, I'm very concerned about users shipping custom languages to npm uncompiled. This creates a performance and tooling problem that is "invisible" if everything just worked after I guess I just want to understand the user use cases that drive this need, so we can do this in a way that doesn't become a viral customization that will start to impinge on the health of the ecosystem. |
you need to use a bundler for this case because of cjs and node-specific apis and etc. the idea of node_modules somehow being magically browser safe is absolutely untenable.
maybe we can use gzemnid to find packages that modify another part is that the current |
@guybedford that same thing applies to a "type" or "mode" or "exports" field in package.json - you always have to know the possibilities, and build your tooling to accomodate them - otherwise, we can't allow any deviation from the defaults (which includes, writing ESM in anything that doesn't end in |
Certainly, but "type": "module" in package.json does not create a pattern for publishing packages on npm that will lead to me unwittingly installing code that compiles at runtime. |
I'm not sure what you mean by "compiles at runtime"; there's nothing about this proposal that forces that, and as for "generating the import map", that's also something npm could optimize by doing at publish time. |
Ahh, sorry I was referring to @MylesBorins's proposal here. Just to clarify, your proposal is the following: package.json {
"extensionMap": {
".asdf": "esm"
}
} where the possible keys are any extension and the possible values are the "translators" in core, which is currently just "esm" and "commonjs". My concern is specifically when we allow packages to define their own "translators" easily. |
@guybedford yes, exactly. Per-package loaders/translators is not what i am proposing and in no way do i think that's necessary for my use cases. |
Even with no first class support, you could write an esm package that compiles some code at startup and then imports it using dynamic import. Not really sure how that's any better or worse.
I would love if the builtin mechanisms for accomplishing default behaviors were exposed like this (assuming those paths actually resolve in normal code to a real thing-that's-used-as-part-of-resolution). That makes it much easier to 1. track upstream changes, and 2. compose them. Months ago, that's what I was requesting we do for the named-exports-for-builtin-libraries-loader; if it's so useful it's used within node, I see no reason why people using node shouldn't be able to choose to use it, should their constraints align similarly. |
There are issues with this for dynamic import due to the fact that transitive rewriting into data / blobs gets into complications with circular references. This requires deep technical knowledge to pull off properly, while a package.json hook can be used by anyone. Which is a huge difference in adoption.
As mentioned in #283 (comment), it appears this is not what @ljharb is proposing or asking for in this issue here. We are just considering the |
ok, so, to recap:
What objections have i missed? Are there any others? |
I would like to note that Node.js does not have a great support for source maps. Some diagnostics tools do not even make sense in a source-mapped world (flamegraph for example). The moment this gets widespread we are losing some of the diagnostics capabilities of Node.js: "hidden transpilation" is not something I would recommend to run in any production server. I know other disagree, and that's ok. The toolchain should include a mode to "eject" and have it all working without "hidden transpilation". |
@mcollina on that note I've always wondered why Node.js doesn't support source maps natively? http://npmjs.org/package/source-map-support is incredibly widely used and seems a straightforward approach to adopt natively. Would be interested to hear the arguments. Hidden transpilation is a huge worry though, yes, for many reasons. |
I think that's a different conversation. Probably better to open it in nodejs/node. It's also something that we could change. |
@mcollina hidden transpilation is not relevant to this issue whatsoever; I’m not proposing it, and it’s already possible today and people do it anyways. |
I know people are doing it. However, most module author ships transpiled version when publishing to npm. This is proposing hidden transpilation on a per module-basis, as a core-recognized way of doing things. This is the principle I’m in disagreement. I think this would be great to have if some sort of ejection mechanisms is taken into consideration when designing things. |
@mcollina it is most certainly not proposing that - that would be loaders. All I’m proposing is static config in package.json to remap already supported extensions and parse goals. |
Oh, I misread some of the thread. I'm definitely in favor of your proposal. |
Here’s a concrete user use case that this proposed generic solution would address: nodejs/help#1850 |
@ljharb do you want to put this on the agenda for this week? Elaborating on the use case can only help. |
Myles and I haven’t had a chance to discuss it yet; I’m not sure it’s worth agenda time prior to that. |
bump in light of this seeming to be relevant to nodejs/node#30514 |
Internally in the fork, we seem to have an
extensionFormatMap
, which issomethingexactly like:My suggestion is a field in package.json, "extensionsMap". The implementation would roughly be like this:
change https://github.com/nodejs/ecmascript-modules/blob/da0667d4b0c4fcd26b595a5af3fafd6d743cd2d1/lib/internal/modules/esm/default_resolve.js#L75 to:
Now, you've got a mechanism to add new extensions, override existing ones, and even set them to
null
, perhaps, to block them from being loaded - all per package boundary. If this were to land, I'd have no objection to a "type" or "mode" field, as either mutually exclusive sugar for this field, or, as sugar for also merging in{ '.js': 'esm' }
, or whichever.Thoughts?
The text was updated successfully, but these errors were encountered: