diff --git a/README.md b/README.md index 59ff940c5..88639436f 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,11 @@ leverage the logic outside of the Deno CLI from JavaScript/TypeScript. ## Rust usage -### `create_graph()` +### `ModuleGraph::new(...)` -`create_graph()` is the main way of interfacing with the crate. It requires the -root module specifiers/URLs for the graph and an implementation of the +`ModuleGraph::new(GraphKind::All)` creates a new module graph. From there, you +can use the `.build(...).await` method to add roots. The `build` method requires +the root module specifiers/URLs for the graph and an implementation of the `source::Loader` trait. It also optionally takes implementation of the `source::Resolver` trait. It will load and parse the root module and recursively all of its dependencies, returning asynchronously a resulting `ModuleGraph`. @@ -68,21 +69,38 @@ A minimal example would look like this: use deno_graph::create_graph; use deno_graph::ModuleSpecifier; use deno_graph::source::MemoryLoader; +use deno_graph::source::Source; use futures::executor::block_on; fn main() { let mut loader = MemoryLoader::new( vec![ - ("file:///test.ts", Ok(("file:///test.ts", None, "import * as a from \"./a.ts\";"))), - ("file:///a.ts", Ok(("file:///a.ts", None, "export const a = \"a\";"))), - ] + ( + "file:///test.ts", + Source::Module { + specifier: "file:///test.ts", + maybe_headers: None, + content: "import * as a from \"./a.ts\";" + } + ), + ( + "file:///a.ts", + Source::Module { + specifier: "file:///a.ts", + maybe_headers: None, + content: "export const a = \"a\";", + } + ), + ], + Vec::new(), ); let roots = vec![ModuleSpecifier::parse("file:///test.ts").unwrap()]; let future = async move { - let graph = create_graph(roots, &mut loader, Default::default()).await; + let mut graph = ModuleGraph::default(); + graph.build(roots, &mut loader, Default::default()).await; println!("{}", graph); }; - block_on() + block_on(future) } ``` diff --git a/lib/deno_graph_wasm.generated.js b/lib/deno_graph_wasm.generated.js index f4740be05..881fcfd73 100644 --- a/lib/deno_graph_wasm.generated.js +++ b/lib/deno_graph_wasm.generated.js @@ -1,7 +1,7 @@ // @generated file from wasmbuild -- do not edit // deno-lint-ignore-file // deno-fmt-ignore-file -// source-hash: 2397aff89324df2c6a510476c774e6e5627bc179 +// source-hash: 524dc85b1c90c720808be7ab778c604fcc5bf618 let wasm; const heap = new Array(32).fill(undefined); @@ -241,7 +241,7 @@ function __wbg_adapter_48(arg0, arg1, arg2) { * @param {Function | undefined} maybe_cache_info * @param {Function | undefined} maybe_resolve * @param {Function | undefined} maybe_resolve_types - * @param {string | undefined} maybe_build_kind + * @param {string | undefined} maybe_graph_kind * @param {any} maybe_imports * @returns {Promise} */ @@ -253,7 +253,7 @@ export function createGraph( maybe_cache_info, maybe_resolve, maybe_resolve_types, - maybe_build_kind, + maybe_graph_kind, maybe_imports, ) { var ptr0 = isLikeNone(maybe_default_jsx_import_source) @@ -272,10 +272,10 @@ export function createGraph( wasm.__wbindgen_realloc, ); var len1 = WASM_VECTOR_LEN; - var ptr2 = isLikeNone(maybe_build_kind) + var ptr2 = isLikeNone(maybe_graph_kind) ? 0 : passStringToWasm0( - maybe_build_kind, + maybe_graph_kind, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc, ); diff --git a/lib/deno_graph_wasm_bg.wasm b/lib/deno_graph_wasm_bg.wasm index 2534958dd..dc8dde637 100644 Binary files a/lib/deno_graph_wasm_bg.wasm and b/lib/deno_graph_wasm_bg.wasm differ diff --git a/lib/lib.rs b/lib/lib.rs index 2b4b96d0e..fc0b1c26e 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -14,7 +14,9 @@ use deno_graph::source::LoadFuture; use deno_graph::source::Loader; use deno_graph::source::Resolver; use deno_graph::source::DEFAULT_JSX_IMPORT_SOURCE_MODULE; -use deno_graph::BuildKind; +use deno_graph::BuildOptions; +use deno_graph::GraphKind; +use deno_graph::ModuleGraph; use deno_graph::ModuleKind; use deno_graph::ModuleSpecifier; use deno_graph::Range; @@ -185,7 +187,7 @@ pub async fn js_create_graph( maybe_cache_info: Option, maybe_resolve: Option, maybe_resolve_types: Option, - maybe_build_kind: Option, + maybe_graph_kind: Option, maybe_imports: JsValue, ) -> Result { let roots_vec: Vec = serde_wasm_bindgen::from_value(roots) @@ -229,24 +231,25 @@ pub async fn js_create_graph( Vec::new() }; - let build_kind = match maybe_build_kind.as_deref() { - Some("typesOnly") => BuildKind::TypesOnly, - Some("codeOnly") => BuildKind::CodeOnly, - _ => BuildKind::All, + let graph_kind = match maybe_graph_kind.as_deref() { + Some("typesOnly") => GraphKind::TypesOnly, + Some("codeOnly") => GraphKind::CodeOnly, + _ => GraphKind::All, }; - let graph = deno_graph::create_graph( - roots, - &mut loader, - deno_graph::GraphOptions { - is_dynamic: false, - resolver: maybe_resolver.as_ref().map(|r| r as &dyn Resolver), - module_analyzer: None, - build_kind, - imports, - reporter: None, - }, - ) - .await; + let mut graph = ModuleGraph::new(graph_kind); + graph + .build( + roots, + &mut loader, + BuildOptions { + is_dynamic: false, + resolver: maybe_resolver.as_ref().map(|r| r as &dyn Resolver), + module_analyzer: None, + imports, + reporter: None, + }, + ) + .await; let serializer = serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true); Ok(graph.serialize(&serializer).unwrap()) diff --git a/src/graph.rs b/src/graph.rs index e967b876a..845ca2988 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -7,6 +7,7 @@ use crate::analyzer::ModuleInfo; use crate::analyzer::PositionRange; use crate::analyzer::SpecifierWithRange; use crate::analyzer::TypeScriptReference; +use crate::DefaultModuleAnalyzer; use crate::ReferrerImports; use crate::module_specifier::resolve_import; @@ -635,32 +636,27 @@ fn to_result<'a>( /// module graph without requiring the dependencies to be analyzed. This is /// intended to be used for importing type dependencies or other externally /// defined dependencies, like JSX runtimes. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone)] pub struct GraphImport { - /// The referring module specifier to be used to resolve relative dependencies - /// from. This is typically the meta data file that defined the dependency, - /// such as a configuration file. - pub referrer: ModuleSpecifier, /// A map of resolved dependencies, where the key is the value originally /// provided for the import and the value is the resolved dependency. - #[serde(serialize_with = "serialize_dependencies")] pub dependencies: BTreeMap, } impl GraphImport { pub fn new( - referrer_imports: ReferrerImports, + referrer: &ModuleSpecifier, + imports: Vec, maybe_resolver: Option<&dyn Resolver>, ) -> Self { - let dependencies = referrer_imports - .imports + let referrer_range = Range { + specifier: referrer.clone(), + start: Position::zeroed(), + end: Position::zeroed(), + }; + let dependencies = imports .iter() .map(|import| { - let referrer_range = Range { - specifier: referrer_imports.referrer.clone(), - start: Position::zeroed(), - end: Position::zeroed(), - }; let maybe_type = resolve(import, &referrer_range, maybe_resolver); ( import.clone(), @@ -673,38 +669,68 @@ impl GraphImport { ) }) .collect(); - Self { - referrer: referrer_imports.referrer, - dependencies, - } + Self { dependencies } } } +#[derive(Default)] +pub struct BuildOptions<'a> { + pub is_dynamic: bool, + /// Additional imports that should be brought into the scope of + /// the module graph to add to the graph's "imports". This may + /// be extra modules such as TypeScript's "types" option or JSX + /// runtime types. + pub imports: Vec, + pub resolver: Option<&'a dyn Resolver>, + pub module_analyzer: Option<&'a dyn ModuleAnalyzer>, + pub reporter: Option<&'a dyn Reporter>, +} + /// The structure which represents a module graph, which can be serialized as /// well as "printed". The roots of the graph represent the "starting" point /// which can be located in the module "slots" in the graph. The graph also /// contains any redirects where the requested module specifier was redirected /// to another module specifier when being loaded. -#[derive(Debug, Serialize)] +#[derive(Debug, Default)] pub struct ModuleGraph { + graph_kind: GraphKind, pub roots: Vec, - #[serde(serialize_with = "serialize_modules", rename = "modules")] pub(crate) module_slots: BTreeMap, - #[serde(skip_serializing_if = "Vec::is_empty")] - pub imports: Vec, + pub imports: BTreeMap, pub redirects: BTreeMap, } impl ModuleGraph { - fn new(roots: Vec) -> Self { + pub fn new(graph_kind: GraphKind) -> Self { Self { - roots, + graph_kind, + roots: Default::default(), module_slots: Default::default(), imports: Default::default(), redirects: Default::default(), } } + pub async fn build<'a>( + &mut self, + roots: Vec, + loader: &mut dyn Loader, + options: BuildOptions<'a>, + ) { + let default_analyzer = DefaultModuleAnalyzer::default(); + Builder::build( + self, + roots, + options.imports, + options.is_dynamic, + options.resolver, + loader, + options.module_analyzer.unwrap_or(&default_analyzer), + options.reporter, + ) + .await + } + /// Returns `true` if the specifier resolves to a module within a graph, /// otherwise returns `false`. pub fn contains(&self, specifier: &ModuleSpecifier) -> bool { @@ -788,9 +814,7 @@ impl ModuleGraph { { let dependency = referring_module.dependencies.get(specifier)?; self.resolve_dependency_specifier(dependency, prefer_types) - } else if let Some(graph_import) = - self.imports.iter().find(|i| i.referrer == referrer) - { + } else if let Some(graph_import) = self.imports.get(&referrer) { let dependency = graph_import.dependencies.get(specifier)?; self.resolve_dependency_specifier(dependency, prefer_types) } else { @@ -992,6 +1016,26 @@ fn resolve( } } +impl Serialize for ModuleGraph { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut graph = serializer.serialize_struct("ModuleGraph", 4)?; + graph.serialize_field("roots", &self.roots)?; + graph + .serialize_field("modules", &SerializableModules(&self.module_slots))?; + if self.imports.is_empty() { + graph.skip_field("imports")?; + } else { + graph + .serialize_field("imports", &SerializableGraphImports(&self.imports))?; + } + graph.serialize_field("redirects", &self.redirects)?; + graph.end() + } +} + /// With the provided information, parse a module and return its "module slot" #[allow(clippy::too_many_arguments)] pub(crate) fn parse_module( @@ -1289,8 +1333,9 @@ fn is_untyped(media_type: &MediaType) -> bool { ) } -/// The kind of build to perform. -pub enum BuildKind { +/// The kind of module graph. +#[derive(Debug)] +pub enum GraphKind { /// All types of dependencies should be analyzed and included in the graph. All, /// Only code dependencies should be analyzed and included in the graph. This @@ -1308,7 +1353,7 @@ pub enum BuildKind { TypesOnly, } -impl Default for BuildKind { +impl Default for GraphKind { fn default() -> Self { Self::All } @@ -1317,60 +1362,74 @@ impl Default for BuildKind { pub type LoadWithSpecifierFuture = Pin + 'static>>; -pub(crate) struct Builder<'a> { +struct Builder<'a, 'graph> { in_dynamic_branch: bool, - graph: ModuleGraph, + graph: &'graph mut ModuleGraph, loader: &'a mut dyn Loader, - maybe_resolver: Option<&'a dyn Resolver>, + resolver: Option<&'a dyn Resolver>, pending: FuturesUnordered, pending_assert_types: HashMap>>, dynamic_branches: HashMap>, module_analyzer: &'a dyn ModuleAnalyzer, - maybe_reporter: Option<&'a dyn Reporter>, + reporter: Option<&'a dyn Reporter>, } -impl<'a> Builder<'a> { - pub fn new( +impl<'a, 'graph> Builder<'a, 'graph> { + #[allow(clippy::too_many_arguments)] + pub async fn build( + graph: &'graph mut ModuleGraph, roots: Vec, - is_dynamic_root: bool, + imports: Vec, + is_dynamic: bool, + resolver: Option<&'a dyn Resolver>, loader: &'a mut dyn Loader, - maybe_resolver: Option<&'a dyn Resolver>, module_analyzer: &'a dyn ModuleAnalyzer, - maybe_reporter: Option<&'a dyn Reporter>, - ) -> Self { + reporter: Option<&'a dyn Reporter>, + ) { Self { - in_dynamic_branch: is_dynamic_root, - graph: ModuleGraph::new(roots), + in_dynamic_branch: is_dynamic, + graph, loader, - maybe_resolver, + resolver, + module_analyzer, + reporter, pending: FuturesUnordered::new(), pending_assert_types: HashMap::new(), dynamic_branches: HashMap::new(), - module_analyzer, - maybe_reporter, } + .fill(roots, imports) + .await } - pub async fn build( - mut self, - build_kind: BuildKind, + async fn fill( + &mut self, + roots: Vec, imports: Vec, - ) -> ModuleGraph { - let roots = self.graph.roots.clone(); + ) { + let roots = roots + .into_iter() + .filter(|r| !self.graph.roots.contains(r)) + .collect::>(); + let imports = imports + .into_iter() + .filter(|r| !self.graph.imports.contains_key(&r.referrer)) + .collect::>(); + self.graph.roots.extend(roots.clone()); for root in roots { self.load(&root, self.in_dynamic_branch, None); } // Process any imports that are being added to the graph. for referrer_imports in imports { - let graph_import = - GraphImport::new(referrer_imports, self.maybe_resolver); + let referrer = referrer_imports.referrer; + let imports = referrer_imports.imports; + let graph_import = GraphImport::new(&referrer, imports, self.resolver); for dep in graph_import.dependencies.values() { if let Resolved::Ok { specifier, .. } = &dep.maybe_type { self.load(specifier, self.in_dynamic_branch, None); } } - self.graph.imports.push(graph_import); + self.graph.imports.insert(referrer, graph_import); } loop { @@ -1379,7 +1438,7 @@ impl<'a> Builder<'a> { let assert_types = self.pending_assert_types.remove(&specifier).unwrap(); for maybe_assert_type in assert_types { - self.visit(&specifier, &response, &build_kind, maybe_assert_type) + self.visit(&specifier, &response, maybe_assert_type) } Some(specifier) } @@ -1402,9 +1461,7 @@ impl<'a> Builder<'a> { } _ => None, }; - if let (Some(specifier), Some(reporter)) = - (specifier, self.maybe_reporter) - { + if let (Some(specifier), Some(reporter)) = (specifier, self.reporter) { let modules_total = self.graph.module_slots.len(); let modules_done = modules_total - self.pending.len(); reporter.on_load(&specifier, modules_done, modules_total); @@ -1438,8 +1495,6 @@ impl<'a> Builder<'a> { module.maybe_cache_info = self.loader.get_cache_info(&module.specifier); } } - - self.graph } /// Checks if the specifier is redirected or not and updates any redirects in @@ -1504,7 +1559,6 @@ impl<'a> Builder<'a> { &mut self, requested_specifier: &ModuleSpecifier, response: &LoadResponse, - build_kind: &BuildKind, maybe_assert_type: Option, ) { let (specifier, module_slot) = match response { @@ -1529,7 +1583,6 @@ impl<'a> Builder<'a> { ModuleKind::Esm, maybe_headers.as_ref(), content.clone(), - build_kind, maybe_assert_type, ), ) @@ -1548,7 +1601,6 @@ impl<'a> Builder<'a> { kind: ModuleKind, maybe_headers: Option<&HashMap>, content: Arc, - build_kind: &BuildKind, maybe_assert_type: Option, ) -> ModuleSlot { use std::borrow::BorrowMut; @@ -1560,19 +1612,21 @@ impl<'a> Builder<'a> { content, maybe_assert_type.as_deref(), Some(kind), - self.maybe_resolver, + self.resolver, self.module_analyzer, is_root, self.in_dynamic_branch, ); if let ModuleSlot::Module(module) = module_slot.borrow_mut() { - if matches!(build_kind, BuildKind::All | BuildKind::CodeOnly) + if matches!(self.graph.graph_kind, GraphKind::All | GraphKind::CodeOnly) || module.maybe_types_dependency.is_none() { for dep in module.dependencies.values_mut() { - if matches!(build_kind, BuildKind::All | BuildKind::CodeOnly) - || dep.maybe_type.is_none() + if matches!( + self.graph.graph_kind, + GraphKind::All | GraphKind::CodeOnly + ) || dep.maybe_type.is_none() { if let Resolved::Ok { specifier, .. } = &dep.maybe_code { if dep.is_dynamic && !self.in_dynamic_branch { @@ -1591,7 +1645,10 @@ impl<'a> Builder<'a> { dep.maybe_code = Resolved::None; } - if matches!(build_kind, BuildKind::All | BuildKind::TypesOnly) { + if matches!( + self.graph.graph_kind, + GraphKind::All | GraphKind::TypesOnly + ) { if let Resolved::Ok { specifier, .. } = &dep.maybe_type { if dep.is_dynamic && !self.in_dynamic_branch { self @@ -1613,7 +1670,8 @@ impl<'a> Builder<'a> { module.dependencies.clear(); } - if matches!(build_kind, BuildKind::All | BuildKind::TypesOnly) { + if matches!(self.graph.graph_kind, GraphKind::All | GraphKind::TypesOnly) + { if let Some((_, Resolved::Ok { specifier, .. })) = &module.maybe_types_dependency { @@ -1627,9 +1685,9 @@ impl<'a> Builder<'a> { } } -struct SerializeableResolved<'a>(&'a Resolved); +struct SerializableResolved<'a>(&'a Resolved); -impl<'a> Serialize for SerializeableResolved<'a> { +impl<'a> Serialize for SerializableResolved<'a> { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -1638,9 +1696,9 @@ impl<'a> Serialize for SerializeableResolved<'a> { } } -struct SerializeableDependency<'a>(&'a str, &'a Dependency); +struct SerializableDependency<'a>(&'a str, &'a Dependency); -impl<'a> Serialize for SerializeableDependency<'a> { +impl<'a> Serialize for SerializableDependency<'a> { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -1648,11 +1706,11 @@ impl<'a> Serialize for SerializeableDependency<'a> { let mut map = serializer.serialize_map(None)?; map.serialize_entry("specifier", self.0)?; if !self.1.maybe_code.is_none() { - let serializeable_resolved = SerializeableResolved(&self.1.maybe_code); + let serializeable_resolved = SerializableResolved(&self.1.maybe_code); map.serialize_entry("code", &serializeable_resolved)?; } if !self.1.maybe_type.is_none() { - let serializeable_resolved = SerializeableResolved(&self.1.maybe_type); + let serializeable_resolved = SerializableResolved(&self.1.maybe_type); map.serialize_entry("type", &serializeable_resolved)?; } if self.1.is_dynamic { @@ -1665,6 +1723,17 @@ impl<'a> Serialize for SerializeableDependency<'a> { } } +struct SerializableDependencies<'a>(&'a BTreeMap); + +impl<'a> Serialize for SerializableDependencies<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serialize_dependencies(self.0, serializer) + } +} + fn serialize_dependencies( dependencies: &BTreeMap, serializer: S, @@ -1674,15 +1743,15 @@ where { let mut seq = serializer.serialize_seq(Some(dependencies.iter().count()))?; for (specifier_str, dep) in dependencies.iter() { - let serializeable_dependency = SerializeableDependency(specifier_str, dep); + let serializeable_dependency = SerializableDependency(specifier_str, dep); seq.serialize_element(&serializeable_dependency)?; } seq.end() } -struct SerializeableModuleSlot<'a>(&'a ModuleSpecifier, &'a ModuleSlot); +struct SerializableModuleSlot<'a>(&'a ModuleSpecifier, &'a ModuleSlot); -impl<'a> Serialize for SerializeableModuleSlot<'a> { +impl<'a> Serialize for SerializableModuleSlot<'a> { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -1708,19 +1777,52 @@ impl<'a> Serialize for SerializeableModuleSlot<'a> { } } -fn serialize_modules( - modules: &BTreeMap, - serializer: S, -) -> Result -where - S: Serializer, -{ - let mut seq = serializer.serialize_seq(Some(modules.iter().count()))?; - for (specifier, slot) in modules.iter() { - let serializeable_module_slot = SerializeableModuleSlot(specifier, slot); - seq.serialize_element(&serializeable_module_slot)?; +struct SerializableModules<'a>(&'a BTreeMap); + +impl<'a> Serialize for SerializableModules<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.0.len()))?; + for (specifier, slot) in self.0.iter() { + let serializeable_module_slot = SerializableModuleSlot(specifier, slot); + seq.serialize_element(&serializeable_module_slot)?; + } + seq.end() + } +} + +struct SerializableGraphImports<'a>(&'a BTreeMap); + +impl<'a> Serialize for SerializableGraphImports<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.0.len()))?; + for (key, value) in self.0.iter() { + seq.serialize_element(&SerializableGraphImport(key, value))?; + } + seq.end() + } +} + +struct SerializableGraphImport<'a>(&'a ModuleSpecifier, &'a GraphImport); + +impl<'a> Serialize for SerializableGraphImport<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut val = serializer.serialize_struct("GraphImport", 2)?; + val.serialize_field("referrer", self.0)?; + val.serialize_field( + "dependencies", + &SerializableDependencies(&self.1.dependencies), + )?; + val.end() } - seq.end() } fn serialize_type_dependency( @@ -1734,7 +1836,7 @@ where Some((ref specifier, ref resolved)) => { let mut state = serializer.serialize_struct("TypesDependency", 2)?; state.serialize_field("specifier", specifier)?; - let serializeable_resolved = SerializeableResolved(resolved); + let serializeable_resolved = SerializableResolved(resolved); state.serialize_field("dependency", &serializeable_resolved)?; state.end() } @@ -1925,16 +2027,14 @@ mod tests { loaded_bar: false, loaded_baz: false, }; - let module_analyzer = DefaultModuleAnalyzer::default(); - let builder = Builder::new( - vec![Url::parse("file:///foo.js").unwrap()], - false, - &mut loader, - None, - &module_analyzer, - None, - ); - builder.build(BuildKind::All, Vec::new()).await; + let mut graph = ModuleGraph::new(GraphKind::All); + graph + .build( + vec![Url::parse("file:///foo.js").unwrap()], + &mut loader, + Default::default(), + ) + .await; assert!(loader.loaded_foo); assert!(loader.loaded_bar); assert!(loader.loaded_baz); @@ -1964,16 +2064,14 @@ mod tests { } } let mut loader = TestLoader; - let module_analyzer = DefaultModuleAnalyzer::default(); - let builder = Builder::new( - vec![Url::parse("file:///foo.js").unwrap()], - false, - &mut loader, - None, - &module_analyzer, - None, - ); - let graph = builder.build(BuildKind::All, Vec::new()).await; + let mut graph = ModuleGraph::new(GraphKind::All); + graph + .build( + vec![Url::parse("file:///foo.js").unwrap()], + &mut loader, + Default::default(), + ) + .await; assert!(graph .try_get(&Url::parse("file:///foo.js").unwrap()) .is_ok()); @@ -2029,16 +2127,14 @@ mod tests { } } let mut loader = TestLoader; - let module_analyzer = DefaultModuleAnalyzer::default(); - let builder = Builder::new( - vec![Url::parse("file:///foo.js").unwrap()], - false, - &mut loader, - None, - &module_analyzer, - None, - ); - let graph = builder.build(BuildKind::All, Vec::new()).await; + let mut graph = ModuleGraph::new(GraphKind::All); + graph + .build( + vec![Url::parse("file:///foo.js").unwrap()], + &mut loader, + Default::default(), + ) + .await; let specifiers = graph.specifiers().collect::>(); dbg!(&specifiers); assert_eq!(specifiers.len(), 4); diff --git a/src/lib.rs b/src/lib.rs index 7021b9a84..bd4812b91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ mod module_specifier; pub mod source; mod text_encoding; -use graph::Builder; pub use graph::ModuleKind; use graph::ModuleSlot; use source::Resolver; @@ -35,9 +34,10 @@ pub use ast::DefaultParsedSourceStore; pub use ast::ModuleParser; pub use ast::ParsedSourceStore; pub use deno_ast::MediaType; -pub use graph::BuildKind; +pub use graph::BuildOptions; pub use graph::Dependency; pub use graph::GraphImport; +pub use graph::GraphKind; pub use graph::Module; pub use graph::ModuleGraph; pub use graph::ModuleGraphError; @@ -49,9 +49,6 @@ pub use module_specifier::resolve_import; pub use module_specifier::ModuleSpecifier; pub use module_specifier::SpecifierError; -use source::Loader; -use source::Reporter; - pub struct ReferrerImports { /// The referrer to resolve the imports from. pub referrer: ModuleSpecifier, @@ -59,41 +56,6 @@ pub struct ReferrerImports { pub imports: Vec, } -#[derive(Default)] -pub struct GraphOptions<'graph> { - pub is_dynamic: bool, - /// Additional imports that should be brought into the scope of - /// the module graph to add to the graph's "imports". This may - /// be extra modules such as TypeScript's "types" option or JSX - /// runtime types. - pub imports: Vec, - pub resolver: Option<&'graph dyn Resolver>, - pub module_analyzer: Option<&'graph dyn ModuleAnalyzer>, - pub reporter: Option<&'graph dyn Reporter>, - pub build_kind: BuildKind, -} - -/// Create a module graph, based on loading and recursively analyzing the -/// dependencies of the module, returning the resulting graph. -pub async fn create_graph<'graph>( - roots: Vec, - loader: &mut dyn Loader, - options: GraphOptions<'graph>, -) -> ModuleGraph { - let default_module_analyzer = ast::DefaultModuleAnalyzer::default(); - let module_analyzer = - options.module_analyzer.unwrap_or(&default_module_analyzer); - let builder = Builder::new( - roots, - options.is_dynamic, - loader, - options.resolver, - module_analyzer, - options.reporter, - ); - builder.build(options.build_kind, options.imports).await -} - /// Parse an individual module, returning the module as a result, otherwise /// erroring with a module graph error. pub fn parse_module( @@ -165,7 +127,7 @@ mod tests { } #[tokio::test] - async fn test_create_graph() { + async fn test_build_graph() { let mut loader = setup( vec![ ( @@ -189,12 +151,14 @@ mod tests { ); let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!(graph.module_slots.len(), 2); assert_eq!(graph.roots, vec![root_specifier.clone()]); assert!(graph.contains(&root_specifier)); @@ -226,7 +190,7 @@ mod tests { } #[tokio::test] - async fn test_create_graph_multiple_roots() { + async fn test_build_graph_multiple_roots() { let mut loader = setup( vec![ ( @@ -268,9 +232,89 @@ mod tests { ModuleSpecifier::parse("file:///a/test01.ts").unwrap(), ModuleSpecifier::parse("https://example.com/a.ts").unwrap(), ]; - let graph = - create_graph(roots.clone(), &mut loader, Default::default()).await; + let mut graph = ModuleGraph::default(); + graph + .build(roots.clone(), &mut loader, Default::default()) + .await; + assert_eq!(graph.module_slots.len(), 4); + assert_eq!(graph.roots, roots); + assert!( + graph.contains(&ModuleSpecifier::parse("file:///a/test01.ts").unwrap()) + ); + assert!( + graph.contains(&ModuleSpecifier::parse("file:///a/test02.ts").unwrap()) + ); + assert!(graph + .contains(&ModuleSpecifier::parse("https://example.com/a.ts").unwrap())); + assert!(graph + .contains(&ModuleSpecifier::parse("https://example.com/c.ts").unwrap())); + } + + #[tokio::test] + async fn test_build_graph_multiple_times() { + let mut loader = setup( + vec![ + ( + "file:///a/test01.ts", + Source::Module { + specifier: "file:///a/test01.ts", + maybe_headers: None, + content: r#"import * as b from "./test02.ts";"#, + }, + ), + ( + "file:///a/test02.ts", + Source::Module { + specifier: "file:///a/test02.ts", + maybe_headers: None, + content: r#"import "https://example.com/c.ts"; export const b = "b";"#, + }, + ), + ( + "https://example.com/a.ts", + Source::Module { + specifier: "https://example.com/a.ts", + maybe_headers: None, + content: r#"import * as c from "./c.ts";"#, + }, + ), + ( + "https://example.com/c.ts", + Source::Module { + specifier: "https://example.com/c.ts", + maybe_headers: None, + content: r#"import "./d.ts"; export const c = "c";"#, + }, + ), + ( + "https://example.com/d.ts", + Source::Module { + specifier: "https://example.com/d.ts", + maybe_headers: None, + content: r#"export const d = "d";"#, + }, + ), + ], + vec![], + ); + let first_root = ModuleSpecifier::parse("file:///a/test01.ts").unwrap(); + let second_root = + ModuleSpecifier::parse("https://example.com/a.ts").unwrap(); + let third_root = + ModuleSpecifier::parse("https://example.com/d.ts").unwrap(); + let mut graph = ModuleGraph::default(); + graph + .build(vec![first_root.clone()], &mut loader, Default::default()) + .await; assert_eq!(graph.module_slots.len(), 4); + assert_eq!(graph.roots, vec![first_root.clone()]); + + // now build with the second root + graph + .build(vec![second_root.clone()], &mut loader, Default::default()) + .await; + let mut roots = vec![first_root, second_root]; + assert_eq!(graph.module_slots.len(), 5); assert_eq!(graph.roots, roots); assert!( graph.contains(&ModuleSpecifier::parse("file:///a/test01.ts").unwrap()) @@ -282,10 +326,20 @@ mod tests { .contains(&ModuleSpecifier::parse("https://example.com/a.ts").unwrap())); assert!(graph .contains(&ModuleSpecifier::parse("https://example.com/c.ts").unwrap())); + assert!(graph + .contains(&ModuleSpecifier::parse("https://example.com/d.ts").unwrap())); + + // now try making one of the already existing modules a root + graph + .build(vec![third_root.clone()], &mut loader, Default::default()) + .await; + roots.push(third_root); + assert_eq!(graph.module_slots.len(), 5); + assert_eq!(graph.roots, roots); } #[tokio::test] - async fn test_create_graph_json_module_root() { + async fn test_build_graph_json_module_root() { let mut loader = setup( vec![( "file:///a/test.json", @@ -298,15 +352,17 @@ mod tests { vec![], ); let roots = vec![ModuleSpecifier::parse("file:///a/test.json").unwrap()]; - let graph = create_graph( - roots.clone(), - &mut loader, - GraphOptions { - is_dynamic: true, - ..Default::default() - }, - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + roots.clone(), + &mut loader, + BuildOptions { + is_dynamic: true, + ..Default::default() + }, + ) + .await; assert_eq!( json!(graph), json!({ @@ -327,7 +383,7 @@ mod tests { } #[tokio::test] - async fn test_create_graph_dynamic_json_ignores_assert() { + async fn test_build_graph_dynamic_json_ignores_assert() { let mut loader = setup( vec![ ( @@ -352,15 +408,17 @@ mod tests { vec![], ); let roots = vec![ModuleSpecifier::parse("file:///a/test.js").unwrap()]; - let graph = create_graph( - roots.clone(), - &mut loader, - GraphOptions { - is_dynamic: true, - ..Default::default() - }, - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + roots.clone(), + &mut loader, + BuildOptions { + is_dynamic: true, + ..Default::default() + }, + ) + .await; assert_eq!( json!(graph), json!({ @@ -434,12 +492,14 @@ console.log(a); ); let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert!(graph.valid().is_ok()); assert!(graph.valid_types_only().is_err()); } @@ -462,12 +522,14 @@ console.log(a); ); let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert!(graph.valid().is_err()); assert_eq!( graph.valid().err().unwrap().to_string(), @@ -477,7 +539,7 @@ console.log(a); } #[tokio::test] - async fn test_create_graph_imports() { + async fn test_build_graph_imports() { let mut loader = setup( vec![ ( @@ -514,15 +576,17 @@ console.log(a); referrer: config_specifier, imports: vec!["./types.d.ts".to_string()], }]; - let graph = create_graph( - vec![root_specifier], - &mut loader, - GraphOptions { - imports, - ..Default::default() - }, - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier], + &mut loader, + BuildOptions { + imports, + ..Default::default() + }, + ) + .await; assert_eq!( json!(graph), json!({ @@ -594,7 +658,7 @@ console.log(a); } #[tokio::test] - async fn test_create_graph_imports_imported() { + async fn test_build_graph_imports_imported() { let mut loader = setup( vec![ ( @@ -640,15 +704,17 @@ console.log(a); referrer: config_specifier, imports: vec!["https://esm.sh/preact/runtime-jsx".to_string()], }]; - let graph = create_graph( - vec![root_specifier], - &mut loader, - GraphOptions { - imports, - ..Default::default() - }, - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier], + &mut loader, + BuildOptions { + imports, + ..Default::default() + }, + ) + .await; assert_eq!( json!(graph), json!({ @@ -721,7 +787,7 @@ console.log(a); } #[tokio::test] - async fn test_create_graph_imports_resolve_dependency() { + async fn test_build_graph_imports_resolve_dependency() { let mut loader = setup( vec![ ( @@ -764,15 +830,17 @@ console.log(a); referrer: config_specifier.clone(), imports: vec!["https://example.com/jsx-runtime".to_string()], }]; - let graph = create_graph( - vec![root_specifier], - &mut loader, - GraphOptions { - imports, - ..Default::default() - }, - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier], + &mut loader, + BuildOptions { + imports, + ..Default::default() + }, + ) + .await; assert_eq!( graph.resolve_dependency( "https://example.com/jsx-runtime", @@ -795,7 +863,7 @@ console.log(a); } #[tokio::test] - async fn test_create_graph_with_headers() { + async fn test_build_graph_with_headers() { let mut loader = setup( vec![( "https://example.com/a", @@ -812,12 +880,14 @@ console.log(a); ); let root_specifier = ModuleSpecifier::parse("https://example.com/a").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!(graph.module_slots.len(), 1); assert_eq!(graph.roots, vec![root_specifier.clone()]); let maybe_root_module = graph.module_slots.get(&root_specifier); @@ -831,7 +901,7 @@ console.log(a); } #[tokio::test] - async fn test_create_graph_jsx_import_source() { + async fn test_build_graph_jsx_import_source() { let mut loader = setup( vec![ ( @@ -863,12 +933,14 @@ console.log(a); ); let root_specifier = ModuleSpecifier::parse("file:///a/test01.tsx").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!( json!(graph), json!({ @@ -929,12 +1001,14 @@ console.log(a); ); let root_specifier = ModuleSpecifier::parse("file:///a/test.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; let result = graph.valid(); assert!(result.is_err()); let err = result.unwrap_err(); @@ -967,12 +1041,14 @@ console.log(a); ); let root_specifier = ModuleSpecifier::parse("file:///a/test.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; let result = graph.valid(); assert!(result.is_err()); let err = result.unwrap_err(); @@ -1007,12 +1083,14 @@ console.log(a); ); let root_specifier = ModuleSpecifier::parse("file:///a/test01").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert!(graph.valid().is_ok()); } @@ -1041,12 +1119,14 @@ console.log(a); ); let root_specifier = ModuleSpecifier::parse("file:///a.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!( json!(graph), json!({ @@ -1092,7 +1172,7 @@ console.log(a); } #[tokio::test] - async fn test_create_graph_with_jsdoc_imports() { + async fn test_build_graph_with_jsdoc_imports() { let mut loader = setup( vec![ ( @@ -1133,8 +1213,10 @@ export function a(a) { vec![], ); let root = ModuleSpecifier::parse("file:///a/test.js").unwrap(); - let graph = - create_graph(vec![root.clone()], &mut loader, Default::default()).await; + let mut graph = ModuleGraph::default(); + graph + .build(vec![root.clone()], &mut loader, Default::default()) + .await; assert_eq!( json!(graph), json!({ @@ -1201,7 +1283,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_with_redirects() { + async fn test_build_graph_with_redirects() { let mut loader = setup( vec![ ( @@ -1231,12 +1313,14 @@ export function a(a) { ); let root_specifier = ModuleSpecifier::parse("https://example.com/a").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!( graph.roots, vec![ModuleSpecifier::parse("https://example.com/a").unwrap(),] @@ -1265,7 +1349,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_with_circular_redirects() { + async fn test_build_graph_with_circular_redirects() { let mut loader = setup( vec![ ( @@ -1296,12 +1380,14 @@ export function a(a) { ); let root_specifier = ModuleSpecifier::parse("https://example.com/a").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!( graph.roots, vec![ModuleSpecifier::parse("https://example.com/a").unwrap(),] @@ -1330,7 +1416,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_with_data_url() { + async fn test_build_graph_with_data_url() { let mut loader = setup( vec![ ( @@ -1354,12 +1440,14 @@ export function a(a) { ); let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!(graph.module_slots.len(), 3); let data_specifier = ModuleSpecifier::parse("data:application/typescript,export%20*%20from%20%22https://example.com/c.ts%22;").unwrap(); let maybe_module = graph.get(&data_specifier); @@ -1370,7 +1458,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_with_resolver() { + async fn test_build_graph_with_resolver() { let mut loader = setup( vec![ ( @@ -1398,15 +1486,17 @@ export function a(a) { ); let maybe_resolver: Option<&dyn Resolver> = Some(&resolver); let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").unwrap(); - let graph = create_graph( - vec![root_specifier], - &mut loader, - GraphOptions { - resolver: maybe_resolver, - ..Default::default() - }, - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier], + &mut loader, + BuildOptions { + resolver: maybe_resolver, + ..Default::default() + }, + ) + .await; let maybe_module = graph.get(&graph.roots[0]); assert!(maybe_module.is_some()); let module = maybe_module.unwrap(); @@ -1424,7 +1514,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_with_resolve_types() { + async fn test_build_graph_with_resolve_types() { let mut loader = setup( vec![ ( @@ -1462,15 +1552,17 @@ export function a(a) { ); let maybe_resolver: Option<&dyn Resolver> = Some(&resolver); let root_specifier = ModuleSpecifier::parse("file:///a.js").unwrap(); - let graph = create_graph( - vec![root_specifier], - &mut loader, - GraphOptions { - resolver: maybe_resolver, - ..Default::default() - }, - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier], + &mut loader, + BuildOptions { + resolver: maybe_resolver, + ..Default::default() + }, + ) + .await; let maybe_module = graph.get(&graph.roots[0]); assert!(maybe_module.is_some()); let module = maybe_module.unwrap(); @@ -1491,7 +1583,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_import_assertions() { + async fn test_build_graph_import_assertions() { let mut loader = setup( vec![ ( @@ -1545,12 +1637,14 @@ export function a(a) { ); let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!( json!(graph), json!({ @@ -1666,7 +1760,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_mixed_assertions() { + async fn test_build_graph_mixed_assertions() { let mut loader = setup( vec![ ( @@ -1693,12 +1787,14 @@ export function a(a) { ); let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!( json!(graph), json!({ @@ -1744,7 +1840,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_import_assertion_errors() { + async fn test_build_graph_import_assertion_errors() { let mut loader = setup( vec![ ( @@ -1806,12 +1902,14 @@ export function a(a) { ); let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!( json!(graph), json!({ @@ -1959,7 +2057,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_with_reporter() { + async fn test_build_graph_with_reporter() { let mut loader = setup( vec![ ( @@ -2012,15 +2110,17 @@ export function a(a) { let reporter = CollectingReporter { on_loads: RefCell::new(vec![]), }; - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - GraphOptions { - reporter: Some(&reporter), - ..Default::default() - }, - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + BuildOptions { + reporter: Some(&reporter), + ..Default::default() + }, + ) + .await; assert_eq!(graph.modules().count(), 5); let on_loads = reporter.on_loads.into_inner(); @@ -2061,7 +2161,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_types_only() { + async fn test_build_graph_types_only() { let mut loader = setup( vec![ ( @@ -2148,15 +2248,14 @@ export function a(a) { ); let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - GraphOptions { - build_kind: BuildKind::TypesOnly, - ..Default::default() - }, - ) - .await; + let mut graph = ModuleGraph::new(GraphKind::TypesOnly); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!( json!(graph), json!({ @@ -2289,7 +2388,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_code_only() { + async fn test_build_graph_code_only() { let mut loader = setup( vec![ ( @@ -2376,15 +2475,14 @@ export function a(a) { ); let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - GraphOptions { - build_kind: BuildKind::CodeOnly, - ..Default::default() - }, - ) - .await; + let mut graph = ModuleGraph::new(GraphKind::CodeOnly); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!( json!(graph), json!({ @@ -2500,7 +2598,7 @@ export function a(a) { } #[tokio::test] - async fn test_create_graph_with_builtin_external() { + async fn test_build_graph_with_builtin_external() { let mut loader = setup( vec![ ( @@ -2524,12 +2622,14 @@ export function a(a) { ); let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url"); - let graph = create_graph( - vec![root_specifier.clone()], - &mut loader, - Default::default(), - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build( + vec![root_specifier.clone()], + &mut loader, + Default::default(), + ) + .await; assert_eq!( json!(graph), json!({