How to generate ES6 modules with Cheerp?

/opt/cheerp/bin/clang++ yourCode.cpp -o yourModule.js -O2 -cheerp-make-module=es6

This generally generates two files: yourModule.js and yourModule.wasm that togheter implement whatever logic or functionality of the code base you are compiling.

How to use ES6 modules generated by Cheerp?

import instantiateFunction from './yourModule.js'

instantiateFunction().then(module => 
	{
		console.log("Module has been instantiated!");
		module.someJSExportedFunc();
	}
);

Or using top level await:

import instantiateFunction from './yourModule.js'

var module = await instantiateFunction();
console.log("Module has been instantiated!");
module.someJSExportedFunc();

There are a couple of important things to notice:

  • Cheerp ES6 modules export an unnamed function, in the import directive you can name it as you prefer
  • This function once called will return a Promise that will resolve when the module has been instantiated, and will return an object with the JSExported function and classes as properties
  • The instantiation function has to be called only once. Any additional call is Undefined Behaviour (in the current implementation it would perform initialization of C++ state twice, so the logic integrity of the program will be compromised, but do not rely on any specific behaviour since it might change in future versions).

Why using ES6 modules with Cheerp?

In regular JavaScript a script has no knowledge of its own location, so all information about location of a file are relative to the root of the server. This means that for regular Cheerp compilation you will have to know where a file is located in advance (and provide this information at compile time via -cheerp-secondary-output-file=... and -cheerp-secondary-output-path=...). Modules offer a more manageable solution: the default will be having the WebAssembly file in the same folder of the JavaScript one, compilation arguments will overwrite this, and advanced options allows even to provide at runtime the location or content of the WebAssembly file.

How to use ES6 modules in a Web page?

<script type="module" src="someES6Module.js"></script>

In the case of Cheerp generated modules, where the instantiation logic is necessary, it could be done directly from the HTML like:

<script type="module">
	import instantiateFunction from './yourModule.js'

	instantiateFunction().then(module => 
		{
			console.log("Module has been instantiated!");
			module.someJSExportedFunc();
		}
	);
</script>

or by wrapping the instantiation logic inside a module, and then simply adding a line including the initializing code like:

<script type="module" src="wrapperForYourModule.js"></script>

What if you want to use the [[cheerp::jsexport]]-ed capabilities from regular JavaScript code?

<script type="module">
	//This is a JavaScript Module, so you can call import
	import instantiateFunction from './yourModule.js'

	window.modulePromise = instantiateFunction();
</script>
<script>
	//This is regular JavaScript
	window.modulePromise.then(module =>
		{
			console.log("Module has been instantiated!");
			module.someJSExportedFunc(); //<---- functionality is now available from "regular" JavaScript
		}
	);
</script>

What should I know about ES6 modules?

The basic needed to understand the behaviour of modules are:

  • modules code is executed in a separate realm/scope, meaning that the global namespace will not be polluted
  • export directives allow module to expose JavaScript function, object or classes as an interface
  • import directives allow external code to access the expored functions
  • modules can be imported only by other modules
  • modules initialization code is only ever executed once (even if imported multiple times) For plenty of background and technical information around ES6 modules we reccomend MDN’s guide of V8’s guide on JavaScript modules.

Modules support

Check MDN’s compatibility data. Modules used in the main thread are widely supported by all modern browsers (but not by Internet Explorer). More recent work on top level await inside modules is being carried on, and depending on your specific support matrix it could already be used, while the possibility of using Modules inside WebWorker is not complete yet (at the time of writing, 2021).

Cheerp generated ES6 modules are also compatible with Node.js.

.js versus .mjs

In the examples I used the regular extension .js, but Cheerp is agnostic about file extensions, respecting whatever you specify as output file. Eg. -o someFile.js, -o someFile.mjs, -o someFile.someOtherExtension are all valid compiler options and will produce JavaScript files with the exact same content. It’s up to you/your project to pick a convention. Additional read on this: MDN’s guide or V8’s blog.

Cheerp’s ES6 Modules API

  • Module instantiating function has to be called exactly once
  • The module instantiating function takes either no arguments (so default are used) or an object with options to be passed to the instantiating logic. Currently supported parameters are:
    • absPath - the absolute Path of the secondary file (in the common case, the Wasm file) from the root of the page
    • buffer - a buffer containing the content of the secondary file (in the common case, the Wasm file)
  • The module instantiating function returns a Promise that will resolve to an object (possibly empty) wrapping the [[cheerp::jsexport]]-ed functions or classes.
  • Both static or dynamic imports are supported

These instantiation are all somehow equivalent:

  • instantiation with no arguments
    import instantiateFunction from './yourModule.js'
    instantiateFunction().then(module =>
      {
          console.log("Module has been instantiated!");
          module.someJSExportedFunc();
      });
    
  • instantiation with no absolute path
    import instantiateFunction from './yourModule.js'
    instantiateFunction({absPath:'/absolute/path/to/yourModule.wasm'}).then(module =>
      {
          console.log("Module has been instantiated!");
          module.someJSExportedFunc();
      });
    
  • instantiation with buffer
    import instantiateFunction from './yourModule.js'
    fetch('/absolute/path/to/yourModule.wasm').then(buffer => {
      instantiateFunction({buffer:buffer}).then(module =>
          {
              console.log("Module has been instantiated!");
              module.someJSExportedFunc();
          }
      });
    
  • instantiation with path relative to current module
    import instantiateFunction from './yourModule.js'
    instantiateFunction({absPath:new URL('/relative/path/to/yourModule.wasm', import.meta.url)}).then(module =>
      {
          console.log("Module has been instantiated!");
          module.someJSExportedFunc();
      });
    
  • dynamic instantiation with no arguments
    import('./yourModule.js').then(instantiateFunction =>
      instantiateFunction()).then(module =>
          {
              console.log("Module has been instantiated!");
              module.someJSExportedFunc();
          });
    

ES6 modules and top-level await

import instantiateFunction from './someFile.js'

var module = await instantiateFunction();
console.log("Module has been instantiated!");

export module;		// or destructuring the different exports

Why using ES6 Modules (in general)?

ES6 Modules main advantages are that they offer the possibility of using relative paths thanks to import.meta.url information. ES6 Modules also encode more clearly what interface a given piece of code offer, allowing post-processing tools and bundlers to do a better job.

More on this can be found on “Bundling non-js resources” article.

How to get started with Cheerp generated ES6 Modules?

If you project is already compiled with Cheerp, it should be just a matter of adding the command line flag -cheerp-make-module=es6 and then calling the instantiation function / integrating the module in your WebPage.

Questions or Feedback ?

Support for ES6 modules is stable in Cheerp 2.7. Any problem / clarification or suggestion on how to improve this to better serve your project could be done via our support channels(reach us via email or Gitter) or opening an Issue on GitHub.