5.4 Module Specifier
The first step in running a program is to create a module specifier for the main module. The main module is the code or file or program that's given as input to Deno. This is also called the root module or main module.
let main_module = ModuleSpecifier::resolve_url_or_path(&script)?;
Module specifier is present everywhere in Deno code. The purpose of ModuleSpecifier is to convert an input to a URL format which could be:
- file
- http
- https
There are two main functions in ModuleSpecifier:
- resolve_url_or_path
- resolve_import
Both of these functions work more or less the same way. They are used to resolve the input into something that follows a URI scheme. It applies to both local and remote files. The output of this function is the ModuleSpecifier object.
Both the main functions present in ModuleSpecifier calls either of the two internal functions:
- resolve_url
- resolve_path
As the name suggests, the first one is to resolve a URL while the second one is to resolve a path (local file).

Here is the source of the function resolve_url_or_path:
pub fn resolve_url_or_path(
specifier: &str,
) -> Result<ModuleSpecifier, ModuleResolutionError> {
if Self::specifier_has_uri_scheme(specifier) {
Self::resolve_url(specifier)
} else {
Self::resolve_path(specifier)
}
}
The code is quite straightforward.
- If input already has a URI scheme
- resolve as URL
- Otherwise,
- resolve as a path (for local files)
Let's take an example to understand this better.
deno run helloLog.ts
// RESOLVED PATH --
file:///Users/mayankc/Work/source/deno-vs-nodejs/helloLog.ts
// --
deno run https://raw.githubusercontent.com/mayankchoubey/deno-vs-nodejs/master/helloLog.ts
// RESOLVED PATH --
https://raw.githubusercontent.com/mayankchoubey/deno-vs-nodejs/master/helloLog.ts
The source of resolve_url is quite simple:
pub fn resolve_url(
url_str: &str,
) -> Result<ModuleSpecifier, ModuleResolutionError> {
Url::parse(url_str)
.map(ModuleSpecifier)
.map_err(ModuleResolutionError::InvalidUrl)
}
As it's already a URL, URL is parsed just to confirm that it is a valid URL.
The source of resolve_path is also quite simple:
pub fn resolve_path(
path_str: &str,
) -> Result<ModuleSpecifier, ModuleResolutionError> {
let path = current_dir().unwrap().join(path_str);
let path = normalize_path(&path);
Url::from_file_path(path.clone())
.map(ModuleSpecifier)
.map_err(|()| ModuleResolutionError::InvalidPath(path))
}
In resolving the path, it takes three steps:
- Append current directory path to input path
- Example:
/Users/mayankc/Work/source/deno-vs-nodejs/
+helloLog.ts
- The path is normalized
- Example:
/Users/mayankc/Work/source/deno-vs-nodejs/
+../../ABC.ts
gets normalized to/Users/mayankc/Work/ABC.ts
- Build a file URI scheme from the path
- Example:
/Users/mayankc/Work/source/deno-vs-nodejs/helloLog.ts
gets converted tofile:///Users/mayankc/Work/source/deno-vs-nodejs/helloLog.ts
Resolve import is called from many places to resolve the import path into a URL. The source of resolve_import is a bit long, but it does the same thing.
pub fn resolve_import(
specifier: &str,
base: &str,
) -> Result<ModuleSpecifier, ModuleResolutionError> {
let url = match Url::parse(specifier) {
Ok(url) => url,
Err(ParseError::RelativeUrlWithoutBase)
if !(specifier.starts_with('/')
|| specifier.starts_with("./")
|| specifier.starts_with("../")) =>
{
let maybe_referrer = if base.is_empty() {
None
} else {
Some(base.to_string())
};
return Err(ImportPrefixMissing(specifier.to_string(), maybe_referrer));
}
Err(ParseError::RelativeUrlWithoutBase) => {
let base = if ModuleSpecifier::is_dummy_specifier(base) {
let path = current_dir().unwrap().join(base);
Url::from_file_path(path).unwrap()
} else {
Url::parse(base).map_err(InvalidBaseUrl)?
};
base.join(&specifier).map_err(InvalidUrl)?
}
Err(err) => return Err(InvalidUrl(err)),
};
Ok(ModuleSpecifier(url))
}