I'm trying to transpose to vue.js this simple html page add.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<input type="button" value="Add" onclick="callAdd()" />
<script>
function callAdd() {
const result = Module.ccall('Add',
'number',
['number', 'number'],
[1, 2]);
console.log(`Result: ${result}`);
}
</script>
<script src="js_plumbing.js"></script>
</body>
</html>
which calls the Add function defined in add.c :
#include <stdlib.h>
#include <emscripten.h>
// If this is an Emscripten (WebAssembly) build then...
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#ifdef __cplusplus
extern "C" { // So that the C++ compiler does not rename our function names
#endif
EMSCRIPTEN_KEEPALIVE
int Add(int value1, int value2)
{
return (value1 + value2);
}
#ifdef __cplusplus
}
#endif
and converted to js_plumbing and js_plumbling.wasm files through the command:
emcc add.c -o js_plumbing.js -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s
ENVIRONMENT='web','worker'
In console of google chrome I get these errors:
GET http://localhost:8080/dist/js_plumbing.wasm 404 (Not Found) @ js_plumbing.js?2b2c:1653
Where in js_plumbing_js :
// Prefer streaming instantiation if available.
function instantiateAsync() {
if (!wasmBinary &&
typeof WebAssembly.instantiateStreaming === 'function' &&
!isDataURI(wasmBinaryFile) &&
typeof fetch === 'function') {
fetch(wasmBinaryFile, { credentials: 'same-origin' }).then(function (response) { // <---------------!!!
var result = WebAssembly.instantiateStreaming(response, info);
return result.then(receiveInstantiatedSource, function(reason) {
// We expect the most common failure cause to be a bad MIME type for the binary,
// in which case falling back to ArrayBuffer instantiation should work.
err('wasm streaming compile failed: ' + reason);
err('falling back to ArrayBuffer instantiation');
instantiateArrayBuffer(receiveInstantiatedSource);
});
});
} else {
return instantiateArrayBuffer(receiveInstantiatedSource);
}
}
In Google Chrome: createWasm @ js_plumbing.js?2b2c:1680
line 1680 of js_plumbing.js:
instantiateAsync();
in Google Chrome: eval @ js_plumbing.js?2b2c:1930
line 1930 of js_plumbing.js:
<pre><font color="#4E9A06">var</font> asm = createWasm();</pre>
And many other errors related to wasm :
https://drive.google.com/open?id=1-aY2Iae1BRPjiLsslQ9P5khUKzuVZJLm
https://drive.google.com/open?id=1tlhlp38XNXUp61Vc0pZagz8hWN9eCKpb
So... how should I modify the callAdd() method in Result.vue in order to correctly execute the Add function in js_plumbing.js and in js_plumbing.wasm files?
methods: {
callAdd() {
const result = Module.ccall('Add',
'number',
['number', 'number'],
[1, 2]);
console.log('Result: ${result}');
}
}
Testing this on my side, the issue appears to be that an error gets thrown when compiling the module due to the ENVIRONMENT flag. If you change the command line to the following, the module is generated and the web page runs:
emcc add.c -o js_plumbing.js -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s
ENVIRONMENT='web,worker'
Hi @cggallant Gerard!
I solved that error importing in this way: import * as js_plumbing from â./js_plumbingâ
and then
methods: {
callAdd() {
const result = js_plumbing.Module.ccall('Add', // <---------------
'number',
['number', 'number'],
[1, 2]);
console.log('Result: ${result}');
console.log(result);
}
}
Now Iâm facing this problem: âCannot read property âccallâ of undefinedâ :

