From af922d83e58596021476006564edb6270069d437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Mon, 26 Aug 2024 08:42:53 +0900 Subject: [PATCH] feat(es/minifier): Support mangle cache (#9489) **Description:** This PR adds a name cache for the mangler. It's implemented using a `Mutex`, and it's exposed to JS as an experimental API. Note that JS API only provides an opaque object. --- .changeset/clever-cars-scream.md | 5 + Cargo.lock | 1 + bindings/binding_core_node/Cargo.toml | 1 + bindings/binding_core_node/src/minify.rs | 50 +- bindings/binding_minifier_node/src/minify.rs | 56 +- crates/binding_macros/src/wasm.rs | 2 +- crates/dbg-swc/src/es/exec_test.rs | 1 + crates/dbg-swc/src/util/minifier.rs | 1 + crates/swc/benches/minify.rs | 1 + crates/swc/examples/minify.rs | 6 +- crates/swc/src/builder.rs | 2 + crates/swc/src/lib.rs | 20 +- crates/swc/tests/projects.rs | 12 +- crates/swc_bundler/examples/bundle.rs | 1 + crates/swc_bundler/tests/deno.rs | 1 + crates/swc_ecma_compat_es2015/Cargo.toml | 1 + .../src/block_scoping/vars.rs | 12 +- crates/swc_ecma_minifier/benches/full.rs | 1 + crates/swc_ecma_minifier/examples/compress.rs | 1 + crates/swc_ecma_minifier/examples/minifier.rs | 6 +- .../swc_ecma_minifier/examples/minify-all.rs | 1 + crates/swc_ecma_minifier/src/lib.rs | 14 +- crates/swc_ecma_minifier/src/option/mod.rs | 49 +- .../src/pass/mangle_names/mod.rs | 80 ++- .../src/pass/mangle_names/private_name.rs | 11 +- crates/swc_ecma_minifier/tests/compress.rs | 1 + crates/swc_ecma_minifier/tests/exec.rs | 1 + crates/swc_ecma_minifier/tests/format.rs | 1 + crates/swc_ecma_minifier/tests/mangle.rs | 3 + crates/swc_ecma_minifier/tests/terser_exec.rs | 1 + .../src/rename/analyzer/scope.rs | 4 +- .../src/rename/mod.rs | 88 ++- .../src/rename/ops.rs | 7 +- crates/swc_html_minifier/src/lib.rs | 1 + .../core/__tests__/minify/name_cache.test.mjs | 41 ++ packages/core/binding.d.ts | 122 ++-- packages/core/binding.js | 642 +++++++++--------- packages/core/src/index.ts | 36 +- 38 files changed, 768 insertions(+), 516 deletions(-) create mode 100644 .changeset/clever-cars-scream.md create mode 100644 packages/core/__tests__/minify/name_cache.test.mjs diff --git a/.changeset/clever-cars-scream.md b/.changeset/clever-cars-scream.md new file mode 100644 index 000000000000..dbb9fd61053f --- /dev/null +++ b/.changeset/clever-cars-scream.md @@ -0,0 +1,5 @@ +--- +swc_ecma_transforms_base: minor +--- + +feat(es/minifier): Support mangle cache diff --git a/Cargo.lock b/Cargo.lock index 0ef7d0213c52..2e8626f66572 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4340,6 +4340,7 @@ dependencies = [ "arrayvec", "indexmap 2.2.6", "is-macro", + "rustc-hash", "serde", "serde_derive", "smallvec", diff --git a/bindings/binding_core_node/Cargo.toml b/bindings/binding_core_node/Cargo.toml index 4943b8fbb169..1436f1f2e35d 100644 --- a/bindings/binding_core_node/Cargo.toml +++ b/bindings/binding_core_node/Cargo.toml @@ -49,6 +49,7 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } swc_core = { workspace = true, features = [ "allocator_node", "ecma_ast", + "ecma_minifier", "ecma_codegen", "ecma_ast_serde", "common_concurrent", diff --git a/bindings/binding_core_node/src/minify.rs b/bindings/binding_core_node/src/minify.rs index 25e2a30a14b4..ef7ba2778d5d 100644 --- a/bindings/binding_core_node/src/minify.rs +++ b/bindings/binding_core_node/src/minify.rs @@ -1,25 +1,33 @@ use std::sync::Arc; use napi::{ - bindgen_prelude::{AbortSignal, AsyncTask, Buffer}, - Task, + bindgen_prelude::{AbortSignal, AsyncTask, Buffer, External}, + Env, JsExternal, JsObject, JsUnknown, Task, }; use serde::Deserialize; use swc_core::{ base::{ config::{ErrorFormat, JsMinifyOptions}, - TransformOutput, + JsMinifyExtras, TransformOutput, }, common::{collections::AHashMap, sync::Lrc, FileName, SourceFile, SourceMap}, + ecma::minifier::option::{MangleCache, SimpleMangleCache}, node::{deserialize_json, get_deserialized, MapErr}, }; use crate::{get_compiler, util::try_with}; +#[napi(object)] +pub struct NapiMinifyExtra { + #[napi(ts_type = "object")] + pub mangle_name_cache: Option, +} + struct MinifyTask { c: Arc, code: String, options: String, + extras: JsMinifyExtras, } #[derive(Deserialize)] @@ -62,7 +70,7 @@ impl Task for MinifyTask { try_with(self.c.cm.clone(), false, ErrorFormat::Normal, |handler| { let fm = input.to_file(self.c.cm.clone()); - self.c.minify(fm, handler, &options) + self.c.minify(fm, handler, &options, self.extras.clone()) }) .convert_err() } @@ -72,24 +80,50 @@ impl Task for MinifyTask { } } +type NameMangleCache = External>; + +#[napi(ts_return_type = "object")] +fn new_mangle_name_cache() -> NameMangleCache { + let cache = Arc::new(SimpleMangleCache::default()); + External::new(cache) +} + #[napi] -fn minify(code: Buffer, opts: Buffer, signal: Option) -> AsyncTask { +fn minify( + code: Buffer, + opts: Buffer, + extras: NapiMinifyExtra, + signal: Option, +) -> AsyncTask { crate::util::init_default_trace_subscriber(); let code = String::from_utf8_lossy(code.as_ref()).to_string(); let options = String::from_utf8_lossy(opts.as_ref()).to_string(); + let extras = JsMinifyExtras::default() + .with_mangle_name_cache(extras.mangle_name_cache.as_deref().cloned()); let c = get_compiler(); - let task = MinifyTask { c, code, options }; + let task = MinifyTask { + c, + code, + options, + extras, + }; AsyncTask::with_optional_signal(task, signal) } #[napi] -pub fn minify_sync(code: Buffer, opts: Buffer) -> napi::Result { +pub fn minify_sync( + code: Buffer, + opts: Buffer, + extras: NapiMinifyExtra, +) -> napi::Result { crate::util::init_default_trace_subscriber(); let code: MinifyTarget = get_deserialized(code)?; let opts = get_deserialized(opts)?; + let extras = JsMinifyExtras::default() + .with_mangle_name_cache(extras.mangle_name_cache.as_deref().cloned()); let c = get_compiler(); @@ -100,7 +134,7 @@ pub fn minify_sync(code: Buffer, opts: Buffer) -> napi::Result false, // TODO(kdy1): Maybe make this configurable? ErrorFormat::Normal, - |handler| c.minify(fm, handler, &opts), + |handler| c.minify(fm, handler, &opts, extras), ) .convert_err() } diff --git a/bindings/binding_minifier_node/src/minify.rs b/bindings/binding_minifier_node/src/minify.rs index 5eac047a6103..051e2d2fe5e8 100644 --- a/bindings/binding_minifier_node/src/minify.rs +++ b/bindings/binding_minifier_node/src/minify.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use anyhow::{Context, Error}; use napi::{ - bindgen_prelude::{AbortSignal, AsyncTask, Buffer}, - Task, + bindgen_prelude::{AbortSignal, AsyncTask, Buffer, External}, + JsObject, Task, }; use serde::Deserialize; use swc_compiler_base::{ @@ -11,6 +11,7 @@ use swc_compiler_base::{ }; use swc_config::config_types::BoolOr; use swc_core::{ + base::JsMinifyExtras, common::{ collections::AHashMap, comments::{Comments, SingleThreadedComments}, @@ -20,7 +21,7 @@ use swc_core::{ ecma::{ minifier::{ js::{JsMinifyCommentOption, JsMinifyOptions}, - option::{MinifyOptions, TopLevelOptions}, + option::{MangleCache, MinifyOptions, SimpleMangleCache, TopLevelOptions}, }, parser::{EsSyntax, Syntax}, transforms::base::{fixer::fixer, hygiene::hygiene, resolver}, @@ -31,9 +32,16 @@ use swc_nodejs_common::{deserialize_json, get_deserialized, MapErr}; use crate::util::try_with; +#[napi(object)] +pub struct NapiMinifyExtra { + #[napi(ts_type = "object")] + pub mangle_name_cache: Option, +} + struct MinifyTask { code: String, options: String, + extras: JsMinifyExtras, } #[derive(Deserialize)] @@ -64,7 +72,11 @@ impl MinifyTarget { } } -fn do_work(input: MinifyTarget, options: JsMinifyOptions) -> napi::Result { +fn do_work( + input: MinifyTarget, + options: JsMinifyOptions, + extras: JsMinifyExtras, +) -> napi::Result { let cm: Arc = Arc::default(); let fm = input.to_file(cm.clone()); @@ -183,6 +195,7 @@ fn do_work(input: MinifyTarget, options: JsMinifyOptions) -> napi::Result napi::Result { @@ -245,22 +258,47 @@ impl Task for MinifyTask { } } +type NameMangleCache = External>; + +#[napi(ts_return_type = "object")] +fn new_mangle_name_cache() -> NameMangleCache { + let cache = Arc::new(SimpleMangleCache::default()); + External::new(cache) +} + #[napi] -fn minify(code: Buffer, opts: Buffer, signal: Option) -> AsyncTask { +fn minify( + code: Buffer, + opts: Buffer, + extras: NapiMinifyExtra, + signal: Option, +) -> AsyncTask { crate::util::init_default_trace_subscriber(); let code = String::from_utf8_lossy(code.as_ref()).to_string(); let options = String::from_utf8_lossy(opts.as_ref()).to_string(); + let extras = JsMinifyExtras::default() + .with_mangle_name_cache(extras.mangle_name_cache.as_deref().cloned()); - let task = MinifyTask { code, options }; + let task = MinifyTask { + code, + options, + extras, + }; AsyncTask::with_optional_signal(task, signal) } #[napi] -pub fn minify_sync(code: Buffer, opts: Buffer) -> napi::Result { +pub fn minify_sync( + code: Buffer, + opts: Buffer, + extras: NapiMinifyExtra, +) -> napi::Result { crate::util::init_default_trace_subscriber(); let input: MinifyTarget = get_deserialized(code)?; let options = get_deserialized(opts)?; + let extras = JsMinifyExtras::default() + .with_mangle_name_cache(extras.mangle_name_cache.as_deref().cloned()); - do_work(input, options) + do_work(input, options, extras) } diff --git a/crates/binding_macros/src/wasm.rs b/crates/binding_macros/src/wasm.rs index b92eb1fb65ba..e4655fc0de27 100644 --- a/crates/binding_macros/src/wasm.rs +++ b/crates/binding_macros/src/wasm.rs @@ -112,7 +112,7 @@ macro_rules! build_minify_sync { }; let fm = c.cm.new_source_file($crate::wasm::FileName::Anon.into(), s.into()); - let program = $crate::wasm::anyhow::Context::context(c.minify(fm, handler, &opts), "failed to minify file")?; + let program = $crate::wasm::anyhow::Context::context(c.minify(fm, handler, &opts, Default::default()), "failed to minify file")?; program .serialize($crate::wasm::compat_serializer().as_ref()) diff --git a/crates/dbg-swc/src/es/exec_test.rs b/crates/dbg-swc/src/es/exec_test.rs index 71ff9ec7e048..73445644273c 100644 --- a/crates/dbg-swc/src/es/exec_test.rs +++ b/crates/dbg-swc/src/es/exec_test.rs @@ -73,6 +73,7 @@ impl TestMinifiedBundleCommand { &swc_ecma_minifier::option::ExtraOptions { unresolved_mark: bundle.unresolved_mark, top_level_mark: bundle.top_level_mark, + mangle_name_cache: None, }, ) .expect_module() diff --git a/crates/dbg-swc/src/util/minifier.rs b/crates/dbg-swc/src/util/minifier.rs index fff396c745ca..f16183c468a9 100644 --- a/crates/dbg-swc/src/util/minifier.rs +++ b/crates/dbg-swc/src/util/minifier.rs @@ -59,6 +59,7 @@ pub fn get_minified_with_opts( &swc_ecma_minifier::option::ExtraOptions { unresolved_mark: m.unresolved_mark, top_level_mark: m.top_level_mark, + mangle_name_cache: None, }, ) .expect_module() diff --git a/crates/swc/benches/minify.rs b/crates/swc/benches/minify.rs index 6f9be3e8f7af..97f848326331 100644 --- a/crates/swc/benches/minify.rs +++ b/crates/swc/benches/minify.rs @@ -46,6 +46,7 @@ fn bench_minify(b: &mut Bencher, filename: &str) { emit_source_map_columns: true, ..Default::default() }, + Default::default(), ) }) }) diff --git a/crates/swc/examples/minify.rs b/crates/swc/examples/minify.rs index 8596ab43b8dc..816816a8aeab 100644 --- a/crates/swc/examples/minify.rs +++ b/crates/swc/examples/minify.rs @@ -1,8 +1,9 @@ use std::{path::Path, sync::Arc}; use anyhow::Context; -use swc::{config::JsMinifyOptions, try_with_handler, BoolOrDataConfig}; +use swc::{config::JsMinifyOptions, try_with_handler, BoolOrDataConfig, JsMinifyExtras}; use swc_common::{SourceMap, GLOBALS}; +use swc_ecma_minifier::option::SimpleMangleCache; fn main() { let cm = Arc::::default(); @@ -23,6 +24,9 @@ fn main() { mangle: BoolOrDataConfig::from_bool(true), ..Default::default() }, + // Mangle name cache example. You may not need this. + JsMinifyExtras::default() + .with_mangle_name_cache(Some(Arc::new(SimpleMangleCache::default()))), ) .context("failed to minify") }) diff --git a/crates/swc/src/builder.rs b/crates/swc/src/builder.rs index 1096afe491a6..20fe98237a66 100644 --- a/crates/swc/src/builder.rs +++ b/crates/swc/src/builder.rs @@ -440,6 +440,7 @@ impl VisitMut for MinifierPass<'_> { &swc_ecma_minifier::option::ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ) .expect_module() @@ -502,6 +503,7 @@ impl VisitMut for MinifierPass<'_> { &swc_ecma_minifier::option::ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ) .expect_script() diff --git a/crates/swc/src/lib.rs b/crates/swc/src/lib.rs index f54f2d981d03..83f5e11158a6 100644 --- a/crates/swc/src/lib.rs +++ b/crates/swc/src/lib.rs @@ -140,7 +140,7 @@ use swc_ecma_codegen::{to_code, Node}; use swc_ecma_loader::resolvers::{ lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver, }; -use swc_ecma_minifier::option::{MinifyOptions, TopLevelOptions}; +use swc_ecma_minifier::option::{MangleCache, MinifyOptions, TopLevelOptions}; use swc_ecma_parser::{EsSyntax, Syntax}; use swc_ecma_transforms::{ fixer, @@ -752,6 +752,7 @@ impl Compiler { fm: Arc, handler: &Handler, opts: &JsMinifyOptions, + extras: JsMinifyExtras, ) -> Result { self.run(|| { let _timer = timer!("Compiler::minify"); @@ -871,6 +872,7 @@ impl Compiler { &swc_ecma_minifier::option::ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: extras.mangle_name_cache, }, ); @@ -1045,6 +1047,22 @@ impl Compiler { } } +#[non_exhaustive] +#[derive(Clone, Default)] +pub struct JsMinifyExtras { + pub mangle_name_cache: Option>, +} + +impl JsMinifyExtras { + pub fn with_mangle_name_cache( + mut self, + mangle_name_cache: Option>, + ) -> Self { + self.mangle_name_cache = mangle_name_cache; + self + } +} + fn find_swcrc(path: &Path, root: &Path, root_mode: RootMode) -> Option { let mut parent = path.parent(); while let Some(dir) = parent { diff --git a/crates/swc/tests/projects.rs b/crates/swc/tests/projects.rs index 8d8fe5503039..7a3b568c854d 100644 --- a/crates/swc/tests/projects.rs +++ b/crates/swc/tests/projects.rs @@ -908,8 +908,13 @@ fn issue_1984() { .into(), ); - c.minify(fm, &handler, &serde_json::from_str("{}").unwrap()) - .unwrap(); + c.minify( + fm, + &handler, + &serde_json::from_str("{}").unwrap(), + Default::default(), + ) + .unwrap(); Ok(()) }) @@ -1157,6 +1162,7 @@ fn issue_7513_2() { toplevel: Some(true), ..Default::default() }, + Default::default(), ) .context("failed to minify") }) @@ -1261,7 +1267,7 @@ fn minify(input_js: PathBuf) { config.source_map = BoolOrDataConfig::from_bool(true); } - let output = c.minify(fm, &handler, &config).unwrap(); + let output = c.minify(fm, &handler, &config, Default::default()).unwrap(); NormalizedOutput::from(output.code) .compare_to_file(input_dir.join("output.js")) diff --git a/crates/swc_bundler/examples/bundle.rs b/crates/swc_bundler/examples/bundle.rs index 37394cf4532e..ef11847dfb4b 100644 --- a/crates/swc_bundler/examples/bundle.rs +++ b/crates/swc_bundler/examples/bundle.rs @@ -132,6 +132,7 @@ fn do_test(_entry: &Path, entries: HashMap, inline: bool, mini &ExtraOptions { unresolved_mark: Mark::new(), top_level_mark: Mark::new(), + mangle_name_cache: None, }, ) .expect_module(); diff --git a/crates/swc_bundler/tests/deno.rs b/crates/swc_bundler/tests/deno.rs index f5ab0644e46d..6cee599ff199 100644 --- a/crates/swc_bundler/tests/deno.rs +++ b/crates/swc_bundler/tests/deno.rs @@ -1068,6 +1068,7 @@ fn bundle(url: &str, minify: bool) -> String { &swc_ecma_minifier::option::ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ) .expect_module(); diff --git a/crates/swc_ecma_compat_es2015/Cargo.toml b/crates/swc_ecma_compat_es2015/Cargo.toml index 4c0746587f34..2411cf3eeeff 100644 --- a/crates/swc_ecma_compat_es2015/Cargo.toml +++ b/crates/swc_ecma_compat_es2015/Cargo.toml @@ -16,6 +16,7 @@ version = "0.11.1" arrayvec = { workspace = true } indexmap = { workspace = true } is-macro = { workspace = true } +rustc-hash = { workspace = true } serde = { workspace = true } serde_derive = { workspace = true } smallvec = { workspace = true } diff --git a/crates/swc_ecma_compat_es2015/src/block_scoping/vars.rs b/crates/swc_ecma_compat_es2015/src/block_scoping/vars.rs index 70633575f1bd..0035b359ca8f 100644 --- a/crates/swc_ecma_compat_es2015/src/block_scoping/vars.rs +++ b/crates/swc_ecma_compat_es2015/src/block_scoping/vars.rs @@ -1,9 +1,7 @@ use indexmap::IndexMap; +use rustc_hash::FxHashMap; use swc_atoms::JsWord; -use swc_common::{ - collections::{AHashMap, ARandomState}, - Mark, SyntaxContext, -}; +use swc_common::{collections::ARandomState, Mark, SyntaxContext}; use swc_ecma_ast::*; use swc_ecma_transforms_base::{rename::remap, scope::ScopeKind}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; @@ -70,7 +68,7 @@ impl BlockScopedVars { vars: &empty_vars, }; - let mut rename_map = AHashMap::default(); + let mut rename_map = FxHashMap::default(); // dbg!(&self.scope); @@ -105,7 +103,7 @@ impl BlockScopedVars { #[swc_trace] impl Scope { - fn rename(&mut self, parent: ParentScope, rename_map: &mut AHashMap, fn_only: bool) { + fn rename(&mut self, parent: ParentScope, rename_map: &mut FxHashMap, fn_only: bool) { for s in self.children.iter_mut() { let parent = ParentScope { parent: Some(&parent), @@ -189,7 +187,7 @@ impl Scope { .for_each(|s| s.collect_candidates(parent, symbols)); } - fn rename_decls(&self, symbols: &[JsWord], rename_map: &mut AHashMap) { + fn rename_decls(&self, symbols: &[JsWord], rename_map: &mut FxHashMap) { for (id, _) in &self.vars { if !symbols.contains(&id.0) { continue; diff --git a/crates/swc_ecma_minifier/benches/full.rs b/crates/swc_ecma_minifier/benches/full.rs index 75e1b26e3c36..02f6b22c371a 100644 --- a/crates/swc_ecma_minifier/benches/full.rs +++ b/crates/swc_ecma_minifier/benches/full.rs @@ -95,6 +95,7 @@ fn run(src: &str) { &ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ) .expect_module(); diff --git a/crates/swc_ecma_minifier/examples/compress.rs b/crates/swc_ecma_minifier/examples/compress.rs index 6c22076e8975..af4805c6558c 100644 --- a/crates/swc_ecma_minifier/examples/compress.rs +++ b/crates/swc_ecma_minifier/examples/compress.rs @@ -59,6 +59,7 @@ fn main() { &ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ) .expect_module(); diff --git a/crates/swc_ecma_minifier/examples/minifier.rs b/crates/swc_ecma_minifier/examples/minifier.rs index b5277b388a43..eeb423488a15 100644 --- a/crates/swc_ecma_minifier/examples/minifier.rs +++ b/crates/swc_ecma_minifier/examples/minifier.rs @@ -2,13 +2,13 @@ extern crate swc_malloc; -use std::{env::args, fs, path::Path}; +use std::{env::args, fs, path::Path, sync::Arc}; use swc_common::{errors::HANDLER, sync::Lrc, Mark, SourceMap}; use swc_ecma_codegen::text_writer::{omit_trailing_semi, JsWriter}; use swc_ecma_minifier::{ optimize, - option::{ExtraOptions, MangleOptions, MinifyOptions}, + option::{ExtraOptions, MangleOptions, MinifyOptions, SimpleMangleCache}, }; use swc_ecma_parser::parse_file_as_module; use swc_ecma_transforms_base::{ @@ -59,6 +59,8 @@ fn main() { &ExtraOptions { unresolved_mark, top_level_mark, + // Mangle name cache example. You may not need this. + mangle_name_cache: Some(Arc::new(SimpleMangleCache::default())), }, ) .expect_module(); diff --git a/crates/swc_ecma_minifier/examples/minify-all.rs b/crates/swc_ecma_minifier/examples/minify-all.rs index 2678fcfe29c3..215eaaa4fd2c 100644 --- a/crates/swc_ecma_minifier/examples/minify-all.rs +++ b/crates/swc_ecma_minifier/examples/minify-all.rs @@ -74,6 +74,7 @@ fn main() { &ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ) .expect_module(); diff --git a/crates/swc_ecma_minifier/src/lib.rs b/crates/swc_ecma_minifier/src/lib.rs index d4bfdb92d71c..ec5a239b4c73 100644 --- a/crates/swc_ecma_minifier/src/lib.rs +++ b/crates/swc_ecma_minifier/src/lib.rs @@ -39,6 +39,7 @@ #![allow(clippy::match_like_matches_macro)] use once_cell::sync::Lazy; +use pass::mangle_names::mangle_names; use swc_common::{comments::Comments, pass::Repeated, sync::Lrc, SourceMap, SyntaxContext}; use swc_ecma_ast::*; use swc_ecma_transforms_optimization::debug_assert_valid; @@ -53,11 +54,8 @@ use crate::{ mode::{Minification, Mode}, option::{CompressOptions, ExtraOptions, MinifyOptions}, pass::{ - global_defs, - mangle_names::{idents_to_preserve, name_mangler}, - mangle_props::mangle_properties, - merge_exports::merge_exports, - postcompress::postcompress_optimizer, + global_defs, mangle_names::idents_to_preserve, mangle_props::mangle_properties, + merge_exports::merge_exports, postcompress::postcompress_optimizer, precompress::precompress_optimizer, }, // program_data::ModuleInfo, @@ -241,12 +239,14 @@ pub fn optimize( ) .compile(); - n.visit_mut_with(&mut name_mangler( + mangle_names( + &mut n, mangle.clone(), preserved, chars, extra.top_level_mark, - )); + extra.mangle_name_cache.clone(), + ); if let Some(property_mangle_options) = &mangle.props { mangle_properties(&mut n, property_mangle_options.clone(), chars); diff --git a/crates/swc_ecma_minifier/src/option/mod.rs b/crates/swc_ecma_minifier/src/option/mod.rs index d74fb6a43f30..6dda8d32686d 100644 --- a/crates/swc_ecma_minifier/src/option/mod.rs +++ b/crates/swc_ecma_minifier/src/option/mod.rs @@ -1,10 +1,14 @@ #![cfg_attr(not(feature = "extra-serde"), allow(unused))] +use std::sync::Arc; + +use parking_lot::RwLock; +use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; -use swc_atoms::JsWord; +use swc_atoms::{Atom, JsWord}; use swc_common::{collections::AHashMap, Mark}; use swc_config::{merge::Merge, CachedRegex}; -use swc_ecma_ast::{EsVersion, Expr}; +use swc_ecma_ast::{EsVersion, Expr, Id}; /// Implement default using serde. macro_rules! impl_default { @@ -20,13 +24,14 @@ macro_rules! impl_default { pub mod terser; /// This is not serializable. -#[derive(Debug)] pub struct ExtraOptions { /// It should be the [Mark] used for `resolver`. pub unresolved_mark: Mark, /// It should be the [Mark] used for `resolver`. pub top_level_mark: Mark, + + pub mangle_name_cache: Option>, } #[derive(Debug, Default, Clone)] @@ -436,3 +441,41 @@ impl Default for CompressOptions { } } } + +pub trait MangleCache: Send + Sync { + fn vars_cache(&self, op: &mut dyn FnMut(&FxHashMap)); + + fn props_cache(&self, op: &mut dyn FnMut(&FxHashMap)); + + fn update_vars_cache(&self, new_data: &FxHashMap); + + fn update_props_cache(&self, new_data: &FxHashMap); +} + +#[derive(Debug, Default)] +pub struct SimpleMangleCache { + pub vars: RwLock>, + pub props: RwLock>, +} + +impl MangleCache for SimpleMangleCache { + fn vars_cache(&self, op: &mut dyn FnMut(&FxHashMap)) { + let vars = self.vars.read(); + op(&vars); + } + + fn props_cache(&self, op: &mut dyn FnMut(&FxHashMap)) { + let props = self.props.read(); + op(&props); + } + + fn update_vars_cache(&self, new_data: &FxHashMap) { + let mut vars = self.vars.write(); + vars.extend(new_data.iter().map(|(k, v)| (k.clone(), v.clone()))); + } + + fn update_props_cache(&self, new_data: &FxHashMap) { + let mut props = self.props.write(); + props.extend(new_data.iter().map(|(k, v)| (k.clone(), v.clone()))); + } +} diff --git a/crates/swc_ecma_minifier/src/pass/mangle_names/mod.rs b/crates/swc_ecma_minifier/src/pass/mangle_names/mod.rs index 31bc4aa7e38e..522cfc8ab03d 100644 --- a/crates/swc_ecma_minifier/src/pass/mangle_names/mod.rs +++ b/crates/swc_ecma_minifier/src/pass/mangle_names/mod.rs @@ -1,44 +1,68 @@ +use std::{borrow::Cow, sync::Arc}; + use rustc_hash::{FxHashMap, FxHashSet}; -use swc_atoms::JsWord; -use swc_common::{chain, Mark}; +use swc_atoms::Atom; +use swc_common::Mark; use swc_ecma_ast::*; -use swc_ecma_transforms_base::rename::{renamer, Renamer}; +use swc_ecma_transforms_base::rename::{renamer, RenameMap, Renamer}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; pub(crate) use self::preserver::idents_to_preserve; -use crate::{option::MangleOptions, util::base54::Base54Chars}; +use crate::{ + option::{MangleCache, MangleOptions}, + util::base54::Base54Chars, +}; mod preserver; mod private_name; -pub(crate) fn name_mangler( +pub(crate) fn mangle_names( + program: &mut Program, options: MangleOptions, preserved: FxHashSet, chars: Base54Chars, top_level_mark: Mark, -) -> impl VisitMut { - chain!( - LabelMangler { + mangle_name_cache: Option>, +) { + program.visit_mut_with(&mut LabelMangler { + chars, + cache: Default::default(), + n: Default::default(), + }); + + program.visit_mut_with(&mut self::private_name::private_name_mangler( + options.keep_private_props, + chars, + )); + + let mut cache = RenameMap::default(); + + if let Some(mangle_cache) = &mangle_name_cache { + mangle_cache + .vars_cache(&mut |v| cache.extend(v.iter().map(|(k, v)| (k.clone(), v.clone())))); + } + + program.visit_mut_with(&mut renamer( + swc_ecma_transforms_base::hygiene::Config { + keep_class_names: options.keep_class_names, + top_level_mark, + ignore_eval: options.eval, + ..Default::default() + }, + ManglingRenamer { chars, - cache: Default::default(), - n: Default::default(), + preserved, + cache, + mangle_name_cache, }, - self::private_name::private_name_mangler(options.keep_private_props, chars), - renamer( - swc_ecma_transforms_base::hygiene::Config { - keep_class_names: options.keep_class_names, - top_level_mark, - ignore_eval: options.eval, - ..Default::default() - }, - ManglingRenamer { chars, preserved } - ) - ) + )); } struct ManglingRenamer { chars: Base54Chars, preserved: FxHashSet, + cache: RenameMap, + mangle_name_cache: Option>, } impl Renamer for ManglingRenamer { @@ -53,14 +77,24 @@ impl Renamer for ManglingRenamer { self.preserved.clone() } - fn new_name_for(&self, _: &Id, n: &mut usize) -> JsWord { + fn new_name_for(&self, _: &Id, n: &mut usize) -> Atom { self.chars.encode(n, true) } + + fn get_cached(&self) -> Option> { + Some(Cow::Borrowed(&self.cache)) + } + + fn store_cache(&mut self, update: &RenameMap) { + if let Some(cacher) = &self.mangle_name_cache { + cacher.update_vars_cache(update); + } + } } struct LabelMangler { chars: Base54Chars, - cache: FxHashMap, + cache: FxHashMap, n: usize, } diff --git a/crates/swc_ecma_minifier/src/pass/mangle_names/private_name.rs b/crates/swc_ecma_minifier/src/pass/mangle_names/private_name.rs index 31f4e84dd65f..79def22eb1ac 100644 --- a/crates/swc_ecma_minifier/src/pass/mangle_names/private_name.rs +++ b/crates/swc_ecma_minifier/src/pass/mangle_names/private_name.rs @@ -1,20 +1,17 @@ use swc_atoms::JsWord; use swc_common::collections::AHashMap; use swc_ecma_ast::*; -use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; +use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; use super::Base54Chars; -pub(crate) fn private_name_mangler( - keep_private_props: bool, - chars: Base54Chars, -) -> impl Fold + VisitMut { - as_folder(PrivateNameMangler { +pub(crate) fn private_name_mangler(keep_private_props: bool, chars: Base54Chars) -> impl VisitMut { + PrivateNameMangler { keep_private_props, private_n: Default::default(), renamed_private: Default::default(), chars, - }) + } } struct PrivateNameMangler { diff --git a/crates/swc_ecma_minifier/tests/compress.rs b/crates/swc_ecma_minifier/tests/compress.rs index aee393dcae7e..7b3696c05a9d 100644 --- a/crates/swc_ecma_minifier/tests/compress.rs +++ b/crates/swc_ecma_minifier/tests/compress.rs @@ -247,6 +247,7 @@ fn run( &ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ); let end = Instant::now(); diff --git a/crates/swc_ecma_minifier/tests/exec.rs b/crates/swc_ecma_minifier/tests/exec.rs index daf0d4021f79..1ec5f9cc0163 100644 --- a/crates/swc_ecma_minifier/tests/exec.rs +++ b/crates/swc_ecma_minifier/tests/exec.rs @@ -150,6 +150,7 @@ fn run( &ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ) .expect_module(); diff --git a/crates/swc_ecma_minifier/tests/format.rs b/crates/swc_ecma_minifier/tests/format.rs index 733232c8cf1f..6010807a5fa4 100644 --- a/crates/swc_ecma_minifier/tests/format.rs +++ b/crates/swc_ecma_minifier/tests/format.rs @@ -56,6 +56,7 @@ fn assert_format(src: &str, expected: &str, opts: Config) { &ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ) .expect_module(); diff --git a/crates/swc_ecma_minifier/tests/mangle.rs b/crates/swc_ecma_minifier/tests/mangle.rs index b38a24d13a21..241217edaa55 100644 --- a/crates/swc_ecma_minifier/tests/mangle.rs +++ b/crates/swc_ecma_minifier/tests/mangle.rs @@ -104,6 +104,7 @@ fn snapshot_compress_fixture(input: PathBuf) { &ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ); @@ -166,6 +167,7 @@ fn fixture(input: PathBuf) { &ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ); @@ -203,6 +205,7 @@ fn assert_mangled(src: &str, expected: &str, opts: MangleOptions) { &ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ); diff --git a/crates/swc_ecma_minifier/tests/terser_exec.rs b/crates/swc_ecma_minifier/tests/terser_exec.rs index 0ca5e51b51dc..89a2beb4fb28 100644 --- a/crates/swc_ecma_minifier/tests/terser_exec.rs +++ b/crates/swc_ecma_minifier/tests/terser_exec.rs @@ -233,6 +233,7 @@ fn run(cm: Lrc, handler: &Handler, input: &Path, config: &str) -> Opt &ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ); let end = Instant::now(); diff --git a/crates/swc_ecma_transforms_base/src/rename/analyzer/scope.rs b/crates/swc_ecma_transforms_base/src/rename/analyzer/scope.rs index 67fedeff1387..4d595124d2be 100644 --- a/crates/swc_ecma_transforms_base/src/rename/analyzer/scope.rs +++ b/crates/swc_ecma_transforms_base/src/rename/analyzer/scope.rs @@ -14,7 +14,7 @@ use swc_ecma_ast::*; use tracing::debug; use super::reverse_map::ReverseMap; -use crate::rename::Renamer; +use crate::rename::{RenameMap, Renamer}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum ScopeKind { @@ -36,8 +36,6 @@ pub(crate) struct Scope { pub(super) children: Vec, } -pub(crate) type RenameMap = AHashMap; - #[derive(Debug, Default)] pub(super) struct ScopeData { /// All identifiers used by this scope or children. diff --git a/crates/swc_ecma_transforms_base/src/rename/mod.rs b/crates/swc_ecma_transforms_base/src/rename/mod.rs index bf0cada069ad..fdd55aa9415b 100644 --- a/crates/swc_ecma_transforms_base/src/rename/mod.rs +++ b/crates/swc_ecma_transforms_base/src/rename/mod.rs @@ -1,9 +1,9 @@ #![allow(unused_imports)] -use std::borrow::Cow; +use std::{borrow::Cow, collections::hash_map::Entry}; -use rustc_hash::FxHashSet; -use swc_atoms::JsWord; +use rustc_hash::{FxHashMap, FxHashSet}; +use swc_atoms::Atom; use swc_common::collections::AHashMap; use swc_ecma_ast::*; use swc_ecma_utils::stack_size::maybe_grow_default; @@ -14,7 +14,7 @@ use self::renamer_concurrent::{Send, Sync}; #[cfg(not(feature = "concurrent-renamer"))] use self::renamer_single::{Send, Sync}; use self::{ - analyzer::{scope::RenameMap, Analyzer}, + analyzer::Analyzer, collector::{collect_decls, CustomBindingCollector, IdCollector}, eval::contains_eval, ops::Operator, @@ -41,15 +41,23 @@ pub trait Renamer: Send + Sync { Default::default() } + fn get_cached(&self) -> Option> { + None + } + + fn store_cache(&mut self, _update: &RenameMap) {} + /// Should increment `n`. - fn new_name_for(&self, orig: &Id, n: &mut usize) -> JsWord; + fn new_name_for(&self, orig: &Id, n: &mut usize) -> Atom; } -pub fn rename(map: &AHashMap) -> impl '_ + Fold + VisitMut { +pub type RenameMap = FxHashMap; + +pub fn rename(map: &RenameMap) -> impl '_ + Fold + VisitMut { rename_with_config(map, Default::default()) } -pub fn rename_with_config(map: &AHashMap, config: Config) -> impl '_ + Fold + VisitMut { +pub fn rename_with_config(map: &RenameMap, config: Config) -> impl '_ + Fold + VisitMut { as_folder(Operator { rename: map, config, @@ -57,7 +65,7 @@ pub fn rename_with_config(map: &AHashMap, config: Config) -> impl '_ }) } -pub fn remap(map: &AHashMap, config: Config) -> impl '_ + Fold + VisitMut { +pub fn remap(map: &FxHashMap, config: Config) -> impl '_ + Fold + VisitMut { as_folder(Operator { rename: map, config, @@ -74,6 +82,8 @@ where renamer, preserved: Default::default(), unresolved: Default::default(), + previous_cache: Default::default(), + total_map: None, }) } @@ -86,14 +96,21 @@ where renamer: R, preserved: FxHashSet, - unresolved: FxHashSet, + unresolved: FxHashSet, + + previous_cache: RenameMap, + + /// Used to store cache. + /// + /// [Some] if the [`Renamer::get_cached`] returns [Some]. + total_map: Option, } impl RenamePass where R: Renamer, { - fn get_unresolved(&self, n: &N, has_eval: bool) -> FxHashSet + fn get_unresolved(&self, n: &N, has_eval: bool) -> FxHashSet where N: VisitWith + VisitWith>, { @@ -120,13 +137,7 @@ where .collect() } - fn get_map( - &self, - node: &N, - skip_one: bool, - top_level: bool, - has_eval: bool, - ) -> AHashMap + fn get_map(&mut self, node: &N, skip_one: bool, top_level: bool, has_eval: bool) -> RenameMap where N: VisitWith + VisitWith>, N: VisitWith, @@ -168,7 +179,7 @@ where scope.rename_in_mangle_mode( &self.renamer, &mut map, - &Default::default(), + &self.previous_cache, &Default::default(), &self.preserved, &unresolved, @@ -178,14 +189,41 @@ where scope.rename_in_normal_mode( &self.renamer, &mut map, - &Default::default(), + &self.previous_cache, &mut Default::default(), &unresolved, ); } + if let Some(total_map) = &mut self.total_map { + total_map.reserve(map.len()); + + for (k, v) in &map { + match total_map.entry(k.clone()) { + Entry::Occupied(old) => { + unreachable!( + "{} is already renamed to {}, but it's renamed as {}", + k.0, + old.get(), + v + ); + } + Entry::Vacant(e) => { + e.insert(v.clone()); + } + } + } + } + map } + + fn load_cache(&mut self) { + if let Some(cache) = self.renamer.get_cached() { + self.previous_cache = cache.into_owned(); + self.total_map = Some(Default::default()); + } + } } /// Mark a node as a unit of minification. @@ -267,6 +305,8 @@ where } fn visit_mut_module(&mut self, m: &mut Module) { + self.load_cache(); + self.preserved = self.renamer.preserved_ids_for_module(m); let has_eval = !self.config.ignore_eval && contains_eval(m, true); @@ -303,9 +343,15 @@ where if !map.is_empty() { m.visit_mut_with(&mut rename_with_config(&map, self.config.clone())); } + + if let Some(total_map) = &self.total_map { + self.renamer.store_cache(total_map); + } } fn visit_mut_script(&mut self, m: &mut Script) { + self.load_cache(); + self.preserved = self.renamer.preserved_ids_for_script(m); let has_eval = !self.config.ignore_eval && contains_eval(m, true); @@ -321,6 +367,10 @@ where if !map.is_empty() { m.visit_mut_with(&mut rename_with_config(&map, self.config.clone())); } + + if let Some(total_map) = &self.total_map { + self.renamer.store_cache(total_map); + } } } diff --git a/crates/swc_ecma_transforms_base/src/rename/ops.rs b/crates/swc_ecma_transforms_base/src/rename/ops.rs index 3e582ccf0482..54a9ae2ff412 100644 --- a/crates/swc_ecma_transforms_base/src/rename/ops.rs +++ b/crates/swc_ecma_transforms_base/src/rename/ops.rs @@ -1,5 +1,5 @@ +use rustc_hash::FxHashMap; use swc_common::{ - collections::AHashMap, util::{move_map::MoveMap, take::Take}, Spanned, SyntaxContext, DUMMY_SP, }; @@ -7,6 +7,7 @@ use swc_ecma_ast::*; use swc_ecma_utils::{ident::IdentLike, stack_size::maybe_grow_default}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; +use super::RenameMap; use crate::{ hygiene::Config, perf::{cpu_count, ParExplode, Parallel, ParallelExt}, @@ -16,7 +17,7 @@ pub(super) struct Operator<'a, I> where I: IdentLike, { - pub rename: &'a AHashMap, + pub rename: &'a FxHashMap, pub config: Config, pub extra: Vec, @@ -36,7 +37,7 @@ where { // Remove span hygiene of the class. - let mut rename = AHashMap::default(); + let mut rename = RenameMap::default(); rename.insert(ident.to_id(), orig_name.sym.clone()); diff --git a/crates/swc_html_minifier/src/lib.rs b/crates/swc_html_minifier/src/lib.rs index 0d8db8172215..faff9cdfbd1a 100644 --- a/crates/swc_html_minifier/src/lib.rs +++ b/crates/swc_html_minifier/src/lib.rs @@ -2055,6 +2055,7 @@ impl Minifier<'_, C> { &swc_ecma_minifier::option::ExtraOptions { unresolved_mark, top_level_mark, + mangle_name_cache: None, }, ); diff --git a/packages/core/__tests__/minify/name_cache.test.mjs b/packages/core/__tests__/minify/name_cache.test.mjs new file mode 100644 index 000000000000..188bc70933ed --- /dev/null +++ b/packages/core/__tests__/minify/name_cache.test.mjs @@ -0,0 +1,41 @@ +import swc from "../.."; + +const mangleNameCache = swc.experimental_newMangleNameCache() + +it('should not output same result if no name cache', async () => { + const { code: code1 } = await swc.minify(` + /*#__NOINLINE__*/ const fff = 1; + export const f = fff; + `, { toplevel: true, module: true }); + const { code: code2 } = await swc.minify(` + /*#__NOINLINE__*/ const fff = 1; + const eeeeee = 2; + `, { toplevel: true, module: true }) + + expect(code1).not.toBe(code2) +}) + + +it('should output same result if name cache', async () => { + const { code: code1 } = await swc.minify(` + /*#__NOINLINE__*/ const fff = 1; + export const f = fff; + `, { + toplevel: true, + module: true + }, { + mangleNameCache + }); + const { code: code2 } = await swc.minify(` + /*#__NOINLINE__*/ const fff = 1; + const eeeeee = 2; + export const f = fff; + `, { + toplevel: true, + module: true + }, { + mangleNameCache + }) + + expect(code1).toBe(code2) +}) \ No newline at end of file diff --git a/packages/core/binding.d.ts b/packages/core/binding.d.ts index 4edaaabad4b3..84b19e4899db 100644 --- a/packages/core/binding.d.ts +++ b/packages/core/binding.d.ts @@ -2,91 +2,55 @@ /* eslint-disable */ export class Compiler { - constructor(); + constructor() } -export type JsCompiler = Compiler; - -export function bundle( - confItems: Buffer, - signal?: AbortSignal | undefined | null -): Promise<{ [index: string]: { code: string; map?: string } }>; - -export function getTargetTriple(): string; - -export function initCustomTraceSubscriber( - traceOutFilePath?: string | undefined | null -): void; - -export function minify( - code: Buffer, - opts: Buffer, - signal?: AbortSignal | undefined | null -): Promise; - -export function minifySync(code: Buffer, opts: Buffer): TransformOutput; - -export function parse( - src: string, - options: Buffer, - filename?: string | undefined | null, - signal?: AbortSignal | undefined | null -): Promise; - -export function parseFile( - path: string, - options: Buffer, - signal?: AbortSignal | undefined | null -): Promise; - -export function parseFileSync(path: string, opts: Buffer): string; - -export function parseSync( - src: string, - opts: Buffer, - filename?: string | undefined | null -): string; - -export function print( - programJson: string, - options: Buffer, - signal?: AbortSignal | undefined | null -): Promise; - -export function printSync(program: string, options: Buffer): TransformOutput; - -export function transform( - src: string, - isModule: boolean, - options: Buffer, - signal?: AbortSignal | undefined | null -): Promise; - -export function transformFile( - src: string, - isModule: boolean, - options: Buffer, - signal?: AbortSignal | undefined | null -): Promise; - -export function transformFileSync( - s: string, - isModule: boolean, - opts: Buffer -): TransformOutput; +export type JsCompiler = Compiler + +export declare function bundle(confItems: Buffer, signal?: AbortSignal | undefined | null): Promise<{ [index: string]: { code: string, map?: string } }> + +export declare function getTargetTriple(): string + +export declare function initCustomTraceSubscriber(traceOutFilePath?: string | undefined | null): void + +export declare function minify(code: Buffer, opts: Buffer, extras: NapiMinifyExtra, signal?: AbortSignal | undefined | null): Promise + +export declare function minifySync(code: Buffer, opts: Buffer, extras: NapiMinifyExtra): TransformOutput + +export interface NapiMinifyExtra { + mangleNameCache?: object +} + +export declare function newMangleNameCache(): object + +export declare function parse(src: string, options: Buffer, filename?: string | undefined | null, signal?: AbortSignal | undefined | null): Promise + +export declare function parseFile(path: string, options: Buffer, signal?: AbortSignal | undefined | null): Promise + +export declare function parseFileSync(path: string, opts: Buffer): string + +export declare function parseSync(src: string, opts: Buffer, filename?: string | undefined | null): string + +export declare function print(programJson: string, options: Buffer, signal?: AbortSignal | undefined | null): Promise + +export declare function printSync(program: string, options: Buffer): TransformOutput + +export declare function transform(src: string, isModule: boolean, options: Buffer, signal?: AbortSignal | undefined | null): Promise + +export declare function transformFile(src: string, isModule: boolean, options: Buffer, signal?: AbortSignal | undefined | null): Promise + +export declare function transformFileSync(s: string, isModule: boolean, opts: Buffer): TransformOutput export interface TransformOutput { - code: string; - map?: string; + code: string + map?: string + output?: string } /** Hack for `Type Generation` */ export interface TransformOutput { - code: string; - map?: string; + code: string + map?: string } -export function transformSync( - s: string, - isModule: boolean, - opts: Buffer -): TransformOutput; +export declare function transformSync(s: string, isModule: boolean, opts: Buffer): TransformOutput + diff --git a/packages/core/binding.js b/packages/core/binding.js index 5ef8e846a468..4094ce9b731e 100644 --- a/packages/core/binding.js +++ b/packages/core/binding.js @@ -4,358 +4,352 @@ const { readFileSync } = require('fs') -let nativeBinding = null; -const loadErrors = []; +let nativeBinding = null +const loadErrors = [] const isMusl = () => { - let musl = false; - if (process.platform === "linux") { - musl = isMuslFromFilesystem(); - if (musl === null) { - musl = isMuslFromReport(); - } - if (musl === null) { - musl = isMuslFromChildProcess(); - } + let musl = false + if (process.platform === 'linux') { + musl = isMuslFromFilesystem() + if (musl === null) { + musl = isMuslFromReport() } - return musl; -}; + if (musl === null) { + musl = isMuslFromChildProcess() + } + } + return musl +} -const isFileMusl = (f) => f.includes("libc.musl-") || f.includes("ld-musl-"); +const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-') const isMuslFromFilesystem = () => { - try { - return readFileSync("/usr/bin/ldd", "utf-8").includes("musl"); - } catch { - return null; - } -}; + try { + return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl') + } catch { + return null + } +} const isMuslFromReport = () => { - const report = - typeof process.report.getReport === "function" - ? process.report.getReport() - : null; - if (!report) { - return null; - } - if (report.header && report.header.glibcVersionRuntime) { - return false; + const report = typeof process.report.getReport === 'function' ? process.report.getReport() : null + if (!report) { + return null + } + if (report.header && report.header.glibcVersionRuntime) { + return false + } + if (Array.isArray(report.sharedObjects)) { + if (report.sharedObjects.some(isFileMusl)) { + return true } - if (Array.isArray(report.sharedObjects)) { - if (report.sharedObjects.some(isFileMusl)) { - return true; - } - } - return false; -}; + } + return false +} const isMuslFromChildProcess = () => { + try { + return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl') + } catch (e) { + // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false + return false + } +} + +function requireNative() { + if (process.platform === 'android') { + if (process.arch === 'arm64') { + try { + return require('./swc.android-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-android-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm') { + try { + return require('./swc.android-arm-eabi.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-android-arm-eabi') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)) + } + } else if (process.platform === 'win32') { + if (process.arch === 'x64') { + try { + return require('./swc.win32-x64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-win32-x64-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'ia32') { + try { + return require('./swc.win32-ia32-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-win32-ia32-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm64') { + try { + return require('./swc.win32-arm64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-win32-arm64-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)) + } + } else if (process.platform === 'darwin') { try { - return require("child_process") - .execSync("ldd --version", { encoding: "utf8" }) - .includes("musl"); - } catch (e) { - // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false - return false; + return require('./swc.darwin-universal.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-darwin-universal') + } catch (e) { + loadErrors.push(e) + } + + if (process.arch === 'x64') { + try { + return require('./swc.darwin-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-darwin-x64') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm64') { + try { + return require('./swc.darwin-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-darwin-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)) } -}; + } else if (process.platform === 'freebsd') { + if (process.arch === 'x64') { + try { + return require('./swc.freebsd-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-freebsd-x64') + } catch (e) { + loadErrors.push(e) + } -function requireNative() { - if (process.platform === "android") { - if (process.arch === "arm64") { - try { - return require("./swc.android-arm64.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-android-arm64"); - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === "arm") { - try { - return require("./swc.android-arm-eabi.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-android-arm-eabi"); - } catch (e) { - loadErrors.push(e); - } - } else { - loadErrors.push( - new Error(`Unsupported architecture on Android ${process.arch}`) - ); - } - } else if (process.platform === "win32") { - if (process.arch === "x64") { - try { - return require("./swc.win32-x64-msvc.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-win32-x64-msvc"); - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === "ia32") { - try { - return require("./swc.win32-ia32-msvc.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-win32-ia32-msvc"); - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === "arm64") { - try { - return require("./swc.win32-arm64-msvc.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-win32-arm64-msvc"); - } catch (e) { - loadErrors.push(e); - } - } else { - loadErrors.push( - new Error( - `Unsupported architecture on Windows: ${process.arch}` - ) - ); - } - } else if (process.platform === "darwin") { + } else if (process.arch === 'arm64') { + try { + return require('./swc.freebsd-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-freebsd-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)) + } + } else if (process.platform === 'linux') { + if (process.arch === 'x64') { + if (isMusl()) { try { - return require("./swc.darwin-universal.node"); - } catch (e) { - loadErrors.push(e); - } + return require('./swc.linux-x64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-linux-x64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { + try { + return require('./swc.linux-x64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-linux-x64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'arm64') { + if (isMusl()) { + try { + return require('./swc.linux-arm64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-linux-arm64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { try { - return require("@swc/core-darwin-universal"); - } catch (e) { - loadErrors.push(e); - } + return require('./swc.linux-arm64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-linux-arm64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'arm') { + try { + return require('./swc.linux-arm-gnueabihf.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-linux-arm-gnueabihf') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'riscv64') { + if (isMusl()) { + try { + return require('./swc.linux-riscv64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-linux-riscv64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { + try { + return require('./swc.linux-riscv64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-linux-riscv64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 's390x') { + try { + return require('./swc.linux-s390x-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@swc/core-linux-s390x-gnu') + } catch (e) { + loadErrors.push(e) + } - if (process.arch === "x64") { - try { - return require("./swc.darwin-x64.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-darwin-x64"); - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === "arm64") { - try { - return require("./swc.darwin-arm64.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-darwin-arm64"); - } catch (e) { - loadErrors.push(e); - } - } else { - loadErrors.push( - new Error(`Unsupported architecture on macOS: ${process.arch}`) - ); - } - } else if (process.platform === "freebsd") { - if (process.arch === "x64") { - try { - return require("./swc.freebsd-x64.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-freebsd-x64"); - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === "arm64") { - try { - return require("./swc.freebsd-arm64.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-freebsd-arm64"); - } catch (e) { - loadErrors.push(e); - } - } else { - loadErrors.push( - new Error( - `Unsupported architecture on FreeBSD: ${process.arch}` - ) - ); - } - } else if (process.platform === "linux") { - if (process.arch === "x64") { - if (isMusl()) { - try { - return require("./swc.linux-x64-musl.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-linux-x64-musl"); - } catch (e) { - loadErrors.push(e); - } - } else { - try { - return require("./swc.linux-x64-gnu.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-linux-x64-gnu"); - } catch (e) { - loadErrors.push(e); - } - } - } else if (process.arch === "arm64") { - if (isMusl()) { - try { - return require("./swc.linux-arm64-musl.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-linux-arm64-musl"); - } catch (e) { - loadErrors.push(e); - } - } else { - try { - return require("./swc.linux-arm64-gnu.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-linux-arm64-gnu"); - } catch (e) { - loadErrors.push(e); - } - } - } else if (process.arch === "arm") { - try { - return require("./swc.linux-arm-gnueabihf.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-linux-arm-gnueabihf"); - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === "riscv64") { - if (isMusl()) { - try { - return require("./swc.linux-riscv64-musl.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-linux-riscv64-musl"); - } catch (e) { - loadErrors.push(e); - } - } else { - try { - return require("./swc.linux-riscv64-gnu.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-linux-riscv64-gnu"); - } catch (e) { - loadErrors.push(e); - } - } - } else if (process.arch === "s390x") { - try { - return require("./swc.linux-s390x-gnu.node"); - } catch (e) { - loadErrors.push(e); - } - try { - return require("@swc/core-linux-s390x-gnu"); - } catch (e) { - loadErrors.push(e); - } - } else { - loadErrors.push( - new Error(`Unsupported architecture on Linux: ${process.arch}`) - ); - } } else { - loadErrors.push( - new Error( - `Unsupported OS: ${process.platform}, architecture: ${process.arch}` - ) - ); + loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)) } + } else { + loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`)) + } } -nativeBinding = requireNative(); +nativeBinding = requireNative() if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { + try { + nativeBinding = require('./swc.wasi.cjs') + } catch (err) { + if (process.env.NAPI_RS_FORCE_WASI) { + console.error(err) + } + } + if (!nativeBinding) { try { - nativeBinding = require("./swc.wasi.cjs"); + nativeBinding = require('@swc/core-wasm32-wasi') } catch (err) { - if (process.env.NAPI_RS_FORCE_WASI) { - console.error(err); - } - } - if (!nativeBinding) { - try { - nativeBinding = require("@swc/core-wasm32-wasi"); - } catch (err) { - if (process.env.NAPI_RS_FORCE_WASI) { - console.error(err); - } - } + if (process.env.NAPI_RS_FORCE_WASI) { + console.error(err) + } } + } } if (!nativeBinding) { - if (loadErrors.length > 0) { - // TODO Link to documentation with potential fixes - // - The package owner could build/publish bindings for this arch - // - The user may need to bundle the correct files - // - The user may need to re-install node_modules to get new packages - throw new Error("Failed to load native binding", { cause: loadErrors }); - } - throw new Error(`Failed to load native binding`); + if (loadErrors.length > 0) { + // TODO Link to documentation with potential fixes + // - The package owner could build/publish bindings for this arch + // - The user may need to bundle the correct files + // - The user may need to re-install node_modules to get new packages + throw new Error('Failed to load native binding', { cause: loadErrors }) + } + throw new Error(`Failed to load native binding`) } -module.exports.Compiler = nativeBinding.Compiler; -module.exports.JsCompiler = nativeBinding.JsCompiler; -module.exports.bundle = nativeBinding.bundle; -module.exports.getTargetTriple = nativeBinding.getTargetTriple; -module.exports.initCustomTraceSubscriber = - nativeBinding.initCustomTraceSubscriber; -module.exports.minify = nativeBinding.minify; -module.exports.minifySync = nativeBinding.minifySync; -module.exports.parse = nativeBinding.parse; -module.exports.parseFile = nativeBinding.parseFile; -module.exports.parseFileSync = nativeBinding.parseFileSync; -module.exports.parseSync = nativeBinding.parseSync; -module.exports.print = nativeBinding.print; -module.exports.printSync = nativeBinding.printSync; -module.exports.transform = nativeBinding.transform; -module.exports.transformFile = nativeBinding.transformFile; -module.exports.transformFileSync = nativeBinding.transformFileSync; -module.exports.transformSync = nativeBinding.transformSync; +module.exports.Compiler = nativeBinding.Compiler +module.exports.JsCompiler = nativeBinding.JsCompiler +module.exports.bundle = nativeBinding.bundle +module.exports.getTargetTriple = nativeBinding.getTargetTriple +module.exports.initCustomTraceSubscriber = nativeBinding.initCustomTraceSubscriber +module.exports.minify = nativeBinding.minify +module.exports.minifySync = nativeBinding.minifySync +module.exports.newMangleNameCache = nativeBinding.newMangleNameCache +module.exports.parse = nativeBinding.parse +module.exports.parseFile = nativeBinding.parseFile +module.exports.parseFileSync = nativeBinding.parseFileSync +module.exports.parseSync = nativeBinding.parseSync +module.exports.print = nativeBinding.print +module.exports.printSync = nativeBinding.printSync +module.exports.transform = nativeBinding.transform +module.exports.transformFile = nativeBinding.transformFile +module.exports.transformFileSync = nativeBinding.transformFileSync +module.exports.transformSync = nativeBinding.transformSync diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 43fe13d559d1..ee7b3e77d9bf 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -10,8 +10,11 @@ import type { JsMinifyOptions, } from "@swc/types"; export type * from "@swc/types"; +// @ts-ignore +export { newMangleNameCache as experimental_newMangleNameCache } from "./binding"; import { BundleInput, compileBundleOptions } from "./spack"; import * as assert from "assert"; +import type { NapiMinifyExtra } from "../binding"; // Allow overrides to the location of the .node binding file const bindingsOverride = process.env["SWC_BINARY_PATH"]; @@ -58,18 +61,18 @@ export function plugins(ps: Plugin[]): Plugin { export class Compiler { private fallbackBindingsPluginWarningDisplayed = false; - async minify(src: string, opts?: JsMinifyOptions): Promise { + async minify(src: string, opts?: JsMinifyOptions, extras?: NapiMinifyExtra): Promise { if (bindings) { - return bindings.minify(toBuffer(src), toBuffer(opts ?? {})); + return bindings.minify(toBuffer(src), toBuffer(opts ?? {}), extras ?? {}); } else if (fallbackBindings) { return fallbackBindings.minify(src, opts); } throw new Error("Bindings not found."); } - minifySync(src: string, opts?: JsMinifyOptions): Output { + minifySync(src: string, opts?: JsMinifyOptions, extras?: NapiMinifyExtra): Output { if (bindings) { - return bindings.minifySync(toBuffer(src), toBuffer(opts ?? {})); + return bindings.minifySync(toBuffer(src), toBuffer(opts ?? {}), extras ?? {}); } else if (fallbackBindings) { return fallbackBindings.minifySync(src, opts); } @@ -220,10 +223,10 @@ export class Compiler { const m = typeof src === "string" ? await this.parse( - src, - options?.jsc?.parser, - options.filename - ) + src, + options?.jsc?.parser, + options.filename + ) : src; return this.transform(plugin(m), newOptions); } @@ -263,10 +266,10 @@ export class Compiler { const m = typeof src === "string" ? this.parseSync( - src, - options?.jsc?.parser, - options.filename - ) + src, + options?.jsc?.parser, + options.filename + ) : src; return this.transformSync(plugin(m), newOptions); } @@ -475,13 +478,14 @@ export function bundle( export async function minify( src: string, - opts?: JsMinifyOptions + opts?: JsMinifyOptions, + extras?: NapiMinifyExtra ): Promise { - return compiler.minify(src, opts); + return compiler.minify(src, opts, extras); } -export function minifySync(src: string, opts?: JsMinifyOptions): Output { - return compiler.minifySync(src, opts); +export function minifySync(src: string, opts?: JsMinifyOptions, extras?: NapiMinifyExtra): Output { + return compiler.minifySync(src, opts, extras); } /**