-
Notifications
You must be signed in to change notification settings - Fork 10
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
RFC: Add support for local JS snippets in wasm-bindgen #6
Changes from all commits
1c57ae8
9fe2a6f
51c09bf
1c3e9ff
b83aabb
6e6eb0a
e6d588a
4d990bb
32952b5
02c4e1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,374 @@ | ||
- Start Date: 2018-01-08 | ||
- RFC PR: (leave this empty) | ||
- Tracking Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Add the ability for `#[wasm_bindgen]` to process, load, and handle dependencies | ||
on local JS files. | ||
|
||
* The `module` attribute can now be used to import files explicitly: | ||
|
||
```rust | ||
#[wasm_bindgen(module = "/js/foo.js")] | ||
extern "C" { | ||
// ... | ||
} | ||
``` | ||
|
||
* The `inline_js` attribute can now be used to import JS modules inline: | ||
|
||
```rust | ||
#[wasm_bindgen(inline_js = "export function foo() {}")] | ||
extern "C" { | ||
fn foo(); | ||
} | ||
``` | ||
|
||
* The `--browser` flag is repurposed to generate an ES module for the browser | ||
and `--no-modules` is deprecated in favor of this flag. | ||
|
||
* The `--nodejs` will not immediately support local JS snippets, but will do so | ||
in the future. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
The goal of `wasm-bindgen` is to enable easy interoperation between Rust and JS. | ||
While it's very easy to write custom Rust code, it's actually pretty difficult | ||
to write custom JS and hook it up with `#[wasm_bindgen]` (see | ||
[rustwasm/wasm-bindgen#224][issue]). The `#[wasm_bindgen]` | ||
attribute currently only supports importing functions from ES modules, but even | ||
then the support is limited and simply assumes that the ES module string exists | ||
in the final application build step. | ||
|
||
[issue]: https://github.com/rustwasm/wasm-bindgen/issues/224 | ||
|
||
Currently there is no composable way for a crate to have some auxiliary JS that | ||
it is built with which ends up seamlessly being included into a final built | ||
application. For example the `rand` crate can't easily include local JS (perhaps | ||
to detect what API for randomness it's supposed to use) without imposing strong | ||
requirements on the final artifact. | ||
|
||
Ergonomically support imports from custom JS files also looks to be required by | ||
frameworks like `stdweb` to build a macro like `js!`. This involves generating | ||
snippets of JS at compile time which need to be included into the final bundle, | ||
which is intended to be powered by this new attribute. | ||
|
||
# Stakeholders | ||
[stakeholders]: #stakeholders | ||
|
||
Some major stakeholders in this RFC are: | ||
|
||
* Users of `#[wasm_bindgen]` | ||
* Crate authors wishing to add wasm support to their crate. | ||
* The `stdweb` authors | ||
* Bundler (webpack) and `wasm-bindgen` integration folks. | ||
|
||
Most of the various folks here will be cc'd onto the RFC, and reaching out to | ||
more is always welcome! | ||
|
||
# Detailed Explanation | ||
[detailed-explanation]: #detailed-explanation | ||
|
||
This proposal involves a number of moving pieces, all of which are intended to | ||
work in concert to provide a streamlined story to including local JS files into | ||
a final `#[wasm_bindgen]` artifact. We'll take a look at each piece at a time | ||
here. | ||
|
||
### New Syntactical Features | ||
|
||
The most user-facing change proposed here is the reinterpretation of the | ||
`module` attribute inside of `#[wasm_bindgen]` and the addition of an | ||
`inline_js` attribute. They can now be used to import local files and define | ||
local imports like so: | ||
|
||
```rust | ||
#[wasm_bindgen(module = "/js/foo.js")] | ||
extern "C" { | ||
// ... definitions | ||
} | ||
|
||
#[wasm_bindgen(inline_js = "export function foo() {}")] | ||
extern "C" { | ||
fn foo(); | ||
} | ||
``` | ||
|
||
The first declaration says that the block of functions and types and such are | ||
all imported from the `/js/foo.js` file, relative to the current file and rooted | ||
at the crate root. The second declaration lists the JS inline as a string | ||
literal and the `extern` block describes the exports of the inline module. | ||
|
||
The following rules are proposed for interpreting a `module` attribute. | ||
|
||
* If the strings starts with the platform-specific representation of an absolute | ||
path to the cargo build directory (identified by `$OUT_DIR`) then the string | ||
is interpreted as a file path in the output directory. This is intended for | ||
build scripts which generate JS files as part of the build. | ||
|
||
* If the string starts with `/`, `./`, or `../` then it's considered a path to a | ||
local file. If not, then it's passed through verbatim as the ES module import. | ||
|
||
* All paths are resolved relative to the current file, like Rust's own | ||
`#[path]`, `include_str!`, etc. At this time, however, it's unknown how we'd | ||
actually do this for relative files. As a result all paths will be required to | ||
start with `/`. When `proc_macro` has a stable API (or we otherwise figure | ||
out how) we can start allowing `./` and `../`-prefixed paths. | ||
|
||
This will hopefully roughly match what programmers expect as well as preexisting | ||
conventions in browsers and bundlers. | ||
|
||
The `inline_js` attribute isn't really intended to be used for general-purpose | ||
development, but rather a way for procedural macros which can't currently today | ||
rely on the presence of `$OUT_DIR` to generate JS to import. | ||
|
||
### Format of imported JS | ||
|
||
All imported JS is required to written with ES module syntax. Initially the JS | ||
must be hand-written and cannot be postprocessed. For example the JS cannot be | ||
written with TypeScript, nor can it be compiled by Babel or similar. | ||
|
||
As an example, a library may contain: | ||
|
||
```rust | ||
// src/lib.rs | ||
#[wasm_bindgen(module = "/js/foo.js")] | ||
extern "C" { | ||
fn call_js(); | ||
} | ||
``` | ||
|
||
accompanied with: | ||
|
||
```js | ||
// js/foo.js | ||
|
||
export function call_js() { | ||
// ... | ||
} | ||
``` | ||
|
||
Note that `js/foo.js` uses ES module syntax to export the function `call_js`. | ||
When `call_js` is called from Rust it will call the `call_js` function in | ||
`foo.js`. | ||
|
||
### Propagation Through Dependencies | ||
|
||
The purpose of the `file` attribute is to work seamlessly with dependencies. | ||
When building a project with `#[wasm_bindgen]` you shouldn't be required to know | ||
whether your dependencies are using local JS snippets or not! | ||
|
||
The `#[wasm_bindgen]` macro, at compile time, will read the contents of the file | ||
provided, if any. This file will be serialized into the wasm-bindgen custom | ||
sections in a wasm-bindgen specific format. The final wasm artifact produced by | ||
rustc will contain all referenced JS file contents in its custom sections. | ||
|
||
The `wasm-bindgen` CLI tool will extract all this JS and write it out to the | ||
filesystem. The wasm file (or the wasm-bindgen-generated shim JS file) emitted | ||
will import all the emitted JS files with relative imports. | ||
|
||
### Updating `wasm-bindgen` output modes | ||
|
||
The `wasm-bindgen` has a few modes of output generation today. These output | ||
modes are largely centered around modules vs no modules and how modules are | ||
defined. This RFC proposes that we move away from this moreso towards | ||
*environments*, such as node.js-compatible vs browser-compatible code (which | ||
involves more than only module format). This means that in cases where an | ||
environment supports multiple module systems, or the module system is optional | ||
(browsers support es modules and also no modules) `wasm-bindgen` will choose | ||
what module system it thinks is best as long as it is compatible with that | ||
environment. | ||
|
||
The current output modes of `wasm-bindgen` are: | ||
|
||
* **Default** - by default `wasm-bindgen` emits output that assumes the wasm | ||
module itself is an ES module. This will naturally work with custom JS | ||
snippets that are themselves ES modules, as they'll just be more modules in | ||
the graph all found in the local output directory. This output mode is | ||
currently only consumable by bundlers like Webpack, the default output cannot | ||
be loaded in either a web browser or Node.js. | ||
|
||
* **`--no-modules`** - the `--no-modules` flag to `wasm-bindgen` is incompatible | ||
with ES modules because it's intended to be included via a `<script>` tag | ||
which is not a module. This mode, like today, will fail to work if upstream | ||
crates contain local JS snippets. | ||
|
||
* **`--nodejs`** - this flag to `wasm-bindgen` indicates that the output should | ||
be tailored for Node.js, notably using CommonJS module conventions. In this | ||
mode `wasm-bindgen` will eventually use a JS parser in Rust to rewrite ES | ||
syntax of locally imported JS modules into CommonJS syntax. | ||
|
||
* **`--browser`** - currently this flag is the same as the default output mode | ||
except that the output is tailored slightly for a browser environment (such as | ||
assuming that `TextEncoder` is ambiently available). | ||
|
||
This RFC proposes | ||
repurposing this flag (breaking it) to instead generate an ES module natively | ||
loadable inside the web browser, but otherwise having a similar interface to | ||
`--no-modules` today, detailed below. | ||
|
||
This RFC proposes rethinking these output modes as follows: | ||
|
||
| Target Environment | CLI Flag | Module Format | User Experience | How are Local JS Snippets Loaded? | | ||
|-------------------------|-------------|---------------|------------------------------------------|----------------------------------------------------------------------------------------------| | ||
| Node.js without bundler | `--nodejs` | Common.js | `require()` the main JS glue file | Main JS glue file `require()`s crates' local JS snippets. | | ||
| Web without bundler | `--browser` | ES Modules | `<script>` pointing to main JS glue file, using `type=module` | `import` statements cause additional network requests for crates' local snippets. | | ||
| Web with bundler | none | ES Modules | `<script>` pointing to main JS glue file | Bundler links crates' local snippets into main JS glue file. No additional network requests except for the `wasm` module itself. | | ||
|
||
It is notable that browser with and without bundler is almost the same as far | ||
as `wasm-bindgen` is concerned: the only difference is that if we assume a | ||
bundler, we can rely on the bundler polyfilling wasm-as-ES-module for us. | ||
Note the `--browser` here is relatively radically different today and as such | ||
would be a breaking change. It's thought that the usage of `--browser` is small | ||
enough that we can get away with this, but feedback is always welcome on this | ||
point! | ||
|
||
The `--no-modules` flag doesn't really fit any more as the `--browser` use case | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sounds rather strange: old browsers don't support ES6 modules, so we still need So it seems to me that the distinction isn't about "browser" vs "Node", it's about "no modules" vs "ES6 modules" vs "CommonJS modules". It should absolutely be possible to use ES6 modules with Node, and to use "no modules" with either Node or the browsers. So could you explain more about the motivations behind removing "no modules" and assuming that browsers have ES6 support? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'm personally more wary on this, expecially with Node and ES6 modules. AFAIK everyone agrees that ES6 modules should come to node but there hasn't been any consensus or real timeline to getting it added. The current We definitely have two axes here, though. One is Node vs browser environments (like where you get I'm hoping that we can pick a somewhat opinionated set of defaults and support. For example no modules on Node while possible I don't think would have many users. Similar for CommonJS in browsers I'm not really sure what the use case. Along these lines it seems that the --no-modules use case for browsers is becoming less useful and I'm not entirely certain where it'd be used. (I mentioned above as well, but for older browser compat it seems like you'd almost always use a bundler) This definitely isn't a strong motivation for removing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, so your perspective is basically "ES6 modules + bundler" is the replacement for That sounds reasonable to me, though I know some people won't like that. I agree that ES6 modules aren't ready for Node (and won't be anytime soon), I just don't want us to be locked into that sort of decision: we should be able to support ES6 modules in Node eventually (even if not right now). So, as you say, there are really two different axes here: runtime and module. Perhaps you're right and we can condense that down to one axis. Of course if we wanted to be really reductionistic we could only support one output format and rely upon bundlers like Webpack to handle all the intricacies of browser vs Node (some people do indeed use bundlers even on Node). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW I'm thinking we should continue this conversation below |
||
is intended to subsume that. Note that the this RFC proposes only having the | ||
bundler-oriented and browser-oriented modes supporting local JS snippets for | ||
now, while paving a way forward to eventually support local JS snippets in | ||
Node.js. The `--no-modules` could eventually also be supported in the same | ||
manner as Node.js is (once we're parsing the JS file and rewriting the exports), | ||
but it's proposed here to generally move away from `--no-modules` towards | ||
`--browser`. | ||
|
||
|
||
The `--browser` output is currently considered to export an initialization | ||
function which, after called and the returned promise is resolved (like | ||
`--no-modules` today) will cause all exports to work when called. Before the | ||
promise resolves all exports will throw an error when called. | ||
|
||
### JS files depending on other JS files | ||
|
||
One tricky point about this RFC is when a local JS snippet depends on other JS | ||
files. For example your JS might look like: | ||
|
||
```js | ||
// js/foo.js | ||
|
||
import { foo } from '@some/npm-package'; | ||
import { bar } from './bar.js' | ||
|
||
// ... | ||
``` | ||
|
||
As designed above, these imports would not work. It's intended that we | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had covered this extensively several months ago: rustwasm/team#92 (comment) You don't need to read them, I'll summarize it here. Basically, wasm-bindgen would look through the crate and find all files that end in When generating the output, it would then recreate the same folder structure and place the This has a few major advantages:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A compelling alternative! There's a few things though that make me wary of this, and I think overall I'm not personally convinced we should take the "search the whole crate" strategy:
Ideally though we could end up on a design which doesn't block this from ever happening. It seems reasonable to have as a configuration option at least! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That is true, that negates one of the advantages. Though I think the other advantages are still really good!
Oh, I hadn't considered that. That's a good point. If users manually specified the
I had put a lot of thought into that (several months ago), and I had a few solutions for that. They basically all involved putting each crate into a different subfolder, thus avoiding the namespace issue entirely. So, similar to your As for absolute imports, that's not a problem: ES6 modules and bundlers already don't support absolute imports (or at least, support them very poorly). And in order to publish your packages, you can't use absolute imports anyways. So there's no issue with mandating file-relative or crate-relative imports.
I hadn't thought too much about the actual implementation in wasm-bindgen. It does seem rather tricky, since you can't have top-level function calls. And given that it has issues with incremental compilation, maybe we should consider other alternatives instead. The only alternative I know of is to manually list There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that we'll likely end up in a position where you have to list the JS files to be included, but I like the idea of automatically namespacing everything and preserving file structure to support things like dynamic import. I don't think we'd want to disallow static absolute paths if we force Rust to use absolute paths for now, but with a JS parser we could rewrite to absolute paths (or at least with our hash prefix) and force dynamic imports to never use absolute paths. I do really like the UI though of "just pull in all JS", and I'm wondering if we should add a feature to Cargo to support this. It wouldn't be too too hard to say "Cargo I depend on this entire directory, anything added or changed here please rerun me" and that could be used to easily include a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we're using a different meaning for "absolute path"? To me, "absolute path" means an absolute path on the user's harddrive, which isn't acceptable since the filepath is outside of the crate (and thus it will break for anybody other than that specific user). Perhaps you mean something different? Absolute paths are basically never used (for the above reason), instead relative paths are the norm, so what do you mean by "we could rewrite to absolute paths"?
How can that be enforced? Dynamic imports are resolved at runtime, so it's impossible (in the general case) to figure out what file they are importing. That's why the "slurp up all the
That sounds like it could be a useful feature even outside of wasm-bindgen! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sorry, yes we are using the same term for different things! This is about local files importing other local files, so I'd imagine that we do something like this: import { foo } from './other-local.js'; // allowed, it's relative
import { bar } from '../other-local.js'; // allowed, it's relative
import { baz } from '/other-local.js'; // allowed, but we have to rewrite this import
import('./x.js'); // we'll make sure this works
import('/x.js'); // this will always fail at runtime, no way we can make this work In Rust code we'd have: #[wasm_bindgen(module = "./foo.js")] // disallowed, don't know how to implement today
extern {}
#[wasm_bindgen(module = "../foo.js")] // disallowed, don't know how to implement today
extern {}
#[wasm_bindgen(module = "/foo.js")] // allowed, rooted-at-the-crate-root absolute path
extern {} By "absolute" I mean "absolute but rooted at the crate root". I think how this all works though is somewhat orthogonal to whether we slurp up JS files or not. On one axis we have what all the paths are, and on another axis we have "do you list the files to include or do we automatically include?". I think on "how do handle paths" it's relatively unambiguous about what to do perhaps modulo minor tweaks. For "do we slurp up JS or not?" I'm proposing that we don't do it yet, but we leave ourselves an option in the future for doing so. (but also not doing a list-some-files-to-include for now) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had assumed that I think that it would be confusing and inconsistent to have different behavior with Also, using
Okay, glad we're on the same page now. I would personally call those "crate paths" or something. Maybe we should bikeshed the name a little bit, since it will show up in the docs?
Sure, that's fine. It's only needed for dynamic
This comment was marked as outdated.
Sorry, something went wrong. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm ok sorry, I may not be being clear. I'll try to respond to those points:
Sorry this is so confusing, I'm trying to both lay out a plan for what we should do now in this RFC with wasm-bindgen while also handle all of what you're saying and plan for the eventual support for all these features. I think that a lot is getting lost in the middle as it's not clear what phase of development is being discussed. For absolute paths and/or crate paths, we may want to figure out a different syntax if it's very rarely used in the rest of the JS ecosystem. We could try out like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We are in agreement here.
We are in disagreement here. I think I think the inconsistency between And I think that we shouldn't break the already existing conventions for
I understand and empathize with that viewpoint, but I don't agree with it (for the above reasons). In different circumstances, I would agree with it. I think that Especially because relative paths in I agree that it certainly would be nice if we could make the path system consistent between Rust and JS, but given the existence of |
||
explicitly say this is an initial limitation of this design. We won't support | ||
imports between JS snippets just yet, but we should eventually be able to do so. | ||
|
||
In the long run to support `--nodejs` we'll need some level of ES module parser | ||
for JS. Once we can parse the imports themselves it would be relatively | ||
straightforward for `#[wasm_bindgen]`, during expansion, to load transitively | ||
included files. For example in the file above we'd include `./bar.js` into the | ||
wasm custom section. In this future world we'd just rewrite `./bar.js` (if | ||
necessary) when the final output artifact is emitted. Additionally with NPM | ||
package support in `wasm-pack` and `wasm-bindgen` (a future goal) we could | ||
alexcrichton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
validate entries in `package.json` are present for imports found. | ||
|
||
### Accessing wasm Memory/Table | ||
|
||
JS snippets interacting with the wasm module may commonly need to work with the | ||
`WebAssembly.Memory` and `WebAssembly.Table` instances associated with the wasm | ||
module. This RFC proposes using the wasm itself to pass along these objects, | ||
like so: | ||
|
||
```rust | ||
// lib.rs | ||
|
||
#[wasm_bindgen(module = "/js/local-snippet.js")] | ||
extern { | ||
fn take_u8_slice(memory: &JsValue, ptr: u32, len: u32); | ||
} | ||
|
||
#[wasm_bindgen] | ||
pub fn call_local_snippet() { | ||
let vec = vec![0,1,2,3,4]; | ||
let mem = wasm_bindgen::memory(); | ||
take_u8_slice(&mem, vec.as_ptr() as usize as u32, vec.len() as u32); | ||
} | ||
``` | ||
|
||
```js | ||
// js/local-snippet.js | ||
|
||
export function take_u8_slice(memory, ptr, len) { | ||
let slice = new UInt8Array(memory.arrayBuffer, ptr, len); | ||
// ... | ||
} | ||
``` | ||
|
||
Here the `wasm_bindgen::memory()` existing intrinsic is used to pass along the | ||
memory object to the imported JS snippet. To mirror this we'll add | ||
`wasm_bindgen::function_table()` as well to the `wasm-bindgen` crate as an | ||
intrinsic to access the function table and return it as a `JsValue`. | ||
|
||
Eventually we may want a more explicit way to import the memory/table, but for | ||
now this should be sufficient for expressiveness. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
* The initial RFC is fairly conservative. It doesn't work with `--nodejs` out of | ||
the gate nor `--no-modules`. Additionally it doesn't support JS snippets | ||
importing other JS initially. Note that all of these are intended to be | ||
supported in the future, it's just thought that it may take more design than | ||
we need at the get-go for now. | ||
|
||
* JS snippets must be written in vanilla ES module JS syntax. Common | ||
preprocessors like TypeScript can't be used. It's unclear how such | ||
preprocessed JS would be imported. It's hoped that JS snippets are small | ||
alexcrichton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
enough that this isn't too much of a problem. Larger JS snippets can always be | ||
extracted to an NPM package and postprocessed there. Note that it's always | ||
possible for authors to manually run the TypeScript compiler by hand for these | ||
use cases, though. | ||
|
||
* The relatively popular `--no-modules` flag is proposed to be deprecated in | ||
favor of a `--browser` flag, which itself will have a breaking change relative | ||
to today. It's thought though that `--browser` is only very rarely used so is | ||
safe to break, and it's also thought that we'll want to avoid breaking | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do have concerns about changing the meaning of existing flags and whether or not we would need to do a breaking bump. I am not sure that this is "safe" (I have no idea and no data and I don't think you have any more data than I have!) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sorry meant to respond to this earlier, but you're spot on that I have very little data about this :) I'm curious to hear what others think about this, although I suspect you're right and that we'll need to make new flags instead of trying to repurpose existing flags. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This PR seems primarily focused on module resolution/loaders, so perhaps their names could reflect this better? For example, we could have something like
There is some precedent in TypeScript too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm the module resolution aspect looks like it's more worried about how to resolve the paths in file dependencies (aka the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, the concepts are fairly related though (especially with mjs in Node). Maybe
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They're similar yeah, but the flags proposed here are intended to capture the use cases that basically everyone seems to fall into, which is "browsers with bundlers", "browsers without bundlers", and "node" |
||
`--no-modules` as-is today. | ||
|
||
* Local JS snippets are required to be written in ES module syntax. This may be | ||
alexcrichton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
a somewhat opinionated stance, but it's intended to make it easier to add | ||
future features to `wasm-bindgen` while continuing to work with JS. The ES | ||
module system, however, is the only known official standard throughout the | ||
ecosystem, so it's hoped that this is a clear choice for writing local JS | ||
snippets. | ||
|
||
# Rationale and Alternatives | ||
[alternatives]: #rationale-and-alternatives | ||
|
||
The primary alternative to this system is a macro like `js!` from stdweb. This | ||
allows written small snippets of JS code directly in Rust code, and then | ||
`wasm-bindgen` would have the knowledge to generate appropriate shims. This RFC | ||
proposes recognizing `module` paths instead of this approach as it's thought to | ||
be a more general approach. Additionally it's intended that the `js!` macro can | ||
be built on the `module` directive including local file paths. The | ||
`wasm-bindgen` crate may grow a `js!`-like macro one day, but it's thought that | ||
it's best to start with a more conservative approach. | ||
|
||
One alternative for ES modules is to simply concatenate all JS together. This | ||
alexcrichton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
way we wouldn't have to parse anything but we'd instead just throw everything | ||
into one file. The downside of this approach, however, is that it can easily | ||
lead to namespacing conflicts and it also forces everyone to agree on module | ||
formats and runs the risk of forcing the module format of the final product. | ||
|
||
Another alternative to emitting small files at wasm-bindgen time is to instead | ||
alexcrichton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
unpack all files at *runtime* by leaving them in custom sections of the wasm | ||
executable. This in turn, however, may violate some CSP settings (particularly | ||
strict ones). | ||
|
||
# Unresolved Questions | ||
[unresolved]: #unresolved-questions | ||
|
||
- Is it necessary to support `--nodejs` initially? | ||
|
||
- Is it necessary to support local JS imports in local JS snippets initially? | ||
|
||
- Are there known parsers of JS ES modules today? Are we forced to include a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we simply want to support "JS files importing other JS files", I think a very minimal parser should be fine. If we want to support ES6 module concatenation, then we probably need a full parser. But as I said above, I think we should try to use existing solutions rather than making our own (unless there's a good reason to make our own). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One aspect I'm worried about is that we have to also parse global declarations like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want to support concatenation built-in to wasm-bindgen, then yeah we need a full parser. But if it's just parsing Actually, come to think of it, how will incremental building work with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My hope is to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see! So I guess that would also work for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK we don't actually have much reason to worry about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So how is that intended to work (especially with transitive crates)? I know that's going a bit beyond this RFC, but since it relates to dynamic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm I may be missing some context to the question, can you be a bit more specific about what you're curious will work? To try to answer ahead of time, though, you're right that In that sense the outer tools don't have direct access to transitive crates, but the macro can encode and provide all the information they should need to operate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's what I'm concerned about. In order to make things work correctly, it is necessary for So if Maybe we should move this conversation to another issue, since it seems to be getting quite off-topic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sorry my point is that I agree that transitive crates should always work, and the (moving to a separate issue is also fine by me |
||
full JS parser or can we have a minimal one which only deals with ES syntax? | ||
|
||
- How would we handle other assets like CSS, HTML, or images that want to be | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this goes far beyond supporting The typical solution is to import the CSS/HTML/whatever normally, and then let the bundler (e.g. Webpack/Parcel) handle it. That's not really a great solution, but it's the best solution found in the JS world. We should revisit this question much later, after we have more experience with the system. |
||
referenced by the final wasm 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.
I think we need to support ES6 parsing + concatenation to support this use case, and I think it's worth it to do so.
Perhaps not necessary in the MVP, but shortly after the MVP.
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's true yeah that this can be supported! I ended up realizing that a bit later when writing this and didn't come back to fully update this section.
I'm not certain though that we want to continue to support it even if we add an ES6 parser. I agree that technically we'll be empowered to support it with such a parser, but it's becoming less clear to me at least what the main use case is. It seems like this is largely mostly used for demo purposes which almost always can assume a newer browser anyway. Apart from that if you want browser compatibility then it seems like you almost for sure want a bundler and want to avoid
--no-modules
(as the module format is likely the least of the concerns at that point, e.g.TextEncoder
)Overall this may be a case where we just need to put more thought into wasm-bindgen's output format. I find it unfortunate that we have to choose from so many options, it feels like we're just inheriting a lot of issues with the existing JS ecosystem and having to deal with them all at once...
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 believe the primary use case of
--no-modules
is for people who want a fast lightweight solution without needing to deal with npm or Webpack or Node.In other words, people with Rust experience who want to avoid the JS ecosystem as much as possible.
There is some merit to that, since npm and Webpack are rather slow (and have a lot of dependencies).
I don't have particularly strong opinions on it, I've comfortably used Webpack for many years (it is slow though).
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.
FWIW I'm thinking we should continue this conversation below