diff options
Diffstat (limited to 'cli/compat/mod.rs')
-rw-r--r-- | cli/compat/mod.rs | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/cli/compat/mod.rs b/cli/compat/mod.rs index 0c30a58fc..0f1c36084 100644 --- a/cli/compat/mod.rs +++ b/cli/compat/mod.rs @@ -3,11 +3,15 @@ mod errors; mod esm_resolver; +use crate::file_fetcher::FileFetcher; +use deno_ast::MediaType; use deno_core::error::AnyError; use deno_core::located_script_name; use deno_core::url::Url; use deno_core::JsRuntime; +use deno_core::ModuleSpecifier; use once_cell::sync::Lazy; +use std::sync::Arc; pub use esm_resolver::check_if_should_use_esm_loader; pub(crate) use esm_resolver::NodeEsmResolver; @@ -155,3 +159,96 @@ pub fn setup_builtin_modules( js_runtime.execute_script("setup_node_builtins.js", &script)?; Ok(()) } + +/// 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( + file_fetcher: &FileFetcher, + specifier: &ModuleSpecifier, + code: String, + media_type: MediaType, +) -> Result<String, AnyError> { + let parsed_source = deno_ast::parse_script(deno_ast::ParseParams { + specifier: specifier.to_string(), + source: deno_ast::SourceTextInfo::new(Arc::new(code)), + media_type, + capture_tokens: true, + scope_analysis: false, + maybe_syntax: None, + })?; + let analysis = parsed_source.analyze_cjs(); + + let mut source = vec![ + r#"import { createRequire } from "node:module";"#.to_string(), + r#"const require = createRequire(import.meta.url);"#.to_string(), + ]; + + // if there are reexports, handle them first + for (idx, reexport) in analysis.reexports.iter().enumerate() { + // Firstly, resolve relate reexport specifier + let resolved_reexport = node_resolver::node_resolve( + reexport, + &specifier.to_file_path().unwrap(), + // FIXME(bartlomieju): check if these conditions are okay, probably + // should be `deno-require`, because `deno` is already used in `esm_resolver.rs` + &["deno", "require", "default"], + )?; + let reexport_specifier = + ModuleSpecifier::from_file_path(&resolved_reexport).unwrap(); + // Secondly, read the source code from disk + let reexport_file = file_fetcher.get_source(&reexport_specifier).unwrap(); + // Now perform analysis again + { + let parsed_source = deno_ast::parse_script(deno_ast::ParseParams { + specifier: reexport_specifier.to_string(), + source: deno_ast::SourceTextInfo::new(reexport_file.source), + media_type: reexport_file.media_type, + capture_tokens: true, + scope_analysis: false, + maybe_syntax: None, + })?; + let analysis = parsed_source.analyze_cjs(); + + source.push(format!( + "const reexport{} = require(\"{}\");", + idx, reexport + )); + + for export in analysis.exports.iter().filter(|e| e.as_str() != "default") + { + // TODO(bartlomieju): Node actually checks if a given export exists in `exports` object, + // but it might not be necessary here since our analysis is more detailed? + source.push(format!( + "export const {} = reexport{}.{};", + export, idx, export + )); + } + } + } + + source.push(format!( + "const mod = require(\"{}\");", + specifier + .to_file_path() + .unwrap() + .to_str() + .unwrap() + .replace('\\', "\\\\") + .replace('\'', "\\\'") + .replace('\"', "\\\"") + )); + source.push("export default mod".to_string()); + + for export in analysis.exports.iter().filter(|e| e.as_str() != "default") { + // TODO(bartlomieju): Node actually checks if a given export exists in `exports` object, + // but it might not be necessary here since our analysis is more detailed? + source.push(format!("export const {} = mod.{};", export, export)); + } + + let translated_source = source.join("\n"); + Ok(translated_source) +} |