mirror of
https://github.com/denoland/deno.git
synced 2025-09-26 20:29:11 +00:00
perf: reduce heap allocations and memory creating cjs wrapper module (#30344)
This commit is contained in:
parent
0f865b474b
commit
180480c06b
4 changed files with 118 additions and 78 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -5997,6 +5997,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"boxed_error",
|
"boxed_error",
|
||||||
|
"capacity_builder",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"deno_config",
|
"deno_config",
|
||||||
"deno_error",
|
"deno_error",
|
||||||
|
@ -6009,6 +6010,7 @@ dependencies = [
|
||||||
"lazy-regex",
|
"lazy-regex",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"path-clean",
|
"path-clean",
|
||||||
|
"pretty_assertions",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -1142,7 +1142,7 @@ impl CliFactory {
|
||||||
},
|
},
|
||||||
node_code_translator_mode: match options.sub_command() {
|
node_code_translator_mode: match options.sub_command() {
|
||||||
DenoSubcommand::Bundle(_) => {
|
DenoSubcommand::Bundle(_) => {
|
||||||
node_resolver::analyze::NodeCodeTranslatorMode::Bundling
|
node_resolver::analyze::NodeCodeTranslatorMode::Disabled
|
||||||
}
|
}
|
||||||
_ => node_resolver::analyze::NodeCodeTranslatorMode::ModuleLoader,
|
_ => node_resolver::analyze::NodeCodeTranslatorMode::ModuleLoader,
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,6 +21,7 @@ sync = ["deno_package_json/sync"]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
boxed_error.workspace = true
|
boxed_error.workspace = true
|
||||||
|
capacity_builder.workspace = true
|
||||||
dashmap.workspace = true
|
dashmap.workspace = true
|
||||||
deno_config.workspace = true
|
deno_config.workspace = true
|
||||||
deno_error.workspace = true
|
deno_error.workspace = true
|
||||||
|
@ -33,6 +34,7 @@ futures.workspace = true
|
||||||
lazy-regex.workspace = true
|
lazy-regex.workspace = true
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
path-clean.workspace = true
|
path-clean.workspace = true
|
||||||
|
pretty_assertions.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|
|
@ -555,7 +555,7 @@ pub struct NodeCodeTranslator<
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
pub enum NodeCodeTranslatorMode {
|
pub enum NodeCodeTranslatorMode {
|
||||||
Bundling,
|
Disabled,
|
||||||
#[default]
|
#[default]
|
||||||
ModuleLoader,
|
ModuleLoader,
|
||||||
}
|
}
|
||||||
|
@ -602,8 +602,7 @@ impl<
|
||||||
entry_specifier: &Url,
|
entry_specifier: &Url,
|
||||||
source: Option<Cow<'a, str>>,
|
source: Option<Cow<'a, str>>,
|
||||||
) -> Result<Cow<'a, str>, TranslateCjsToEsmError> {
|
) -> Result<Cow<'a, str>, TranslateCjsToEsmError> {
|
||||||
let all_exports = if matches!(self.mode, NodeCodeTranslatorMode::Bundling) {
|
let all_exports = if matches!(self.mode, NodeCodeTranslatorMode::Disabled) {
|
||||||
// let the bundler handle it instead of the module loader
|
|
||||||
return Ok(source.unwrap());
|
return Ok(source.unwrap());
|
||||||
} else {
|
} else {
|
||||||
let analysis = self
|
let analysis = self
|
||||||
|
@ -616,48 +615,10 @@ impl<
|
||||||
ResolvedCjsAnalysis::Cjs(all_exports) => all_exports,
|
ResolvedCjsAnalysis::Cjs(all_exports) => all_exports,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Ok(Cow::Owned(exports_to_wrapper_module(
|
||||||
// todo(dsherret): use capacity_builder here to remove all these heap
|
entry_specifier,
|
||||||
// allocations and make the string writing faster
|
&all_exports,
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -734,10 +695,69 @@ static RESERVED_WORDS: Lazy<HashSet<&str>> = Lazy::new(|| {
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
fn add_export(
|
fn exports_to_wrapper_module(
|
||||||
source: &mut Vec<String>,
|
entry_specifier: &Url,
|
||||||
name: &str,
|
all_exports: &BTreeSet<String>,
|
||||||
initializer: &str,
|
) -> 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("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,
|
temp_var_count: &mut usize,
|
||||||
) {
|
) {
|
||||||
fn is_valid_var_decl(name: &str) -> bool {
|
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,
|
// 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
|
// so assign it to a temporary variable that won't have a conflict, then re-export
|
||||||
// it as a string
|
// it as a string
|
||||||
source.push(format!(
|
builder.append("const __deno_export_");
|
||||||
"const __deno_export_{temp_var_count}__ = {initializer};"
|
builder.append(*temp_var_count);
|
||||||
));
|
builder.append("__ = ");
|
||||||
source.push(format!(
|
build_initializer(builder);
|
||||||
"export {{ __deno_export_{temp_var_count}__ as {} }};",
|
builder.append(";\nexport { __deno_export_");
|
||||||
to_double_quote_string(name)
|
builder.append(*temp_var_count);
|
||||||
));
|
builder.append("__ as ");
|
||||||
|
builder.append(quoted_name);
|
||||||
|
builder.append(" };\n");
|
||||||
} else {
|
} 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_export() {
|
fn test_exports_to_wrapper_module() {
|
||||||
let mut temp_var_count = 0;
|
let url = Url::parse("file:///test/test.ts").unwrap();
|
||||||
let mut source = vec![];
|
let exports = BTreeSet::from(
|
||||||
|
["static", "server", "app", "dashed-export", "3d"].map(|s| s.to_string()),
|
||||||
let exports = vec!["static", "server", "app", "dashed-export", "3d"];
|
);
|
||||||
for export in exports {
|
let text = exports_to_wrapper_module(&url, &exports);
|
||||||
add_export(&mut source, export, "init", &mut temp_var_count);
|
|
||||||
}
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
source,
|
text,
|
||||||
vec![
|
r#"import { createRequire as __internalCreateRequire, Module as __internalModule } from "node:module";
|
||||||
"const __deno_export_1__ = init;".to_string(),
|
const require = __internalCreateRequire(import.meta.url);
|
||||||
"export { __deno_export_1__ as \"static\" };".to_string(),
|
let mod;
|
||||||
"export const server = init;".to_string(),
|
if (import.meta.main) {
|
||||||
"export const app = init;".to_string(),
|
mod = __internalModule._load("/test/test.ts", null, true)
|
||||||
"const __deno_export_2__ = init;".to_string(),
|
} else {
|
||||||
"export { __deno_export_2__ as \"dashed-export\" };".to_string(),
|
mod = require("/test/test.ts");
|
||||||
"const __deno_export_3__ = init;".to_string(),
|
}
|
||||||
"export { __deno_export_3__ as \"3d\" };".to_string(),
|
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]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue