5.16 Instantiate module

Once registration is done, the ES module has been compiled into v8. It's time to instantiate it.


This is the next step after registration and this is the last step in module loading. At the end of this step, all the static module loading is done. If there are dynamic imports, those would be handled at runtime.
The registration step returned the root module id. The same root module id is used to instantiate the root module into v8. As our example has no imports, there would be only one module to instantiate.
Here is the last step in the load_module function:
let root_id = load.root_module_id.expect("Root module id empty");
self.mod_instantiate(root_id).map(|_| root_id)


The module instantiation work is quite simple.
Here is the function:
fn mod_instantiate(&mut self, id: ModuleId) -> Result<(), AnyError> {
let state_rc = Self::state(self.v8_isolate());
let context = self.global_context();
let scope = &mut v8::HandleScope::with_context(self.v8_isolate(), context);
let tc_scope = &mut v8::TryCatch::new(scope);
let module = state_rc
.map(|handle| v8::Local::new(tc_scope, handle))
.expect("ModuleInfo not found");
if module.get_status() == v8::ModuleStatus::Errored {
exception_to_err_result(tc_scope, module.get_exception(), false)?
let result =
module.instantiate_module(tc_scope, bindings::module_resolve_callback);
match result {
Some(_) => Ok(()),
None => {
let exception = tc_scope.exception().unwrap();
exception_to_err_result(tc_scope, exception, false)
This is a simple function with the following steps:
  • Get the module for input id (root module id)
  • Instantiate it into v8
The call to instantiate a module into v8 takes a callback:
  • module_resolve_callback
If there are imports, v8 will use this callback to get their handles. In our example, this callback would never come as there are no imports.
This finishes the loading step. That was a lot! We went through a lot of concepts. From nothing to a loaded module. Loading of a module is considered done after instantiation. Let's go back to the worker code:
pub async fn execute_module(
&mut self,
module_specifier: &ModuleSpecifier,
) -> Result<(), AnyError> {
let id = self.preload_module(module_specifier).await?;
We've finished the preload_module step. At this point, all the modules and their dependencies have been fetched, cached, transpiled, compiled, and loaded into v8. Preload_module returns the module id of the root module. The next step and the last step in execute_module is to evaluate the module using the root module id. Evaluation of a module means running it. Finally, it's time to run our code.