Closed lgarron closed 7 months ago
On the server, module resolution has to be async. We don't know where a module might point to until we've visited other files (like package.json). We may even need to download package.json to find out. That operation should not block the server by default.
Browsers don't have this problem because browser module resolution is very simple.
Bun does implement import.meta.resolve
, however it follows what Node.js does which is not the same as what browsers do.
import.meta
is one of the few places where runtimes are allowed to have different APIs on the same property.
Per the ECMAScript spec, import.meta
is host-defined:
13.3.12.1.1 HostGetImportMetaProperties ( moduleRecord ) The host-defined abstract operation HostGetImportMetaProperties takes argument moduleRecord (a Module Record) and returns a List of Records with fields [[Key]] (a property key) and [[Value]] (an ECMAScript language value). It allows hosts to provide property keys and values for the object returned from import.meta.
That being said, on the bundler side of things Bun can implement the new URL
pattern as well as inline the result of import.meta.resolve
when statically known (but it does not yet)
On the server, module resolution has to be async.
Hmm, deno
implements it by limiting the scope of resolution (in their case, relative paths as well as bare names using import maps, the former of which at least should be unambiguous in any environment) and node
is working on a special sync implementation. Would either of those approaches work?
That being said, on the bundler side of things Bun can implement the
new URL
pattern as well as inline the result ofimport.meta.resolve
when statically known (but it does not yet)
new URL(…, import.meta.url)
(or new URL(…, import.meta.url).href
) is certainly very useful, and I'd be glad to see support!
On the server, module resolution has to be async.
Just wanted to make a note that node
has landed synchronous import.meta.resolve(…)
for node v20.0.0
: https://github.com/nodejs/node/pull/44710#event-8993642914
If/when this goes to stable next week, this means that all the following will support the same synchronous import.meta.resolve(…)
functionality:
node
(when using --experimental-import-meta-resolve
)deno
I'd like to be able to support bun
in my libraries and don't see an issue with await
ing import.meta.resolve(…)
in my own code. This will mostly be needed in places where asynchronous code will already be used, so it shouldn't require a lot of extra work.
However, it seems likely to me that developers and apps in the wider ecosystem will start assuming a synchronous result based on all the other environments. I can also imagine bundlers/minifiers removing the await
based on an expected synchronous result, changing bun
-compatible code into incompatible code. So this could be subtle compability risk for bun
in the future.
Just wanted to make a note that
node
has landed synchronousimport.meta.resolve(…)
for nodev20.0.0
: nodejs/node#44710 (comment)If/when this goes to stable next week, this means that all the following will support the same synchronous
import.meta.resolve(…)
functionality:
FWIW, node
did ship with this in v20.0.0
and has released v20.1.0
since, so it looks like it's going to stick. So bun
is now unfortunately the lone one out in supporting async import.meta.resolve(…)
but not sync. 😭
(I'm still fine with using await
for my own code, but as mentioned above I still think this is going to trip up developers whose code works in all other environments the same way.)
If I may, I'd like to just comment on the usage of new URL(...)
and import.meta.resolve(...)
.
We at PeacockTV and SkyShowtime need to support all kinds of TVs, set-top boxes and console, some of which were launched more than 10 years ago (PlayStation 4 is one such example). Some of these are based on older versions of Chromium, and lack URL
support. Likewise, even if import.meta.resolve(…)
is already supported in all major browsers, it most surely is not supported on these devices that I mentioned.
I know we could add polyfills but in order to keep our bundle size small we usually try to rewrite our code before adding one.
What @Jarred-Sumner said:
inline the result of
import.meta.resolve
when statically known
Is super important and would allow us to deliver smaller builds to this other class of devices.
Hello 👋 I’m part of the team responsible for landing sync import.meta.resolve
in Node 20. I found this issue because we’re trying to start a process with WinterCG to standardize import.meta
across server-side runtimes such as Node, Deno and Bun. I invite you to join us! See https://github.com/wintercg/proposal-common-minimum-api/pull/49 and https://github.com/wintercg/proposal-common-minimum-api/issues/50.
@guilhermesimoes how do you plan to get import.meta.resolve
inlined? Would you be careful to always pass it static strings like import.meta.resolve('foo')
and never variables (import.meta.resolve(someStr)
)? Also hello from Disney, I work on Disney+ and ESPN and related systems and I know your pain 😄
We already have import.meta.resolveSync
, so I think that function can replace import.meta.resolve
, though I'm not sure if the sync version handles all async resolution cases. it also doesnt return a url string yet.
We already have
import.meta.resolveSync
, so I think that function can replaceimport.meta.resolve
, though I'm not sure if the sync version handles all async resolution cases. it also doesnt return a url string yet.
I think that makes sense when writing code that's only meant for bun
. However, import.meta.resolve(…)
is defined to be sync in all other environments, which is going to cause issues for code that is not written by someone familiar with bun
's diverging behaviour.
If a code author knows about bun
and can't await
[^1] in a given piece of code, it's theoretically possible to write something like:
import.meta.resolveSync?.(…) ?? import.meta.resolve(…)
(not DRY) orconst resolve = import.meta.resolveSync?.bind(import.meta) ?? import.meta.resolve.bind(import.meta); /* … */; resolve(…)
… but I wouldn't want to make a bet that this will be handled correctly by all browsers[^2], bundlers, and transpilers[^3]. I also wouldn't be too enthused to make a pull request to every project that uses import.meta.resolve(…)
if I want to use it in bun
.
[^1]: For example, top-level await
is super tricky for engines and bundlers to get right and may be reasonable to avoid for some projects.
[^2]: Browsers tend to be finicky about the semantics of import
stuff, such as causing the entire script to error when import(…)
is present in a non-module (i.e. "classic") script, even if it's not used. That theoretically shouldn't be an issue here, but it still makes me super wary of making any assumptions about code transformations even if they "shouldn't" cause issues. The need to call the functions with the correct this
is also rather non-obvious, especially if you've only seen built-in functions that don't require it (e.g. console.log
).
[^3]: Many of them use exact code matches for functionality like this. (For example, for new URL(…, import.meta.url)
and import(…)
.) And if a particular tool doesn't recognize every variant of such syntax that's needed for workarounds, it may break downstream tools or some target environments.
If there is a need for both sync and async flavors, I'd strongly advocate for import.meta.resolve(…)
to be sync import.meta.resolveAsync(…)
to be the alternative in this case.
what i meant was we could use our existing implementation of resolveSync
, and rename it to resolve
(plus fixing it to return a url string). the mention of the other function was to say most of the work is implemented, and not to say "just use that in your code"
If there is a need for both sync and async flavors, I'd strongly advocate for import.meta.resolve(…) to be sync import.meta.resolveAsync(…) to be the alternative in this case.
This sounds like a good idea to me, most of the work is just swapping the two functions around. There will be a later effort of getting synchronous resolve to work with asynchronous Bun.plugin
, though we don't actually support that at all right now.
Also want to point out that it should be possible to resolve anything that dose not resolve into a module.
import.meta.resolve('./' + Math.random())
Deno and browser dose not look up any module when resolving paths.
it's more or less a direct approach to new URL('./dist', import.meta.url).toString()
with also regards to import-maps..
i did kind of expect import.meta.resolve('./dist)
to work more or less similar to: new URL('./dist', import.meta.url).toString()
or how the browsers import.meta.resolve()
works... but it did not. it throws an error instead
i kind of expect all of the node_modules folder + node core modules to behave more or less import-maps
and import.meta.resolve('dust)
to throw an TypeError
TypeError: Failed to execute 'resolve' on 'import.meta': Failed to resolve module specifier "dust": Relative references must start with either "/", "./", or "../". Or using import-maps
One thing that's happening in Bun is:
<Promise> Error [ERR_MODULE_NOT_FOUND]: Cannot find module <X> imported from <Y>
i think Bun should kind of be creating a import-map.json up front when it install any dependencies
NodeJS just fixed this: https://github.com/nodejs/node/issues/49010
what i meant was we could use our existing implementation of
resolveSync
, and rename it toresolve
(plus fixing it to return a url string). the mention of the other function was to say most of the work is implemented, and not to say "just use that in your code"If there is a need for both sync and async flavors, I'd strongly advocate for import.meta.resolve(…) to be sync import.meta.resolveAsync(…) to be the alternative in this case.
This sounds like a good idea to me, most of the work is just swapping the two functions around. There will be a later effort of getting synchronous resolve to work with asynchronous
Bun.plugin
, though we don't actually support that at all right now.
@paperdave Would you accept a PR for this / if I wanted to tackle this, is there any advice you could give me?
The import.meta.resolve(…)
compat chart is sooo close to all green, and I'd love to get it over the finish line. 😄
we definitely need to align what import.meta.resolve does in node
something im still wondering is if node.js is going to make import.meta.resolve return a string like web browsers and deno do, or keep it as a URL object.
would appreciate a pr for this. most of the code is already there to do this.
something im still wondering is if node.js is going to make import.meta.resolve return a string like web browsers and deno do, or keep it as a URL object.
I hope they keep it as a string to align with what browser are doing.
we definitely need to align what import.meta.resolve does in node
🤩
something im still wondering is if node.js is going to make import.meta.resolve return a string like web browsers and deno do, or keep it as a URL object.
I hope they keep it as a string to align with what browser are doing.
node
has chosen to keep it a URL
object. I don't know if the ship has sailed on that, but this seems to be the most pertinent issue: https://github.com/nodejs/node/issues/48994
(FWIW, deno
returns a string.)
would appreciate a pr for this. most of the code is already there to do this.
Alright, I'll see if I can get my bun
checkout compiling again!
(Is there an easy way to build bun
in a codespace yet?
node
has chosen to keep it aURL
object. I don't know if the ship has sailed on that, but this seems to be the most pertinent issue: nodejs/node#48994
Hmm, interesting. There actually seems to be a mismatch with the documentation: https://nodejs.org/api/esm.html#importmetaresolvespecifier
Returns:
<string>
The absolute (file:) URL string for the resolved module.
EDIT: filed an issue at https://github.com/nodejs/node/issues/49695
node has chosen to keep it a URL object.
Node's import.meta.resolve
returns an URL string, not an URL instance.
node has chosen to keep it a URL object.
Node's
import.meta.resolve
returns an URL string, not an URL instance.
Could you describe the difference?
This is what I see in node
20.6:
> echo "console.log(process.versions.node); console.log(import.meta.resolve('./rel'));" > /tmp/test.mjs
> node /tmp/test.mjs
20.6.1
URL {
href: 'file:///private/tmp/rel',
origin: 'null',
protocol: 'file:',
username: '',
password: '',
host: '',
hostname: '',
port: '',
pathname: '/private/tmp/rel',
search: '',
searchParams: URLSearchParams {},
hash: ''
}
import.meta.resolve('./rel') instanceof URL
also evaluates to true
.
node has chosen to keep it a URL object.
Node's
import.meta.resolve
returns an URL string, not an URL instance.Could you describe the difference?
This is what I see in
node
20.6:
wtf, that is weird, i had to try it out for myself.
i installed the latest version 20.6.1
used import.meta.resolve()
and i got a string back 👀
then i tried your exact version and then i got back a URL instance. like what gives?!?!
turns out if the file or the directory exist, then you get back a string. if the path dose not exist then it gives you a URL instance.
Sooooo inconsistent
node has chosen to keep it a URL object.
Node's
import.meta.resolve
returns an URL string, not an URL instance.Could you describe the difference? This is what I see in
node
20.6:wtf, that is weird, i had to try it out for myself. i installed the latest version 20.6.1 used
import.meta.resolve()
and i got a string back 👀then i tried your exact version and then i got back a URL instance. like what gives?!?!
turns out if the file or the directory exist, then you get back a string. if the path dose not exist then it gives you a URL instance.
Sooooo inconsistent
Oh, woah, nice debugging — definitely looks like it!
> echo "console.log(process.versions.node); console.log(import.meta.resolve('.'));" > /tmp/test.mjs
> node /tmp/test.mjs
20.6.1
file:///private/tmp/
turns out if the file or the directory exist, then you get back a string. if the path dose not exist then it gives you a URL instance.
It’s a bug, fixed in https://github.com/nodejs/node/pull/49698.
i have to write tests and make sure edge cases are handled but:
(bd is my development bun)
i have to write tests and make sure edge cases are handled but:
(bd is my development bun)
🥳
Do you plan to submit a PR?
I had trouble getting my bun
checkout to compile (and it still won't compile on my main computer), but happy to leave it to you if you're planning on a PR in the near future.
What is the problem this feature would solve?
It is common to write apps and library code in JS that references relative file paths, such as:
import.meta.resolve(…)
provides a standard way to refer to a relative resource, and also supports bare names and absolute paths. It is already supported in all major browsers anddeno
. It would be valuable forimport.meta.resolve(…)
to be available in all target environments, includingbun
.What is the feature you are proposing to solve the problem?
Implement https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve
What alternatives have you considered?
As a library author, I can use code that uses
new URL(…, import.meta.url).href
instead. However:import.meta.resolve(…)
is more ergonomic and conveys clear intentions compared to being a "magic pattern", and