diff --git a/text/006-local-js-dependencies.md b/text/006-local-js-dependencies.md new file mode 100644 index 0000000..8988347 --- /dev/null +++ b/text/006-local-js-dependencies.md @@ -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 `