Skip to content

Commit

Permalink
examples: Add module embedding example
Browse files Browse the repository at this point in the history
This closes Issue #22.
  • Loading branch information
moztcampbell committed Sep 1, 2020
1 parent b27cd35 commit 7bdcc60
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/README.md
Expand Up @@ -56,3 +56,4 @@ in the `meson.build` file, and add a description of it to this
lazy property resolution.
Use this in cases where defining properties and methods in your class
upfront might be slow.
- **modules.cpp** - Example of how to load ES Module sources.
121 changes: 121 additions & 0 deletions examples/modules.cpp
@@ -0,0 +1,121 @@
#include <map>
#include <string>

#include <jsapi.h>

#include <js/CompilationAndEvaluation.h>
#include <js/Modules.h>
#include <js/SourceText.h>

#include "boilerplate.h"

// This examples demonstrates how to compile ES modules in an embedding.
//
// See 'boilerplate.cpp' for the parts of this example that are reused in many
// simple embedding examples.

// Translates source code into a JSObject representing the compiled module. This
// module is not yet linked/instantiated.
static JSObject* CompileExampleModule(JSContext* cx, const char* filename,
const char* code) {
JS::CompileOptions options(cx);
options.setFileAndLine(filename, 1);

JS::SourceText<mozilla::Utf8Unit> source;
if (!source.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed)) {
return nullptr;
}

// Compile the module source to bytecode.
//
// NOTE: This generates a JSObject instead of a JSScript. This contains
// additional metadata to resolve imports/exports. This object should not be
// exposed to other JS code or unexpected behaviour may occur.
return JS::CompileModule(cx, options, source);
}

// Maintain a registry of imported modules. The ResolveHook may be called
// multiple times for the same specifier and we need to return the same compiled
// module.
//
// NOTE: This example assumes only one JSContext/GlobalObject is used, but in
// general the registry needs to be distinct for each GlobalObject.
static std::map<std::u16string, JS::PersistentRootedObject> moduleRegistry;

// Callback for embedding to provide modules for import statements. This example
// hardcodes sources, but an embedding would normally load files here.
static JSObject* ExampleResolveHook(JSContext* cx,
JS::HandleValue modulePrivate,
JS::HandleString spec) {
// Convert specifier to a std::u16char for simplicity.
JS::UniqueTwoByteChars specChars(JS_CopyStringCharsZ(cx, spec));
if (!specChars) {
return nullptr;
}
std::u16string filename(specChars.get());

// If we already resolved before, return same module.
auto search = moduleRegistry.find(filename);
if (search != moduleRegistry.end()) {
return search->second;
}

JS::RootedObject mod(cx);

if (filename == u"a") {
mod = CompileExampleModule(cx, "a", "export const C1 = 1;");
if (!mod) {
return nullptr;
}
}

// Register result in table.
if (mod) {
moduleRegistry.emplace(filename, JS::PersistentRootedObject(cx, mod));
return mod;
}

JS_ReportErrorASCII(cx, "Cannot resolve import specifier");
return nullptr;
}

static bool ModuleExample(JSContext* cx) {
JS::RootedObject global(cx, boilerplate::CreateGlobal(cx));
if (!global) {
return false;
}

JSAutoRealm ar(cx, global);

// Register a hook in order to provide modules
JS::SetModuleResolveHook(JS_GetRuntime(cx), ExampleResolveHook);

// Compile the top module.
JS::RootedObject mod(
cx, CompileExampleModule(cx, "top", "import {C1} from 'a';"));
if (!mod) {
boilerplate::ReportAndClearException(cx);
return false;
}

// Resolve imports by loading and compiling additional scripts.
if (!JS::ModuleInstantiate(cx, mod)) {
boilerplate::ReportAndClearException(cx);
return false;
}

// Execute the module bytecode.
if (!JS::ModuleEvaluate(cx, mod)) {
boilerplate::ReportAndClearException(cx);
return false;
}

return true;
}

int main(int argc, const char* argv[]) {
if (!boilerplate::RunExample(ModuleExample)) {
return 1;
}
return 0;
}
1 change: 1 addition & 0 deletions meson.build
Expand Up @@ -70,3 +70,4 @@ executable('cookbook', 'examples/cookbook.cpp', 'examples/boilerplate.cpp', depe
executable('repl', 'examples/repl.cpp', 'examples/boilerplate.cpp', dependencies: [spidermonkey, readline])
executable('tracing', 'examples/tracing.cpp', 'examples/boilerplate.cpp', dependencies: spidermonkey)
executable('resolve', 'examples/resolve.cpp', 'examples/boilerplate.cpp', dependencies: [spidermonkey, zlib])
executable('modules', 'examples/modules.cpp', 'examples/boilerplate.cpp', dependencies: [spidermonkey])

0 comments on commit 7bdcc60

Please sign in to comment.