-
Notifications
You must be signed in to change notification settings - Fork 17
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
Naming bikeshed - require(esm) interop flag #221
Comments
I would still prefix it with |
@mcollina thanks, I do tend to support similar, would you be able to share motivation further here for the prefixing? |
Let users seeing this it's something internal. |
In the case of libraries shipping ES modules that they want to define with
non-object values in CJS, the goal is for authors to add this themselves.
So perhaps that’s an argument against it then?
…On Sun, Aug 25, 2024 at 19:55 Matteo Collina ***@***.***> wrote:
Let users seeing this it's something internal.
—
Reply to this email directly, view it on GitHub
<#221 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAESFSRKXL3L5VACLLESNIDZTKKJJAVCNFSM6AAAAABMPELMI2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMBZGIYDCMZVGU>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
I am not sure how internal this actually is - it's meant for library authors who want to ship ESM to keep their exports look the same to CJS users. Say for this pretty common pattern on npm: // package foo is just this file
module.exports = function foo () { ... }; Now the author wants to ship it as ESM, then they change it to // package foo is just this file
export default function foo() { ... } Now that works for ESM users, but then for CJS users, it would require code changes for them to use the newer version of the library (shipping ESM-only should better be semver-major regardless, because it makes the exports un-mockable, since ESM exports are immutable - but at the end of the day this is up to the discretion of the library author). const foo = require('foo'); // This now doesn't work
const foo = require('foo').default; // It now needs to be like this So the new flag helps library authors to unwrap their default exports in export default function foo() { ... }
export const whateverThisFlagIsCalled = true; // This tells Node.js to return namespace.default from require() Now to their existing CJS users, |
Okay, agreed that as a user feature and since we have a very unique name, that removing the prefix can be considered. I'm updating the PR to that effect now. |
I dunno, it's still a magic string, and |
I agree it should start with |
__esModule is a convention used by tools and humans are not supposed to hand-write them. But this flag is meant to be hand-written by human beings. I don’t feel strong enough to insist on it, anyway. Just be aware this flag isn’t private - it will be documented, and is meant to be hand-written by human beings. |
If this was not on the module namespace object but on a random object, it would probably be a symbol. It's not to hide it, but just to clearly communicate that it's a special value and not just part of the arbitrary keys namespace that random objects can have. For example, we have We cannot have symbols on module namespaces, but we should still have some visual marker that says "this property is somewhat different from the other exports". |
I wonder if we can do this instead: function foo() {}
export { foo as "module.exports" }; // Or prefix it with `nodejs`, but "module.exports" feel way nicer
export default foo; |
@joyeecheung love it |
That's already a valid export name, though (as are all strings), so I don't think it avoids the need for a human-visible string indicator that it's internal to node, like |
These are all valid names, but |
When I said "it should be have a marker that shows that it's not a normal export" what I was only thinking about a prefix, like export default function foo() {}
let unwrapDefault = true;
export { unwrapDefault as "@@unwrapDefault" }; However, I quite like the
I would have a question though: if we are exporting the export const PI = 2.14;
export default function circ(radius) {
return PI * radius * 2;
}
// for CJS, we still want to have a "default" function export and a "named" variable
const circClone = circ.bind();
circClone.PI = PI;
export { circClone as "module.exports" } |
I think it's always better to give more options to the users, and let linters catch these mistakes instead of closing the doors with a runtime error. |
I like the sentiment of We've already incurred the cost of the conceptual simplicity of the Given we have the |
I propose that we do a poll either within TSC or among the bigger collaborator/user base. WDYT? |
This assumes that we actually want to mutate the CJS wrapper namespace, which I don't think we have consensus about anyway. If import { isCJSModule } from 'node:module'; // Or use process.getBuiltinModule();
import * as empty from './empty.cjs';
isCJSModule(empty); // true
// Instead of checking empty.whateverFlag that is added by Node.js to CJS wrappers I think that would be a less confusing and more generally useful API overall. So we get two more intuitive APIs, instead of introducing a cryptic API that tries to do both. |
Would there be a similar predicate for isESMModule? Either way wouldn’t those predicates cause changing format to be a breaking change? (which it is now, but hopefully require(esm) allows it to no longer be one) |
There are two confusions I was pointing out, and I'll repeat the arguments so these are clear:
Using the @joyeecheung this is a naming bikeshed discussion, not a debate over |
I find those questions are a bit far-fetched. How often do people import * from a package and decide to what export to use based on personal guessing about the name, without consulting the documentation? I feel that in most cases, you just follow (copy-paste) what the documentation tells you to use. If there are any undocumented exports, a regular user is not even likely to notice them, or would not think about using them unless they know what they are doing. |
This is unfortunately not true, as editor autocompletion shows all the available exports. |
Using the import syntax, how do you actually get the string name autocompleted though? At least when I try with VSCode, it doesn't work with string names. It's also another story about how the import ... from ... syntax itself doesn't work with autocomplete that well in the first place - personally I have rarely used it with import syntax because of this, and when I do I already know what export I am looking for (otherwise I don't know what to type after import). When I am using a library I don't know well, I just copy paste the import statement from the documentation. We could also just suggest editors not to autocomplete that specific name (if they do support it), or tools to not emit definitions for this (I guess the same needs to be done for __cjsUnwrapDefault anyway). At the end of the day, common sense applies - it's an undocumented export if the package author doesn't document it, and people typically just ignore undocumented stuff. Is this concern really worth asking the package authors to hand-write a more cryptic export? |
You need to use TypeScript 5.6 beta, which is the only TS version that supports let a;
export { a };
export { a as "x" };
export { a as "module.exports" }; It is however possible to hide it, even if it's opt-in: let a;
export { a };
export { a as "x" };
export { /** @deprecated */ a as "module.exports" }; |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
My not so informed preference would be:
|
This would make adding named exports to the ESM semver-major, though. Also previously a CommonJS package can do something like this to provide both default and named exports to their CJS + ESM users: class Klass { static foo() {} };
module.exports = Klass;
module.exports.foo = Klass.foo; // make the lexer detect this If only the single default exports can be unwrapped for CJS users, they need to choose between either breaking CJS users for keeping named exports, or breaking ESM users for losing the named exports. That would demotivate migration to ESM in the first place or keep more people clinging on to faux/dual packages. But if we want to add a marker to help with the case where both default and named exports, then it would be less of a footgun to require it always when there is a default export/it used to reassign |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This is not the thread to discuss the which module systems to keep or drop from Node.js. |
Marked the off-topic comments as off-topic. @ert78gb if you want to propose breaking CommonJS, please open a separate issue. |
This is a follow-up to the naming bikeshed in nodejs/loaders#221 which is for the implementation in nodejs/node#54563 A summary can be found in nodejs/node#54563 (review)
* Start a vote on require(esm) markers This is a follow-up to the naming bikeshed in nodejs/loaders#221 which is for the implementation in nodejs/node#54563 A summary can be found in nodejs/node#54563 (review)
The vote concluded today in favour of |
As discussed in today's meeting, we need to come up with a name for the flag that will allow require(esm) to support a custom value for a CJS module.
The pattern is that users can write:
where
module.exports
can be any type of object, which will be returned byrequire(esm)
when it sees this__cjsModule
flag, instead of returning the full namespace.@joyeecheung suggested a good name might be one that describes what it does, eg
cjsUnwrapDefault
.It should be unique enough that it will not collide with an existing export name for any ES module in use today.
Opening this thread for further discussion and suggestions, although I'm happy to go with something like @joyeecheung's option here above as well.
The text was updated successfully, but these errors were encountered: