By default, Emscripten creates a module which can be used from both Node.JS and the browser, but it has the following issues:
- The module pollutes the global namespace
- The module is created with the name
Module(in my case, I requirestreamingPercentiles) - The module cannot be loaded by some module loaders such as require.js
While the above issues can (mostly) be corrected by using –s MODULARIZE=1, it changes the semantics of the resulting JavaScript file, as the module now returns a function rather than an object. For example, code which previously read var x = new Module.Klass() would become var x = new Module().Klass(). I found this semantic change unacceptable, so I decided to abandon Emscripten’s -s MODULARIZE=1 option in favor of hand-crafting a UMD module.
I determined that the most appropriate pattern for my use case was the no dependencies pattern from UMD’s templates/returnExports.js. Applied to an Emscripten module, and using the default module name streamingPercentiles, the stanzas look like the following:
umdprefix.js:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
// streamingPercentiles is the 'default' name of the module
root.streamingPercentiles = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
umdsuffix.js:
return Module;
}));
While I might be able to use Emscripten’s --pre-js and --post-js“s options to prepend and append the above JavaScript files, these options do not guarantee in all cases that the above JavaScript files will be first and last. Therefore, I decided to prepend and append the JavaScript manually.
As my build system is CMake based, I needed to change change the compilation process to generate an intermediate file streamingPercentiles-unwrapped.v1.js, and then use some CMake magic to prepend and append the above JavaScript files:
add_executable(streamingPercentiles-unwrapped.v1.js ${STMPCT_JS_SRC})
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/concat.cmake "
file(WRITE \${DST} \"\")
file(READ \${SRC1} S1)
file(APPEND \${DST} \"\${S1}\")
file(READ \${SRC2} S2)
file(APPEND \${DST} \"\${S2}\")
file(READ \${SRC3} S3)
file(APPEND \${DST} \"\${S3}\")
")
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/streamingPercentiles.v1.js
COMMAND ${CMAKE_COMMAND} -D SRC1=${CMAKE_CURRENT_SOURCE_DIR}/umdprefix.js
-D SRC2=${CMAKE_CURRENT_BINARY_DIR}/streamingPercentiles-unwrapped.v1.js
-D SRC3=${CMAKE_CURRENT_SOURCE_DIR}/umdsuffix.js
-D DST=${CMAKE_CURRENT_BINARY_DIR}/streamingPercentiles.v1.js
-P ${CMAKE_CURRENT_BINARY_DIR}/concat.cmake
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/umdprefix.js ${CMAKE_CURRENT_BINARY_DIR}/streamingPercentiles-unwrapped.v1.js ${CMAKE_CURRENT_SOURCE_DIR}/umdsuffix.js)
With the above code, all of the original three issues are fixed without any semantic changes for users.