Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down
2 changes: 2 additions & 0 deletions libs/node_resolver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
190 changes: 113 additions & 77 deletions libs/node_resolver/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ pub struct NodeCodeTranslator<

#[derive(Debug, Default, Clone, Copy)]
pub enum NodeCodeTranslatorMode {
Bundling,
Disabled,
#[default]
ModuleLoader,
}
Expand Down Expand Up @@ -602,8 +602,7 @@ impl<
entry_specifier: &Url,
source: Option<Cow<'a, str>>,
) -> Result<Cow<'a, str>, 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
Expand All @@ -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,
)))
}
}

Expand Down Expand Up @@ -734,10 +695,69 @@ static RESERVED_WORDS: Lazy<HashSet<&str>> = Lazy::new(|| {
])
});

fn add_export(
source: &mut Vec<String>,
name: &str,
initializer: &str,
fn exports_to_wrapper_module(
entry_specifier: &Url,
all_exports: &BTreeSet<String>,
) -> 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::<Vec<_>>();
capacity_builder::StringBuilder::<String>::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(&quoted_entry_specifier_text);
builder.append(
r#", null, true)
} else {
mod = require("#,
);
builder.append(&quoted_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 {
Expand All @@ -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");
}
}

Expand All @@ -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]
Expand Down
Loading