-
Notifications
You must be signed in to change notification settings - Fork 43
Proposals for configuring parse goals of files in --experimental-modules #160
Conversation
:) Since
|
the thing here seems to be people can't agree on how node will key module types. i would argue that we shouldn't be focusing on where the map of types goes (a resolve hook could look in a package.json, we don't not need to implement that behaviour) but rather what system we want to use to key the types. personally i am in favour of mimes. if node itself is as verbose as possible, userland hooks can narrow down, but you can't go the other direction. |
education and consistency. these have been discussed in many issues and I'm not really wanting to review every API design you post in these threads but can if given enough time.
We should not use the configuration encoding you have here. In particular it needs to be 1 file extension to 1 format. The usage of arrays in your JSON simply leads to conflicts. Just an API design problem where you should have used a 1-1 dictionary. Since multiple file extensions can map to the same MIME, it needs to be keyed off the file extension.
I'm not sure what this means.
I presume it would fail once if it gets routed to format disambiguation in the default loader. Note that userland loaders can supercede their parents, and may still allow those files to work either by not delegating to the default loader or by ignoring any errors.
The default loader would return For any given format mechanism I would expect similar cases for unparsable values and values that are not known how to turn into Module Records.
Loaders have a well defined order in which they can call each other in various designs. Consider a more concrete case where we have 3 userland loaders: node --loader jsx --loader apm --loader code-coverage test.js
By tightly coupling formats to resolution semantics we get into an odd situation: which of these loaders formats is the proper one for node --loader apm --loader jsx --loader code-coverage test.jsx How does This is increasingly complicated if people mix usage of Essentially, I'm saying we shouldn't try to tie these configuration mechanisms to the same composition as multiple loaders.
A runtime loader doesn't need to work without a JS VM, but other preprocessing tools are in our ecosystem as well. It would be ideal if we could support them as long as we don't burden our own implementations. I don't think Node should become a transpiler or anything, but we shouldn't make things more difficult for them if avoidable. Imagine a costly transform like
Consistency matters for both education and allowing systems to avoid creating conflict with others. If we have multiple ways of defining the format of a given resource, that means that we need to keep those multiple format disambiguation methods in sync and keep parity in features between them. There is no clear reason to encourage having multiple different ways to describe the format of a resource, but there are reasons to encourage having a single way to describe the format. |
Some kind of education for the education sake. I'm arguing that the what to educate is fairly important here and simply disagree with the currently proposed contents of this proposal, since there are imho 'better' (simpler, more explicit) ways available Consistency (see below *)
*Confusing as it is theoretically possible to override any <!-- main.js { 'Content-type': 'text/javascript' } -->
<script src="/main.js"></script>
<!--
module.js { 'Content-type': 'text/javascript' }
+ the module attribute is mandatory to determine the parse goal,
MIME isn't envolved in determining the parse goal here
-->
<script src="/module.js" type="module"></script> {
name: 'pkg'
main: 'src/',
mime: {
// As I currently understand it, this assumes the MIME Type 'text/javascript' === isModule,
// which is not defined in the MIME Standard and therefore incorrect and
// would be a node specific interpretation for 'text/javascript'
// being inconsistent with other host like e.g the browser
'.mjs': 'text/javascript',
// This would be correct, but doesn't exist
'.mjs': 'text/javascript;goal=module'
'.js': 'application/node'
}
}
{
name: 'pkg',
main: 'dist/', // dist/index.ext => { type: {MIME}, module: false }
module: 'src/', // src/index.ext => { type: {MIME}, module: true }
} Plus
The ordering problem is a common problem for any plugin/loader system. At some point one simply has to know when a particular plugin/loader needs to be executed [in relation to other plugins/loaders]. This can only be mitigated and due to that fact it is always the fundamental question if introducing such a system solves more issues then the intrinsic complexity adds // Nesting within @import already doesn't work
// and the complexity of transforming CSS in a certain way
// is magnitudes lower compared to what 'certain' could mean for JS
postcss([ nested, imports ])
In the context of
Yes, but in the context of that example, there would nevertheless be no need to educate anyone about |
You need to know the parse goal for wasm too, and for any future module type. It’s not just JS. |
hmmm, but for WASM it would definitely not work then, since there is only |
Or we could rely directly on extension, since .wasm is pretty clear-cut. |
I don't see them being explained? Which 'better' ways are you talking about, and how do they fix the problems I've listed in the thread above. In particular, how are they going to keep parity when all other designs are being done using MIMEs and APIs are even using MIMEs (e.g.
I think you are getting a bit confused, the Script goal of JS is never checked for. You can serve
This is a limitation that seems fine to me as most people seem to be able to work only in one format or are able to use multiple file extensions? Use a per package
I don't understand if this is for or against keeping the configuration points decoupled. It seems like it is neither for or against keeping things coupled so I'm just going to state that it seems simple to keep them decoupled and means you don't have to execute JS to determine such things.
Yes, but this is not necessarily being done when the application runs. Loaders can be run ahead of time such as if someone wants to do things like a transpiler loader or resolve all URLs to absolute URLs ahead of running an application. The claim here is that there isn't really any advantage to
WASM modules are first class already in terms of language design. There doesn't need to be any special MIME for them so I'm a bit confused at this comment. All that is being discussed is how to properly make facades for them to integrate with ESM since the current Abstract Module Record and Source Text Module Record constraints do not satisfy the needs of WASM integration. |
Just to clarify to everyone. It was always my intention in my gist mentioned for mappings that such databases not only be built into Node but also to be configurable by users and tools. I'd be fine if there was both the If we could amend the proposal to perhaps have both forms of this as I don't think they conflict with each other? We should probably document removing existing mappings from being delegated to like |
I’m trying to clarify @bmeck’s proposal and how it allows user-specified mappings. How about something like this: Assume a new "mimes": {
"js": "text/javascript",
"cjs": "application/node"
} That "mimes": [
"./jsx.json",
"./typescript.json"
] Each JSON file would be an object with “extension: mime” mappings like the first example. They would be combined using const packageJsonMimes = JSON.parse(fs.readFileSync('./package.json')).mimes;
let mimes = {};
if (Array.isArray(packageJsonMimes) {
packageJsonMimes.forEach((filePath) => {
const mimeMappings = JSON.parse(fs.readFileSync(filePath));
Object.assign(mimes, mimeMappings);
}
} else {
mimes = packageJsonMimes;
}
return mimes; And that’s it. The idea is that not just Node but also build tools need to be able to parse this @bmeck is this a fair characterization of what your proposal would look like in practice? Everyone else, is the utility of defining these mappings in external files worth the additional complexity? |
@GeoffreyBooth it wouldn't be challenging at all; every build tool already uses something like https://npmjs.com/resolve to do just that. More to the point, if it allowed any file path whatsoever, it'd have to be a require path imo, otherwise it would make no sense. |
It also included the idea of removing mappings and a null database to ensure no default mappings are applied, so some things would change slightly, to be more composed like: const packageJsonMimes = JSON.parse(fs.readFileSync('./package.json')).mimes;
let mimes = Object.create(null);
if (Array.isArray(packageJsonMimes) {
for (const filePath of packageJsonMimes) {
if (filePath === null) break;
const mimeMappings = JSON.parse(fs.readFileSync(filePath));
Object.assign(mimes, mimeMappings);
}
} else {
mimes = packageJsonMimes;
}
return mimes;
I'm not sure how valuable delegation is if it cannot be in a shared package. A lot of places already have the node resolution algorithm in place, Java had it for Closure Compiler/Ocaml for Flow/JS via the lib @ljharb mentioned/etc. have libraries and if package name maps were introduced those tools would need to implement that resolution algorithm as well. If it only is for relative paths I don't think it is valuable enough to warrant being shipped since it would lead to people using those fragile paths instead of encouraging tools to use libraries that implement the node resolution algorithm that already exist for various languages. |
This presents a problem though. If |
Globally installed packages are not part of resolution. |
Then what do you mean by “ |
@GeoffreyBooth something like a node_modules like this:
where both foo and baz do |
Okay, if you’re telling me that there won’t be resolution issues, like for something like the NPM website trying to read the package to list stats about it on its webpage, then sure. @bmeck what’s the use case for |
@GeoffreyBooth main use case is disabling formats that node would have by default. JSON, C++, and CJS are not importable on the web natively, so setting them to be errors/not found would ensure they are not used in a package. |
|
||
### [nodejs/node/pull/18392](https://github.com/nodejs/node/pull/18392) | ||
|
||
This pull request adds support for a `"mode": "esm"` flag to be added to `package.json`, that tells Node to treat all `.js` files within the package boundary of that `package.json` file as ESM. The package boundary of a `package.json` file is deemed to be all modules in that folder and subfolders until the next nested `package.json` file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this imply that every import './x/y/z.js'
would necessarily trigger a search for package.json
files along the path hierarchy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd expect so, that's how the current require implementation, and the ecosystem, works.
Is this something we want to continue to try and land or should we close it? |
1 similar comment
Is this something we want to continue to try and land or should we close it? |
I think let’s please keep it open for now? We’re referring to the |
Just drifting through to applaud those who worked on this solution. This is easily the most well-rounded and elegant solution I've read to date. Wouldn't have thought to define MIME types in Just one question: will it be possible to declare an optional character encoding in the same field? E.g.: "mimes": {
"js": "application/node;charset= Shift_JIS"
} If not, then perhaps it'd be more "correct" to use Otherwise, great work! |
@Alhadis MIME includes parameters since its first RFC. However, note that compliant usage of MIME requires that unrecognized parameter names be ignored. For |
That's all well and good for ESM, but when authors start plugging their own loaders and formats into the module system, it's harder to guarantee authors will always be using UTF-8... 😞 |
Most of the JS ecosystem assumes UTF-8. in node you should be able to use buffers and text decoders to handle the difference. |
Ah that's different. Sorry, I thought the MIME mapping was attempting to address custom loaders as well. My bad. |
@Alhadis custom loaders should be able to query these, but for a variety of scenarios we cannot tie them together. E.g. if a package supports
We have nested compilation in that scenario and it shows why you need loaders to generate the appropriate formats that the runtime supports by default. If you hand |
Wow, an Iced CoffeeScript reference. @bmeck you warm my heart. @Alhadis This proposal is based on the currently-shipping |
Ah, crap, I'd thought it'd installed because I've been monitoring the ESM API docs for changes, and never assumed there was progress because, well, the original EP link got archived but it was always there, so... 😅 I'm working on a library that's probably following this wayyyy closer than it should be, so... 😀 Consider that new repo |
My 2c: |
One other thought: we could even have |
soooo an idea. What if "loaders" and "mode" were the same thing.... we have a number of built in loaders that are document e.g loaders: {
".js": "nodejs:cjs",
".esm": "esm",
".mjs": "nodejs:esm",
".wasm": "@loader-central/wasm",
".ts": "./loaders/typescript.js"
} As you can see here we have a combination of built-in loaders Thoughts? edit: adding example from bradmeck for using an array to compose multiple loaders into a pipeline loaders: {
".jsx": ["@babel/...", "nodejs:cjs"]
} |
I like this direction, especially being able to specify loaders. |
Except for the builtin module part being a url instead of a scope (but assuming that we’d stick with whatever node core decided), that looks great to me. (I’d expect all builtin loaders to be under some kind of |
I like the idea of the progressive enhancement, but would want to keep the string value as the Phase 2 approach, where basically the string value effectively determines exactly the full map without having to carefully specify each extension, which most users shouldn't need to do I don't think. Also, are we sure we want to make it easy to publish uncompiled source formats to npm? The module translator buffet still feels like a slippery slope to me, that we might want to leave to loaders in general. |
@guybedford you don't need to specify every combo, just the ones you want to override. additionally |
@guybedford nothing is preventing people from requiring a global loader to compile their code properly to run. I've stated in the past being able to statically know your loaders is preferable as well which global loaders do not have since they are just coming from the CLI/ENV. I like that this approach does let people see what loaders are expected and allows tooling to do things with that knowledge statically. I do have a list of features I would expect regarding multiple points of composition like the array example above.
{
"loaders": {".node": null}
} Disabling default handling of ".node", return "" (or some well known signifier) as the expected format.
{
"loaders": [
{".ts": "always-typescript", ".json": "always-json"},
"always-cjs"
]
}
{
// what goes here so I don't have to spin up JS to just say it has a mime of `application/vnd.coffeescript`
"loaders": {".coffee": ...}
} |
@GeoffreyBooth btw there is more down here based on some additional constraints which isn't reflected in the "consensus" section at the top. |
@devsnek Sure, I think at this point we would need to start a new thread or a new repo like one of the recent proposal repos. The consensus here was based on the currently-shipped But still, there was a lot of thought put into the discussion here, so this should be at least a starting point I think for whatever we continue working on. |
Closing as this was based on the old |
Following up from #150, this thread is for determining the user experience of an addition to
package.json
that configures Node to treat.js
files as ESM within a package boundary (i.e. with the “module” parse goal). This assumes the--experimental-modules
implementation as a baseline; other implementations might not have this issue, for example if they don’t allow importing ESM viaimport
statements. For suggestions of other implementations, can I invite people to open their own threads for alternate proposals? And let’s keep this one focused on what ESM in.js
in--experimental-modules
should look like.Readable view: https://github.com/GeoffreyBooth/modules/blob/esm-in-js-ux/proposals/esm-in-js-ux.md
To discuss particular proposals, please add comments as PR notes after those proposals. Add general comments to suggest new proposals or broader changes.
Update: we have reached consensus! Of the people who have commented on this thread, at least. Here’s the consensus proposal:
MIME types/webserver as metaphor, defined in
package.json
and/or in linked JSON filesIn browsers, users must configure their webservers to serve
.js
or.mjs
files with a JavaScript MIME type liketext/javascript
in order for the browser to recognize the file as ESM. The browsers pay no attention to file extensions, but most webservers use file extensions to determine what MIME types to use to serve files. This is similar toAddType video/webm .webm
that you may have seen in Apache webserver configuration files.The idea here is to mimic this webserver configuration in a new
package.json
section calledmimes
:In this example,
.js
files would be treated as CommonJS, which has the MIME typeapplication/node
..mjs
files would be treated as ESM (text/javascript
, orapplication/javascript
) and.json
files would be treated as JSON. The configuration in this example would be the default, what Node uses if amimes
field was missing, as historically.js
files were always treated as CommonJS.Here’s another example:
This tells Node to treat
.js
files within this package boundary as ESM, and a new.cjs
file extension as CommonJS. The.cjs
extension could be anything—just as in Apache a user could add the configurationAddType video/webm .foo
, thismimes
block provides the flexibility for new file extensions to be defined and mapped to MIME types that Node understands. Since.mjs
isn’t listed here, Node falls back to the default mapping for it (text/javascript
), and likewise for any other file extensions that Node recognizes by default (.json
, etc.).That
mimes
section could alternatively take an array of strings, which would be relative or resolved paths to JSON files:Each JSON file would be an object with “extension: mime” mappings like the first example. They would be combined using
Object.assign
. If the first element in the array isnull
, Node’s default MIME mappings are discarded first before the array elements are merged, e.g. something like:Through this
mimes
section that can take either an object or an array of strings that reference JSON files (ofmimes
mapping objects), users can configure Node’s handling of file extensions.