5.10 Load module
This is a very important section, maybe the most important one. This section and the next ones talk about how Deno loads modules into v8. So far, the only work Deno has done specifically for our program is to convert the path into a module specifier. Then Deno did most of its initialization work. Now is the time to focus on our program.
JsRuntime's load_module is a big function, not in terms of lines of code, but in terms of functionality. We'll go through some of the important parts. The loading of a module is a recursive procedure. Module loading ends when the module and all its dependencies have been successfully loaded.
The loading of the module happens in a series of steps:

We've gone through these steps many times in the last few sections. Start with the root module. Load it. Load all the dependencies. Instantiate root module and its dependencies. At the end of the loading step, the main module and all its dependencies have been instantiated into v8.
The load module happens in a series of steps. Here is the overview of each of the step:
- Get loader (which is the CLI module loader)
- Recursively load the main module
- The result of recursive loading is something called a module graph
- Go through the graph and instantiate the modules
The code of JS runtime's load_module is:
pub async fn load_module(
&mut self,
specifier: &ModuleSpecifier,
code: Option<String>,
) -> Result<ModuleId, AnyError> {
self.shared_init();
let loader = {
let state_rc = Self::state(self.v8_isolate());
let state = state_rc.borrow();
state.loader.clone()
};
let load = RecursiveModuleLoad::main(
self.op_state(),
&specifier.to_string(),
code,
loader,
);
let (_load_id, prepare_result) = load.prepare().await;
let mut load = prepare_result?;
while let Some(info_result) = load.next().await {
let info = info_result?;
self.register_during_load(info, &mut load)?;
}
let root_id = load.root_module_id.expect("Root module id empty");
self.mod_instantiate(root_id).map(|_| root_id)
}
Let's go over the steps briefly.
Recursive module loading is a complicated procedure that happens through the following steps:
- Start with the root or the main module
- Fetch it
- Add it as the root module of the module graph
- Parse the main module
- Go through all the direct dependencies
- Fetch dependency
- Parse it
- Add to graph
- The recursive procedure continues till all the dependencies are done
- Any imports done in the code corresponds to the ES modules
- The code will recursively go till the end of the tree and load all the modules.
- Transpile the graph that has been built in the above steps
- Visit graph and register modules
- Registration sends all the modules details to the v8 engine for initialization
- Instantiate module
- Instantiates module and all its dependencies into v8
Those were a lot of steps! Module loading is complicated work. We'll see it in detail as we go forward.
From the v8 point of view, there are two steps for the module loading:
- Load
- Instantiate
Asynchronously loop through the graph and register all the modules. Compared to recursive module loading, this is a very simple step.
At the end of registration, the main module and all its dependencies have been loaded into v8. It's time to instantiate the root module. Only the root module needs to be instantiated. V8 will correlate and fetch all the dependencies via callbacks.
--
This was just an introduction. Let's go to the next section and understand how the module graph gets built. The module graph is a very important data structure. It can also be considered the result of the recursive module loading step.