But I compiled the add.c file, creating js_plumbing.js and js_plumbing.wasm files, with this command, which exports the methods âccallâ and âcwrapâ :
emcc add.c -o js_plumbing.js -s EXTRA_EXPORTED_RUNTIME_METHODS=[âccallâ,âcwrapâ] -s ENVIRONMENT=âwebâ,âworkerâ
In order to import the Emscripten code like an ES6 module, you'll need to compile the module with the -s EXPORT_ES6=1 -s MODULARIZE=1 flags:
emcc add.c -o js_plumbing.js -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1
Because the Modularize flag is used, you'll need to create an instance of the Module object before you can call into the module (it doesn't get downloaded and instantiated until you create an instance of the object):
import Module from './js_plumbing.js'
Module().then(myModule => {
const result = myModule.ccall('Add',
'number',
['number', 'number'],
[1, 2]);
console.log(`Result: ${result}`);
});
I "solved" through a sort of an hack, which I do not like at all.
This is the Result.vue file:
<template>
<div>
<p button @click="callAdd">Add!</p>
<p>Result: {{ result }}</p>
</div>
</template>
<script>
import * as js_plumbing from './js_plumbing'
import Module from './js_plumbing'
export default {
data () {
return {
result: null
}
},
methods: {
callAdd () {
const result = js_plumbing.Module.ccall('Add',
'number',
['number', 'number'],
[1, 2]);
this.result = result;
}
}
}
</script>
which is exactly the same as the one used before, as you can see.
The only thing I've done to make it working, is to add export to the definition of Module in js_plumbing.js :
js_plumbing.js
// Copyright 2010 The Emscripten Authors. All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License. Both these licenses can be
// found in the LICENSE file.
// The Module object: Our interface to the outside world. We import
// and export values on it. There are various ways Module can be used:
// 1. Not defined. We create it here
// 2. A function parameter, function(Module) { ..generated code.. }
// 3. pre-run appended it, var Module = {}; ..generated code..
// 4. External script tag defines var Module.
// We need to check if Module already exists (e.g. case 3 above).
// Substitution will be replaced with actual code on later stage of the build,
// this way Closure Compiler will not mangle it (e.g. case 4. above).
// Note that if you want to run closure, and also to use Module
// after the generated code, you will need to define var Module = {};
// before the code. Then that object will be used in the code, and you
// can continue to use Module afterwards as well.
export var Module = typeof Module !== 'undefined' ? Module : {};

