0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 17:34:47 -05:00

perf: analyze cjs exports and emit typescript in parallel (#23856)

This commit is contained in:
David Sherret 2024-05-18 11:42:03 -04:00 committed by GitHub
parent fcb6a18b2b
commit a2dbcf9e0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 398 additions and 153 deletions

1
Cargo.lock generated
View file

@ -1661,6 +1661,7 @@ version = "0.89.0"
dependencies = [ dependencies = [
"aead-gcm-stream", "aead-gcm-stream",
"aes", "aes",
"async-trait",
"brotli 3.5.0", "brotli 3.5.0",
"bytes", "bytes",
"cbc", "cbc",

View file

@ -17,8 +17,8 @@ use std::sync::Arc;
pub struct Emitter { pub struct Emitter {
emit_cache: EmitCache, emit_cache: EmitCache,
parsed_source_cache: Arc<ParsedSourceCache>, parsed_source_cache: Arc<ParsedSourceCache>,
transpile_options: deno_ast::TranspileOptions, transpile_and_emit_options:
emit_options: deno_ast::EmitOptions, Arc<(deno_ast::TranspileOptions, deno_ast::EmitOptions)>,
// cached hash of the transpile and emit options // cached hash of the transpile and emit options
transpile_and_emit_options_hash: u64, transpile_and_emit_options_hash: u64,
} }
@ -39,16 +39,16 @@ impl Emitter {
Self { Self {
emit_cache, emit_cache,
parsed_source_cache, parsed_source_cache,
emit_options, transpile_and_emit_options: Arc::new((transpile_options, emit_options)),
transpile_options,
transpile_and_emit_options_hash, transpile_and_emit_options_hash,
} }
} }
pub fn cache_module_emits( pub async fn cache_module_emits(
&self, &self,
graph: &ModuleGraph, graph: &ModuleGraph,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
// todo(dsherret): we could do this concurrently
for module in graph.modules() { for module in graph.modules() {
if let Module::Js(module) = module { if let Module::Js(module) = module {
let is_emittable = matches!( let is_emittable = matches!(
@ -60,11 +60,13 @@ impl Emitter {
| MediaType::Tsx | MediaType::Tsx
); );
if is_emittable { if is_emittable {
self.emit_parsed_source( self
&module.specifier, .emit_parsed_source(
module.media_type, &module.specifier,
&module.source, module.media_type,
)?; &module.source,
)
.await?;
} }
} }
} }
@ -81,42 +83,70 @@ impl Emitter {
self.emit_cache.get_emit_code(specifier, source_hash) self.emit_cache.get_emit_code(specifier, source_hash)
} }
pub fn emit_parsed_source( pub async fn emit_parsed_source(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
media_type: MediaType, media_type: MediaType,
source: &Arc<str>, source: &Arc<str>,
) -> Result<ModuleCodeString, AnyError> { ) -> Result<ModuleCodeString, AnyError> {
let source_hash = self.get_source_hash(source); // Note: keep this in sync with the sync version below
let helper = EmitParsedSourceHelper(self);
match helper.pre_emit_parsed_source(specifier, source) {
PreEmitResult::Cached(emitted_text) => Ok(emitted_text),
PreEmitResult::NotCached { source_hash } => {
let parsed_source_cache = self.parsed_source_cache.clone();
let transpile_and_emit_options =
self.transpile_and_emit_options.clone();
let transpile_result = deno_core::unsync::spawn_blocking({
let specifier = specifier.clone();
let source = source.clone();
move || -> Result<_, AnyError> {
EmitParsedSourceHelper::transpile(
&parsed_source_cache,
&specifier,
source.clone(),
media_type,
&transpile_and_emit_options.0,
&transpile_and_emit_options.1,
)
}
})
.await
.unwrap()?;
Ok(helper.post_emit_parsed_source(
specifier,
transpile_result,
source_hash,
))
}
}
}
if let Some(emit_code) = pub fn emit_parsed_source_sync(
self.emit_cache.get_emit_code(specifier, source_hash) &self,
{ specifier: &ModuleSpecifier,
Ok(emit_code.into()) media_type: MediaType,
} else { source: &Arc<str>,
// nothing else needs the parsed source at this point, so remove from ) -> Result<ModuleCodeString, AnyError> {
// the cache in order to not transpile owned // Note: keep this in sync with the async version above
let parsed_source = self.parsed_source_cache.remove_or_parse_module( let helper = EmitParsedSourceHelper(self);
specifier, match helper.pre_emit_parsed_source(specifier, source) {
source.clone(), PreEmitResult::Cached(emitted_text) => Ok(emitted_text),
media_type, PreEmitResult::NotCached { source_hash } => {
)?; let transpile_result = EmitParsedSourceHelper::transpile(
let transpiled_source = match parsed_source &self.parsed_source_cache,
.transpile(&self.transpile_options, &self.emit_options)? specifier,
{ source.clone(),
TranspileResult::Owned(source) => source, media_type,
TranspileResult::Cloned(source) => { &self.transpile_and_emit_options.0,
debug_assert!(false, "Transpile owned failed."); &self.transpile_and_emit_options.1,
source )?;
} Ok(helper.post_emit_parsed_source(
}; specifier,
debug_assert!(transpiled_source.source_map.is_none()); transpile_result,
self.emit_cache.set_emit_code( source_hash,
specifier, ))
source_hash, }
&transpiled_source.text,
);
Ok(transpiled_source.text.into())
} }
} }
@ -134,10 +164,10 @@ impl Emitter {
let parsed_source = self let parsed_source = self
.parsed_source_cache .parsed_source_cache
.remove_or_parse_module(specifier, source_arc, media_type)?; .remove_or_parse_module(specifier, source_arc, media_type)?;
let mut options = self.emit_options.clone(); let mut options = self.transpile_and_emit_options.1.clone();
options.source_map = SourceMapOption::None; options.source_map = SourceMapOption::None;
let transpiled_source = parsed_source let transpiled_source = parsed_source
.transpile(&self.transpile_options, &options)? .transpile(&self.transpile_and_emit_options.0, &options)?
.into_source(); .into_source();
Ok(transpiled_source.text) Ok(transpiled_source.text)
} }
@ -152,3 +182,66 @@ impl Emitter {
.finish() .finish()
} }
} }
enum PreEmitResult {
Cached(ModuleCodeString),
NotCached { source_hash: u64 },
}
/// Helper to share code between async and sync emit_parsed_source methods.
struct EmitParsedSourceHelper<'a>(&'a Emitter);
impl<'a> EmitParsedSourceHelper<'a> {
pub fn pre_emit_parsed_source(
&self,
specifier: &ModuleSpecifier,
source: &Arc<str>,
) -> PreEmitResult {
let source_hash = self.0.get_source_hash(source);
if let Some(emit_code) =
self.0.emit_cache.get_emit_code(specifier, source_hash)
{
PreEmitResult::Cached(emit_code.into())
} else {
PreEmitResult::NotCached { source_hash }
}
}
pub fn transpile(
parsed_source_cache: &ParsedSourceCache,
specifier: &ModuleSpecifier,
source: Arc<str>,
media_type: MediaType,
transpile_options: &deno_ast::TranspileOptions,
emit_options: &deno_ast::EmitOptions,
) -> Result<TranspileResult, AnyError> {
// nothing else needs the parsed source at this point, so remove from
// the cache in order to not transpile owned
let parsed_source = parsed_source_cache
.remove_or_parse_module(specifier, source, media_type)?;
Ok(parsed_source.transpile(transpile_options, emit_options)?)
}
pub fn post_emit_parsed_source(
&self,
specifier: &ModuleSpecifier,
transpile_result: TranspileResult,
source_hash: u64,
) -> ModuleCodeString {
let transpiled_source = match transpile_result {
TranspileResult::Owned(source) => source,
TranspileResult::Cloned(source) => {
debug_assert!(false, "Transpile owned failed.");
source
}
};
debug_assert!(transpiled_source.source_map.is_none());
self.0.emit_cache.set_emit_code(
specifier,
source_hash,
&transpiled_source.text,
);
transpiled_source.text.into()
}
}

