Skip to content
This repository was archived by the owner on Aug 25, 2025. It is now read-only.
Prev Previous commit
Next Next commit
Reword output changes
  • Loading branch information
alexcrichton committed Jan 10, 2019
commit b83aabbe20ca8eeb1478bfada447f7bb70d43334
66 changes: 39 additions & 27 deletions text/006-local-js-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,46 +136,59 @@ 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. This PR
proposes repurposing the existing `--browser` flag, deprecating the
`--no-modules` flag, and canonicalizing the output in three modes. All
modes will operate as follows:
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.
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
Copy link

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.

Copy link
Contributor Author

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...

Copy link

@Pauan Pauan Jan 14, 2019

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).

Copy link
Contributor Author

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

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. As a result, the `--no-modules` flag will
essentially be deprecated as a result of this change.
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. This mode
will, in the immediate term, fail if the crate graph includes any local JS
snippets. This failure mode is intended to be a temporary measure. Eventually
it should be relatively trivial with a JS parser in Rust to rewrite ES syntax
of locally imported JS modules into CommonJS syntax.
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
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.

In summary, the three modes of output for `wasm-bindgen` will be:

* Bundler-oriented (no flags passed) intended for consumption by bundlers like
Webpack which consider the wasm module a full-fledged ES module.
This RFC proposes rethinking these output modes as follows:

* Node.js-oriented (`--nodejs`) intended for consumption only in Node.js itself.
| 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. |

* Browser-oriented without a bundler (`--browser`) intended for consumption in
any web browser supporting JS ES modules that also supports wasm. This mode is
explicitly not intended to be usable with bundlers like webpack.
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
Copy link

Choose a reason for hiding this comment

The 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 --no-modules for the foreseeable future. In addition, Node will be adding in support for ES6 modules.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 mjs implementation feels pretty bad to use and definitely seems like something that we shouldn't be targeting.

We definitely have two axes here, though. One is Node vs browser environments (like where you get TextEncoder from). The other is the module system used (ES, none, CommonJS, etc). The "ideal" of "ES6 everywhere" definitely can't be a reality today largely because of Node I think. Apart from that we're sort of just doing the best we can.

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 --no-modules though. I mentioned above too, but we can definitely keep supporting it. That may be the best for now while we continue to wait for the module story in JS to settle...

Copy link

Choose a reason for hiding this comment

The 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 --no-modules?

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).

Copy link
Contributor Author

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

is intended to subsume that. Note that the this RFC proposes only having the
Expand All @@ -186,12 +199,11 @@ 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`.

For some more detail about the `--browser` output, it's intended to look from
the outside like `--no-modules` does today. When using `--browser` a single
`wasm_bindgen` function will be exported from the module. This function takes
either a path to the wasm file or the `WebAssembly.Module` itself, and then it
returns a promise which resolves to a JS object that has the full wasm-bindgen
interface on it.

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

Expand Down