diff --git a/Cargo.lock b/Cargo.lock index 9af6e1f167..2b90eb4c26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5997,6 +5997,7 @@ dependencies = [ "anyhow", "async-trait", "boxed_error", + "capacity_builder", "dashmap", "deno_config", "deno_error", @@ -6009,6 +6010,7 @@ dependencies = [ "lazy-regex", "once_cell", "path-clean", + "pretty_assertions", "regex", "serde", "serde_json", diff --git a/cli/factory.rs b/cli/factory.rs index 90a7135919..fed862245d 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -1142,7 +1142,7 @@ impl CliFactory { }, node_code_translator_mode: match options.sub_command() { DenoSubcommand::Bundle(_) => { - node_resolver::analyze::NodeCodeTranslatorMode::Bundling + node_resolver::analyze::NodeCodeTranslatorMode::Disabled } _ => node_resolver::analyze::NodeCodeTranslatorMode::ModuleLoader, }, diff --git a/libs/node_resolver/Cargo.toml b/libs/node_resolver/Cargo.toml index 9d7bb60709..7e22bf85e1 100644 --- a/libs/node_resolver/Cargo.toml +++ b/libs/node_resolver/Cargo.toml @@ -21,6 +21,7 @@ sync = ["deno_package_json/sync"] anyhow.workspace = true async-trait.workspace = true boxed_error.workspace = true +capacity_builder.workspace = true dashmap.workspace = true deno_config.workspace = true deno_error.workspace = true @@ -33,6 +34,7 @@ futures.workspace = true lazy-regex.workspace = true once_cell.workspace = true path-clean.workspace = true +pretty_assertions.workspace = true regex.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/libs/node_resolver/analyze.rs b/libs/node_resolver/analyze.rs index 9fc5be592c..c59583b561 100644 --- a/libs/node_resolver/analyze.rs +++ b/libs/node_resolver/analyze.rs @@ -555,7 +555,7 @@ pub struct NodeCodeTranslator< #[derive(Debug, Default, Clone, Copy)] pub enum NodeCodeTranslatorMode { - Bundling, + Disabled, #[default] ModuleLoader, } @@ -602,8 +602,7 @@ impl< entry_specifier: &Url, source: Option>, ) -> Result, TranslateCjsToEsmError> { - let all_exports = if matches!(self.mode, NodeCodeTranslatorMode::Bundling) { - // let the bundler handle it instead of the module loader + let all_exports = if matches!(self.mode, NodeCodeTranslatorMode::Disabled) { return Ok(source.unwrap()); } else { let analysis = self @@ -616,48 +615,10 @@ impl< 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)) + Ok(Cow::Owned(exports_to_wrapper_module( + entry_specifier, + &all_exports, + ))) } } @@ -734,10 +695,69 @@ static RESERVED_WORDS: Lazy> = Lazy::new(|| { ]) }); -fn add_export( - source: &mut Vec, - name: &str, - initializer: &str, +fn exports_to_wrapper_module( + entry_specifier: &Url, + all_exports: &BTreeSet, +) -> String { + let quoted_entry_specifier_text = to_double_quote_string( + url_to_file_path(entry_specifier).unwrap().to_str().unwrap(), + ); + let export_names_with_quoted = all_exports + .iter() + .map(|export| (export.as_str(), to_double_quote_string(export))) + .collect::>(); + capacity_builder::StringBuilder::::build(|builder| { + let mut temp_var_count = 0; + builder.append( + r#"import { createRequire as __internalCreateRequire, Module as __internalModule } from "node:module"; +const require = __internalCreateRequire(import.meta.url); +let mod; +if (import.meta.main) { + mod = __internalModule._load("#, + ); + builder.append("ed_entry_specifier_text); + builder.append( + r#", null, true) +} else { + mod = require("#, + ); + builder.append("ed_entry_specifier_text); + builder.append(r#"); +} +"#); + + for (export_name, quoted_name) in &export_names_with_quoted { + if !matches!(*export_name, "default" | "module.exports") { + add_export( + builder, + export_name, + quoted_name, + |builder| { + builder.append("mod["); + builder.append(quoted_name); + builder.append("]"); + }, + &mut temp_var_count, + ); + } + } + + builder.append("export default mod;\n"); + add_export( + builder, + "module.exports", + "\"module.exports\"", + |builder| builder.append("mod"), + &mut temp_var_count, + ); + }).unwrap() +} + +fn add_export<'a>( + builder: &mut capacity_builder::StringBuilder<'a, String>, + name: &'a str, + quoted_name: &'a str, + build_initializer: impl FnOnce(&mut capacity_builder::StringBuilder<'a, String>), temp_var_count: &mut usize, ) { fn is_valid_var_decl(name: &str) -> bool { @@ -764,15 +784,21 @@ fn add_export( // we can't create an identifier with a reserved word or invalid identifier name, // so assign it to a temporary variable that won't have a conflict, then re-export // it as a string - source.push(format!( - "const __deno_export_{temp_var_count}__ = {initializer};" - )); - source.push(format!( - "export {{ __deno_export_{temp_var_count}__ as {} }};", - to_double_quote_string(name) - )); + builder.append("const __deno_export_"); + builder.append(*temp_var_count); + builder.append("__ = "); + build_initializer(builder); + builder.append(";\nexport { __deno_export_"); + builder.append(*temp_var_count); + builder.append("__ as "); + builder.append(quoted_name); + builder.append(" };\n"); } else { - source.push(format!("export const {name} = {initializer};")); + builder.append("export const "); + builder.append(name); + builder.append(" = "); + build_initializer(builder); + builder.append(";\n"); } } @@ -783,30 +809,40 @@ fn to_double_quote_string(text: &str) -> String { #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; + use super::*; #[test] - fn test_add_export() { - let mut temp_var_count = 0; - let mut source = vec![]; - - let exports = vec!["static", "server", "app", "dashed-export", "3d"]; - for export in exports { - add_export(&mut source, export, "init", &mut temp_var_count); - } + fn test_exports_to_wrapper_module() { + let url = Url::parse("file:///test/test.ts").unwrap(); + let exports = BTreeSet::from( + ["static", "server", "app", "dashed-export", "3d"].map(|s| s.to_string()), + ); + let text = exports_to_wrapper_module(&url, &exports); assert_eq!( - source, - vec![ - "const __deno_export_1__ = init;".to_string(), - "export { __deno_export_1__ as \"static\" };".to_string(), - "export const server = init;".to_string(), - "export const app = init;".to_string(), - "const __deno_export_2__ = init;".to_string(), - "export { __deno_export_2__ as \"dashed-export\" };".to_string(), - "const __deno_export_3__ = init;".to_string(), - "export { __deno_export_3__ as \"3d\" };".to_string(), - ] - ) + text, + r#"import { createRequire as __internalCreateRequire, Module as __internalModule } from "node:module"; +const require = __internalCreateRequire(import.meta.url); +let mod; +if (import.meta.main) { + mod = __internalModule._load("/test/test.ts", null, true) +} else { + mod = require("/test/test.ts"); +} +const __deno_export_1__ = mod["3d"]; +export { __deno_export_1__ as "3d" }; +export const app = mod["app"]; +const __deno_export_2__ = mod["dashed-export"]; +export { __deno_export_2__ as "dashed-export" }; +export const server = mod["server"]; +const __deno_export_3__ = mod["static"]; +export { __deno_export_3__ as "static" }; +export default mod; +const __deno_export_4__ = mod; +export { __deno_export_4__ as "module.exports" }; +"# + ); } #[test]