From 7bdcc609880a183af8a0aecd2c2d402cc66ee802 Mon Sep 17 00:00:00 2001 From: Ted Campbell Date: Tue, 1 Sep 2020 09:22:41 -0400 Subject: [PATCH] examples: Add module embedding example This closes Issue #22. --- examples/README.md | 1 + examples/modules.cpp | 121 +++++++++++++++++++++++++++++++++++++++++++ meson.build | 1 + 3 files changed, 123 insertions(+) create mode 100644 examples/modules.cpp diff --git a/examples/README.md b/examples/README.md index 025fe1d..fe976c4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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. diff --git a/examples/modules.cpp b/examples/modules.cpp new file mode 100644 index 0000000..cb6ab15 --- /dev/null +++ b/examples/modules.cpp @@ -0,0 +1,121 @@ +#include +#include + +#include + +#include +#include +#include + +#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 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 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; +} diff --git a/meson.build b/meson.build index 248ffbf..1ac775d 100644 --- a/meson.build +++ b/meson.build @@ -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])