facebook / create-react-app

Set up a modern web app by running one command.
https://create-react-app.dev
MIT License
102.56k stars 26.79k forks source link

@next react-scripts(webpack.config.js) to resolve `.wasm` extension? #4912

Open ins429 opened 6 years ago

ins429 commented 6 years ago

I was curious if there was any reason .wasm extension was left out from resolve.extensions list?

Webpack also default to include .wasm.

It'd be nice to start using/experimenting wasm from create-react-app!

thomashorrobin commented 5 years ago

@ins429 as of CRA 2.0 you can add a rule to module.rules which might achieve what you're talking about, I've been working on a simular project here

iansu commented 5 years ago

I don't think it was left out or removed intentionally. @Timer do you see any reason not to resolve .wasm?

Timer commented 5 years ago

I dunno, are we supporting wasm yet? We need tests before we turn it on.

pd4d10 commented 5 years ago

It seems work for me to simply add wasm to resolve.extensions in webpack config, and exclude it from fallback.

PR here: https://github.com/facebook/create-react-app/pull/5924

CI tests fail at fixtures/typescript-advanced/index.test.js (link). Not quite sure where the problem is. Try to fix it later

Not finished. Please see discussions below

0xcaff commented 5 years ago

@pd4d10 It doesn't seem that just adding wasm to resolve.extensions works.

I've been trying to implement this in #6049 but am having some issues.

Async Import

It seems that wasm modules need to be imported asynchronously. This makes sense because the recommended way to load a WASM module is async (WebAssembly.instantiateStreaming).

I'm not sure how to make a jest transformer which requires being imported with an async import(). Any ideas? Jest also doesn't seem to be using my transformer with an async import.

Imports

The second argument to WebAssembly.instantiateStreaming is an imports object. Where should this come from? All examples I've seen of using WebAssembly and webpack either use it explicitly (handle loading in application) or don't use it at all.

Interface

I'm kinda confused about the interface which should be exposed. When you do

import * as wasm from './add.wasm';

What wasm be? The exports from ./add.wasm? A function which takes importObjects and returns an instance like wasm-loader? Should this import be an import()?

0xcaff commented 5 years ago

Hmm, it seems WASM will have some kind of esmodules support in the future, but it doesn’t yet. https://github.com/WebAssembly/proposals/issues/12

For now we should probably just use wasm-loader. What do you think @iansu ? Should we wait for esmodules + wasm or use wasm loader now with some kind of experimental wasm extension (‘.wasm.expirmental’)?

heyimalex commented 5 years ago

Yeah, it's weird. I got wasm support working in dev and build though react-app-rewired with this change:

module.exports = function override(config, env) {
  config.resolve.extensions.push(".wasm");

  config.module.rules.forEach(rule => {
    (rule.oneOf || []).forEach(oneOf => {
      if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) {
        // Make file-loader ignore WASM files
        oneOf.exclude.push(/\.wasm$/);
      }
    });
  });

  return config;
};

But then importing is really strange. You need to use the async import syntax at some point, and it won't work if you don't. But where you import is kind of up to you, as long as the wasm file is a transitive dependency of your async imported code. At that point, the imported wasm file acts as a WebAssembly.Instance that's been instantiated with the exports of the module it was imported in.

This was all pretty unintuitive to me, but maybe I just expected less magic?

The code I was trying to run was rust built with wasm-pack, so even though I don't understand why it works it seems like webpack and wasm-pack both agree on the es modules format at least. The trouble with using wasm-loader is that it would break code that expects it to work this way. There aren't a lot of wasm packages on npm, but they've got to standardize on an expected loader at some point.

I honestly am not an expert here, but it seems like the ecosystem is moving quick, so committing to a specific wasm loader now might be premature? Even though I really want this!

shayc commented 5 years ago

It's tricky using Wasm in Webpack, @surma from Google created an interesting example: https://gist.github.com/surma/b2705b6cca29357ebea1c9e6e15684cc

I managed to get it working, but I ejected CRA I'm going to fork react-scripts and add support to my own project as well.

Is it interesting for Create React App project? I can make a Pull Request.

heyimalex commented 5 years ago

@shayc You can hold off on making a PR; I really think we should wait for some stabilization in the library ecosystem since by and large people won't be writing their own wasm, they'll be importing it from npm. Today the rust toolchain makes code that's pretty webpack friendly, but I think that go and llvm emit wasm directly and expect you to call WebAssembly.instantiateStreaming. You can handle that today in CRA by putting your wasm in public and fetching it, which is why I care more about importing from external packages.

I might be wrong though! I'm going to take some time this weekend to do a survey of the current state of things.

heyimalex commented 5 years ago

Ok, I did some reading and my opinion is pretty much the same; hold off for a while until this stabilizes more. The most relevant info I could find is the WASM ES module proposal and it's only stage 2. Until then, users can use the imperative js apis with their wasm files saved in public.

tocttou commented 4 years ago

Any updates on this?

ianschmitz commented 4 years ago

See #7911.

tocttou commented 4 years ago

Looks like ejecting is the only way to use wasm with cra. Thanks.

alojzmilicevic commented 4 years ago

Is there no way of making it work by overriding config in config-overrides?

dlech commented 4 years ago

From what I have seen in other discussions, it seems like the create-react-app philosophy is to ignore the fact that it uses webpack internally - in other words, webpack features should not be relied upon. However, webpack includes a match for .wasm in defaultRules which does not magically work for all .wasm files.

For example, when using a .wasm file created with Emscripten (https://github.com/webpack/webpack/issues/7352) in a create-react-app project, we get the error:

Failed to compile.

./src/sagas/mpy-cross.wasm
Module parse failed: magic header not detected
File was processed with these loaders:
* ./node_modules/file-loader/dist/cjs.js
You may need an additional loader to handle the result of these loaders.
Error: magic header not detected

So it would be nice if create-create-react app could disable this default rule in webpack so users can handle .wasm files however they like without webpack interfering.

As a workaround, I changed the file extension of the .wasm file to .emcwasm so that webpack just treated it like a normal binary file instead of trying to load it as a JavaScript module. (And had to configure the web server to serve .emcwasm files with the application/wasm MIME type.)

parthopdas commented 3 years ago

As a workaround, I changed the file extension of the .wasm file to .emcwasm so that webpack just treated it like a normal binary file instead of trying to load it as a JavaScript module.

@dlech what would people do without people like you ;) thank you!

jdbertron commented 2 years ago

Regarding the application for react-app-rewired, since the oneOf rules now have multiple loaders, this seems to work better to exclude wasm from the file loader.

 config.module.rules.forEach(rule => {
      (rule.oneOf || []).forEach(oneOf => {
         (oneOf.use || []).forEach(use => {
            if (use.loader && use.loader.indexOf("file-loader") >= 0) {
              // Make file-loader ignore WASM files
              if (oneOf.exclude) 
                oneOf.exclude.push(/\.wasm$/);
             else 
                oneOf.exclude = [/\.wasm$/];
            }
          });
      });
    });