wasmerio / wasmer-js

Monorepo for Javascript WebAssembly packages by Wasmer
https://wasmerio.github.io/wasmer-js/
MIT License
907 stars 80 forks source link

How to use in browsers? #305

Open michaelfranzl opened 1 year ago

michaelfranzl commented 1 year ago

The README of v1.0.2 states that browsers are supported. However, neither the README nor the example directory contain usage examples for the browser. Using the best guess in the browser,

import { init, WASI } from '@wasmer/wasi';
await init()

gives the following error:

Library.esm.min.js:25 Uncaught (in promise) ReferenceError: Buffer is not defined
    at Z (Library.esm.min.js:25:15897)

This comes from https://github.com/wasmerio/wasmer-js/blob/8f3f221a5431eff5c5370f48d9189eba3547f451/lib.ts#L62 Indeed there is no Buffer global in browsers.

How to use @wasmer/wasi in the browser? If Buffer is supposed to be polyfilled, could you add this to the README and refer to a library which fulfills the required API? Thanks.

Jack-Edwards commented 1 year ago

I was probably about to encounter the same problem. I'd like to use this library in the browser as well.

corwin-of-amber commented 1 year ago

Hi! Currently the Wasmer-JS libraries rely on a bundler for browser integration. Webpack and Rollup work well. Notice #290 and the comments below about defining wasmer_wasi_js_bg.wasm as external; it is actually not needed as its content is inlined in Library.esm.min.js (as a data URL).

michaelfranzl commented 1 year ago

@corwin-of-amber When you say that it "relies on a bundler", do you mean that a bundler has to be configured to polyfill Buffer? Like from rollup-plugin-polyfill-node or @esbuild-plugins/node-modules-polyfill?

In contrast, in v0.12.0, Buffer was included in wasmer-js/wasi (imported from buffer-es6 and injected as a dependency): https://github.com/wasmerio/wasmer-js/blob/v0.12.0/packages/wasi/src/index.ts#L11

corwin-of-amber commented 1 year ago

Yes, exactly. I am pasting the contents of webpack.config.js from a hello-world project that I did to learn how to use it; if you find it useful then perhaps I can make a PR to add it to the WasmerJS docs.

const path = require('path');
const webpack = require('webpack');

module.exports = {
    name: 'hello',
    entry: './hello.js',
    mode: 'development',
    target: 'webworker',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'hello.js'
    },
    module: {
        rules: [
            {
                test: /\.wasm$/,
                type: 'asset/resource'
            }
        ]
    },
    externals: {
        'wasmer_wasi_js_bg.wasm': true
    },
    resolve: {
        fallback: {buffer: require.resolve('buffer/')}
    },
    plugins: [new webpack.ProvidePlugin({
        process: 'process/browser.js',
        Buffer: ['buffer', 'Buffer']
    })]
}

It is true that previous versions were bundled with Buffer. IMO this is now old-style; having your dependencies bundled hinders inter-operability esp. if types from the bundled package are part of the API. It makes passing the "right" objects quite tricky. Forcing the user to provide their own polyfills is certainly more cumbersome and goes against the OOP encapsulation concepts, but pragmatically is much more flexible.

cdecompilador commented 1 year ago

I'd also like to use the library on the browser, also note that in parcel doesn't work out of the box sadly

guregu commented 1 year ago

Here is an esbuild configuration I am using successfully in the browser: https://github.com/guregu/trealla-js/blob/9dfaf8b886033ea339cd36d005b2555091c56efa/make.cjs#L17-L19

The important bit:


const stdLibBrowser = require('node-stdlib-browser');

(async () => {
    await esbuild.build({
        entryPoints: ['index.js'],
// ...
        inject: [require.resolve('node-stdlib-browser/helpers/esbuild/shim')],
        define: {
            Buffer: 'Buffer'
        },
        plugins: [plugin(stdLibBrowser)]
Cloudef commented 1 year ago

After polyfilling buffer I'm getting Uncaught (in promise) Error: Failed to instantiate WASI: RuntimeError: JsValue(TypeError: import object field 'env' is not an Object

EDIT: After compiling in all the functions that weren't in wasi-libc ... would be nice if you could provide env through JS too in case you want to implement the functions there, now I get this:

Error while running start function: RuntimeError: JsValue(Object({"ptr":1122200}))

Works in wasmtime though :/

EDIT2: prob this issue https://github.com/wasmerio/wasmer/issues/2568 the app calls exit() It's dumb but this workaround works

var exitCode = 1;
try { exitCode = wasi.start(); } catch (e) {}
frog-o commented 1 year ago

One thing I hate( so far) is everything i can find requires vite Wich indirectly uses node. One of the cool things about wasm(if i not mistaken) looks like wasm should not need a bundler or anything other than a web browser ;i am reading wasm are binary(all though it could be text). I would like to use wapm without node i am surprised they have no instructions un using it with unpkg or skypack and no install IMHO it should need wasmer.

zees-dev commented 1 year ago

If you're using vite you can use this plugin to polyfill for node: https://github.com/sodatea/vite-plugin-node-stdlib-browser

// vite.config.ts
import { defineConfig } from 'vite';
import nodePolyfills from 'vite-plugin-node-stdlib-browser'

export default defineConfig({
  server: {
    port: 8080,
  },
  plugins: [nodePolyfills()]
});
vmx commented 12 months ago

After polyfilling buffer I'm getting Uncaught (in promise) Error: Failed to instantiate WASI: RuntimeError: JsValue(TypeError: import object field 'env' is not an Object

I had the same error. My problems were the imports. I've used:

const imports = {
  myfun: () => {
    console.log("my function")
  }
}

But when I looked at the WAT output (thanks to https://uptointerpretation.com/posts/webassembly-woes/, pointing to https://stackoverflow.com/questions/54598317/rust-wasm-module-not-found-error-cant-resolve-env-in, where the answer suggests running wasm2wat), I saw that myfun is imported under env.

So the correct import is:

const imports = {
  env: {
    myfun: () => {
      console.log("my function")
    }
  }
}

I've been using this from Rust, the extern there is just:

extern "C" {
    fn myfun();
}