5.3 Main program of Deno
We'll use the following command to run our code in Deno:
deno run helloLog.ts
- deno is the name of the executable
- run is the subcommand
- helloLog.ts is the program to run
This simple program doesn't need any permissions, but we'll still take a look at permissions in this chapter as permissions are one of the foundations of Deno. They help to sandbox the runtime.
Deno is written in Rust, so the main program also resides in the Rust code. The main program of Deno is present inside CLI, which is both the orchestrator and service provider.
Here is the main program of Deno:
pub fn main() {
#[cfg(windows)]
colors::enable_ansi(); // For Windows 10
let args: Vec<String> = env::args().collect();
if let Err(err) = standalone::try_run_standalone_binary(args.clone()) {
eprintln!("{}: {}", colors::red_bold("error"), err.to_string());
std::process::exit(1);
}
let flags = flags::flags_from_vec(args);
if !flags.v8_flags.is_empty() {
init_v8_flags(&*flags.v8_flags);
}
init_logger(flags.log_level);
let subcommand_future = get_subcommand(flags);
let result = tokio_util::run_basic(subcommand_future);
if let Err(err) = result {
eprintln!("{}: {}", colors::red_bold("error"), err.to_string());
std::process::exit(1);
}
}
The deno command is a toolchain and by itself can't run. Deno needs subcommands to work its magic.
The main function of Deno does the following:

- Build flags from command line args
- Initialize logger
- Get a future of the subcommand
- Run the future
Futures in Rust are like promises in Javascript. Futures finish at some time in the future. Futures are extremely useful in writing asynchronous code.
A flag is an object that parses the sub-command specific command-line arguments and stores them for ease of access. Deno has many subcommands and each subcommand has different arguments. Based on the subcommand, flags parses the relevant args and saves them.
Here is the master list of all the parsed attributes present in the flags object:
- subcommand
- allow_env
- allow_hrtime
- allow_net
- allow_plugin
- allow_read
- allow_run
- allow_write
- cache_blocklist
- ca_file
- cached_only
- config_path
- coverage
- ignore
- import_map_path
- inspect
- inspect_brk
- lock
- lock_write
- log_level
- net_allowlist
- no_check
- no_prompts
- no_remote
- read_allowlist
- reload
- repl
- seed
- unstable
- v8_flags
- version
- watch
- write_allowlist
Some of the attributes are boolean like allow_read, allow_write, allow_env, etc. Some of the attributes are lists like write_allowlist, read_allowlist, etc.
Deno has a long list of subcommands, however, the focus of this book is on running code. So, we'll go deep into run command only.
The purpose of the run command is to run the program. The implementation of the run command looks very simple, but it does a lot in the back. First, let's take a look at the source of the run command.
async fn run_command(flags: Flags, script: String) -> Result<(), AnyError> {
// Read script content from stdin
if script == "-" {
return run_from_stdin(flags).await;
}
if flags.watch {
return run_with_watch(flags, script).await;
}
let main_module = ModuleSpecifier::resolve_url_or_path(&script)?;
let program_state = ProgramState::new(flags.clone())?;
let permissions = Permissions::from_options(&flags.clone().into());
let mut worker =
create_main_worker(&program_state, main_module.clone(), permissions);
debug!("main_module {}", main_module);
worker.execute_module(&main_module).await?;
worker.execute("window.dispatchEvent(new Event('load'))")?;
worker.run_event_loop().await?;
worker.execute("window.dispatchEvent(new Event('unload'))")?;
Ok(())
}
That's it! This is all the code that's present in the implementation of the run command. Surely it looks very simple. However, there is a lot happening in the functions that get called from run_command.

Let's go over line by line and get an overview of all the important functions. We'll go into detail in subsequent sections. We'll skip the first two ifs present in the above function. These ifs aren't related in this context.
Deno converts the input script path to URL format. The URL format could be either file:// or http:// or https://. Even for local files, it uses URL format which is file://. This is to maintain consistency in the handling of paths.
Creates a new program state which in turn consists of:
- flags
- Deno's working directory
- Deno needs a separate working directory where it stores the files
- file fetcher
- Typescript compiler
- etc.
Create a new main worker. Note that the main worker runs in the main thread, not a new thread. Creating the main worker also creates a new JS runtime.
Fetch, load, and evaluate the main module (we'll see this in detail very soon).
At this point, the main module is loaded and evaluated, so send a 'load' event to all the listeners.
Runs the event loop till the program finishes.
The event loop has completed now, which means that the program has ended. So, send an 'unload' event to all the listeners
--
This was only a very high-level description of what is happening inside the run command. Let's go over all the steps in detail. We'll start with the concept of ModuleSpecifier.