View file

@ -120,7 +120,7 @@ async fn run_subcommand(flags: Flags) -> Result<i32, AnyError> {
main_graph_container main_graph_container
.load_and_type_check_files(&cache_flags.files) .load_and_type_check_files(&cache_flags.files)
.await?; .await?;
emitter.cache_module_emits(&main_graph_container.graph()) emitter.cache_module_emits(&main_graph_container.graph()).await
}), }),
DenoSubcommand::Check(check_flags) => spawn_subcommand(async move { DenoSubcommand::Check(check_flags) => spawn_subcommand(async move {
let factory = CliFactory::from_flags(flags)?; let factory = CliFactory::from_flags(flags)?;

View file

@ -275,7 +275,7 @@ impl CliModuleLoaderFactory {
root_permissions: PermissionsContainer, root_permissions: PermissionsContainer,
dynamic_permissions: PermissionsContainer, dynamic_permissions: PermissionsContainer,
) -> ModuleLoaderAndSourceMapGetter { ) -> ModuleLoaderAndSourceMapGetter {
let loader = Rc::new(CliModuleLoader { let loader = Rc::new(CliModuleLoader(Rc::new(CliModuleLoaderInner {
lib, lib,
root_permissions, root_permissions,
dynamic_permissions, dynamic_permissions,
@ -283,7 +283,7 @@ impl CliModuleLoaderFactory {
emitter: self.shared.emitter.clone(), emitter: self.shared.emitter.clone(),
parsed_source_cache: self.shared.parsed_source_cache.clone(), parsed_source_cache: self.shared.parsed_source_cache.clone(),
shared: self.shared.clone(), shared: self.shared.clone(),
}); })));
ModuleLoaderAndSourceMapGetter { ModuleLoaderAndSourceMapGetter {
module_loader: loader.clone(), module_loader: loader.clone(),
source_map_getter: Some(loader), source_map_getter: Some(loader),
@ -322,7 +322,7 @@ impl ModuleLoaderFactory for CliModuleLoaderFactory {
} }
} }
struct CliModuleLoader<TGraphContainer: ModuleGraphContainer> { struct CliModuleLoaderInner<TGraphContainer: ModuleGraphContainer> {
lib: TsTypeLib, lib: TsTypeLib,
/// The initial set of permissions used to resolve the static imports in the /// The initial set of permissions used to resolve the static imports in the
/// worker. These are "allow all" for main worker, and parent thread /// worker. These are "allow all" for main worker, and parent thread
@ -337,8 +337,10 @@ struct CliModuleLoader<TGraphContainer: ModuleGraphContainer> {
graph_container: TGraphContainer, graph_container: TGraphContainer,
} }
impl<TGraphContainer: ModuleGraphContainer> CliModuleLoader<TGraphContainer> { impl<TGraphContainer: ModuleGraphContainer>
fn load_sync( CliModuleLoaderInner<TGraphContainer>
{
async fn load_inner(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>, maybe_referrer: Option<&ModuleSpecifier>,
@ -353,11 +355,12 @@ impl<TGraphContainer: ModuleGraphContainer> CliModuleLoader<TGraphContainer> {
let code_source = if let Some(result) = self let code_source = if let Some(result) = self
.shared .shared
.npm_module_loader .npm_module_loader
.load_sync_if_in_npm_package(specifier, maybe_referrer, permissions) .load_if_in_npm_package(specifier, maybe_referrer, permissions)
.await
{ {
result? result?
} else { } else {
self.load_prepared_module(specifier, maybe_referrer)? self.load_prepared_module(specifier, maybe_referrer).await?
}; };
let code = if self.shared.is_inspecting { let code = if self.shared.is_inspecting {
// we need the code with the source map in order for // we need the code with the source map in order for
@ -574,27 +577,98 @@ impl<TGraphContainer: ModuleGraphContainer> CliModuleLoader<TGraphContainer> {
Ok(Some(timestamp)) Ok(Some(timestamp))
} }
fn load_prepared_module( async fn load_prepared_module(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>, maybe_referrer: Option<&ModuleSpecifier>,
) -> Result<ModuleCodeStringSource, AnyError> { ) -> Result<ModuleCodeStringSource, AnyError> {
// Note: keep this in sync with the sync version below
let graph = self.graph_container.graph();
match self.load_prepared_module_or_defer_emit(
&graph,
specifier,
maybe_referrer,
) {
Ok(CodeOrDeferredEmit::Code(code_source)) => Ok(code_source),
Ok(CodeOrDeferredEmit::DeferredEmit {
specifier,
media_type,
source,
}) => {
let transpile_result = self
.emitter
.emit_parsed_source(specifier, media_type, source)
.await?;
// at this point, we no longer need the parsed source in memory, so free it
self.parsed_source_cache.free(specifier);
Ok(ModuleCodeStringSource {
code: transpile_result,
found_url: specifier.clone(),
media_type,
})
}
Err(err) => Err(err),
}
}
fn load_prepared_module_sync(
&self,
specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>,
) -> Result<ModuleCodeStringSource, AnyError> {
// Note: keep this in sync with the async version above
let graph = self.graph_container.graph();
match self.load_prepared_module_or_defer_emit(
&graph,
specifier,
maybe_referrer,
) {
Ok(CodeOrDeferredEmit::Code(code_source)) => Ok(code_source),
Ok(CodeOrDeferredEmit::DeferredEmit {
specifier,
media_type,
source,
}) => {
let transpile_result = self
.emitter
.emit_parsed_source_sync(specifier, media_type, source)?;
// at this point, we no longer need the parsed source in memory, so free it
self.parsed_source_cache.free(specifier);
Ok(ModuleCodeStringSource {
code: transpile_result,
found_url: specifier.clone(),
media_type,
})
}
Err(err) => Err(err),
}
}
fn load_prepared_module_or_defer_emit<'graph>(
&self,
graph: &'graph ModuleGraph,
specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>,
) -> Result<CodeOrDeferredEmit<'graph>, AnyError> {
if specifier.scheme() == "node" { if specifier.scheme() == "node" {
unreachable!(); // Node built-in modules should be handled internally. unreachable!(); // Node built-in modules should be handled internally.
} }
let graph = self.graph_container.graph();
match graph.get(specifier) { match graph.get(specifier) {
Some(deno_graph::Module::Json(JsonModule { Some(deno_graph::Module::Json(JsonModule {
source, source,
media_type, media_type,
specifier, specifier,
.. ..
})) => Ok(ModuleCodeStringSource { })) => Ok(CodeOrDeferredEmit::Code(ModuleCodeStringSource {
code: source.clone().into(), code: source.clone().into(),
found_url: specifier.clone(), found_url: specifier.clone(),
media_type: *media_type, media_type: *media_type,
}), })),
Some(deno_graph::Module::Js(JsModule { Some(deno_graph::Module::Js(JsModule {
source, source,
media_type, media_type,
@ -615,10 +689,11 @@ impl<TGraphContainer: ModuleGraphContainer> CliModuleLoader<TGraphContainer> {
| MediaType::Cts | MediaType::Cts
| MediaType::Jsx | MediaType::Jsx
| MediaType::Tsx => { | MediaType::Tsx => {
// get emit text return Ok(CodeOrDeferredEmit::DeferredEmit {
self specifier,
.emitter media_type: *media_type,
.emit_parsed_source(specifier, *media_type, source)? source,
});
} }
MediaType::TsBuildInfo | MediaType::Wasm | MediaType::SourceMap => { MediaType::TsBuildInfo | MediaType::Wasm | MediaType::SourceMap => {
panic!("Unexpected media type {media_type} for {specifier}") panic!("Unexpected media type {media_type} for {specifier}")
@ -628,11 +703,11 @@ impl<TGraphContainer: ModuleGraphContainer> CliModuleLoader<TGraphContainer> {
// at this point, we no longer need the parsed source in memory, so free it // at this point, we no longer need the parsed source in memory, so free it
self.parsed_source_cache.free(specifier); self.parsed_source_cache.free(specifier);
Ok(ModuleCodeStringSource { Ok(CodeOrDeferredEmit::Code(ModuleCodeStringSource {
code, code,
found_url: specifier.clone(), found_url: specifier.clone(),
media_type: *media_type, media_type: *media_type,
}) }))
} }
Some( Some(
deno_graph::Module::External(_) deno_graph::Module::External(_)
@ -650,6 +725,20 @@ impl<TGraphContainer: ModuleGraphContainer> CliModuleLoader<TGraphContainer> {
} }
} }
enum CodeOrDeferredEmit<'a> {
Code(ModuleCodeStringSource),
DeferredEmit {
specifier: &'a ModuleSpecifier,
media_type: MediaType,
source: &'a Arc<str>,
},
}
// todo(dsherret): this double Rc boxing is not ideal
struct CliModuleLoader<TGraphContainer: ModuleGraphContainer>(
Rc<CliModuleLoaderInner<TGraphContainer>>,
);
impl<TGraphContainer: ModuleGraphContainer> ModuleLoader impl<TGraphContainer: ModuleGraphContainer> ModuleLoader
for CliModuleLoader<TGraphContainer> for CliModuleLoader<TGraphContainer>
{ {
@ -672,8 +761,8 @@ impl<TGraphContainer: ModuleGraphContainer> ModuleLoader
Ok(()) Ok(())
} }
let referrer = self.resolve_referrer(referrer)?; let referrer = self.0.resolve_referrer(referrer)?;
let specifier = self.inner_resolve(specifier, &referrer, kind)?; let specifier = self.0.inner_resolve(specifier, &referrer, kind)?;
ensure_not_jsr_non_jsr_remote_import(&specifier, &referrer)?; ensure_not_jsr_non_jsr_remote_import(&specifier, &referrer)?;
Ok(specifier) Ok(specifier)
} }
@ -685,15 +774,22 @@ impl<TGraphContainer: ModuleGraphContainer> ModuleLoader
is_dynamic: bool, is_dynamic: bool,
requested_module_type: RequestedModuleType, requested_module_type: RequestedModuleType,
) -> deno_core::ModuleLoadResponse { ) -> deno_core::ModuleLoadResponse {
// NOTE: this block is async only because of `deno_core` interface let inner = self.0.clone();
// requirements; module was already loaded when constructing module graph let specifier = specifier.clone();
// during call to `prepare_load` so we can load it synchronously. let maybe_referrer = maybe_referrer.cloned();
deno_core::ModuleLoadResponse::Sync(self.load_sync( deno_core::ModuleLoadResponse::Async(
specifier, async move {
maybe_referrer, inner
is_dynamic, .load_inner(
requested_module_type, &specifier,
)) maybe_referrer.as_ref(),
is_dynamic,
requested_module_type,
)
.await
}
.boxed_local(),
)
} }
fn prepare_load( fn prepare_load(
@ -702,22 +798,23 @@ impl<TGraphContainer: ModuleGraphContainer> ModuleLoader
_maybe_referrer: Option<String>, _maybe_referrer: Option<String>,
is_dynamic: bool, is_dynamic: bool,
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> { ) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> {
if self.shared.node_resolver.in_npm_package(specifier) { if self.0.shared.node_resolver.in_npm_package(specifier) {
return Box::pin(deno_core::futures::future::ready(Ok(()))); return Box::pin(deno_core::futures::future::ready(Ok(())));
} }
let specifier = specifier.clone(); let specifier = specifier.clone();
let graph_container = self.graph_container.clone(); let inner = self.0.clone();
let module_load_preparer = self.shared.module_load_preparer.clone();
let root_permissions = if is_dynamic {
self.dynamic_permissions.clone()
} else {
self.root_permissions.clone()
};
let lib = self.lib;
async move { async move {
let graph_container = inner.graph_container.clone();
let module_load_preparer = inner.shared.module_load_preparer.clone();
let root_permissions = if is_dynamic {
inner.dynamic_permissions.clone()
} else {
inner.root_permissions.clone()
};
let lib = inner.lib;
let mut update_permit = graph_container.acquire_update_permit().await; let mut update_permit = graph_container.acquire_update_permit().await;
let graph = update_permit.graph_mut(); let graph = update_permit.graph_mut();
module_load_preparer module_load_preparer
@ -740,9 +837,10 @@ impl<TGraphContainer: ModuleGraphContainer> ModuleLoader
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
code_cache: &[u8], code_cache: &[u8],
) -> Pin<Box<dyn Future<Output = ()>>> { ) -> Pin<Box<dyn Future<Output = ()>>> {
if let Some(cache) = self.shared.code_cache.as_ref() { if let Some(cache) = self.0.shared.code_cache.as_ref() {
let media_type = MediaType::from_specifier(specifier); let media_type = MediaType::from_specifier(specifier);
let code_hash = self let code_hash = self
.0
.get_code_hash_or_timestamp(specifier, media_type) .get_code_hash_or_timestamp(specifier, media_type)
.ok() .ok()
.flatten(); .flatten();
@ -774,7 +872,7 @@ impl<TGraphContainer: ModuleGraphContainer> SourceMapGetter
"wasm" | "file" | "http" | "https" | "data" | "blob" => (), "wasm" | "file" | "http" | "https" | "data" | "blob" => (),
_ => return None, _ => return None,
} }
let source = self.load_prepared_module(&specifier, None).ok()?; let source = self.0.load_prepared_module_sync(&specifier, None).ok()?;
source_map_from_code(&source.code) source_map_from_code(&source.code)
} }
@ -783,7 +881,7 @@ impl<TGraphContainer: ModuleGraphContainer> SourceMapGetter
file_name: &str, file_name: &str,
line_number: usize, line_number: usize,
) -> Option<String> { ) -> Option<String> {
let graph = self.graph_container.graph(); let graph = self.0.graph_container.graph();
let code = match graph.get(&resolve_url(file_name).ok()?) { let code = match graph.get(&resolve_url(file_name).ok()?) {
Some(deno_graph::Module::Js(module)) => &module.source, Some(deno_graph::Module::Js(module)) => &module.source,
Some(deno_graph::Module::Json(module)) => &module.source, Some(deno_graph::Module::Json(module)) => &module.source,

View file

@ -1,5 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::sync::Arc;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_ast::ModuleSpecifier; use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError; use deno_core::error::AnyError;
@ -56,7 +58,7 @@ impl CliCjsCodeAnalyzer {
Self { cache, fs } Self { cache, fs }
} }
fn inner_cjs_analysis( async fn inner_cjs_analysis(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
source: &str, source: &str,
@ -77,23 +79,32 @@ impl CliCjsCodeAnalyzer {
}); });
} }
let parsed_source = deno_ast::parse_program(deno_ast::ParseParams { let analysis = deno_core::unsync::spawn_blocking({
specifier: specifier.clone(), let specifier = specifier.clone();
text_info: deno_ast::SourceTextInfo::new(source.into()), let source: Arc<str> = source.into();
media_type, move || -> Result<_, deno_ast::ParseDiagnostic> {
capture_tokens: true, let parsed_source = deno_ast::parse_program(deno_ast::ParseParams {
scope_analysis: false, specifier,
maybe_syntax: None, text_info: deno_ast::SourceTextInfo::new(source),
})?; media_type,
let analysis = if parsed_source.is_script() { capture_tokens: true,
let analysis = parsed_source.analyze_cjs(); scope_analysis: false,
CliCjsAnalysis::Cjs { maybe_syntax: None,
exports: analysis.exports, })?;
reexports: analysis.reexports, if parsed_source.is_script() {
let analysis = parsed_source.analyze_cjs();
Ok(CliCjsAnalysis::Cjs {
exports: analysis.exports,
reexports: analysis.reexports,
})
} else {
Ok(CliCjsAnalysis::Esm)
}
} }
} else { })
CliCjsAnalysis::Esm .await
}; .unwrap()?;
self self
.cache .cache
.set_cjs_analysis(specifier.as_str(), &source_hash, &analysis); .set_cjs_analysis(specifier.as_str(), &source_hash, &analysis);
@ -102,19 +113,23 @@ impl CliCjsCodeAnalyzer {
} }
} }
#[async_trait::async_trait(?Send)]
impl CjsCodeAnalyzer for CliCjsCodeAnalyzer { impl CjsCodeAnalyzer for CliCjsCodeAnalyzer {
fn analyze_cjs( async fn analyze_cjs(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
source: Option<String>, source: Option<String>,
) -> Result<ExtNodeCjsAnalysis, AnyError> { ) -> Result<ExtNodeCjsAnalysis, AnyError> {
let source = match source { let source = match source {
Some(source) => source, Some(source) => source,
None => self None => {
.fs self
.read_text_file_sync(&specifier.to_file_path().unwrap(), None)?, .fs
.read_text_file_async(specifier.to_file_path().unwrap(), None)
.await?
}
}; };
let analysis = self.inner_cjs_analysis(specifier, &source)?; let analysis = self.inner_cjs_analysis(specifier, &source).await?;
match analysis { match analysis {
CliCjsAnalysis::Esm => Ok(ExtNodeCjsAnalysis::Esm(source)), CliCjsAnalysis::Esm => Ok(ExtNodeCjsAnalysis::Esm(source)),
CliCjsAnalysis::Cjs { exports, reexports } => { CliCjsAnalysis::Cjs { exports, reexports } => {

View file

@ -1,6 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use dashmap::DashMap; use dashmap::DashMap;
use dashmap::DashSet;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::anyhow::anyhow; use deno_core::anyhow::anyhow;
use deno_core::anyhow::Context; use deno_core::anyhow::Context;
@ -8,7 +9,6 @@ use deno_core::error::AnyError;
use deno_core::futures::future; use deno_core::futures::future;
use deno_core::futures::future::LocalBoxFuture; use deno_core::futures::future::LocalBoxFuture;
use deno_core::futures::FutureExt; use deno_core::futures::FutureExt;
use deno_core::parking_lot::Mutex;
use deno_core::ModuleCodeString; use deno_core::ModuleCodeString;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_graph::source::NpmPackageReqResolution; use deno_graph::source::NpmPackageReqResolution;
@ -34,7 +34,6 @@ use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq; use deno_semver::package::PackageReq;
use import_map::ImportMap; use import_map::ImportMap;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
@ -272,20 +271,20 @@ impl NpmModuleLoader {
} }
} }
pub fn load_sync_if_in_npm_package( pub async fn load_if_in_npm_package(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>, maybe_referrer: Option<&ModuleSpecifier>,
permissions: &PermissionsContainer, permissions: &PermissionsContainer,
) -> Option<Result<ModuleCodeStringSource, AnyError>> { ) -> Option<Result<ModuleCodeStringSource, AnyError>> {
if self.node_resolver.in_npm_package(specifier) { if self.node_resolver.in_npm_package(specifier) {
Some(self.load_sync(specifier, maybe_referrer, permissions)) Some(self.load(specifier, maybe_referrer, permissions).await)
} else { } else {
None None
} }
} }
fn load_sync( pub async fn load(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>, maybe_referrer: Option<&ModuleSpecifier>,
@ -294,7 +293,8 @@ impl NpmModuleLoader {
let file_path = specifier.to_file_path().unwrap(); let file_path = specifier.to_file_path().unwrap();
let code = self let code = self
.fs .fs
.read_text_file_sync(&file_path, None) .read_text_file_async(file_path.clone(), None)
.await
.map_err(AnyError::from) .map_err(AnyError::from)
.with_context(|| { .with_context(|| {
if file_path.is_dir() { if file_path.is_dir() {
@ -329,11 +329,10 @@ impl NpmModuleLoader {
let code = if self.cjs_resolutions.contains(specifier) { let code = if self.cjs_resolutions.contains(specifier) {
// translate cjs to esm if it's cjs and inject node globals // translate cjs to esm if it's cjs and inject node globals
self.node_code_translator.translate_cjs_to_esm( self
specifier, .node_code_translator
Some(code), .translate_cjs_to_esm(specifier, Some(code), permissions)
permissions, .await?
)?
} else { } else {
// esm and json code is untouched // esm and json code is untouched
code code
@ -348,15 +347,15 @@ impl NpmModuleLoader {
/// Keeps track of what module specifiers were resolved as CJS. /// Keeps track of what module specifiers were resolved as CJS.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct CjsResolutionStore(Mutex<HashSet<ModuleSpecifier>>); pub struct CjsResolutionStore(DashSet<ModuleSpecifier>);
impl CjsResolutionStore { impl CjsResolutionStore {
pub fn contains(&self, specifier: &ModuleSpecifier) -> bool { pub fn contains(&self, specifier: &ModuleSpecifier) -> bool {
self.0.lock().contains(specifier) self.0.contains(specifier)
} }
pub fn insert(&self, specifier: ModuleSpecifier) { pub fn insert(&self, specifier: ModuleSpecifier) {
self.0.lock().insert(specifier); self.0.insert(specifier);
} }
} }

View file

@ -193,33 +193,33 @@ impl ModuleLoader for EmbeddedModuleLoader {
)); ));
} }
let permissions = if is_dynamic { if self.shared.node_resolver.in_npm_package(original_specifier) {
&self.dynamic_permissions let npm_module_loader = self.shared.npm_module_loader.clone();
} else { let original_specifier = original_specifier.clone();
&self.root_permissions let maybe_referrer = maybe_referrer.cloned();
}; let permissions = if is_dynamic {
if let Some(result) = self.dynamic_permissions.clone()
self.shared.npm_module_loader.load_sync_if_in_npm_package( } else {
original_specifier, self.root_permissions.clone()
maybe_referrer, };
permissions, return deno_core::ModuleLoadResponse::Async(
) async move {
{ let code_source = npm_module_loader
return match result { .load(&original_specifier, maybe_referrer.as_ref(), &permissions)
Ok(code_source) => deno_core::ModuleLoadResponse::Sync(Ok( .await?;
deno_core::ModuleSource::new_with_redirect( Ok(deno_core::ModuleSource::new_with_redirect(
match code_source.media_type { match code_source.media_type {
MediaType::Json => ModuleType::Json, MediaType::Json => ModuleType::Json,
_ => ModuleType::JavaScript, _ => ModuleType::JavaScript,
}, },
ModuleSourceCode::String(code_source.code), ModuleSourceCode::String(code_source.code),
original_specifier, &original_specifier,
&code_source.found_url, &code_source.found_url,
None, None,
), ))
)), }
Err(err) => deno_core::ModuleLoadResponse::Sync(Err(err)), .boxed_local(),
}; );
} }
let Some(module) = let Some(module) =

View file

@ -16,6 +16,7 @@ path = "lib.rs"
[dependencies] [dependencies]
aead-gcm-stream = "0.1" aead-gcm-stream = "0.1"
aes.workspace = true aes.workspace = true
async-trait.workspace = true
brotli.workspace = true brotli.workspace = true
bytes.workspace = true bytes.workspace = true
cbc.workspace = true cbc.workspace = true

View file

@ -36,6 +36,7 @@ pub struct CjsAnalysisExports {
} }
/// Code analyzer for CJS and ESM files. /// Code analyzer for CJS and ESM files.
#[async_trait::async_trait(?Send)]
pub trait CjsCodeAnalyzer { pub trait CjsCodeAnalyzer {
/// Analyzes CommonJs code for exports and reexports, which is /// Analyzes CommonJs code for exports and reexports, which is
/// then used to determine the wrapper ESM module exports. /// then used to determine the wrapper ESM module exports.
@ -44,7 +45,7 @@ pub trait CjsCodeAnalyzer {
/// already has it. If the source is needed by the implementation, /// already has it. If the source is needed by the implementation,
/// then it can use the provided source, or otherwise load it if /// then it can use the provided source, or otherwise load it if
/// necessary. /// necessary.
fn analyze_cjs( async fn analyze_cjs(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
maybe_source: Option<String>, maybe_source: Option<String>,
@ -79,7 +80,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
/// For all discovered reexports the analysis will be performed recursively. /// For all discovered reexports the analysis will be performed recursively.
/// ///
/// If successful a source code for equivalent ES module is returned. /// If successful a source code for equivalent ES module is returned.
pub fn translate_cjs_to_esm( pub async fn translate_cjs_to_esm(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
source: Option<String>, source: Option<String>,
@ -88,7 +89,10 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
let mut temp_var_count = 0; let mut temp_var_count = 0;
let mut handled_reexports: HashSet<ModuleSpecifier> = HashSet::default(); let mut handled_reexports: HashSet<ModuleSpecifier> = HashSet::default();
let analysis = self.cjs_code_analyzer.analyze_cjs(specifier, source)?; let analysis = self
.cjs_code_analyzer
.analyze_cjs(specifier, source)
.await?;
let analysis = match analysis { let analysis = match analysis {
CjsAnalysis::Esm(source) => return Ok(source), CjsAnalysis::Esm(source) => return Ok(source),
@ -113,6 +117,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
reexports_to_handle.push_back((reexport, specifier.clone())); reexports_to_handle.push_back((reexport, specifier.clone()));
} }
// todo(dsherret): we could run this analysis concurrently in a FuturesOrdered
while let Some((reexport, referrer)) = reexports_to_handle.pop_front() { while let Some((reexport, referrer)) = reexports_to_handle.pop_front() {
// First, resolve the reexport specifier // First, resolve the reexport specifier
let reexport_specifier = self.resolve( let reexport_specifier = self.resolve(
@ -133,6 +138,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
let analysis = self let analysis = self
.cjs_code_analyzer .cjs_code_analyzer
.analyze_cjs(&reexport_specifier, None) .analyze_cjs(&reexport_specifier, None)
.await
.with_context(|| { .with_context(|| {
format!( format!(
"Could not load '{}' ({}) referenced from {}", "Could not load '{}' ({}) referenced from {}",

View file

@ -929,19 +929,32 @@ async fn inspector_with_ts_files() {
.await; .await;
// receive messages with sources from this test // receive messages with sources from this test
let script1 = tester.recv().await; let mut scripts = vec![
assert_contains!(script1, "testdata/inspector/test.ts"); tester.recv().await,
tester.recv().await,
tester.recv().await,
];
let script1 = scripts.remove(
scripts
.iter()
.position(|s| s.contains("testdata/inspector/test.ts"))
.unwrap(),
);
let script1_id = { let script1_id = {
let v: serde_json::Value = serde_json::from_str(&script1).unwrap(); let v: serde_json::Value = serde_json::from_str(&script1).unwrap();
v["params"]["scriptId"].as_str().unwrap().to_string() v["params"]["scriptId"].as_str().unwrap().to_string()
}; };
let script2 = tester.recv().await; let script2 = scripts.remove(
assert_contains!(script2, "testdata/inspector/foo.ts"); scripts
.iter()
.position(|s| s.contains("testdata/inspector/foo.ts"))
.unwrap(),
);
let script2_id = { let script2_id = {
let v: serde_json::Value = serde_json::from_str(&script2).unwrap(); let v: serde_json::Value = serde_json::from_str(&script2).unwrap();
v["params"]["scriptId"].as_str().unwrap().to_string() v["params"]["scriptId"].as_str().unwrap().to_string()
}; };
let script3 = tester.recv().await; let script3 = scripts.remove(0);
assert_contains!(script3, "testdata/inspector/bar.js"); assert_contains!(script3, "testdata/inspector/bar.js");
let script3_id = { let script3_id = {
let v: serde_json::Value = serde_json::from_str(&script3).unwrap(); let v: serde_json::Value = serde_json::from_str(&script3).unwrap();

View file

@ -1,5 +1,5 @@
{ {
"tempDir": true, "tempDir": true,
"args": "run -A --log-level=debug main.tsx", "args": "run -A run_main_sorted_lines.ts",
"output": "main.out" "output": "main.out"
} }

View file

@ -1,5 +1,5 @@
[WILDCARD]Resolved preact from file:///[WILDLINE]/preact@10.19.6/node_modules/preact/jsx-runtime/dist/jsxRuntime.mjs to [WILDLINE]node_modules[WILDCHAR].deno[WILDCHAR]preact@10.19.6[WILDCHAR]node_modules[WILDCHAR]preact
DEBUG RS - [WILDLINE] - Resolved preact from file:///[WILDLINE]/preact@10.19.6/node_modules/preact/hooks/dist/hooks.mjs to [WILDLINE]node_modules[WILDCHAR].deno[WILDCHAR]preact@10.19.6[WILDCHAR]node_modules[WILDCHAR]preact
[# ensure that preact is resolving to .deno/preact@10.19.6/node_modules/preact and not .deno/preact-render-to-string@6.4.0/node_modules/preact] [# ensure that preact is resolving to .deno/preact@10.19.6/node_modules/preact and not .deno/preact-render-to-string@6.4.0/node_modules/preact]
DEBUG RS - [WILDLINE] - Resolved preact from file:///[WILDLINE]/preact-render-to-string@6.4.0/node_modules/preact-render-to-string/dist/index.mjs to [WILDLINE]node_modules[WILDCHAR].deno[WILDCHAR]preact@10.19.6[WILDCHAR]node_modules[WILDCHAR]preact [WILDCARD]/preact-render-to-string@6.4.0/node_modules/preact-render-to-string/dist/index.mjs to [WILDLINE]node_modules[WILDCHAR].deno[WILDCHAR]preact@10.19.6[WILDCHAR]node_modules[WILDCHAR]preact
[WILDCARD]/preact@10.19.6/node_modules/preact/hooks/dist/hooks.mjs to [WILDLINE]node_modules[WILDCHAR].deno[WILDCHAR]preact@10.19.6[WILDCHAR]node_modules[WILDCHAR]preact
[WILDCARD]/preact@10.19.6/node_modules/preact/jsx-runtime/dist/jsxRuntime.mjs to [WILDLINE]node_modules[WILDCHAR].deno[WILDCHAR]preact@10.19.6[WILDCHAR]node_modules[WILDCHAR]preact
[WILDCARD] [WILDCARD]

View file

@ -0,0 +1,19 @@
const { success, stderr } = new Deno.Command(
Deno.execPath(),
{
args: ["run", "-A", "--log-level=debug", "main.tsx"],
},
).outputSync();
const stderrText = new TextDecoder().decode(stderr);
if (!success) {
console.error(stderrText);
throw new Error("Failed to run script.");
}
// create some stability with the output
const lines = stderrText.split("\n")
.filter((line) => line.includes("Resolved preact from"));
lines.sort();
for (const line of lines) {
console.error(line);
}