5.16 Instantiate module

After completing the registration process, the ES module gets compiled within the V8 engine. Now, it's the right moment to create an instance of the module(s). This step involves initializing and preparing the module for execution. In other words, it's like setting up a workspace for the module to do its job.

Overview

This phase comes right after the registration process and serves as the final stage in loading modules. By the completion of this step, all the modules that are loaded statically have been processed. Should there be any dynamic imports in the code, those are managed during runtime, meaning they're handled while the program is already running.

During the registration step, we obtained the unique identifier for the root module. This identifier is then employed to create an instance of the root module within the V8 engine. Given that our example doesn't involve any imports from other modules, there's just one module that needs to be instantiated at this point.

Now, let's delve into the conclusive action within the load_main_module function:

let root_id = load.root_module_id.expect("Root module should be loaded");
    self.instantiate_module(isolate, root_id).map_err(|e| {
      let scope = &mut self.handle_scope(isolate);
      let exception = v8::Local::new(scope, e);
      exception_to_err_result::<()>(scope, exception, false).unwrap_err()
    })?;
Ok(root_id)

Instantiation

The module instantiation work is quite simple.

Here is the code for the main instantiation function:

pub(crate) fn instantiate_module(
    &mut self,
    scope: &mut v8::HandleScope,
    id: ModuleId,
  ) -> Result<(), v8::Global<v8::Value>> {
    let tc_scope = &mut v8::TryCatch::new(scope);

    let module = self
      .get_handle(id)
      .map(|handle| v8::Local::new(tc_scope, handle))
      .expect("ModuleInfo not found");

    if module.get_status() == v8::ModuleStatus::Errored {
      return Err(v8::Global::new(tc_scope, module.get_exception()));
    }

    tc_scope.set_slot(self as *const _);
    let instantiate_result =
      module.instantiate_module(tc_scope, Self::module_resolve_callback);
    tc_scope.remove_slot::<*const Self>();
    if instantiate_result.is_none() {
      let exception = tc_scope.exception().unwrap();
      return Err(v8::Global::new(tc_scope, exception));
    }

    Ok(())
  }

This section explains a straightforward function using these steps:

  • Obtain the module related to the input id (which is the root module id).

  • Instantiate this module into v8 format

The process of instantiation is usually a repeating one, but for our basic "hello world" instance, it isn't. This is because there aren't any external elements being brought in.

When a module is transformed into v8's format, it's done in a manner that goes in circles, yet for our uncomplicated illustration, this looping doesn't happen. The rationale for this is that there aren't any dependencies being pulled in.

Whenever the module-to-v8 conversion occurs, it requires a particular callback:

  • module_resolve_callback In scenarios where there are dependencies, v8 leverages this callback to acquire their references. However, in our instance, this callback won't be invoked due to the absence of dependencies.

--

This marks the completion of the loading phase. Quite a journey it has been! We delved into numerous concepts, progressing from nothing to a fully loaded module. The process of module loading is deemed finalized once instantiation takes place. Now, let's return to examining the worker code:

pub async fn execute_main_module(
    &mut self,
    module_specifier: &ModuleSpecifier,
  ) -> Result<(), AnyError> {
    let id = self.preload_main_module(module_specifier).await?;
    self.evaluate_module(id).await
}

The loading process is now complete. By this stage, all the modules along with their necessary dependencies have been obtained, stored in memory for quick access, transformed into a suitable format for execution, compiled if needed, and successfully brought into the V8 engine. Moving forward, the subsequent and final action in the execute_main_module sequence entails assessing the module's content using the primary module ID. This evaluation of the module essentially involves executing its instructions. And so, at last, the moment has come to set our code into motion.

Last updated