-
Notifications
You must be signed in to change notification settings - Fork 28.5k
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
esm: introduce node:interop
to expose the path of the current module
#49070
Conversation
Review requested:
|
I know there's some concerns about adding stuff to Node can do what it wants, of course, but personally #48740 makes much more sense and fits better with the language, in my opinion. (Also, as a user: the whole point of #48740 is convenience, and this PR is strictly less convenient than the original.) |
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.
👍 on the implementation—very clever 🙌
I second this. I was doubtful that this would be possible. What if the same module is resolved multiple times, from different places? Like from a sibling file, but then from a file one level down via something like Also I feel like |
I understand that that’s what TC39 intended, but we (and Deno, and WHATWG) are very concerned about code portability and interoperability. |
Sure, but this approach is strictly worse for code portability and interoperability, isn't it? So this approach seems worse from an interop point of view, in addition to a consistency-of-the-language point of view. (And also from a developer experience point of view.) |
No, because it’s obvious. If you’re importing from Ideally we can reach a standard with WinterCG and we can ship that. I don’t think anyone would prefer this over wintercg/proposal-common-minimum-api#50, so I doubt this would land while the process is still in progress for trying to reach a standard. However it’s probably a good idea to explore how we would ship module-specific APIs in the future that weren’t (or couldn’t be) standards, because sooner or later there will be such an API that Node wants to add that we can’t get spec support for. |
I guess I have the opposite opinion: I would regard a design with which it is at least possible to write portable code to be more interoperable than a design which makes that impossible, even though if you write non-portable code it will be more obvious with the latter design. It's good to provide hints to users, but forcing otherwise-portable correct code to be non-portable for the sake of louder errors in the case of incorrect code - that really seems like it has the priorities backwards. |
const path = fileURLToPath(parsedParentURL); | ||
const dir = dirname(path); | ||
return { | ||
url: `data:text/javascript,export const __filename=${JSONStringify(path)};export const __dirname=${JSONStringify(dir)}`, |
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.
can this be implemented as a SyntheticModule?
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.
What would be the benefit of doing so? (Not opposed, just curious.)
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.
Cheaper on VM side / doesn't need to tie it to a poison-able cache key.
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 assume we could also implement these as functions, like:
import { dirname } from 'node:interop'
const __dirname = dirname()
And then maybe we wouldn’t need to create them ahead of time. We could generate the code to be evaluated, and then evaluate it, only when the function is called.
Yes it’s slightly more annoying to type dirname()
instead of __dirname
or dirname
, but if it improves performance then it’s probably worth it.
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 think a cached getter would be a "6 of one", but with better DX :) i think no additional implementation effort (just a choice of A or B).
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.
It isn't just CPU/Mem when having unused values here, data:
will cache the source text in the V8 Context's Module cache and engage a full parser vs just piping values where SyntheticModule ~= a Map and avoiding cache growing.
There was desire for |
I'm with @bakkot that this seems a bit counter to interoperability. By putting stuff on |
I second everything @bakkot says here; import.meta is the only place this sort of thing is intended for, and it violates the spec imo to have a magic module that imports a different value based on who imports it. |
Having something imported that varies based on the module path of the thing importing feels like an inappropriate intimacy code smell to me. |
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'm fairly certain this does not violate the spec, the same specifier can resolve to different URLs based on who is importing it (e.g. The other critics are valid, but subjective (which is fine, but I have nothing to counter them except a different opinion). |
@aduh95 that's fair; it would certainly be incredibly surprising to import something that's not a relative path/URL and have it depend on where you import it from, at least. |
Technically that's the case of all bare imports in the ecosystem, which resolve to different versions and thus content depending on which package imports them. That said here it definitely feels crossing a line. Enabling introspection this way is an interesting thought experiment, but I think I'd personally prefer a boring solution to the boring problem of "where is stored the current script". |
That is valid, but how caches for things like import attributes are designed spec wise, they expect a flat cache in the end. There are plenty of ways to do this but each imported module is expected to have a way to uniquely identify it. So when solving a poisoned cache/side channel issue of using |
I agree with others' sentiment - this is very clever, but I'd prefer the "boring" import.meta.filename approach. It lets us use So I am also -1 on this unless we can't land |
Co-authored-by: Moshe Atlow <moshe@atlow.co.il>
@aduh95 @arcanis Yes, that's why I said:
Ie, if a file at Builtins always resolve to themselves, so if Module-specific stuff belongs on We already have |
#48740 has landed |
This is an alternative to #48740. This exposes a new "magic" module
node:interop
that exports the path of the importing module. This aims to simplify uses of paths from within ESM modules, obviously it only works for modules loaded from thefile:
protocol (i.e. the modules corresponding to files on the FS). I think this would be a better approach than #48740 because it doesn't polluteimport.meta
, and it provides an early error when it is used from an unsupported protocol, and doesn't go in the way of folks who do not want any path within their ESM code.Before it can land, this would need:
node:interop
)__filename
and__dirname
)file:
protocol (currentlyERR_INVALID_URL_SCHEME
)