Skip to content

Commit

Permalink
feat(es/minifier): Support mangle cache (#9489)
Browse files Browse the repository at this point in the history
**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.
  • Loading branch information
kdy1 authored Aug 25, 2024
1 parent 3e73d4c commit af922d8
Show file tree
Hide file tree
Showing 38 changed files with 768 additions and 516 deletions.
5 changes: 5 additions & 0 deletions .changeset/clever-cars-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
swc_ecma_transforms_base: minor
---

feat(es/minifier): Support mangle cache
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions bindings/binding_core_node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
50 changes: 42 additions & 8 deletions bindings/binding_core_node/src/minify.rs
Original file line number Diff line number Diff line change
@@ -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<NameMangleCache>,
}

struct MinifyTask {
c: Arc<swc_core::base::Compiler>,
code: String,
options: String,
extras: JsMinifyExtras,
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -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()
}
Expand All @@ -72,24 +80,50 @@ impl Task for MinifyTask {
}
}

type NameMangleCache = External<Arc<dyn MangleCache>>;

#[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<AbortSignal>) -> AsyncTask<MinifyTask> {
fn minify(
code: Buffer,
opts: Buffer,
extras: NapiMinifyExtra,
signal: Option<AbortSignal>,
) -> AsyncTask<MinifyTask> {
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<TransformOutput> {
pub fn minify_sync(
code: Buffer,
opts: Buffer,
extras: NapiMinifyExtra,
) -> napi::Result<TransformOutput> {
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();

Expand All @@ -100,7 +134,7 @@ pub fn minify_sync(code: Buffer, opts: Buffer) -> napi::Result<TransformOutput>
false,
// TODO(kdy1): Maybe make this configurable?
ErrorFormat::Normal,
|handler| c.minify(fm, handler, &opts),
|handler| c.minify(fm, handler, &opts, extras),
)
.convert_err()
}
56 changes: 47 additions & 9 deletions bindings/binding_minifier_node/src/minify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ 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::{
minify_file_comments, parse_js, IdentCollector, PrintArgs, SourceMapsConfig, TransformOutput,
};
use swc_config::config_types::BoolOr;
use swc_core::{
base::JsMinifyExtras,
common::{
collections::AHashMap,
comments::{Comments, SingleThreadedComments},
Expand All @@ -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},
Expand All @@ -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<NameMangleCache>,
}

struct MinifyTask {
code: String,
options: String,
extras: JsMinifyExtras,
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -64,7 +72,11 @@ impl MinifyTarget {
}
}

fn do_work(input: MinifyTarget, options: JsMinifyOptions) -> napi::Result<TransformOutput> {
fn do_work(
input: MinifyTarget,
options: JsMinifyOptions,
extras: JsMinifyExtras,
) -> napi::Result<TransformOutput> {
let cm: Arc<SourceMap> = Arc::default();

let fm = input.to_file(cm.clone());
Expand Down Expand Up @@ -183,6 +195,7 @@ fn do_work(input: MinifyTarget, options: JsMinifyOptions) -> napi::Result<Transf
&swc_core::ecma::minifier::option::ExtraOptions {
unresolved_mark,
top_level_mark,
mangle_name_cache: extras.mangle_name_cache,
},
);

Expand Down Expand Up @@ -237,30 +250,55 @@ impl Task for MinifyTask {
let input: MinifyTarget = deserialize_json(&self.code)?;
let options: JsMinifyOptions = deserialize_json(&self.options)?;

do_work(input, options)
do_work(input, options, self.extras.clone())
}

fn resolve(&mut self, _env: napi::Env, output: Self::Output) -> napi::Result<Self::JsValue> {
Ok(output)
}
}

type NameMangleCache = External<Arc<dyn MangleCache>>;

#[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<AbortSignal>) -> AsyncTask<MinifyTask> {
fn minify(
code: Buffer,
opts: Buffer,
extras: NapiMinifyExtra,
signal: Option<AbortSignal>,
) -> AsyncTask<MinifyTask> {
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<TransformOutput> {
pub fn minify_sync(
code: Buffer,
opts: Buffer,
extras: NapiMinifyExtra,
) -> napi::Result<TransformOutput> {
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)
}
2 changes: 1 addition & 1 deletion crates/binding_macros/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
1 change: 1 addition & 0 deletions crates/dbg-swc/src/es/exec_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions crates/dbg-swc/src/util/minifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions crates/swc/benches/minify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ fn bench_minify(b: &mut Bencher, filename: &str) {
emit_source_map_columns: true,
..Default::default()
},
Default::default(),
)
})
})
Expand Down
6 changes: 5 additions & 1 deletion crates/swc/examples/minify.rs
Original file line number Diff line number Diff line change
@@ -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::<SourceMap>::default();
Expand All @@ -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")
})
Expand Down
2 changes: 2 additions & 0 deletions crates/swc/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ impl VisitMut for MinifierPass<'_> {
&swc_ecma_minifier::option::ExtraOptions {
unresolved_mark,
top_level_mark,
mangle_name_cache: None,
},
)
.expect_module()
Expand Down Expand Up @@ -502,6 +503,7 @@ impl VisitMut for MinifierPass<'_> {
&swc_ecma_minifier::option::ExtraOptions {
unresolved_mark,
top_level_mark,
mangle_name_cache: None,
},
)
.expect_script()
Expand Down
20 changes: 19 additions & 1 deletion crates/swc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -752,6 +752,7 @@ impl Compiler {
fm: Arc<SourceFile>,
handler: &Handler,
opts: &JsMinifyOptions,
extras: JsMinifyExtras,
) -> Result<TransformOutput, Error> {
self.run(|| {
let _timer = timer!("Compiler::minify");
Expand Down Expand Up @@ -871,6 +872,7 @@ impl Compiler {
&swc_ecma_minifier::option::ExtraOptions {
unresolved_mark,
top_level_mark,
mangle_name_cache: extras.mangle_name_cache,
},
);

Expand Down Expand Up @@ -1045,6 +1047,22 @@ impl Compiler {
}
}

#[non_exhaustive]
#[derive(Clone, Default)]
pub struct JsMinifyExtras {
pub mangle_name_cache: Option<Arc<dyn MangleCache>>,
}

impl JsMinifyExtras {
pub fn with_mangle_name_cache(
mut self,
mangle_name_cache: Option<Arc<dyn MangleCache>>,
) -> Self {
self.mangle_name_cache = mangle_name_cache;
self
}
}

fn find_swcrc(path: &Path, root: &Path, root_mode: RootMode) -> Option<PathBuf> {
let mut parent = path.parent();
while let Some(dir) = parent {
Expand Down
Loading

0 comments on commit af922d8

Please sign in to comment.