Search code examples
v8es6-modulesembedded-v8

How would one enable and use ES6 modules in the V8 javascript engine?


I use an embedded V8 engine in my (Windows Desktop) C++ application. I understand that V8 has support for ES6 modules. How would I go about activating and using this feature in my application?

I would not expect anyone to have a complete worked example of how this works, but a high level answer pointing me (and future readers) in the right direction would entirely fulfil my hopes and aspirations for this question.


Solution

  • In leiu of actual examples from V8 (I was actually planning to write some at some point), I will write one here. For some examples of use in the wild I recommend Node.js's implementation, or my own, both using very similar layouts (having been written by the same people). There is also an implementation in D8, V8's CLI debugger.

    Local<String> source_text = String::NewFromUtf8(
        isolate, "import 'some thing'; 1 + 1");
    
    ScriptOrigin origin(String::NewFromUtf8("main.mjs"),      // specifier
                        Integer::New(isolate, 0),             // line offset
                        Integer::New(isolate, 0),             // column offset
                        False(isolate),                       // is cross origin
                        Local<Integer>(),                     // script id
                        Local<Value>(),                       // source map URL
                        False(isolate),                       // is opaque
                        False(isolate),                       // is WASM
                        True(isolate));                       // is ES6 module
    Context::Scope context_scope(context);
    ScriptCompiler::Source source(source_text, origin);
    Local<Module> module;
    if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) {
      // if you have a v8::TryCatch, you should check it here.
      return;
    }
    
    // You can resolve import requests ahead of time (useful for async)
    for (int i = 0; i < module->GetModuleRequestsLength(); i++) {
      Local<String> specifier = module->GetModuleRequest(i); // "some thing"
    }
    
    // or you can resolve them sync in the InstantiateModule callback
    module->InstantiateModule(context, [](Local<Context> context, // "main.mjs"
                                          Local<String> specifier, // "some thing"
                                          Local<Module> referrer) {
      return Local<Module>();
    });
    
    // setting this callback enables dynamic import
    isolate->SetImportModuleDynamicallyCallback([](Local<Context> context,
                                                   Local<ScriptOrModule> referrer,
                                                   Local<String> specifier) {
      return MaybeLocal<Promise>();
    });
    
    // setting this callback enables import.meta
    isolate->SetHostInitializeImportMetaObjectCallback([](Local<Context> context,
                                                          Local<Module> module,
                                                          Local<Object> meta) {
      // meta->Set(key, value); you could set import.meta.url here
    });
    
    Local<Value> result;
    if (module->Evaluate(context).ToLocal(&result)) {
      String::Utf8Value utf8(isolate, result);
      printf("module eval result: %s\n", *utf8);
    } else {
      // once again, if you have a v8::TryCatch, use it here.
    }