But, as I said, I do not like this hack.
Any suggestions on how to make the Module exportable, thus importable, without manually adding 'export' in js_plumbing.js file?
@cggallant Gerard I'm trying to you use your elegant solution, but I'm encountering some problems.
I complied add.c , as you suggested, in this way:
emcc add.c -o js_plumbing.js -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s
ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1
Then, I modified Result.vue as follows:
<template>
<div>
<p button @click="callAdd">Add!</p>
<p>Result: {{ result }}</p>
</div>
</template>
<script>
import Module from './js_plumbing'
export default {
data () {
return {
result: null
}
},
methods: {
callAdd() {
Module().then(Module => {
const result = Module.ccall('Add',
'number',
['number', 'number'],
[1, 2]);
console.log(`Result: ${result}`);
this.result = result;
});
}
}
}
</script>
But I'm getting this error message:
Failed to compile.
./src/components/js_plumbing.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: Unexpected token, expected ( (3:25)
1 |
2 | var Module = (function() {
> 3 | var _scriptDir = import.meta.url;
| ^
4 |
5 | return (
6 | function(Module) {

Aside from this error message, I do not understand what myModule should be in
methods: {
callAdd() {
Module().then(myModule => {
const result = myModule.ccall('Add',
'number',
['number', 'number'],
[1, 2]);
console.log(`Result: ${result}`);
this.result = result;
});
}
}
}
I haven't used Vue before so I'm not sure where you'd place the variable but you only want to create an instance of the Module object, that was imported from the js_plumbing.js file, the once because it's creating a new instance of the WebAssembly module each time you do Module().
I called it myModule but you can call it whatever you'd like. Once you have that object, you can call into the module.
I'll see if I can get Vue going on my system to see if I can help you out better. Might be a few hours though, my brother and his family just arrived in town.
very kind of you @cggallant Gerard, thank you very much. Much appreciated.
As far as I understand, the error message regards the very first lines of js_plumbing.js created by the command emcc add.c -o js_plumbing.js -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1
var Module = (function() {
var _scriptDir = import.meta.url;
Update (Dec 30, 2019): I was able to get this working. I've updated the following with my results.
There's a USE_ES6_IMPORT_META flag that you can set to 0 when compiling the WebAssembly module that will use an older version of the import.meta.url line of code for systems that don't recognize the import style:
emcc add.c -o js_plumbing.js -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0
In my main.js file, I created a variable on the Vue object ($myModule) so that the module is only downloaded and initialized the once:
import Vue from 'vue';
import App from './App.vue';
Vue.config.productionTip = true;
Vue.prototype.$myModule = null; // Will hold the module's instance when loaded the first time
new Vue({
render: h => h(App)
}).$mount('#app');
In my controller, I check to see if $myModule is null and, if so, the module is loaded:
import Module from '../js_plumbing.js';
export default {
beforeCreate() {
if (this.$myModule === null) {
new Module().then(myModule => {
this.$myModule = myModule;
});
}
},
data() {
return {
result: null
}
},
methods: {
callAdd() {
this.result = this.$myModule.ccall('Add',
'number',
['number', 'number'],
[2, 3]);
}
}
};
This might be because I'm running on a Windows machine, Visual Studio, and just the development web server but, to get the content-type to work, I needed to adjust my vue.config.js file as follows:
const path = require('path');
const contentBase = path.resolve(__dirname, '..', '..');
module.exports = {
configureWebpack: config => {
config.devServer = {
before(app) {
// use proper mime-type for wasm files
app.get('*.wasm', function (req, res, next) {
var options = {
root: contentBase,
dotfiles: 'deny',
headers: {
'Content-Type': 'application/wasm'
}
};
res.sendFile(req.url, options, function (err) {
if (err) {
next(err);
}
});
});
}
}
}
}
I'm not sure that enclosing the Vue instance within the Module in main.js has no side-effects, since the vue instance created in main.js has global coverage on the entire webapp, not just the part related to wasm
It's now working on my machine. I've updated my comment above.
Hi @cggallant Gerard!
I asked help also in vue forum, and yesterday night, thanks to @AnthumChris , another solution was reached. ( https://forum.vuejs.org/t/wasm-how-to-correctly-call-a-webassembly-method-in-vue-js/83422/31 )
I compiled add.c in this way:
emcc add.c -o js_plumbing.js -s EXPORTED_FUNCTIONS="[â_Addâ]" -s
EXTRA_EXPORTED_RUNTIME_METHODS=[âccallâ,âcwrapâ] -s MODULARIZE=1
And, thanks to @AnthumChris , I modified Result.vue as follows:
Result.vue :
<template>
<div>
<p button @click="callAdd()">Add!</p>
<p>Result: {{ result }}</p>
</div>
</template>
<script>
import Module from './js_plumbing'
let instance = {
ready: new Promise(resolve => {
Module({
onRuntimeInitialized() {
instance = Object.assign(this, {
ready: Promise.resolve()
});
resolve();
}
});
})
};
export default {
data () {
return {
result: null
}
},
methods: {
callAdd() {
instance.ready.then(_ => {
console.log(instance._Add(1,2));
this.result = instance._Add(1,2)
});
}
}
}
</script>

@cggallant Gerard your solution works fine with Ubuntu 18.04.02 without needing to modify vue.config.js . Thank you very much for your kind help!!!

I do not understand why in Result.vue we need to create a hook beforeCreate() to check if $myModule is null and, if so, to load the module.
Doing in main.js Vue.prototype.$myModule = null; makes myModule null available for all Vue instances before creation of the vue instance (https://vuejs.org/v2/cookbook/adding-instance-properties.html) .
Actually, I tried to remove these lines from Result.vue:
beforeCreate () {
if (this.$myModule === null) {
new Module().then(myModule => {
this.$myModule = myModule;
});
}
},
And got this error message: Cannot read property 'ccall' of null

but I do not understand this requirement
My thought was that, rather than download and instantiate the module in main.js _(when the app first loads)_, the first component that needs it creates it. Once instantiated, the module is available to all components that need it.
Rather than having a global object, the following worked for me, where the component uses a local variable for the module instance:
import Module from '../js_plumbing.js';
let moduleInstance = null;
export default {
beforeCreate() {
new Module().then(myModule => {
moduleInstance = myModule;
});
},
data() {
return {
result: null
}
},
methods: {
callAdd() {
this.result = moduleInstance.ccall('Add',
'number',
['number', 'number'],
[2, 3]);
}
}
};
Now I do understand... thank you very much Gerard!
And all the Best for 2020!!
(in the next days I will keep reading your book "WebAssemby in Action"
@marcoippolito @cggallant Where do you place the wasm file? Is there a way to config its path in vue?
Most helpful comment
My thought was that, rather than download and instantiate the module in main.js _(when the app first loads)_, the first component that needs it creates it. Once instantiated, the module is available to all components that need it.
Rather than having a global object, the following worked for me, where the component uses a local variable for the module instance: