6.4 Check or transpile
Once the module graph is ready, it's time to do a check or transpile. As mentioned earlier, the decision comes from the user. If startup speed is important, the type-checking can be skipped in favor of a quick transpilation.
In the previous chapter, only one module was transpiled because there were no dependencies. However, the hello world program v2 has some imports. So, transpilation would be required for all the modules that have Typescript code.
To keep it easy, we'll only go over transpilation, and skip check for this example.
Transpilation needs to be done for all the modules present in the module graph. If the modules don't have TS code, they'd be skipped. Also, if the modules have a valid emit, they'd get skipped unless the reload option is specified. This way startup would get faster.
Let's revisit the code of transpiling a graph:
pub fn transpile(
&mut self,
options: TranspileOptions,
) -> Result<ResultInfo, AnyError> {
let start = Instant::now();
// --- PREPARE TS CONFIG -- SUPPRESSED
let emit_options: ast::EmitOptions = ts_config.clone().into();
let mut emit_count: u128 = 0;
let config = ts_config.as_bytes();
for (_, module_slot) in self.modules.iter_mut() {
if let ModuleSlot::Module(module) = module_slot {
if module.media_type == MediaType::Dts {
continue;
}
if !(emit_options.check_js
|| module.media_type == MediaType::JSX
|| module.media_type == MediaType::TSX
|| module.media_type == MediaType::TypeScript)
{
continue;
}
if !options.reload && module.is_emit_valid(&config) {
continue;
}
if module.maybe_parsed_module.is_none() {
module.parse()?;
}
let parsed_module = module.maybe_parsed_module.clone().unwrap();
let emit = parsed_module.transpile(&emit_options)?;
emit_count += 1;
module.maybe_emit = Some(Emit::Cli(emit));
module.set_version(&config);
module.is_dirty = true;
}
}
self.flush()?;
let stats = Stats(vec![
("Files".to_string(), self.modules.len() as u128),
("Emitted".to_string(), emit_count),
("Total time".to_string(), start.elapsed().as_millis()),
]);
Ok(ResultInfo {
diagnostics: Default::default(),
loadable_modules: self.get_loadable_modules(),
maybe_ignored_options,
stats,
})
}
}
Here are the steps in detail:
- Prepare TS compilation config
- Loop through all the modules present in the graph
- If JS needs to be ignored
- skip JS files
- If reload is not specified and emit is valid,
- skip file
- Parse module if not already parsed
- Transpile module
- Return stats and loadable modules
Let's see the same in a flowchart:

Now that we understand transpile logic, let's see how it happens for the hello world v2 program.
- Transpile
file:///Users/mayankc/Work/source/deno-vs-nodejs/helloLogV2.ts
- Transpile
https://deno.land/x/doze/mod.ts
- Transpile
https://deno.land/x/machine_id/mod.ts
- Transpile
https://deno.land/x/doze/doze.ts
It is immediately noticeable that the order of transpilation is the same as how these nodes got added to the graph.
- The dependency of the main module
https://deno.land/x/doze/mod.ts
https://deno.land/x/machine_id/mod.ts
- The dependency of
https://deno.land/x/doze/mod.ts
https://deno.land/x/doze/doze.ts
Once all modules in the graph are transpiled, something called loadable modules gets returned from this step. These are the independent modules. The main module is not considered in this list because it is of course a loadable module. Here is the list of loadable modules after transpilation:
https://deno.land/x/machine_id/mod.ts
https://deno.land/x/doze/mod.ts
Note that the dependency of
https://deno.land/x/doze/mod.ts
is not considered as a module by itself at least not in the way the module is used in our code. If doze.ts is directly imported somewhere, then it could become a loadable module.--
All the modules have been recursively processed and transpiled. There is no TS code from now on. It's time to get them into v8. Let's go through the registration and instantiation process in the next section.