You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This works, but I think going forward life for embedders and tooling/ecosystem projects will be much easier if we prefix these imports with wasi: (or something similar) to note them/control how the imports are done.
There are other ways to indicate the type of an import/control (i.e. import attributes, for example) , but prefixing with wasi: is incredibly simple and offers a point of leverage -- being able to easily tell which imports are "wasi"/underlying paltform imports. This mirrors the convention of node: as an import prefix which is used in more modern code bases (i.e. from "node:fs" rather than from "fs").
Changes may be required "below" and "above" componentize JS to make this work, but I figure that ComponentizeJS is a good place to discuss and eventually start this work.
[EDIT] I changed the title to wasm since wasi is clearly not the right prefix -- wasm:, wit:, component: or something else are clearly more correct, though it's not exactly clear which is best.
Right now, code that uses imports via WASI look like regular imports
What do you mean by "imports via WASI"? If I understand what you're getting at correctly, then this isn't really about WASI, but about WIT / the Component Model? If so, then wasi: would definitely not be the right prefix, because that really is about WASI-specified APIs, not all Component Model imports.
Apart from that I'm also not necessarily entirely convinced that we even should have a solution like this. You mention the node: prefix convention, but that's about APIs built into Node.js, not any APIs loaded by Node.js that aren't implemented in JS. The more direct analog would be Node native modules loaded as shared libraries, and those aren't specifically denoted in source code—nor should they be.
I think it's actively a good thing to be able to implement APIs as CM imports transparently, without the consumer/importer having to know anything about it.
What do you mean by "imports via WASI"? If I understand what you're getting at correctly, then this isn't really about WASI, but about WIT / the Component Model? If so, then wasi: would definitely not be the right prefix, because that really is about WASI-specified APIs, not all Component Model imports.
Ah sorry yes -- I was thinking about WIT imports broadly but the WASI ones in particular. wit: works as well -- the core idea is to designate the modern wasm platform -- i.e. WIT-powered components via the Component Model.
Apart from that I'm also not necessarily entirely convinced that we even should have a solution like this. You mention the node: prefix convention, but that's about APIs built into Node.js, not any APIs loaded by Node.js that aren't implemented in JS. The more direct analog would be Node native modules loaded as shared libraries, and those aren't specifically denoted in source code—nor should they be.
Yeah so the node: prefix works to identify node as a the platform builtin that's being imported -- wit imports are platform provided "builtins" in a similar fashion -- i.e. they are not actually supplied by any local code.
Native modules are regular packages, from the point of the importer (the code that makes them work is locally available), so I'm not sure they are the right analog here.
I think it's actively a good thing to be able to implement APIs as CM imports transparently, without the consumer/importer having to know anything about it.
Hmnn I think I disagree here -- there's no such thing as a "transparent" import for the user, they have to know the WIT interface that they're importing, and they have to know what it means to do that import (any naive use of a bundler will fail, for example).
If you sue a WIT import these days (as we do), no bundler will know what to do with it, and will fail. This is fine for pure JS workflows, but there's not much difference between wasi:ns:... and ns:.... You can't make packages with those names anyway, they can't really resolve easily to regular files AFAIK, etc. There's basically nothing transparent or familiar about these strings as imports.
The point that "transparent" imports make sense is having regular packages export code that runs on the WebAssembly platform, but that's a completely different discussion -- that's more to do with Node's conditional export system and WinterTC runtime-keys. To illustrate, being able to import "axios" and have that resolve to a wasm conditional export as necessary depending on the platform/package resolution/bundler. That's a different problem w/ a different solution.
Ah so the main improvement I think here will be making it easier to know which imports are WIT-related/are related to the component model, similar to how node:... makes it clear where the builtin in supposed to come from.
The primary benefit in the node ecosystem is avoiding name collisions (even when rare) but I know I generally write NodeJS code and use node: prefixes wherever I can to make it very clear what I'm intending to import. But a secondary benefit is that it makes it easier to write code for other JS environments (e.g. Deno's docs related to the node builtins)
Roughly, I'm trying to think ahead to what it would take to write a Rollup (and later Rolldown) plugin that interacts with/triggers for imports that are WIT-based/platform provided.
It's a simple question of detecting an import of ns:pkg/iface@0.1.0 versus wit:ns:pkg/iface@0.1.0 (for example), but having that prefix makes it possible for me to trivially write a rollup plugin that could do something like automatically generate the WIT file from just source code, generate the right types on the fly, etc. Obviously generating the export is much more difficult if we're not using Typescript but at the very least I can tell the difference between some import that is going to be filled in some other way, and one that should be filled in via a WIT import.
Another use case that I'm envisioning here is that if I know that an import is a WIT import, I can attempt to auto-fill/connect implementations (which could be JS packages from NPM or elsewhere). This isn't impossible to do without the prefix, it just makes detecting when it is warranted trivial.
This also enables us to create/template out configurations for bundlers, tsc and other tooling that can by default ignore wit:* imports.
The rationale here is the same as the import ... from "fs" versus import ... from "node:fs", etc. Knowing where the platform dependency is coming and being able to plugin helpful import semantics is useful.
That said, I could totally see an argument for import attributes here rather than the prefix, as the import attributes can pack more information. Given that point, I still think there is some benefit in the prefix as a middle ground and good distinguisher. Imports already look like they're prefixed to the average NodeJS dev -- the prefix is just the WIT namespace (but not intentionally).
The rationale here is the same as the import ... from "fs" versus import ... from "node:fs", etc. Knowing where the platform dependency is coming and being able to plugin helpful import semantics is useful.
This, to me, still isn't right. node: means "this module is available in the Node runtime". Whereas your wit: prefix would only mean "this module uses wit as the interfacing mechanism". I still think that that is much more immediately analogous to native modules, which don't have a specific prefix.
And as you point out, bundlers will already choke on WIT imports because of the ns:pkg syntax.
but having that prefix makes it possible for me to trivially write a rollup plugin that could do something like automatically generate the WIT file from just source code,
How would that work? An import doesn't contain any interface definitions, so I don't see how you could derive a WIT file from it. Doing that from a TypeScript implementation of a component is a different matter and indeed very doable, but also unrelated to importing.
At the end of the day I'm not extremely strongly opposed to a prefix, but I'm still pretty skeptical of the value proposition.
This, to me, still isn't right. node: means "this module is available in the Node runtime". Whereas your wit: prefix would only mean "this module uses wit as the interfacing mechanism". I still think that that is much more immediately analogous to native modules, which don't have a specific prefix.
And as you point out, bundlers will already choke on WIT imports because of the ns:pkg syntax.
Maybe we're over-indexing on the wit: as a prefix -- the point is to identify the import as a component model import, i.e. something that must be provided from the platform. I don't think the wit: prefix only means "this module uses wit as the interfacing mechanism", because WIT is intrinsically tied to the Component Model.
BUT, what it's analogous to is besides the point, I think.
Agree with you that bundlers already choke on WIT imports, and I'd like to be able to write something that easily handles all of them, unambiguously. This would make for a good add-this-to-your-bundler-setup plugin, or something other tooling could add for people.
How would that work? An import doesn't contain any interface definitions, so I don't see how you could derive a WIT file from it. Doing that from a TypeScript implementation of a component is a different matter and indeed very doable, but also unrelated to importing.
At the end of the day I'm not extremely strongly opposed to a prefix, but I'm still pretty skeptical of the value proposition.
I think this is outside scope of comopnentize-js of course, but tooling that knows where the interface is (or is configured properly) would be able to find the relevant WIT and pull it down. In a very simplistic sense, imagine a registry of known packages pre-configured at the tool level, and being able to pull the WITs appropriately.
The dumbest version of this could just gather the imports, put them in a WIT file, and then call wkg wit fetch (which will obviously fail on unknown WIT interfaces, but you get the point).
The relevance of the Typescript was more towards the use case of generating the entire top level wit. We know the imports from the the imports statements, and we actually know the exports from the exports statements, so I was thinking more along the lines of what would it take to generate the exports too -- we basically need to know the types involved, and if we can figure all those out, we could actually work "backwards" and generate the outgoing interfaces too (would probably be impossible to automatically determine if something was trying to export wasi:cli/run, but we at least know some interface named run with a run function is being exported.
Yeah, maybe this is the kind of thing that is much easier to motivate once there's an example/PoC which shows the potential/unlock.
Yeah, maybe this is the kind of thing that is much easier to motivate once there's an example/PoC which shows the potential/unlock.
No no, I totally get the idea of generating WIT from source code. I just think that nothing we do with import statements will ever help with that, because that's simply not the place the type information is available—nor the place where we would even want to generate WIT. For exports however, this absolutely makes sense, and is something I've long been thinking about.
To the overall thing, something I just remembered is that there are now reservations for the wasm: and wasm-js: protocols, which sets a precedent that seems useful here. With that, I think I'm warming up to the idea of wit:. (component: would be better in a world where that term wasn't overloaded by a few 100x.)
No no, I totally get the idea of generating WIT from source code. I just think that nothing we do with import statements will ever help with that, because that's simply not the place the type information is available—nor the place where we would even want to generate WIT. For exports however, this absolutely makes sense, and is something I've long been thinking about.
Yeah funnily enough I think the exports is the hard part and the imports is the easy part! I'm basically thinking that a properly configured
To the overall thing, something I just remembered is that there are now reservations for the wasm: and wasm-js: protocols, which sets a precedent that seems useful here. With that, I think I'm warming up to the idea of wit:. (component: would be better in a world where that term wasn't overloaded by a few 100x.)
Oh, I did not know this -- that's very helpful. I have no idea what this thing should be called, but I really just want a way to lazily know what is wasm-ecosystem related so we can do smart things that hopefully improve DX, feels like there is a lot there.
And yeah... we're at this weird point where the words are either insanely overloaded, or almost completely unfamiliar. wasm:* feels like "take from this component", which IS also a great valid usecase and example and could work a very similar way. wit: fits better with what I was hoping for, but your note makes me think that there is destined to exist somewhere a docs page that explains the difference (so maybe unifying/simplifying could be worth it early... does wasm:* work for everything...?)
The original goal with import { someFn } from "ns:pkg/iface@0.1.0"; was indeed to treat ns: as a namespace of built-ins modules from a JS POV, just like the node: or deno: namespaces (and, perhaps, one day browsers too b/c, from the perspective of JS, they are "built-in", so I'd recommend keeping it that way if possible. If bundlers are choking on ns:pkg/iface, could we perhaps fake up an import map that defines ns:pkg/iface (iiuc, module specifier strings can be any Unicode string, and so import maps can map anything to anything), or perhaps ask Guy for guidance, since he's pretty familiar with both WIT and the bundler ecosystem.
Yeah, I think it would be ideal if we could treat ns: as builtins -- but it really feels like they're builtins that are platform defined (just like node: and deno:) but the key difference is that the platform itself is user defined at this point. IMO it's good to delineate that explicitly.
if the first <segment>: is platform, I think it makes much more sense to have wasm: or component: (probably not reasonable due to how overloaded "component" is) or wit: -- so that we know that whatever comes after is provided by that platform.
If bundlers are choking on ns:pkg/iface, could we perhaps fake up an import map that defines ns:pkg/iface (iiuc, module specifier strings can be any Unicode string, and so import maps can map anything to anything), or perhaps ask Guy for guidance, since he's pretty familiar with both WIT and the bundler ecosystem.
Yeah maybe this wasn't clear -- it's not that bundlers are choking on this -- it's easy to swap do individual replacements (or read some WIT and do all expected imports). The hope is to delineate specifically component model provided imports. This doesn't take much from the ns:pkg/iface style, except for adding a prefix.
Switching it around, any reasons ns:pkg/iface is strictly preferable to wit:ns:pkg/iface ?
but the key difference is that the platform itself is user defined at this point. IMO it's good to delineate that explicitly.
I mean, from the perspective of V8, node: and deno: are also "user defined" and indeed some of these modules are, iiuc, implemented by JS in terms of even lower-level primitives; WIT namespaces wasmcloud: or my-company: doesn't seem categorically different. But is there some other technical limitation that is addressed by putting an extra prefix in front of the WIT namespaces?
I mean, from the perspective of V8, node: and deno: are also "user defined" and indeed some of these modules are, iiuc, implemented by JS in terms of even lower-level primitives; WIT namespaces wasmcloud: or my-company: doesn't seem categorically different.
So I would think of node and deno as platform provided rather than user defined. These imports are distinct from code a user (developer) would actually write in their own project, where most user-written code is just simple unadorned imports.
The JS ecosystem seems to be moving to the prefixes for a similar reason -- fs was hard to distinguish from what "really" meant node:fs, as in "fs as provided by the node platform". I see the move to node:* and deno:* is a sort of step back from bare imports being redefined under the covers to be "right" -- the explicitness in node:* imports is a benefit to tooling.
But is there some other technical limitation that is addressed by putting an extra prefix in front of the WIT namespaces?
It's less a limitation, but more a tradeoff between being explicit and implicit, and the resulting ecosystem/tooling changes and difficulty of developing/building in the ecosystem. I think a world where I can say "all the WASM imports for your NodeJS code are expected to start with wasm: and then your name space (e.g. wasm:ns:pkg/iface)" makes it much easier for tooling to detect, handle and do transformations.
So I would think of node and deno as platform provided rather than user defined.
Yes, and I think wasi:, my-company:, and other namespaces are similarly platforms.
These imports are distinct from code a user (developer) would actually write in their own project, where most user-written code is just simple unadorned imports.
Ah, if you're thinking of "hand-coded" worlds where the imports are specific to the component and not derived from any platform: that's what plain names (which have no namespace:) are for, which then look like normal 'bareword' imports from a JS POV.
Yes, and I think wasi:, my-company:, and other namespaces are similarly platforms.
They're certainly a kind of platform I think -- but in this case they presupposed WebAssembly (and the component model, etc), so I think it's reasonable to have the prefix there.
I'd actually expect here that the platform is the world you're inhabiting, not the namespace itself. It feels like the namespace might be too broad of a platform -- unless we're talking about nested platforms or something like that.
Ah, if you're thinking of "hand-coded" worlds where the imports are specific to the component and not derived from any platform: that's what plain names (which have no namespace:) are for, which then look like normal 'bareword' imports from a JS POV.
Ah no I was more thinking of the distinction between the code that is available via tooling the developer/someone at their company wrote/didn't write.
A major goal I have here is to cut down on confusion as to where imports could be coming from -- node:, deno:, etc do that in the ecosystem right now, and while I think we certainly could have imports look like my-company: (we do today), it's harder from a tooling point of view to recognize all of those and gather/work with them. IMO users would also be somewhat confused unless they're fully bought in/understand what's going on.
The wasm:xxxxx prefix makes it easy to tell that an import is coming from wasm-land (though what that means isn't as straight forward as "from node builtins" or "from deno builtins', I think it will be understood in the same way given it's a similar affordance).
I guess what I'm having a hard time seeing is how, concretely, wasi: or my-company: have a meaningfully-different developer experience from node: or deno: in a way that requires them to be distinguished to the developer. Again, if I'm authoring JS that runs inside a component, they're all just namespaces for functions that are "built-in" and not implemented other modules I wrote or pulled in as dependencies.
I guess what I'm having a hard time seeing is how, concretely, wasi: or my-company: have a meaningfully-different developer experience from node: or deno: in a way that requires them to be distinguished to the developer. Again, if I'm authoring JS that runs inside a component, they're all just namespaces for functions that are "built-in" and not implemented other modules I wrote or pulled in as dependencies.
I agree that they're all builtins, but I'm trying to drive that it's important where the builtin is coming from -- node:fs IMO is clearer about where fs comes from than fs alone. I'd like to have that kind of clarity for imports that come from/are a result of wasm as well.
More concretely, if I wanted to write a thing to process all the wasm imports I find in a JS codebase (versus let's say some other library import), it would be easier in the simple case if there was an expected convention for the import prefixes.
It's for the developer, but somewhat indirectly -- developers benefit from knowing where the import comes from (an import with some-non-ns:thing/export could be unrelated to wasm, but have a similar look). For the tool builder/ecosystem having the convention there makes it much easier to deal with transformations, gather metadata on used/unused interfaces, and the like because we can say with somewhat more certainty what the import statements must look like.
[EDIT] And just to be clear, it's totally up to the user if they want to make node:fs mean something else, or swap out the import -- I'm not saying we should take that away/hurt that use case at all. If someone wants to swap out my-company: before they bulid/do processing that's absolutely fine too.
I am sympathetic to the point that if we had some import my-company:adder, we could allow it to run in Wasm or on traditional platforms by swapping out the code at bundle time without the prefix.
i.e. changing the meaning of the below to either resolve to Wasm-friendly code (in one way or another) or not via the bundler:
import{add}from"my-company:adder"
As opposed to this:
import{add}from"wasm:my-company:adder"
While you can do the exact same thing (change the meaning of wasm:my-company:adder) or remove the prefix, I can see the argument that the former is cleaner.
The second example is certainly slightly less clean than the first, but I do think the benefits from being explicit outweigh that. Just to point again at the node: and deno: prefixes -- I think it's a convention that is really familiar to developers across node, deno and bun at this point.
All this said, I do want to point out that import assertions could also be a very reasonable way to solve this as well, it's just a bit more verbose.
node:fs IMO is clearer about where fs comes from than fs alone. I'd like to have that kind of clarity for imports that come from/are a result of wasm as well.
Yes, but I think we do have this distinction: wasi:filesystem vs filesystem. wasi is not a unit of functionality in the same way that node is not; fs/filesystem are, and so we are symmetric to the JS convention here.
Just to point again at the node: and deno: prefixes -- I think it's a convention that is really familiar to developers across node, deno and bun at this point.
Since wasi:, my-open-source-runtime: and my-company-platform: are serving symmetric roles to node:/deno:/bun:, I think we are indeed leaning into this convention.
Yes, but I think we do have this distinction: wasi:filesystem vs filesystem. wasi is not a unit of functionality in the same way that node is not; fs/filesystem are, and so we are symmetric to the JS convention here.
wasi in this case feels like an arbitrary name like an org, which people currently express in JS packages normally with imports like @company/package -- namespaces map more to node's (really NPMs) idea of packages for me.
The owner of a package doesn't say anything about where the import actually comes from/any platform specific details. In fact, the implicit "protocol" of an import like @company/package is node:/npm: -- those prefixes/protocols are clearly different, they say something about where the import actually comes from.
I think wasm: is a lot closer to node: and deno: than my-company-platform: is -- my-company-platform is a collection of interfaces that just happens to be named that way, the wasm: prefix conveys that whatever comes after is managed by WebAssembly tooling/is resolvable in ways that relate to Wasm.
I don't think my-open-source-runtime: or my-company-platform: do enough to establish intuition -- it says nothing about where the import comes from, i.e. what you should expect to fill the import in, in the normal case. I just simply don't see how these are equivalent to node:/deno:/bun;, one prefix says something about where the dependency comes from (via convention) and the other is just a name that could be anything.
On JS platforms there is usually a default for where non-path names come from, whether in the browser or on the server. Introducing a new kind of non-path import name that is not resolved by either traditional platform without delineating it in any way seems like it will make it easy to be confused.
In general, the right answer for any import is always "whatever the resolver wants, if you have one" -- but being explicit about the protocol adds clarity IMO.
wasi in this case feels like an arbitrary name like an org, which people currently express in JS packages normally with imports like @company/package -- namespaces map more to node's (really NPMs) idea of packages for me.
I don't think so: packages are implementations; wasi: imports (unless virtualized) come from the host, just like node: and deno:. Note that when a component with wasi: imports runs in a browser by implementing the wasi: imports in JS, you're in the virtualization case where JS+Web APIs are playing the role of host.
The owner of a package doesn't say anything about where the import actually comes from/any platform specific details.
Again, I don't think so: a package provides the implementation, should you choose to use it. Instead, node:-, deno:- and wasi:-prefixed names only name an interface, not an implementation; as evidenced by lots of JS runtimes adding "node compat", there can be many distinct implementations of node:-prefixed imports (again, unlike packages where the name selects the implementation).
I think wasm: is a lot closer to node: and deno: than my-company-platform: is -- my-company-platform is a collection of interfaces that just happens to be named that way, the wasm: prefix conveys that whatever comes after is managed by WebAssembly tooling/is resolvable in ways that relate to Wasm.
node: and deno: name entities (companies/projects) that make coordinated decisions to add functions to their respective namespaces, symmetric to wasi: or my-company: in WIT. In contrast, wasm: is just an implementation technology that we want (via WIT/bindgen) to be usable without knowing it's wasm! Thus a wasm: prefix would be more like, to your earlier point, an import assertion; wasm: certainly does not name an entity with coordinated decision-making.
On JS platforms there is usually a default for where non-path names come from, whether in the browser or on the server. Introducing a new kind of non-path import name that is not resolved by either traditional platform without delineating it in any way seems like it will make it easy to be confused.
IIUC, there's already 3 categories of imports in the JS world:
paths
bareword imports expected to be resolved (by package.json or import maps) to some JS implementation
namespace:-prefixed imports expected to be implemented by the host (or, as the exceptional case, virtualized by someone else to fill in for the host)
Given this, wasi:- and my-open-source-runtime:-prefixed imports fit naturally into case 3.
I don't think so: packages are implementations; wasi: imports (unless virtualized) come from the host, just like node: and deno:. Note that when a component with wasi: imports runs in a browser by implementing the wasi: imports in JS, you're in the virtualization case where JS+Web APIs are playing the role of host.
So I'm not sure I agree with this statement -- for lots of reasons:
namespaces aren't implementations
namespaces aren't interfaces or even bundles of interfaces that are usable in a real way (package or worlds are more appropriate)
even interfaces aren't implementations right -- you need platform code or another component to implement the interface
Unless I'm missing something, an interface in a package can represent a specific shape of implementation but semantically (of course) there's no protection there. Sure, if we think of all the possible interfaces and packages that can be accessed under a namespace we could think of it as one implementation, but it's unbounded/a wide open set, type wise.
Sorry getting back to this very late -- I think there's still a difference here between an identifier like node: and one like my-company: versus wasm:, but I think what would make this more concrete and easier to not just be a style preference is how easy it is to build tooling that makes the development process smooth.
If it turns out to be easy to build tooling in NodeJS and the browser without any indication of what is/isn't a wasm import then that's great -- but primarily I added this issue because it seemed like it would become a problem trying to mix regular tracable imports with ones that are supposed to be provided due to a dependency in WIT.
If it doesn't play out to be difficult, then I'm certainly happy to not have a wasm:* prefix -- that said if it does, the risk is that we make life slightly more difficult for everyone that wants to write bundler tooling which doesn't seem like a good default. Some of this also depends on the recognition of a wasm export similar to a browser style export which is also necessary if we want people to be able to "just" use existing packages.
So I'm not sure I agree with this statement -- for lots of reasons:
namespaces aren't implementations
I agree! I was specifically making this same point in reply to your preceding comment which said:
wasi in this case feels like an arbitrary name like an org, which people currently express in JS
packages normally with imports like @company/package -- namespaces map more to node's (really
NPMs) idea of packages for me.
So then sounds like we agree that WIT namespaces are not like packages ( = implementations).
So to be clear, I wasn't saying a namespace is an interface either. My point was that <interfacename> imports (which happen to be prefixed by a namespace but also include a package and interface name) are analogous to JS imports that are implemented by the host runtime (which also happen to be prefixed by a node: or deno: or other namespace , but also include other stuff in the name too). Thus, from a JS guest's POV, they are analogous and so I don't think they need to be in a separate syntactic category unless we can come up with some concrete toolchain use case that demands it.
Something I just realized, it looks like wasm: is going to get used as a import prefix by things like the js-string-imports proposal:
Then a mechanism for importing these Wasm builtin functions is added to the WebAssembly JS-API. These builtins are grouped in modules and exist in a new reserved import namespace wasm: that is enabled at compile-time with a flag.
Another reason we definitely can't have wasm: as a prefix (putting aside the discussion of whether there should be any prefix at all!)
Activity
tschneidereit commentedon Oct 6, 2025
What do you mean by "imports via WASI"? If I understand what you're getting at correctly, then this isn't really about WASI, but about WIT / the Component Model? If so, then
wasi:would definitely not be the right prefix, because that really is about WASI-specified APIs, not all Component Model imports.Apart from that I'm also not necessarily entirely convinced that we even should have a solution like this. You mention the
node:prefix convention, but that's about APIs built into Node.js, not any APIs loaded by Node.js that aren't implemented in JS. The more direct analog would be Node native modules loaded as shared libraries, and those aren't specifically denoted in source code—nor should they be.I think it's actively a good thing to be able to implement APIs as CM imports transparently, without the consumer/importer having to know anything about it.
vados-cosmonic commentedon Oct 6, 2025
Ah sorry yes -- I was thinking about WIT imports broadly but the WASI ones in particular.
wit:works as well -- the core idea is to designate the modern wasm platform -- i.e. WIT-powered components via the Component Model.Yeah so the
node:prefix works to identify node as a the platform builtin that's being imported --witimports are platform provided "builtins" in a similar fashion -- i.e. they are not actually supplied by any local code.Native modules are regular packages, from the point of the importer (the code that makes them work is locally available), so I'm not sure they are the right analog here.
Hmnn I think I disagree here -- there's no such thing as a "transparent" import for the user, they have to know the WIT interface that they're importing, and they have to know what it means to do that import (any naive use of a bundler will fail, for example).
If you sue a WIT import these days (as we do), no bundler will know what to do with it, and will fail. This is fine for pure JS workflows, but there's not much difference between
wasi:ns:...andns:.... You can't make packages with those names anyway, they can't really resolve easily to regular files AFAIK, etc. There's basically nothing transparent or familiar about these strings as imports.The point that "transparent" imports make sense is having regular packages export code that runs on the WebAssembly platform, but that's a completely different discussion -- that's more to do with Node's conditional export system and WinterTC runtime-keys. To illustrate, being able to
import "axios"and have that resolve to awasmconditional export as necessary depending on the platform/package resolution/bundler. That's a different problem w/ a different solution.tschneidereit commentedon Oct 6, 2025
Hmm, I'm not sure I understand what you think this will solve.
Can you say in which specific ways it will make life easier for either embedders or tools?
vados-cosmonic commentedon Oct 6, 2025
Ah so the main improvement I think here will be making it easier to know which imports are WIT-related/are related to the component model, similar to how
node:...makes it clear where the builtin in supposed to come from.The primary benefit in the node ecosystem is avoiding name collisions (even when rare) but I know I generally write NodeJS code and use
node:prefixes wherever I can to make it very clear what I'm intending to import. But a secondary benefit is that it makes it easier to write code for other JS environments (e.g. Deno's docs related to thenodebuiltins)Roughly, I'm trying to think ahead to what it would take to write a Rollup (and later Rolldown) plugin that interacts with/triggers for imports that are WIT-based/platform provided.
It's a simple question of detecting an import of
ns:pkg/iface@0.1.0versuswit:ns:pkg/iface@0.1.0(for example), but having that prefix makes it possible for me to trivially write a rollup plugin that could do something like automatically generate the WIT file from just source code, generate the right types on the fly, etc. Obviously generating the export is much more difficult if we're not using Typescript but at the very least I can tell the difference between some import that is going to be filled in some other way, and one that should be filled in via a WIT import.Another use case that I'm envisioning here is that if I know that an import is a WIT import, I can attempt to auto-fill/connect implementations (which could be JS packages from NPM or elsewhere). This isn't impossible to do without the prefix, it just makes detecting when it is warranted trivial.
This also enables us to create/template out configurations for bundlers,
tscand other tooling that can by default ignorewit:*imports.The rationale here is the same as the
import ... from "fs"versusimport ... from "node:fs", etc. Knowing where the platform dependency is coming and being able to plugin helpful import semantics is useful.That said, I could totally see an argument for import attributes here rather than the prefix, as the import attributes can pack more information. Given that point, I still think there is some benefit in the prefix as a middle ground and good distinguisher. Imports already look like they're prefixed to the average NodeJS dev -- the prefix is just the WIT namespace (but not intentionally).
tschneidereit commentedon Oct 6, 2025
This, to me, still isn't right.
node:means "this module is available in the Node runtime". Whereas yourwit:prefix would only mean "this module uses wit as the interfacing mechanism". I still think that that is much more immediately analogous to native modules, which don't have a specific prefix.And as you point out, bundlers will already choke on WIT imports because of the
ns:pkgsyntax.How would that work? An import doesn't contain any interface definitions, so I don't see how you could derive a WIT file from it. Doing that from a TypeScript implementation of a component is a different matter and indeed very doable, but also unrelated to importing.
At the end of the day I'm not extremely strongly opposed to a prefix, but I'm still pretty skeptical of the value proposition.
vados-cosmonic commentedon Oct 6, 2025
Maybe we're over-indexing on the
wit:as a prefix -- the point is to identify the import as a component model import, i.e. something that must be provided from the platform. I don't think thewit:prefix only means "this module uses wit as the interfacing mechanism", because WIT is intrinsically tied to the Component Model.BUT, what it's analogous to is besides the point, I think.
Agree with you that bundlers already choke on WIT imports, and I'd like to be able to write something that easily handles all of them, unambiguously. This would make for a good add-this-to-your-bundler-setup plugin, or something other tooling could add for people.
I think this is outside scope of comopnentize-js of course, but tooling that knows where the interface is (or is configured properly) would be able to find the relevant WIT and pull it down. In a very simplistic sense, imagine a registry of known packages pre-configured at the tool level, and being able to pull the WITs appropriately.
The dumbest version of this could just gather the imports, put them in a WIT file, and then call
wkg wit fetch(which will obviously fail on unknown WIT interfaces, but you get the point).The relevance of the Typescript was more towards the use case of generating the entire top level wit. We know the imports from the the imports statements, and we actually know the exports from the exports statements, so I was thinking more along the lines of what would it take to generate the exports too -- we basically need to know the types involved, and if we can figure all those out, we could actually work "backwards" and generate the outgoing interfaces too (would probably be impossible to automatically determine if something was trying to export
wasi:cli/run, but we at least know some interface namedrunwith arunfunction is being exported.Yeah, maybe this is the kind of thing that is much easier to motivate once there's an example/PoC which shows the potential/unlock.
tschneidereit commentedon Oct 6, 2025
No no, I totally get the idea of generating WIT from source code. I just think that nothing we do with
importstatements will ever help with that, because that's simply not the place the type information is available—nor the place where we would even want to generate WIT. For exports however, this absolutely makes sense, and is something I've long been thinking about.To the overall thing, something I just remembered is that there are now reservations for the
wasm:andwasm-js:protocols, which sets a precedent that seems useful here. With that, I think I'm warming up to the idea ofwit:. (component:would be better in a world where that term wasn't overloaded by a few 100x.)vados-cosmonic commentedon Oct 7, 2025
Yeah funnily enough I think the exports is the hard part and the imports is the easy part! I'm basically thinking that a properly configured
Oh, I did not know this -- that's very helpful. I have no idea what this thing should be called, but I really just want a way to lazily know what is wasm-ecosystem related so we can do smart things that hopefully improve DX, feels like there is a lot there.
And yeah... we're at this weird point where the words are either insanely overloaded, or almost completely unfamiliar.
wasm:*feels like "take from this component", which IS also a great valid usecase and example and could work a very similar way.wit:fits better with what I was hoping for, but your note makes me think that there is destined to exist somewhere a docs page that explains the difference (so maybe unifying/simplifying could be worth it early... doeswasm:*work for everything...?)lukewagner commentedon Dec 12, 2025
The original goal with
import { someFn } from "ns:pkg/iface@0.1.0";was indeed to treatns:as a namespace of built-ins modules from a JS POV, just like thenode:ordeno:namespaces (and, perhaps, one day browsers too b/c, from the perspective of JS, they are "built-in", so I'd recommend keeping it that way if possible. If bundlers are choking onns:pkg/iface, could we perhaps fake up an import map that definesns:pkg/iface(iiuc, module specifier strings can be any Unicode string, and so import maps can map anything to anything), or perhaps ask Guy for guidance, since he's pretty familiar with both WIT and the bundler ecosystem.vados-cosmonic commentedon Dec 12, 2025
Yeah, I think it would be ideal if we could treat
ns:as builtins -- but it really feels like they're builtins that are platform defined (just likenode:anddeno:) but the key difference is that the platform itself is user defined at this point. IMO it's good to delineate that explicitly.if the first
<segment>:is platform, I think it makes much more sense to havewasm:orcomponent:(probably not reasonable due to how overloaded "component" is) orwit:-- so that we know that whatever comes after is provided by that platform.Yeah maybe this wasn't clear -- it's not that bundlers are choking on this -- it's easy to swap do individual replacements (or read some WIT and do all expected imports). The hope is to delineate specifically component model provided imports. This doesn't take much from the
ns:pkg/ifacestyle, except for adding a prefix.Switching it around, any reasons
ns:pkg/ifaceis strictly preferable towit:ns:pkg/iface?lukewagner commentedon Dec 12, 2025
I mean, from the perspective of V8,
node:anddeno:are also "user defined" and indeed some of these modules are, iiuc, implemented by JS in terms of even lower-level primitives; WIT namespaceswasmcloud:ormy-company:doesn't seem categorically different. But is there some other technical limitation that is addressed by putting an extra prefix in front of the WIT namespaces?vados-cosmonic commentedon Dec 13, 2025
So I would think of
nodeanddenoas platform provided rather than user defined. These imports are distinct from code a user (developer) would actually write in their own project, where most user-written code is just simple unadorned imports.The JS ecosystem seems to be moving to the prefixes for a similar reason --
fswas hard to distinguish from what "really" meantnode:fs, as in "fs as provided by the node platform". I see the move tonode:*anddeno:*is a sort of step back from bare imports being redefined under the covers to be "right" -- the explicitness innode:*imports is a benefit to tooling.It's less a limitation, but more a tradeoff between being explicit and implicit, and the resulting ecosystem/tooling changes and difficulty of developing/building in the ecosystem. I think a world where I can say "all the WASM imports for your NodeJS code are expected to start with
wasm:and then your name space (e.g.wasm:ns:pkg/iface)" makes it much easier for tooling to detect, handle and do transformations.[-]Supporting a 'wasi:' prefix on imports[/-][+]Supporting a 'wasm:' prefix on imports[/+]lukewagner commentedon Dec 15, 2025
Yes, and I think
wasi:,my-company:, and other namespaces are similarly platforms.Ah, if you're thinking of "hand-coded" worlds where the imports are specific to the component and not derived from any platform: that's what plain names (which have no
namespace:) are for, which then look like normal 'bareword' imports from a JS POV.vados-cosmonic commentedon Dec 15, 2025
They're certainly a kind of platform I think -- but in this case they presupposed WebAssembly (and the component model, etc), so I think it's reasonable to have the prefix there.
I'd actually expect here that the platform is the world you're inhabiting, not the namespace itself. It feels like the namespace might be too broad of a platform -- unless we're talking about nested platforms or something like that.
Ah no I was more thinking of the distinction between the code that is available via tooling the developer/someone at their company wrote/didn't write.
A major goal I have here is to cut down on confusion as to where imports could be coming from --
node:,deno:, etc do that in the ecosystem right now, and while I think we certainly could have imports look likemy-company:(we do today), it's harder from a tooling point of view to recognize all of those and gather/work with them. IMO users would also be somewhat confused unless they're fully bought in/understand what's going on.The
wasm:xxxxxprefix makes it easy to tell that an import is coming from wasm-land (though what that means isn't as straight forward as "from node builtins" or "from deno builtins', I think it will be understood in the same way given it's a similar affordance).lukewagner commentedon Dec 15, 2025
I guess what I'm having a hard time seeing is how, concretely,
wasi:ormy-company:have a meaningfully-different developer experience fromnode:ordeno:in a way that requires them to be distinguished to the developer. Again, if I'm authoring JS that runs inside a component, they're all just namespaces for functions that are "built-in" and not implemented other modules I wrote or pulled in as dependencies.vados-cosmonic commentedon Dec 15, 2025
I agree that they're all builtins, but I'm trying to drive that it's important where the builtin is coming from --
node:fsIMO is clearer about wherefscomes from thanfsalone. I'd like to have that kind of clarity for imports that come from/are a result of wasm as well.More concretely, if I wanted to write a thing to process all the wasm imports I find in a JS codebase (versus let's say some other library import), it would be easier in the simple case if there was an expected convention for the import prefixes.
It's for the developer, but somewhat indirectly -- developers benefit from knowing where the import comes from (an import with
some-non-ns:thing/exportcould be unrelated to wasm, but have a similar look). For the tool builder/ecosystem having the convention there makes it much easier to deal with transformations, gather metadata on used/unused interfaces, and the like because we can say with somewhat more certainty what the import statements must look like.[EDIT] And just to be clear, it's totally up to the user if they want to make
node:fsmean something else, or swap out the import -- I'm not saying we should take that away/hurt that use case at all. If someone wants to swap outmy-company:before they bulid/do processing that's absolutely fine too.vados-cosmonic commentedon Dec 15, 2025
I am sympathetic to the point that if we had some import
my-company:adder, we could allow it to run in Wasm or on traditional platforms by swapping out the code at bundle time without the prefix.i.e. changing the meaning of the below to either resolve to Wasm-friendly code (in one way or another) or not via the bundler:
As opposed to this:
While you can do the exact same thing (change the meaning of
wasm:my-company:adder) or remove the prefix, I can see the argument that the former is cleaner.The second example is certainly slightly less clean than the first, but I do think the benefits from being explicit outweigh that. Just to point again at the
node:anddeno:prefixes -- I think it's a convention that is really familiar to developers across node, deno and bun at this point.All this said, I do want to point out that import assertions could also be a very reasonable way to solve this as well, it's just a bit more verbose.
lukewagner commentedon Dec 15, 2025
Yes, but I think we do have this distinction:
wasi:filesystemvsfilesystem.wasiis not a unit of functionality in the same way thatnodeis not;fs/filesystemare, and so we are symmetric to the JS convention here.Since
wasi:,my-open-source-runtime:andmy-company-platform:are serving symmetric roles tonode:/deno:/bun:, I think we are indeed leaning into this convention.vados-cosmonic commentedon Dec 15, 2025
wasiin this case feels like an arbitrary name like an org, which people currently express in JS packages normally with imports like@company/package-- namespaces map more to node's (really NPMs) idea of packages for me.The owner of a package doesn't say anything about where the import actually comes from/any platform specific details. In fact, the implicit "protocol" of an import like
@company/packageisnode:/npm:-- those prefixes/protocols are clearly different, they say something about where the import actually comes from.I think
wasm:is a lot closer tonode:anddeno:thanmy-company-platform:is --my-company-platformis a collection of interfaces that just happens to be named that way, thewasm:prefix conveys that whatever comes after is managed by WebAssembly tooling/is resolvable in ways that relate to Wasm.I don't think
my-open-source-runtime:ormy-company-platform:do enough to establish intuition -- it says nothing about where the import comes from, i.e. what you should expect to fill the import in, in the normal case. I just simply don't see how these are equivalent tonode:/deno:/bun;, one prefix says something about where the dependency comes from (via convention) and the other is just a name that could be anything.On JS platforms there is usually a default for where non-path names come from, whether in the browser or on the server. Introducing a new kind of non-path import name that is not resolved by either traditional platform without delineating it in any way seems like it will make it easy to be confused.
In general, the right answer for any import is always "whatever the resolver wants, if you have one" -- but being explicit about the protocol adds clarity IMO.
lukewagner commentedon Dec 15, 2025
I don't think so: packages are implementations;
wasi:imports (unless virtualized) come from the host, just likenode:anddeno:. Note that when a component withwasi:imports runs in a browser by implementing thewasi:imports in JS, you're in the virtualization case where JS+Web APIs are playing the role of host.Again, I don't think so: a package provides the implementation, should you choose to use it. Instead,
node:-,deno:- andwasi:-prefixed names only name an interface, not an implementation; as evidenced by lots of JS runtimes adding "node compat", there can be many distinct implementations ofnode:-prefixed imports (again, unlike packages where the name selects the implementation).node:anddeno:name entities (companies/projects) that make coordinated decisions to add functions to their respective namespaces, symmetric towasi:ormy-company:in WIT. In contrast,wasm:is just an implementation technology that we want (via WIT/bindgen) to be usable without knowing it's wasm! Thus awasm:prefix would be more like, to your earlier point, an import assertion;wasm:certainly does not name an entity with coordinated decision-making.IIUC, there's already 3 categories of imports in the JS world:
namespace:-prefixed imports expected to be implemented by the host (or, as the exceptional case, virtualized by someone else to fill in for the host)Given this,
wasi:- andmy-open-source-runtime:-prefixed imports fit naturally into case 3.vados-cosmonic commentedon Dec 23, 2025
So I'm not sure I agree with this statement -- for lots of reasons:
Unless I'm missing something, an interface in a package can represent a specific shape of implementation but semantically (of course) there's no protection there. Sure, if we think of all the possible interfaces and packages that can be accessed under a namespace we could think of it as one implementation, but it's unbounded/a wide open set, type wise.
Sorry getting back to this very late -- I think there's still a difference here between an identifier like
node:and one likemy-company:versuswasm:, but I think what would make this more concrete and easier to not just be a style preference is how easy it is to build tooling that makes the development process smooth.If it turns out to be easy to build tooling in NodeJS and the browser without any indication of what is/isn't a wasm import then that's great -- but primarily I added this issue because it seemed like it would become a problem trying to mix regular tracable imports with ones that are supposed to be provided due to a dependency in WIT.
If it doesn't play out to be difficult, then I'm certainly happy to not have a
wasm:*prefix -- that said if it does, the risk is that we make life slightly more difficult for everyone that wants to write bundler tooling which doesn't seem like a good default. Some of this also depends on the recognition of awasmexport similar to abrowserstyle export which is also necessary if we want people to be able to "just" use existing packages.lukewagner commentedon Dec 23, 2025
I agree! I was specifically making this same point in reply to your preceding comment which said:
So then sounds like we agree that WIT namespaces are not like packages ( = implementations).
So to be clear, I wasn't saying a namespace is an interface either. My point was that
<interfacename>imports (which happen to be prefixed by a namespace but also include a package and interface name) are analogous to JS imports that are implemented by the host runtime (which also happen to be prefixed by anode:ordeno:or other namespace , but also include other stuff in the name too). Thus, from a JS guest's POV, they are analogous and so I don't think they need to be in a separate syntactic category unless we can come up with some concrete toolchain use case that demands it.vados-cosmonic commentedon Jan 28, 2026
Something I just realized, it looks like
wasm:is going to get used as a import prefix by things like the js-string-imports proposal:Another reason we definitely can't have
wasm:as a prefix (putting aside the discussion of whether there should be any prefix at all!)