Importing and exporting


In the MVP, only functions, global variables, table and memory can be imported or exported.

All imports include two opaque names: a module name and an import name. When organizing the imported object, the first level should be an object containing properties that might be imported. To some extent, it's like the role that exports of WebAssembly.Instance plays.

For example, if there's a foo module:

(module
    (func $foo1 (export "foo1") (result i32)
        i32.const 1
    )
    (func $foo2 (export "foo2") (result i32)
        i32.const 2
    )    
)

Here you can see the other fashion when exporting. Just write export when defining a function. In fact, this module doesn't invoke any function so function names can be ignored.

If the other module requires exported functions from the foo module:

(module
    (import "env" "log" (func $log (param i32)))
    (import "foo" "foo1" (func $foo1 (result i32)))
    (import "foo" "foo2" (func $foo2 (result i32)))
    (func $main
        call $foo1
        call $log
        call $foo2
        call $log
    )
    (start $main)
)

At the time of writing, browsers don't integrate loading and initializing WebAssembly modules automatically so you have to do it by yourself.

(async () => {
    const foo = await WebAssembly.instantiateStreaming(fetch('foo.wasm'));
    const importObj = {
        env : {
            log(n) {
                console.log(n);
            }
        },
        foo : foo.instance.exports
    };
    WebAssembly.instantiateStreaming(fetch('program.wasm'), importObj);
})();

Of course, you can write code like above because “program.wasm” imports functions from the foo module. The problem here is, how to know what a module imports in advance?

WebAssembly.Module has an imports function which accepts WebAssembly.Module and returns import information declared in a module. The returned value is an array. Each element is an object containing kind, module and name properties. You can know what kind of the imported object, such as 'function', the module name and import name.

You can use WebAssembly.Module.imports to rewrite the above program as follow:

function moduleNames(mod, importObj) {
    return Array.from(
        new Set(
            WebAssembly.Module.imports(mod)
                              .map(impt => impt.module)
                              .filter(name => !(name in importObj))
        )
    );
}

(async () => {
    const importObj = {
        env : {
            log(n) {
                console.log(n);
            }
        }
    };

    const progModule = await WebAssembly.compileStreaming(fetch('program.wasm'));
    const names = moduleNames(progModule, importObj);
    const results = await Promise.all(
        names.map(name => WebAssembly.instantiateStreaming(fetch(`${name}.wasm`)))
    );

    for(let i = 0; i < names.length; i++) {
        importObj[names[i]] = results[i].instance.exports;
    }

    WebAssembly.instantiate(progModule, importObj);
})();

If you want to know information about exported objects from a module, WebAssembly.Module provides an exports function.

Of course, the example here is simple. Dependencies between modules are more complex. Modern browsers might solve import and export problems in the future. If not, there might be module loader libraries to do things as above.