Skip to content

Commit

Permalink
feat: graph supports external and built in modules (#130)
Browse files Browse the repository at this point in the history
Closes #127
  • Loading branch information
kitsonk committed Feb 7, 2022
1 parent 6ee11a5 commit f8adbe4
Show file tree
Hide file tree
Showing 10 changed files with 909 additions and 341 deletions.
4 changes: 2 additions & 2 deletions lib/deno_graph.generated.js
Expand Up @@ -825,8 +825,8 @@ const imports = {
__wbindgen_rethrow: function (arg0) {
throw takeObject(arg0);
},
__wbindgen_closure_wrapper1544: function (arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 288, __wbg_adapter_24);
__wbindgen_closure_wrapper1731: function (arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 302, __wbg_adapter_24);
return addHeapObject(ret);
},
},
Expand Down
Binary file modified lib/deno_graph_bg.wasm
Binary file not shown.
2 changes: 2 additions & 0 deletions lib/loader.ts
Expand Up @@ -38,6 +38,7 @@ export async function load(
await requestRead(url);
const content = await Deno.readTextFile(url);
return {
kind: "module",
specifier,
content,
};
Expand All @@ -57,6 +58,7 @@ export async function load(
headers[key.toLowerCase()] = value;
}
return {
kind: "module",
specifier: response.url,
headers,
content,
Expand Down
14 changes: 13 additions & 1 deletion lib/types.d.ts
Expand Up @@ -31,7 +31,9 @@ export interface ResolveResult {
kind: ModuleKind;
}

export interface LoadResponse {
export interface LoadResponseModule {
/** A module with code has been loaded. */
kind: "module";
/** The string URL of the resource. If there were redirects, the final
* specifier should be set here, otherwise the requested specifier. */
specifier: string;
Expand All @@ -42,6 +44,16 @@ export interface LoadResponse {
content: string;
}

export interface LoadResponseExternalBuiltIn {
/** The loaded module is either _external_ or _built-in_ to the runtime. */
kind: "external" | "builtIn";
/** The strung URL of the resource. If there were redirects, the final
* specifier should be set here, otherwise the requested specifier. */
specifier: string;
}

export type LoadResponse = LoadResponseModule | LoadResponseExternalBuiltIn;

export interface PositionJson {
/** The line number of a position within a source file. The number is a zero
* based index. */
Expand Down
136 changes: 108 additions & 28 deletions src/graph.rs
Expand Up @@ -545,10 +545,18 @@ pub enum ModuleKind {
/// `MediaType` matches the assertion. Dependency analysis does not occur on
/// asserted modules.
Asserted,
/// Represents a module which is built in to a runtime. The module does not
/// contain source and will have no dependencies.
BuiltIn,
/// A CommonJS module.
CommonJs,
/// An ECMAScript Module (JavaScript Module).
Esm,
/// Represents a module which is not statically analyzed and is only available
/// at runtime. It is up to the implementor to ensure that the module is
/// loaded and available as a dependency. The module does not contain source
/// code and will have no dependencies.
External,
/// A JavaScript script module. A slight misnomer, but it allows "plain"
/// scripts to be imported into the module graph, but without supporting any
/// dependency analysis.
Expand Down Expand Up @@ -621,6 +629,23 @@ impl Module {
}
}

pub fn new_without_source(
specifier: ModuleSpecifier,
kind: ModuleKind,
) -> Self {
Self {
dependencies: Default::default(),
kind,
maybe_cache_info: None,
maybe_checksum: None,
maybe_parsed_source: None,
maybe_source: None,
maybe_types_dependency: None,
media_type: MediaType::Unknown,
specifier,
}
}

/// Create a synthetic module from a vector of type imports
pub fn new_from_type_imports(
specifier: ModuleSpecifier,
Expand Down Expand Up @@ -1588,6 +1613,28 @@ impl<'a> Builder<'a> {
self.graph
}

/// Checks if the specifier is redirected or not and updates any redirects in
/// the graph.
fn check_specifier(
&mut self,
requested_specifier: &ModuleSpecifier,
specifier: &ModuleSpecifier,
) {
// If the response was redirected, then we add the module to the redirects
if requested_specifier != specifier {
// remove a potentially pending redirect that will never resolve
if let Some(slot) = self.graph.module_slots.get(requested_specifier) {
if matches!(slot, ModuleSlot::Pending) {
self.graph.module_slots.remove(requested_specifier);
}
}
self
.graph
.redirects
.insert(requested_specifier.clone(), specifier.clone());
}
}

/// Enqueue a request to load the specifier via the loader.
fn load(
&mut self,
Expand Down Expand Up @@ -1626,7 +1673,6 @@ impl<'a> Builder<'a> {
false
}

/// Visit a module, parsing it and resolving any dependencies.
fn visit(
&mut self,
requested_specifier: &ModuleSpecifier,
Expand All @@ -1635,31 +1681,65 @@ impl<'a> Builder<'a> {
build_kind: &BuildKind,
maybe_assert_type: Option<String>,
) {
use std::borrow::BorrowMut;

let maybe_headers = response.maybe_headers.as_ref();
let specifier = response.specifier.clone();

// If the response was redirected, then we add the module to the redirects
if *requested_specifier != specifier {
// remove a potentially pending redirect that will never resolve
if let Some(slot) = self.graph.module_slots.get(requested_specifier) {
if matches!(slot, ModuleSlot::Pending) {
self.graph.module_slots.remove(requested_specifier);
}
let (specifier, module_slot) = match response {
LoadResponse::BuiltIn { specifier } => {
self.check_specifier(requested_specifier, specifier);
let module_slot = ModuleSlot::Module(Module::new_without_source(
specifier.clone(),
ModuleKind::BuiltIn,
));
(specifier, module_slot)
}
self
.graph
.redirects
.insert(requested_specifier.clone(), specifier.clone());
}
LoadResponse::External { specifier } => {
self.check_specifier(requested_specifier, specifier);
let module_slot = ModuleSlot::Module(Module::new_without_source(
specifier.clone(),
ModuleKind::External,
));
(specifier, module_slot)
}
LoadResponse::Module {
specifier,
content,
maybe_headers,
} => {
self.check_specifier(requested_specifier, specifier);
(
specifier,
self.visit_module(
specifier,
kind,
maybe_headers.as_ref(),
content.clone(),
build_kind,
maybe_assert_type,
),
)
}
};
self
.graph
.module_slots
.insert(specifier.clone(), module_slot);
}

let is_root = self.roots_contain(&specifier);
/// Visit a module, parsing it and resolving any dependencies.
fn visit_module(
&mut self,
specifier: &ModuleSpecifier,
kind: &ModuleKind,
maybe_headers: Option<&HashMap<String, String>>,
content: Arc<String>,
build_kind: &BuildKind,
maybe_assert_type: Option<String>,
) -> ModuleSlot {
use std::borrow::BorrowMut;
let is_root = self.roots_contain(specifier);

let mut module_slot = parse_module(
&specifier,
specifier,
maybe_headers,
response.content.clone(),
content,
maybe_assert_type.as_deref(),
Some(kind),
self.maybe_resolver,
Expand Down Expand Up @@ -1739,7 +1819,7 @@ impl<'a> Builder<'a> {
module.maybe_types_dependency = None;
}
}
self.graph.module_slots.insert(specifier, module_slot);
module_slot
}
}

Expand Down Expand Up @@ -2026,7 +2106,7 @@ mod tests {
assert!(!is_dynamic);
self.loaded_foo = true;
Box::pin(async move {
Ok(Some(LoadResponse {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
content: Arc::new("await import('file:///bar.js')".to_string()),
Expand All @@ -2037,7 +2117,7 @@ mod tests {
assert!(is_dynamic);
self.loaded_bar = true;
Box::pin(async move {
Ok(Some(LoadResponse {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
content: Arc::new("import 'file:///baz.js'".to_string()),
Expand All @@ -2048,7 +2128,7 @@ mod tests {
assert!(is_dynamic);
self.loaded_baz = true;
Box::pin(async move {
Ok(Some(LoadResponse {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
content: Arc::new("console.log('Hello, world!')".to_string()),
Expand Down Expand Up @@ -2092,7 +2172,7 @@ mod tests {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///foo.js" => Box::pin(async move {
Ok(Some(LoadResponse {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
content: Arc::new("await import('file:///bar.js')".to_string()),
Expand Down Expand Up @@ -2152,14 +2232,14 @@ mod tests {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///foo.js" => Box::pin(async move {
Ok(Some(LoadResponse {
Ok(Some(LoadResponse::Module {
specifier: Url::parse("file:///foo_actual.js").unwrap(),
maybe_headers: None,
content: Arc::new("import 'file:///bar.js'".to_string()),
}))
}),
"file:///bar.js" => Box::pin(async move {
Ok(Some(LoadResponse {
Ok(Some(LoadResponse::Module {
specifier: Url::parse("file:///bar_actual.js").unwrap(),
maybe_headers: None,
content: Arc::new("(".to_string()),
Expand Down

0 comments on commit f8adbe4

Please sign in to comment.