mirror of
https://github.com/denoland/deno.git
synced 2025-03-09 21:57:40 -04:00
fix(node): support re-exported esm modules in cjs export analysis (#28379)
Adds support for re-exporting an ES module from a CJS one and then importing the CJS module from ESM. Also fixes a bug where require esm wasn't working in deno compile.
This commit is contained in:
parent
0c0757fe66
commit
2292eb1c92
23 changed files with 366 additions and 166 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1609,9 +1609,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_ast"
|
name = "deno_ast"
|
||||||
version = "0.46.0"
|
version = "0.46.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bd3b6e14e5b1235dd613d9f5d955d7a80dec6de0fc00fa34b5d0ef5ca0a9ddb"
|
checksum = "88393f34aaba238da6a3694aef7e046eec4d261c3bf98dc6669d397f1c274e5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"deno_error",
|
"deno_error",
|
||||||
|
|
|
@ -50,7 +50,7 @@ license = "MIT"
|
||||||
repository = "https://github.com/denoland/deno"
|
repository = "https://github.com/denoland/deno"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
deno_ast = { version = "=0.46.0", features = ["transpiling"] }
|
deno_ast = { version = "=0.46.1", features = ["transpiling"] }
|
||||||
deno_core = { version = "0.340.0" }
|
deno_core = { version = "0.340.0" }
|
||||||
|
|
||||||
deno_bench_util = { version = "0.188.0", path = "./bench_util" }
|
deno_bench_util = { version = "0.188.0", path = "./bench_util" }
|
||||||
|
|
|
@ -67,7 +67,7 @@ winapi.workspace = true
|
||||||
winres.workspace = true
|
winres.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
|
deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit", "utils"] }
|
||||||
deno_cache_dir = { workspace = true, features = ["sync"] }
|
deno_cache_dir = { workspace = true, features = ["sync"] }
|
||||||
deno_config = { workspace = true, features = ["sync", "workspace"] }
|
deno_config = { workspace = true, features = ["sync", "workspace"] }
|
||||||
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
||||||
|
|
6
cli/cache/node.rs
vendored
6
cli/cache/node.rs
vendored
|
@ -137,6 +137,8 @@ impl NodeAnalysisCacheInner {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use deno_ast::ModuleExportsAndReExports;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -148,10 +150,10 @@ mod test {
|
||||||
.get_cjs_analysis("file.js", CacheDBHash::new(2))
|
.get_cjs_analysis("file.js", CacheDBHash::new(2))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.is_none());
|
.is_none());
|
||||||
let cjs_analysis = CliCjsAnalysis::Cjs {
|
let cjs_analysis = CliCjsAnalysis::Cjs(ModuleExportsAndReExports {
|
||||||
exports: vec!["export1".to_string()],
|
exports: vec!["export1".to_string()],
|
||||||
reexports: vec!["re-export1".to_string()],
|
reexports: vec!["re-export1".to_string()],
|
||||||
};
|
});
|
||||||
cache
|
cache
|
||||||
.set_cjs_analysis("file.js", CacheDBHash::new(2), &cjs_analysis)
|
.set_cjs_analysis("file.js", CacheDBHash::new(2), &cjs_analysis)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -80,6 +80,7 @@ use crate::http_util::HttpClientProvider;
|
||||||
use crate::module_loader::CliModuleLoaderFactory;
|
use crate::module_loader::CliModuleLoaderFactory;
|
||||||
use crate::module_loader::ModuleLoadPreparer;
|
use crate::module_loader::ModuleLoadPreparer;
|
||||||
use crate::node::CliCjsCodeAnalyzer;
|
use crate::node::CliCjsCodeAnalyzer;
|
||||||
|
use crate::node::CliCjsModuleExportAnalyzer;
|
||||||
use crate::node::CliNodeCodeTranslator;
|
use crate::node::CliNodeCodeTranslator;
|
||||||
use crate::node::CliNodeResolver;
|
use crate::node::CliNodeResolver;
|
||||||
use crate::node::CliPackageJsonResolver;
|
use crate::node::CliPackageJsonResolver;
|
||||||
|
@ -261,6 +262,7 @@ impl<T> Deferred<T> {
|
||||||
struct CliFactoryServices {
|
struct CliFactoryServices {
|
||||||
blob_store: Deferred<Arc<BlobStore>>,
|
blob_store: Deferred<Arc<BlobStore>>,
|
||||||
caches: Deferred<Arc<Caches>>,
|
caches: Deferred<Arc<Caches>>,
|
||||||
|
cjs_module_export_analyzer: Deferred<Arc<CliCjsModuleExportAnalyzer>>,
|
||||||
cjs_tracker: Deferred<Arc<CliCjsTracker>>,
|
cjs_tracker: Deferred<Arc<CliCjsTracker>>,
|
||||||
cli_options: Deferred<Arc<CliOptions>>,
|
cli_options: Deferred<Arc<CliOptions>>,
|
||||||
code_cache: Deferred<Arc<CodeCache>>,
|
code_cache: Deferred<Arc<CodeCache>>,
|
||||||
|
@ -804,6 +806,28 @@ impl CliFactory {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn cjs_module_export_analyzer(
|
||||||
|
&self,
|
||||||
|
) -> Result<&Arc<CliCjsModuleExportAnalyzer>, AnyError> {
|
||||||
|
self
|
||||||
|
.services
|
||||||
|
.cjs_module_export_analyzer
|
||||||
|
.get_or_try_init_async(async {
|
||||||
|
let node_resolver = self.node_resolver().await?.clone();
|
||||||
|
let cjs_code_analyzer = self.create_cjs_code_analyzer()?;
|
||||||
|
|
||||||
|
Ok(Arc::new(CliCjsModuleExportAnalyzer::new(
|
||||||
|
cjs_code_analyzer,
|
||||||
|
self.in_npm_pkg_checker()?.clone(),
|
||||||
|
node_resolver,
|
||||||
|
self.npm_resolver().await?.clone(),
|
||||||
|
self.pkg_json_resolver()?.clone(),
|
||||||
|
self.sys(),
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn node_code_translator(
|
pub async fn node_code_translator(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<&Arc<CliNodeCodeTranslator>, AnyError> {
|
) -> Result<&Arc<CliNodeCodeTranslator>, AnyError> {
|
||||||
|
@ -811,16 +835,9 @@ impl CliFactory {
|
||||||
.services
|
.services
|
||||||
.node_code_translator
|
.node_code_translator
|
||||||
.get_or_try_init_async(async {
|
.get_or_try_init_async(async {
|
||||||
let node_resolver = self.node_resolver().await?.clone();
|
let module_export_analyzer = self.cjs_module_export_analyzer().await?;
|
||||||
let cjs_code_analyzer = self.create_cjs_code_analyzer()?;
|
|
||||||
|
|
||||||
Ok(Arc::new(NodeCodeTranslator::new(
|
Ok(Arc::new(NodeCodeTranslator::new(
|
||||||
cjs_code_analyzer,
|
module_export_analyzer.clone(),
|
||||||
self.in_npm_pkg_checker()?.clone(),
|
|
||||||
node_resolver,
|
|
||||||
self.npm_resolver().await?.clone(),
|
|
||||||
self.pkg_json_resolver()?.clone(),
|
|
||||||
self.sys(),
|
|
||||||
)))
|
)))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -1025,7 +1042,7 @@ impl CliFactory {
|
||||||
) -> Result<DenoCompileBinaryWriter, AnyError> {
|
) -> Result<DenoCompileBinaryWriter, AnyError> {
|
||||||
let cli_options = self.cli_options()?;
|
let cli_options = self.cli_options()?;
|
||||||
Ok(DenoCompileBinaryWriter::new(
|
Ok(DenoCompileBinaryWriter::new(
|
||||||
self.create_cjs_code_analyzer()?,
|
self.cjs_module_export_analyzer().await?,
|
||||||
self.cjs_tracker()?,
|
self.cjs_tracker()?,
|
||||||
self.cli_options()?,
|
self.cli_options()?,
|
||||||
self.deno_dir()?,
|
self.deno_dir()?,
|
||||||
|
|
|
@ -9,7 +9,6 @@ use deno_runtime::deno_permissions::PermissionsOptions;
|
||||||
use deno_runtime::deno_telemetry::OtelConfig;
|
use deno_runtime::deno_telemetry::OtelConfig;
|
||||||
use deno_semver::Version;
|
use deno_semver::Version;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use node_resolver::analyze::CjsAnalysisExports;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -130,7 +129,8 @@ impl<'a> DenoRtDeserializable<'a> for SpecifierId {
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub enum CjsExportAnalysisEntry {
|
pub enum CjsExportAnalysisEntry {
|
||||||
Esm,
|
Esm,
|
||||||
Cjs(CjsAnalysisExports),
|
Cjs(Vec<String>),
|
||||||
|
Error(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
const HAS_TRANSPILED_FLAG: u8 = 1 << 0;
|
const HAS_TRANSPILED_FLAG: u8 = 1 << 0;
|
||||||
|
|
79
cli/node.rs
79
cli/node.rs
|
@ -4,6 +4,7 @@ use std::borrow::Cow;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use deno_ast::MediaType;
|
use deno_ast::MediaType;
|
||||||
|
use deno_ast::ModuleExportsAndReExports;
|
||||||
use deno_ast::ModuleSpecifier;
|
use deno_ast::ModuleSpecifier;
|
||||||
use deno_error::JsErrorBox;
|
use deno_error::JsErrorBox;
|
||||||
use deno_graph::ParsedSourceStore;
|
use deno_graph::ParsedSourceStore;
|
||||||
|
@ -12,6 +13,8 @@ use deno_runtime::deno_fs;
|
||||||
use node_resolver::analyze::CjsAnalysis as ExtNodeCjsAnalysis;
|
use node_resolver::analyze::CjsAnalysis as ExtNodeCjsAnalysis;
|
||||||
use node_resolver::analyze::CjsAnalysisExports;
|
use node_resolver::analyze::CjsAnalysisExports;
|
||||||
use node_resolver::analyze::CjsCodeAnalyzer;
|
use node_resolver::analyze::CjsCodeAnalyzer;
|
||||||
|
use node_resolver::analyze::CjsModuleExportAnalyzer;
|
||||||
|
use node_resolver::analyze::EsmAnalysisMode;
|
||||||
use node_resolver::analyze::NodeCodeTranslator;
|
use node_resolver::analyze::NodeCodeTranslator;
|
||||||
use node_resolver::DenoIsBuiltInNodeModuleChecker;
|
use node_resolver::DenoIsBuiltInNodeModuleChecker;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -24,6 +27,13 @@ use crate::npm::CliNpmResolver;
|
||||||
use crate::resolver::CliCjsTracker;
|
use crate::resolver::CliCjsTracker;
|
||||||
use crate::sys::CliSys;
|
use crate::sys::CliSys;
|
||||||
|
|
||||||
|
pub type CliCjsModuleExportAnalyzer = CjsModuleExportAnalyzer<
|
||||||
|
CliCjsCodeAnalyzer,
|
||||||
|
DenoInNpmPackageChecker,
|
||||||
|
DenoIsBuiltInNodeModuleChecker,
|
||||||
|
CliNpmResolver,
|
||||||
|
CliSys,
|
||||||
|
>;
|
||||||
pub type CliNodeCodeTranslator = NodeCodeTranslator<
|
pub type CliNodeCodeTranslator = NodeCodeTranslator<
|
||||||
CliCjsCodeAnalyzer,
|
CliCjsCodeAnalyzer,
|
||||||
DenoInNpmPackageChecker,
|
DenoInNpmPackageChecker,
|
||||||
|
@ -42,11 +52,11 @@ pub type CliPackageJsonResolver = node_resolver::PackageJsonResolver<CliSys>;
|
||||||
pub enum CliCjsAnalysis {
|
pub enum CliCjsAnalysis {
|
||||||
/// The module was found to be an ES module.
|
/// The module was found to be an ES module.
|
||||||
Esm,
|
Esm,
|
||||||
|
/// The module was found to be an ES module and
|
||||||
|
/// it was analyzed for imports and exports.
|
||||||
|
EsmAnalysis(ModuleExportsAndReExports),
|
||||||
/// The module was CJS.
|
/// The module was CJS.
|
||||||
Cjs {
|
Cjs(ModuleExportsAndReExports),
|
||||||
exports: Vec<String>,
|
|
||||||
reexports: Vec<String>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CliCjsCodeAnalyzer {
|
pub struct CliCjsCodeAnalyzer {
|
||||||
|
@ -75,6 +85,7 @@ impl CliCjsCodeAnalyzer {
|
||||||
&self,
|
&self,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
source: &str,
|
source: &str,
|
||||||
|
esm_analysis_mode: EsmAnalysisMode,
|
||||||
) -> Result<CliCjsAnalysis, JsErrorBox> {
|
) -> Result<CliCjsAnalysis, JsErrorBox> {
|
||||||
let source_hash = CacheDBHash::from_hashable(source);
|
let source_hash = CacheDBHash::from_hashable(source);
|
||||||
if let Some(analysis) =
|
if let Some(analysis) =
|
||||||
|
@ -85,17 +96,16 @@ impl CliCjsCodeAnalyzer {
|
||||||
|
|
||||||
let media_type = MediaType::from_specifier(specifier);
|
let media_type = MediaType::from_specifier(specifier);
|
||||||
if media_type == MediaType::Json {
|
if media_type == MediaType::Json {
|
||||||
return Ok(CliCjsAnalysis::Cjs {
|
return Ok(CliCjsAnalysis::Cjs(Default::default()));
|
||||||
exports: vec![],
|
|
||||||
reexports: vec![],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let cjs_tracker = self.cjs_tracker.clone();
|
let cjs_tracker = self.cjs_tracker.clone();
|
||||||
let is_maybe_cjs = cjs_tracker
|
let is_maybe_cjs = cjs_tracker
|
||||||
.is_maybe_cjs(specifier, media_type)
|
.is_maybe_cjs(specifier, media_type)
|
||||||
.map_err(JsErrorBox::from_err)?;
|
.map_err(JsErrorBox::from_err)?;
|
||||||
let analysis = if is_maybe_cjs {
|
let analysis = if is_maybe_cjs
|
||||||
|
|| esm_analysis_mode == EsmAnalysisMode::SourceImportsAndExports
|
||||||
|
{
|
||||||
let maybe_parsed_source = self
|
let maybe_parsed_source = self
|
||||||
.parsed_source_cache
|
.parsed_source_cache
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -118,22 +128,27 @@ impl CliCjsCodeAnalyzer {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map_err(JsErrorBox::from_err)?;
|
.map_err(JsErrorBox::from_err)?;
|
||||||
let is_script = parsed_source.compute_is_script();
|
let is_script = is_maybe_cjs && parsed_source.compute_is_script();
|
||||||
let is_cjs = cjs_tracker
|
let is_cjs = is_maybe_cjs
|
||||||
.is_cjs_with_known_is_script(
|
&& cjs_tracker
|
||||||
parsed_source.specifier(),
|
.is_cjs_with_known_is_script(
|
||||||
media_type,
|
parsed_source.specifier(),
|
||||||
is_script,
|
media_type,
|
||||||
)
|
is_script,
|
||||||
.map_err(JsErrorBox::from_err)?;
|
)
|
||||||
|
.map_err(JsErrorBox::from_err)?;
|
||||||
if is_cjs {
|
if is_cjs {
|
||||||
let analysis = parsed_source.analyze_cjs();
|
let analysis = parsed_source.analyze_cjs();
|
||||||
Ok(CliCjsAnalysis::Cjs {
|
Ok(CliCjsAnalysis::Cjs(analysis))
|
||||||
exports: analysis.exports,
|
|
||||||
reexports: analysis.reexports,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
Ok(CliCjsAnalysis::Esm)
|
match esm_analysis_mode {
|
||||||
|
EsmAnalysisMode::SourceOnly => Ok(CliCjsAnalysis::Esm),
|
||||||
|
EsmAnalysisMode::SourceImportsAndExports => {
|
||||||
|
Ok(CliCjsAnalysis::EsmAnalysis(
|
||||||
|
parsed_source.analyze_es_runtime_exports(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -157,6 +172,7 @@ impl CjsCodeAnalyzer for CliCjsCodeAnalyzer {
|
||||||
&self,
|
&self,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
source: Option<Cow<'a, str>>,
|
source: Option<Cow<'a, str>>,
|
||||||
|
esm_analysis_mode: EsmAnalysisMode,
|
||||||
) -> Result<ExtNodeCjsAnalysis<'a>, JsErrorBox> {
|
) -> Result<ExtNodeCjsAnalysis<'a>, JsErrorBox> {
|
||||||
let source = match source {
|
let source = match source {
|
||||||
Some(source) => source,
|
Some(source) => source,
|
||||||
|
@ -180,13 +196,22 @@ impl CjsCodeAnalyzer for CliCjsCodeAnalyzer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let analysis = self.inner_cjs_analysis(specifier, &source).await?;
|
let analysis = self
|
||||||
|
.inner_cjs_analysis(specifier, &source, esm_analysis_mode)
|
||||||
|
.await?;
|
||||||
match analysis {
|
match analysis {
|
||||||
CliCjsAnalysis::Esm => Ok(ExtNodeCjsAnalysis::Esm(source)),
|
CliCjsAnalysis::Esm => Ok(ExtNodeCjsAnalysis::Esm(source, None)),
|
||||||
CliCjsAnalysis::Cjs { exports, reexports } => {
|
CliCjsAnalysis::EsmAnalysis(analysis) => Ok(ExtNodeCjsAnalysis::Esm(
|
||||||
|
source,
|
||||||
|
Some(CjsAnalysisExports {
|
||||||
|
exports: analysis.exports,
|
||||||
|
reexports: analysis.reexports,
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
CliCjsAnalysis::Cjs(analysis) => {
|
||||||
Ok(ExtNodeCjsAnalysis::Cjs(CjsAnalysisExports {
|
Ok(ExtNodeCjsAnalysis::Cjs(CjsAnalysisExports {
|
||||||
exports,
|
exports: analysis.exports,
|
||||||
reexports,
|
reexports: analysis.reexports,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ use deno_resolver::npm::NpmReqResolver;
|
||||||
use deno_runtime::deno_fs::FileSystem;
|
use deno_runtime::deno_fs::FileSystem;
|
||||||
use node_resolver::analyze::CjsAnalysis;
|
use node_resolver::analyze::CjsAnalysis;
|
||||||
use node_resolver::analyze::CjsAnalysisExports;
|
use node_resolver::analyze::CjsAnalysisExports;
|
||||||
|
use node_resolver::analyze::EsmAnalysisMode;
|
||||||
use node_resolver::analyze::NodeCodeTranslator;
|
use node_resolver::analyze::NodeCodeTranslator;
|
||||||
use node_resolver::DenoIsBuiltInNodeModuleChecker;
|
use node_resolver::DenoIsBuiltInNodeModuleChecker;
|
||||||
|
|
||||||
|
@ -96,11 +97,17 @@ impl CjsCodeAnalyzer {
|
||||||
match data {
|
match data {
|
||||||
CjsExportAnalysisEntry::Esm => {
|
CjsExportAnalysisEntry::Esm => {
|
||||||
cjs_tracker.set_is_known_script(specifier, false);
|
cjs_tracker.set_is_known_script(specifier, false);
|
||||||
CjsAnalysis::Esm(source)
|
CjsAnalysis::Esm(source, None)
|
||||||
}
|
}
|
||||||
CjsExportAnalysisEntry::Cjs(analysis) => {
|
CjsExportAnalysisEntry::Cjs(exports) => {
|
||||||
cjs_tracker.set_is_known_script(specifier, true);
|
cjs_tracker.set_is_known_script(specifier, true);
|
||||||
CjsAnalysis::Cjs(analysis)
|
CjsAnalysis::Cjs(CjsAnalysisExports {
|
||||||
|
exports,
|
||||||
|
reexports: Vec::new(), // already resolved
|
||||||
|
})
|
||||||
|
}
|
||||||
|
CjsExportAnalysisEntry::Error(err) => {
|
||||||
|
return Err(JsErrorBox::generic(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,11 +126,11 @@ impl CjsCodeAnalyzer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// assume ESM as we don't have access to swc here
|
// assume ESM as we don't have access to swc here
|
||||||
CjsAnalysis::Esm(source)
|
CjsAnalysis::Esm(source, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
CjsAnalysis::Esm(source)
|
CjsAnalysis::Esm(source, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(analysis)
|
Ok(analysis)
|
||||||
|
@ -136,6 +143,7 @@ impl node_resolver::analyze::CjsCodeAnalyzer for CjsCodeAnalyzer {
|
||||||
&self,
|
&self,
|
||||||
specifier: &Url,
|
specifier: &Url,
|
||||||
source: Option<Cow<'a, str>>,
|
source: Option<Cow<'a, str>>,
|
||||||
|
_esm_analysis_mode: EsmAnalysisMode,
|
||||||
) -> Result<CjsAnalysis<'a>, JsErrorBox> {
|
) -> Result<CjsAnalysis<'a>, JsErrorBox> {
|
||||||
let source = match source {
|
let source = match source {
|
||||||
Some(source) => source,
|
Some(source) => source,
|
||||||
|
|
|
@ -74,6 +74,7 @@ use deno_runtime::permissions::RuntimePermissionDescriptorParser;
|
||||||
use deno_runtime::WorkerExecutionMode;
|
use deno_runtime::WorkerExecutionMode;
|
||||||
use deno_runtime::WorkerLogLevel;
|
use deno_runtime::WorkerLogLevel;
|
||||||
use deno_semver::npm::NpmPackageReqReference;
|
use deno_semver::npm::NpmPackageReqReference;
|
||||||
|
use node_resolver::analyze::CjsModuleExportAnalyzer;
|
||||||
use node_resolver::analyze::NodeCodeTranslator;
|
use node_resolver::analyze::NodeCodeTranslator;
|
||||||
use node_resolver::cache::NodeResolutionSys;
|
use node_resolver::cache::NodeResolutionSys;
|
||||||
use node_resolver::errors::ClosestPkgJsonError;
|
use node_resolver::errors::ClosestPkgJsonError;
|
||||||
|
@ -154,18 +155,9 @@ impl ModuleLoader for EmbeddedModuleLoader {
|
||||||
&self,
|
&self,
|
||||||
raw_specifier: &str,
|
raw_specifier: &str,
|
||||||
referrer: &str,
|
referrer: &str,
|
||||||
kind: ResolutionKind,
|
_kind: ResolutionKind,
|
||||||
) -> Result<Url, ModuleLoaderError> {
|
) -> Result<Url, ModuleLoaderError> {
|
||||||
let referrer = if referrer == "." {
|
let referrer = if referrer == "." {
|
||||||
if kind != ResolutionKind::MainModule {
|
|
||||||
return Err(
|
|
||||||
JsErrorBox::generic(format!(
|
|
||||||
"Expected to resolve main module, got {:?} instead.",
|
|
||||||
kind
|
|
||||||
))
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let current_dir = std::env::current_dir().unwrap();
|
let current_dir = std::env::current_dir().unwrap();
|
||||||
deno_core::resolve_path(".", ¤t_dir)
|
deno_core::resolve_path(".", ¤t_dir)
|
||||||
.map_err(JsErrorBox::from_err)?
|
.map_err(JsErrorBox::from_err)?
|
||||||
|
@ -815,7 +807,7 @@ pub async fn run(
|
||||||
}));
|
}));
|
||||||
let cjs_esm_code_analyzer =
|
let cjs_esm_code_analyzer =
|
||||||
CjsCodeAnalyzer::new(cjs_tracker.clone(), modules.clone(), sys.clone());
|
CjsCodeAnalyzer::new(cjs_tracker.clone(), modules.clone(), sys.clone());
|
||||||
let node_code_translator = Arc::new(NodeCodeTranslator::new(
|
let cjs_module_export_analyzer = Arc::new(CjsModuleExportAnalyzer::new(
|
||||||
cjs_esm_code_analyzer,
|
cjs_esm_code_analyzer,
|
||||||
in_npm_pkg_checker,
|
in_npm_pkg_checker,
|
||||||
node_resolver.clone(),
|
node_resolver.clone(),
|
||||||
|
@ -823,6 +815,8 @@ pub async fn run(
|
||||||
pkg_json_resolver.clone(),
|
pkg_json_resolver.clone(),
|
||||||
sys.clone(),
|
sys.clone(),
|
||||||
));
|
));
|
||||||
|
let node_code_translator =
|
||||||
|
Arc::new(NodeCodeTranslator::new(cjs_module_export_analyzer));
|
||||||
let workspace_resolver = {
|
let workspace_resolver = {
|
||||||
let import_map = match metadata.workspace_resolver.import_map {
|
let import_map = match metadata.workspace_resolver.import_map {
|
||||||
Some(import_map) => Some(
|
Some(import_map) => Some(
|
||||||
|
|
|
@ -52,8 +52,7 @@ use deno_path_util::url_from_directory_path;
|
||||||
use deno_path_util::url_to_file_path;
|
use deno_path_util::url_to_file_path;
|
||||||
use deno_resolver::workspace::WorkspaceResolver;
|
use deno_resolver::workspace::WorkspaceResolver;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use node_resolver::analyze::CjsAnalysis;
|
use node_resolver::analyze::ResolvedCjsAnalysis;
|
||||||
use node_resolver::analyze::CjsCodeAnalyzer;
|
|
||||||
|
|
||||||
use super::virtual_fs::output_vfs;
|
use super::virtual_fs::output_vfs;
|
||||||
use crate::args::CliOptions;
|
use crate::args::CliOptions;
|
||||||
|
@ -61,7 +60,7 @@ use crate::args::CompileFlags;
|
||||||
use crate::cache::DenoDir;
|
use crate::cache::DenoDir;
|
||||||
use crate::emit::Emitter;
|
use crate::emit::Emitter;
|
||||||
use crate::http_util::HttpClientProvider;
|
use crate::http_util::HttpClientProvider;
|
||||||
use crate::node::CliCjsCodeAnalyzer;
|
use crate::node::CliCjsModuleExportAnalyzer;
|
||||||
use crate::npm::CliNpmResolver;
|
use crate::npm::CliNpmResolver;
|
||||||
use crate::resolver::CliCjsTracker;
|
use crate::resolver::CliCjsTracker;
|
||||||
use crate::sys::CliSys;
|
use crate::sys::CliSys;
|
||||||
|
@ -190,7 +189,7 @@ pub struct WriteBinOptions<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DenoCompileBinaryWriter<'a> {
|
pub struct DenoCompileBinaryWriter<'a> {
|
||||||
cjs_code_analyzer: CliCjsCodeAnalyzer,
|
cjs_module_export_analyzer: &'a CliCjsModuleExportAnalyzer,
|
||||||
cjs_tracker: &'a CliCjsTracker,
|
cjs_tracker: &'a CliCjsTracker,
|
||||||
cli_options: &'a CliOptions,
|
cli_options: &'a CliOptions,
|
||||||
deno_dir: &'a DenoDir,
|
deno_dir: &'a DenoDir,
|
||||||
|
@ -204,7 +203,7 @@ pub struct DenoCompileBinaryWriter<'a> {
|
||||||
impl<'a> DenoCompileBinaryWriter<'a> {
|
impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cjs_code_analyzer: CliCjsCodeAnalyzer,
|
cjs_module_export_analyzer: &'a CliCjsModuleExportAnalyzer,
|
||||||
cjs_tracker: &'a CliCjsTracker,
|
cjs_tracker: &'a CliCjsTracker,
|
||||||
cli_options: &'a CliOptions,
|
cli_options: &'a CliOptions,
|
||||||
deno_dir: &'a DenoDir,
|
deno_dir: &'a DenoDir,
|
||||||
|
@ -215,7 +214,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
npm_system_info: NpmSystemInfo,
|
npm_system_info: NpmSystemInfo,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cjs_code_analyzer,
|
cjs_module_export_analyzer,
|
||||||
cjs_tracker,
|
cjs_tracker,
|
||||||
cli_options,
|
cli_options,
|
||||||
deno_dir,
|
deno_dir,
|
||||||
|
@ -423,16 +422,18 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
m.is_script,
|
m.is_script,
|
||||||
)? {
|
)? {
|
||||||
let cjs_analysis = self
|
let cjs_analysis = self
|
||||||
.cjs_code_analyzer
|
.cjs_module_export_analyzer
|
||||||
.analyze_cjs(
|
.analyze_all_exports(
|
||||||
module.specifier(),
|
module.specifier(),
|
||||||
Some(Cow::Borrowed(m.source.as_ref())),
|
Some(Cow::Borrowed(m.source.as_ref())),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
maybe_cjs_analysis = Some(match cjs_analysis {
|
maybe_cjs_analysis = Some(match cjs_analysis {
|
||||||
CjsAnalysis::Esm(_) => CjsExportAnalysisEntry::Esm,
|
ResolvedCjsAnalysis::Esm(_) => CjsExportAnalysisEntry::Esm,
|
||||||
CjsAnalysis::Cjs(exports) => {
|
ResolvedCjsAnalysis::Cjs(exports) => {
|
||||||
CjsExportAnalysisEntry::Cjs(exports)
|
CjsExportAnalysisEntry::Cjs(
|
||||||
|
exports.into_iter().collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -544,26 +545,24 @@ impl<'a> DenoCompileBinaryWriter<'a> {
|
||||||
.file_bytes(file.offset)
|
.file_bytes(file.offset)
|
||||||
.map(|text| String::from_utf8_lossy(text));
|
.map(|text| String::from_utf8_lossy(text));
|
||||||
let cjs_analysis_result = self
|
let cjs_analysis_result = self
|
||||||
.cjs_code_analyzer
|
.cjs_module_export_analyzer
|
||||||
.analyze_cjs(&specifier, maybe_source)
|
.analyze_all_exports(&specifier, maybe_source)
|
||||||
.await;
|
.await;
|
||||||
let maybe_analysis = match cjs_analysis_result {
|
let analysis = match cjs_analysis_result {
|
||||||
Ok(CjsAnalysis::Esm(_)) => Some(CjsExportAnalysisEntry::Esm),
|
Ok(ResolvedCjsAnalysis::Esm(_)) => CjsExportAnalysisEntry::Esm,
|
||||||
Ok(CjsAnalysis::Cjs(exports)) => {
|
Ok(ResolvedCjsAnalysis::Cjs(exports)) => {
|
||||||
Some(CjsExportAnalysisEntry::Cjs(exports))
|
CjsExportAnalysisEntry::Cjs(exports.into_iter().collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Ignoring cjs export analysis for '{}': {}",
|
"Had cjs export analysis error for '{}': {}",
|
||||||
specifier,
|
specifier,
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
None
|
CjsExportAnalysisEntry::Error(err.to_string())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(analysis) = &maybe_analysis {
|
to_add.push((file_path, bincode::serialize(&analysis)?));
|
||||||
to_add.push((file_path, bincode::serialize(analysis)?));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (file_path, analysis) in to_add {
|
for (file_path, analysis) in to_add {
|
||||||
|
|
|
@ -36,7 +36,7 @@ use crate::UrlOrPathRef;
|
||||||
pub enum CjsAnalysis<'a> {
|
pub enum CjsAnalysis<'a> {
|
||||||
/// File was found to be an ES module and the translator should
|
/// File was found to be an ES module and the translator should
|
||||||
/// load the code as ESM.
|
/// load the code as ESM.
|
||||||
Esm(Cow<'a, str>),
|
Esm(Cow<'a, str>, Option<CjsAnalysisExports>),
|
||||||
Cjs(CjsAnalysisExports),
|
Cjs(CjsAnalysisExports),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,13 @@ pub struct CjsAnalysisExports {
|
||||||
pub reexports: Vec<String>,
|
pub reexports: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What parts of an ES module should be analyzed.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum EsmAnalysisMode {
|
||||||
|
SourceOnly,
|
||||||
|
SourceImportsAndExports,
|
||||||
|
}
|
||||||
|
|
||||||
/// Code analyzer for CJS and ESM files.
|
/// Code analyzer for CJS and ESM files.
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
pub trait CjsCodeAnalyzer {
|
pub trait CjsCodeAnalyzer {
|
||||||
|
@ -60,31 +67,33 @@ pub trait CjsCodeAnalyzer {
|
||||||
&self,
|
&self,
|
||||||
specifier: &Url,
|
specifier: &Url,
|
||||||
maybe_source: Option<Cow<'a, str>>,
|
maybe_source: Option<Cow<'a, str>>,
|
||||||
|
esm_analysis_mode: EsmAnalysisMode,
|
||||||
) -> Result<CjsAnalysis<'a>, JsErrorBox>;
|
) -> Result<CjsAnalysis<'a>, JsErrorBox>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
pub enum ResolvedCjsAnalysis<'a> {
|
||||||
pub enum TranslateCjsToEsmError {
|
Esm(Cow<'a, str>),
|
||||||
#[class(inherit)]
|
Cjs(BTreeSet<String>),
|
||||||
#[error(transparent)]
|
|
||||||
CjsCodeAnalysis(JsErrorBox),
|
|
||||||
#[class(inherit)]
|
|
||||||
#[error(transparent)]
|
|
||||||
ExportAnalysis(JsErrorBox),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
#[allow(clippy::disallowed_types)]
|
||||||
#[class(generic)]
|
pub type CjsModuleExportAnalyzerRc<
|
||||||
#[error("Could not load '{reexport}' ({reexport_specifier}) referenced from {referrer}")]
|
TCjsCodeAnalyzer,
|
||||||
pub struct CjsAnalysisCouldNotLoadError {
|
TInNpmPackageChecker,
|
||||||
reexport: String,
|
TIsBuiltInNodeModuleChecker,
|
||||||
reexport_specifier: Url,
|
TNpmPackageFolderResolver,
|
||||||
referrer: Url,
|
TSys,
|
||||||
#[source]
|
> = crate::sync::MaybeArc<
|
||||||
source: JsErrorBox,
|
CjsModuleExportAnalyzer<
|
||||||
}
|
TCjsCodeAnalyzer,
|
||||||
|
TInNpmPackageChecker,
|
||||||
|
TIsBuiltInNodeModuleChecker,
|
||||||
|
TNpmPackageFolderResolver,
|
||||||
|
TSys,
|
||||||
|
>,
|
||||||
|
>;
|
||||||
|
|
||||||
pub struct NodeCodeTranslator<
|
pub struct CjsModuleExportAnalyzer<
|
||||||
TCjsCodeAnalyzer: CjsCodeAnalyzer,
|
TCjsCodeAnalyzer: CjsCodeAnalyzer,
|
||||||
TInNpmPackageChecker: InNpmPackageChecker,
|
TInNpmPackageChecker: InNpmPackageChecker,
|
||||||
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
|
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
|
||||||
|
@ -111,7 +120,7 @@ impl<
|
||||||
TNpmPackageFolderResolver: NpmPackageFolderResolver,
|
TNpmPackageFolderResolver: NpmPackageFolderResolver,
|
||||||
TSys: FsCanonicalize + FsMetadata + FsRead,
|
TSys: FsCanonicalize + FsMetadata + FsRead,
|
||||||
>
|
>
|
||||||
NodeCodeTranslator<
|
CjsModuleExportAnalyzer<
|
||||||
TCjsCodeAnalyzer,
|
TCjsCodeAnalyzer,
|
||||||
TInNpmPackageChecker,
|
TInNpmPackageChecker,
|
||||||
TIsBuiltInNodeModuleChecker,
|
TIsBuiltInNodeModuleChecker,
|
||||||
|
@ -142,36 +151,24 @@ impl<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translates given CJS module into ESM. This function will perform static
|
pub async fn analyze_all_exports<'a>(
|
||||||
/// analysis on the file to find defined exports and reexports.
|
|
||||||
///
|
|
||||||
/// For all discovered reexports the analysis will be performed recursively.
|
|
||||||
///
|
|
||||||
/// If successful a source code for equivalent ES module is returned.
|
|
||||||
pub async fn translate_cjs_to_esm<'a>(
|
|
||||||
&self,
|
&self,
|
||||||
entry_specifier: &Url,
|
entry_specifier: &Url,
|
||||||
source: Option<Cow<'a, str>>,
|
source: Option<Cow<'a, str>>,
|
||||||
) -> Result<Cow<'a, str>, TranslateCjsToEsmError> {
|
) -> Result<ResolvedCjsAnalysis<'a>, TranslateCjsToEsmError> {
|
||||||
let mut temp_var_count = 0;
|
|
||||||
|
|
||||||
let analysis = self
|
let analysis = self
|
||||||
.cjs_code_analyzer
|
.cjs_code_analyzer
|
||||||
.analyze_cjs(entry_specifier, source)
|
.analyze_cjs(entry_specifier, source, EsmAnalysisMode::SourceOnly)
|
||||||
.await
|
.await
|
||||||
.map_err(TranslateCjsToEsmError::CjsCodeAnalysis)?;
|
.map_err(TranslateCjsToEsmError::CjsCodeAnalysis)?;
|
||||||
|
|
||||||
let analysis = match analysis {
|
let analysis = match analysis {
|
||||||
CjsAnalysis::Esm(source) => return Ok(source),
|
CjsAnalysis::Esm(source, _) => {
|
||||||
|
return Ok(ResolvedCjsAnalysis::Esm(source))
|
||||||
|
}
|
||||||
CjsAnalysis::Cjs(analysis) => analysis,
|
CjsAnalysis::Cjs(analysis) => analysis,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut source = vec![
|
|
||||||
r#"import {createRequire as __internalCreateRequire, Module as __internalModule } from "node:module";
|
|
||||||
const require = __internalCreateRequire(import.meta.url);"#
|
|
||||||
.to_string(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// use a BTreeSet to make the output deterministic for v8's code cache
|
// use a BTreeSet to make the output deterministic for v8's code cache
|
||||||
let mut all_exports = analysis.exports.into_iter().collect::<BTreeSet<_>>();
|
let mut all_exports = analysis.exports.into_iter().collect::<BTreeSet<_>>();
|
||||||
|
|
||||||
|
@ -193,38 +190,7 @@ impl<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source.push(format!(
|
Ok(ResolvedCjsAnalysis::Cjs(all_exports))
|
||||||
r#"let mod;
|
|
||||||
if (import.meta.main) {{
|
|
||||||
mod = __internalModule._load("{0}", null, true)
|
|
||||||
}} else {{
|
|
||||||
mod = require("{0}");
|
|
||||||
}}"#,
|
|
||||||
url_to_file_path(entry_specifier)
|
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.replace('\\', "\\\\")
|
|
||||||
.replace('\'', "\\\'")
|
|
||||||
.replace('\"', "\\\"")
|
|
||||||
));
|
|
||||||
|
|
||||||
for export in &all_exports {
|
|
||||||
if !matches!(export.as_str(), "default" | "module.exports") {
|
|
||||||
add_export(
|
|
||||||
&mut source,
|
|
||||||
export,
|
|
||||||
&format!("mod[{}]", to_double_quote_string(export)),
|
|
||||||
&mut temp_var_count,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
source.push("export default mod;".to_string());
|
|
||||||
add_export(&mut source, "module.exports", "mod", &mut temp_var_count);
|
|
||||||
|
|
||||||
let translated_source = source.join("\n");
|
|
||||||
Ok(Cow::Owned(translated_source))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_lifetimes)]
|
#[allow(clippy::needless_lifetimes)]
|
||||||
|
@ -239,7 +205,6 @@ impl<
|
||||||
) {
|
) {
|
||||||
struct Analysis {
|
struct Analysis {
|
||||||
reexport_specifier: url::Url,
|
reexport_specifier: url::Url,
|
||||||
referrer: url::Url,
|
|
||||||
analysis: CjsAnalysis<'static>,
|
analysis: CjsAnalysis<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,7 +253,11 @@ impl<
|
||||||
let referrer = referrer.clone();
|
let referrer = referrer.clone();
|
||||||
let future = async move {
|
let future = async move {
|
||||||
let analysis = cjs_code_analyzer
|
let analysis = cjs_code_analyzer
|
||||||
.analyze_cjs(&reexport_specifier, None)
|
.analyze_cjs(
|
||||||
|
&reexport_specifier,
|
||||||
|
None,
|
||||||
|
EsmAnalysisMode::SourceImportsAndExports,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|source| {
|
.map_err(|source| {
|
||||||
JsErrorBox::from_err(CjsAnalysisCouldNotLoadError {
|
JsErrorBox::from_err(CjsAnalysisCouldNotLoadError {
|
||||||
|
@ -301,7 +270,6 @@ impl<
|
||||||
|
|
||||||
Ok(Analysis {
|
Ok(Analysis {
|
||||||
reexport_specifier,
|
reexport_specifier,
|
||||||
referrer,
|
|
||||||
analysis,
|
analysis,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -321,7 +289,6 @@ impl<
|
||||||
// 2. Look at the analysis result and resolve its exports and re-exports
|
// 2. Look at the analysis result and resolve its exports and re-exports
|
||||||
let Analysis {
|
let Analysis {
|
||||||
reexport_specifier,
|
reexport_specifier,
|
||||||
referrer,
|
|
||||||
analysis,
|
analysis,
|
||||||
} = match analysis_result {
|
} = match analysis_result {
|
||||||
Ok(analysis) => analysis,
|
Ok(analysis) => analysis,
|
||||||
|
@ -331,14 +298,7 @@ impl<
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match analysis {
|
match analysis {
|
||||||
CjsAnalysis::Esm(_) => {
|
CjsAnalysis::Cjs(analysis) | CjsAnalysis::Esm(_, Some(analysis)) => {
|
||||||
// todo(dsherret): support this once supporting requiring ES modules
|
|
||||||
errors.push(JsErrorBox::generic(format!(
|
|
||||||
"Cannot require ES module '{}' from '{}'",
|
|
||||||
reexport_specifier, referrer,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
CjsAnalysis::Cjs(analysis) => {
|
|
||||||
if !analysis.reexports.is_empty() {
|
if !analysis.reexports.is_empty() {
|
||||||
handle_reexports(
|
handle_reexports(
|
||||||
reexport_specifier.clone(),
|
reexport_specifier.clone(),
|
||||||
|
@ -355,6 +315,10 @@ impl<
|
||||||
.filter(|e| e.as_str() != "default"),
|
.filter(|e| e.as_str() != "default"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
CjsAnalysis::Esm(_, None) => {
|
||||||
|
// should not hit this due to EsmAnalysisMode::SourceImportsAndExports
|
||||||
|
debug_assert!(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -526,6 +490,137 @@ impl<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
||||||
|
pub enum TranslateCjsToEsmError {
|
||||||
|
#[class(inherit)]
|
||||||
|
#[error(transparent)]
|
||||||
|
CjsCodeAnalysis(JsErrorBox),
|
||||||
|
#[class(inherit)]
|
||||||
|
#[error(transparent)]
|
||||||
|
ExportAnalysis(JsErrorBox),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
||||||
|
#[class(generic)]
|
||||||
|
#[error("Could not load '{reexport}' ({reexport_specifier}) referenced from {referrer}")]
|
||||||
|
pub struct CjsAnalysisCouldNotLoadError {
|
||||||
|
reexport: String,
|
||||||
|
reexport_specifier: Url,
|
||||||
|
referrer: Url,
|
||||||
|
#[source]
|
||||||
|
source: JsErrorBox,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NodeCodeTranslator<
|
||||||
|
TCjsCodeAnalyzer: CjsCodeAnalyzer,
|
||||||
|
TInNpmPackageChecker: InNpmPackageChecker,
|
||||||
|
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
|
||||||
|
TNpmPackageFolderResolver: NpmPackageFolderResolver,
|
||||||
|
TSys: FsCanonicalize + FsMetadata + FsRead,
|
||||||
|
> {
|
||||||
|
module_export_analyzer: CjsModuleExportAnalyzerRc<
|
||||||
|
TCjsCodeAnalyzer,
|
||||||
|
TInNpmPackageChecker,
|
||||||
|
TIsBuiltInNodeModuleChecker,
|
||||||
|
TNpmPackageFolderResolver,
|
||||||
|
TSys,
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
TCjsCodeAnalyzer: CjsCodeAnalyzer,
|
||||||
|
TInNpmPackageChecker: InNpmPackageChecker,
|
||||||
|
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
|
||||||
|
TNpmPackageFolderResolver: NpmPackageFolderResolver,
|
||||||
|
TSys: FsCanonicalize + FsMetadata + FsRead,
|
||||||
|
>
|
||||||
|
NodeCodeTranslator<
|
||||||
|
TCjsCodeAnalyzer,
|
||||||
|
TInNpmPackageChecker,
|
||||||
|
TIsBuiltInNodeModuleChecker,
|
||||||
|
TNpmPackageFolderResolver,
|
||||||
|
TSys,
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
module_export_analyzer: CjsModuleExportAnalyzerRc<
|
||||||
|
TCjsCodeAnalyzer,
|
||||||
|
TInNpmPackageChecker,
|
||||||
|
TIsBuiltInNodeModuleChecker,
|
||||||
|
TNpmPackageFolderResolver,
|
||||||
|
TSys,
|
||||||
|
>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
module_export_analyzer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Translates given CJS module into ESM. This function will perform static
|
||||||
|
/// analysis on the file to find defined exports and reexports.
|
||||||
|
///
|
||||||
|
/// For all discovered reexports the analysis will be performed recursively.
|
||||||
|
///
|
||||||
|
/// If successful a source code for equivalent ES module is returned.
|
||||||
|
pub async fn translate_cjs_to_esm<'a>(
|
||||||
|
&self,
|
||||||
|
entry_specifier: &Url,
|
||||||
|
source: Option<Cow<'a, str>>,
|
||||||
|
) -> Result<Cow<'a, str>, TranslateCjsToEsmError> {
|
||||||
|
let analysis = self
|
||||||
|
.module_export_analyzer
|
||||||
|
.analyze_all_exports(entry_specifier, source)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let all_exports = match analysis {
|
||||||
|
ResolvedCjsAnalysis::Esm(source) => return Ok(source),
|
||||||
|
ResolvedCjsAnalysis::Cjs(all_exports) => all_exports,
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo(dsherret): use capacity_builder here to remove all these heap
|
||||||
|
// allocations and make the string writing faster
|
||||||
|
let mut temp_var_count = 0;
|
||||||
|
let mut source = vec![
|
||||||
|
r#"import {createRequire as __internalCreateRequire, Module as __internalModule } from "node:module";
|
||||||
|
const require = __internalCreateRequire(import.meta.url);"#
|
||||||
|
.to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
source.push(format!(
|
||||||
|
r#"let mod;
|
||||||
|
if (import.meta.main) {{
|
||||||
|
mod = __internalModule._load("{0}", null, true)
|
||||||
|
}} else {{
|
||||||
|
mod = require("{0}");
|
||||||
|
}}"#,
|
||||||
|
url_to_file_path(entry_specifier)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.replace('\\', "\\\\")
|
||||||
|
.replace('\'', "\\\'")
|
||||||
|
.replace('\"', "\\\"")
|
||||||
|
));
|
||||||
|
|
||||||
|
for export in &all_exports {
|
||||||
|
if !matches!(export.as_str(), "default" | "module.exports") {
|
||||||
|
add_export(
|
||||||
|
&mut source,
|
||||||
|
export,
|
||||||
|
&format!("mod[{}]", to_double_quote_string(export)),
|
||||||
|
&mut temp_var_count,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source.push("export default mod;".to_string());
|
||||||
|
add_export(&mut source, "module.exports", "mod", &mut temp_var_count);
|
||||||
|
|
||||||
|
let translated_source = source.join("\n");
|
||||||
|
Ok(Cow::Owned(translated_source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static RESERVED_WORDS: Lazy<HashSet<&str>> = Lazy::new(|| {
|
static RESERVED_WORDS: Lazy<HashSet<&str>> = Lazy::new(|| {
|
||||||
HashSet::from([
|
HashSet::from([
|
||||||
"abstract",
|
"abstract",
|
||||||
|
|
19
tests/specs/node/require_esm_reexport_esm/__test__.jsonc
Normal file
19
tests/specs/node/require_esm_reexport_esm/__test__.jsonc
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"tests": {
|
||||||
|
"run": {
|
||||||
|
"args": "run -A main.mjs",
|
||||||
|
"output": "run.out"
|
||||||
|
},
|
||||||
|
"compile": {
|
||||||
|
"tempDir": true,
|
||||||
|
"steps": [{
|
||||||
|
"args": "compile -A --output out main.mjs",
|
||||||
|
"output": "[WILDCARD]"
|
||||||
|
}, {
|
||||||
|
"commandName": "./out",
|
||||||
|
"args": [],
|
||||||
|
"output": "run.out"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
tests/specs/node/require_esm_reexport_esm/add.mjs
Normal file
3
tests/specs/node/require_esm_reexport_esm/add.mjs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function add(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
4
tests/specs/node/require_esm_reexport_esm/main.mjs
Normal file
4
tests/specs/node/require_esm_reexport_esm/main.mjs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import mod, { add } from "./mod1.cjs";
|
||||||
|
console.log(mod);
|
||||||
|
console.log(mod.add(1, 2));
|
||||||
|
console.log(add(1, 2));
|
1
tests/specs/node/require_esm_reexport_esm/mod1.cjs
Normal file
1
tests/specs/node/require_esm_reexport_esm/mod1.cjs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
module.exports = require("./mod2.cjs");
|
1
tests/specs/node/require_esm_reexport_esm/mod2.cjs
Normal file
1
tests/specs/node/require_esm_reexport_esm/mod2.cjs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
module.exports = require("./add.mjs");
|
3
tests/specs/node/require_esm_reexport_esm/run.out
Normal file
3
tests/specs/node/require_esm_reexport_esm/run.out
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[Module: null prototype] { add: [Function: add] }
|
||||||
|
3
|
||||||
|
3
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"tests": {
|
||||||
|
"run": {
|
||||||
|
"args": "run -A main.mjs",
|
||||||
|
"output": "run.out"
|
||||||
|
},
|
||||||
|
"compile": {
|
||||||
|
"tempDir": true,
|
||||||
|
"steps": [{
|
||||||
|
"args": "compile -A --output out main.mjs",
|
||||||
|
"output": "[WILDCARD]"
|
||||||
|
}, {
|
||||||
|
"commandName": "./out",
|
||||||
|
"args": [],
|
||||||
|
"output": "run.out"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
function add(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { add as "module.exports" };
|
|
@ -0,0 +1,2 @@
|
||||||
|
import add from "./mod1.cjs";
|
||||||
|
console.log(add(1, 2));
|
|
@ -0,0 +1 @@
|
||||||
|
module.exports = require("./mod2.cjs");
|
|
@ -0,0 +1 @@
|
||||||
|
module.exports = require("./add.mjs");
|
|
@ -0,0 +1 @@
|
||||||
|
3
|
Loading…
Add table
Reference in a new issue