perf(es/minifier): Enable parallel processing (#5705)

This commit is contained in:
Donny/강동윤 2022-09-01 13:36:30 +09:00 committed by GitHub
parent 748142e681
commit 1085667049
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 533 additions and 539 deletions

View File

@ -440,14 +440,18 @@ jobs:
- name: Run cargo test (concurrent)
if: runner.os == 'Linux' && matrix.settings.crate != 'swc_ecma_minifier'
shell: bash
env:
SWC_FORCE_CONCURRENT: "1"
run: |
./scripts/ci/test-concurrent.sh ${{ matrix.settings.crate }}
- name: Run cargo test (swc, concurrent)
shell: bash
if: matrix.settings.crate == 'swc' && runner.os == 'Linux'
env:
SWC_FORCE_CONCURRENT: "1"
run: |
cargo test --color always -p swc --features concurrent
cargo test --color always -p swc --all-targets --features concurrent
- name: Install cargo-hack
uses: baptiste0928/cargo-install@v1.1.0

View File

@ -9,7 +9,7 @@ use anyhow::Error;
use ntest::timeout;
use swc_atoms::js_word;
use swc_bundler::{Bundler, Load, ModuleRecord};
use swc_common::{collections::AHashSet, FileName, Mark, Span, GLOBALS};
use swc_common::{collections::AHashSet, errors::HANDLER, FileName, Mark, Span, GLOBALS};
use swc_ecma_ast::*;
use swc_ecma_codegen::{
text_writer::{omit_trailing_semi, JsWriter, WriteJs},
@ -1020,83 +1020,85 @@ fn run(url: &str, exports: &[&str]) {
}
fn bundle(url: &str, minify: bool) -> String {
testing::run_test2(false, |cm, _handler| {
testing::run_test2(false, |cm, handler| {
GLOBALS.with(|globals| {
let mut bundler = Bundler::new(
globals,
cm.clone(),
Loader { cm: cm.clone() },
NodeResolver,
swc_bundler::Config {
require: false,
disable_inliner: false,
..Default::default()
},
Box::new(Hook),
);
let mut entries = HashMap::new();
entries.insert(
"main".to_string(),
if url.starts_with("http") {
FileName::Custom(url.to_string())
} else {
FileName::Real(url.to_string().into())
},
);
let output = bundler.bundle(entries).unwrap();
let mut module = output.into_iter().next().unwrap().module;
if minify {
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
module.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
module = swc_ecma_minifier::optimize(
module.into(),
HANDLER.set(&handler, || {
let mut bundler = Bundler::new(
globals,
cm.clone(),
None,
None,
&swc_ecma_minifier::option::MinifyOptions {
compress: Some(Default::default()),
mangle: Some(MangleOptions {
top_level: true,
..Default::default()
}),
Loader { cm: cm.clone() },
NodeResolver,
swc_bundler::Config {
require: false,
disable_inliner: false,
..Default::default()
},
&swc_ecma_minifier::option::ExtraOptions {
unresolved_mark,
top_level_mark,
Box::new(Hook),
);
let mut entries = HashMap::new();
entries.insert(
"main".to_string(),
if url.starts_with("http") {
FileName::Custom(url.to_string())
} else {
FileName::Real(url.to_string().into())
},
)
.expect_module();
module.visit_mut_with(&mut fixer(None));
}
let mut buf = vec![];
{
let mut wr: Box<dyn WriteJs> =
Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None));
);
let output = bundler.bundle(entries).unwrap();
let mut module = output.into_iter().next().unwrap().module;
if minify {
wr = Box::new(omit_trailing_semi(wr));
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
module.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
module = swc_ecma_minifier::optimize(
module.into(),
cm.clone(),
None,
None,
&swc_ecma_minifier::option::MinifyOptions {
compress: Some(Default::default()),
mangle: Some(MangleOptions {
top_level: true,
..Default::default()
}),
..Default::default()
},
&swc_ecma_minifier::option::ExtraOptions {
unresolved_mark,
top_level_mark,
},
)
.expect_module();
module.visit_mut_with(&mut fixer(None));
}
Emitter {
cfg: swc_ecma_codegen::Config {
minify,
..Default::default()
},
cm: cm.clone(),
comments: None,
wr,
}
.emit_module(&module)
.unwrap();
}
let mut buf = vec![];
{
let mut wr: Box<dyn WriteJs> =
Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None));
Ok(String::from_utf8_lossy(&buf).to_string())
if minify {
wr = Box::new(omit_trailing_semi(wr));
}
Emitter {
cfg: swc_ecma_codegen::Config {
minify,
..Default::default()
},
cm: cm.clone(),
comments: None,
wr,
}
.emit_module(&module)
.unwrap();
}
Ok(String::from_utf8_lossy(&buf).to_string())
})
})
})
.unwrap()

View File

@ -17,9 +17,11 @@ version = "0.150.5"
bench = false
[features]
# This enables global concurrent mode
concurrent = [
"swc_common/concurrent",
"swc_ecma_transforms_base/concurrent-renamer",
"swc_ecma_transforms_optimization/concurrent",
"rayon",
"indexmap/rayon",
]

View File

@ -5,7 +5,7 @@ extern crate swc_node_base;
use std::fs::read_to_string;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use swc_common::{sync::Lrc, FileName, Mark, SourceMap};
use swc_common::{errors::HANDLER, sync::Lrc, FileName, Mark, SourceMap};
use swc_ecma_codegen::text_writer::JsWriter;
use swc_ecma_minifier::{
optimize,
@ -49,60 +49,62 @@ criterion_main!(files);
fn run(src: &str) {
testing::run_test2(false, |cm, handler| {
let fm = cm.new_source_file(FileName::Anon, src.into());
HANDLER.set(&handler, || {
let fm = cm.new_source_file(FileName::Anon, src.into());
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
let program = parse_file_as_module(
&fm,
Default::default(),
Default::default(),
None,
&mut vec![],
)
.map_err(|err| {
err.into_diagnostic(&handler).emit();
let program = parse_file_as_module(
&fm,
Default::default(),
Default::default(),
None,
&mut vec![],
)
.map_err(|err| {
err.into_diagnostic(&handler).emit();
})
.map(|module| module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false)))
.unwrap();
let output = optimize(
program.into(),
cm.clone(),
None,
None,
&MinifyOptions {
rename: false,
compress: Some(CompressOptions {
..Default::default()
}),
mangle: Some(MangleOptions {
props: None,
top_level: true,
keep_class_names: false,
keep_fn_names: false,
keep_private_props: false,
ie8: false,
safari10: false,
reserved: Default::default(),
}),
wrap: false,
enclose: false,
},
&ExtraOptions {
unresolved_mark,
top_level_mark,
},
)
.expect_module();
let output = output.fold_with(&mut fixer(None));
let code = print(cm, &[output], true);
black_box(code);
Ok(())
})
.map(|module| module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false)))
.unwrap();
let output = optimize(
program.into(),
cm.clone(),
None,
None,
&MinifyOptions {
rename: false,
compress: Some(CompressOptions {
..Default::default()
}),
mangle: Some(MangleOptions {
props: None,
top_level: true,
keep_class_names: false,
keep_fn_names: false,
keep_private_props: false,
ie8: false,
safari10: false,
reserved: Default::default(),
}),
wrap: false,
enclose: false,
},
&ExtraOptions {
unresolved_mark,
top_level_mark,
},
)
.expect_module();
let output = output.fold_with(&mut fixer(None));
let code = print(cm, &[output], true);
black_box(code);
Ok(())
})
.unwrap();
}

View File

@ -26,6 +26,7 @@
use once_cell::sync::Lazy;
use swc_common::{comments::Comments, pass::Repeat, sync::Lrc, SourceMap, GLOBALS};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::helpers::{Helpers, HELPERS};
use swc_ecma_visit::VisitMutWith;
use swc_timer::timer;
@ -76,173 +77,177 @@ pub fn optimize(
options: &MinifyOptions,
extra: &ExtraOptions,
) -> Program {
let _timer = timer!("minify");
HELPERS.set(&Helpers::new(true), || {
let _timer = timer!("minify");
let mut marks = Marks::new();
marks.unresolved_mark = extra.unresolved_mark;
let mut marks = Marks::new();
marks.unresolved_mark = extra.unresolved_mark;
if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) {
let _timer = timer!("inline global defs");
// Apply global defs.
//
// As terser treats `CONFIG['VALUE']` and `CONFIG.VALUE` differently, we don't
// have to see if optimized code matches global definition and we can run
// this at startup.
if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) {
let _timer = timer!("inline global defs");
// Apply global defs.
//
// As terser treats `CONFIG['VALUE']` and `CONFIG.VALUE` differently, we don't
// have to see if optimized code matches global definition and we can run
// this at startup.
if !defs.is_empty() {
let defs = defs.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
m.visit_mut_with(&mut global_defs::globals_defs(
defs,
if !defs.is_empty() {
let defs = defs.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
m.visit_mut_with(&mut global_defs::globals_defs(
defs,
extra.unresolved_mark,
extra.top_level_mark,
));
}
}
let module_info = match &m {
Program::Script(_) => ModuleInfo::default(),
Program::Module(m) => ModuleInfo {
blackbox_imports: m
.body
.iter()
.filter_map(|v| v.as_module_decl())
.filter_map(|v| match v {
ModuleDecl::Import(i) => Some(i),
_ => None,
})
.filter(|i| !i.src.value.starts_with("@swc/helpers"))
.flat_map(|v| v.specifiers.iter())
.map(|v| match v {
ImportSpecifier::Named(v) => v.local.to_id(),
ImportSpecifier::Default(v) => v.local.to_id(),
ImportSpecifier::Namespace(v) => v.local.to_id(),
})
.collect(),
// exports: m
// .body
// .iter()
// .filter_map(|v| v.as_module_decl())
// .filter_map(|v| match v {
// ModuleDecl::ExportNamed(i) if i.src.is_none() => Some(i),
// _ => None,
// })
// .flat_map(|v| v.specifiers.iter())
// .filter_map(|v| match v {
// ExportSpecifier::Named(v) => Some(v),
// _ => None,
// })
// .filter_map(|v| match &v.orig {
// ModuleExportName::Ident(i) => Some(i.to_id()),
// ModuleExportName::Str(_) => None,
// })
// .collect(),
},
};
if let Some(options) = &options.compress {
let _timer = timer!("precompress");
m.visit_mut_with(&mut precompress_optimizer(&module_info, options, marks));
}
if options.compress.is_some() {
m.visit_mut_with(&mut info_marker(
options.compress.as_ref(),
comments,
marks,
extra.unresolved_mark,
extra.top_level_mark,
));
}
}
m.visit_mut_with(&mut unique_scope());
let module_info = match &m {
Program::Script(_) => ModuleInfo::default(),
Program::Module(m) => ModuleInfo {
blackbox_imports: m
.body
.iter()
.filter_map(|v| v.as_module_decl())
.filter_map(|v| match v {
ModuleDecl::Import(i) => Some(i),
_ => None,
})
.filter(|i| !i.src.value.starts_with("@swc/helpers"))
.flat_map(|v| v.specifiers.iter())
.map(|v| match v {
ImportSpecifier::Named(v) => v.local.to_id(),
ImportSpecifier::Default(v) => v.local.to_id(),
ImportSpecifier::Namespace(v) => v.local.to_id(),
})
.collect(),
// exports: m
// .body
// .iter()
// .filter_map(|v| v.as_module_decl())
// .filter_map(|v| match v {
// ModuleDecl::ExportNamed(i) if i.src.is_none() => Some(i),
// _ => None,
// })
// .flat_map(|v| v.specifiers.iter())
// .filter_map(|v| match v {
// ExportSpecifier::Named(v) => Some(v),
// _ => None,
// })
// .filter_map(|v| match &v.orig {
// ModuleExportName::Ident(i) => Some(i.to_id()),
// ModuleExportName::Str(_) => None,
// })
// .collect(),
},
};
if let Some(options) = &options.compress {
let _timer = timer!("precompress");
m.visit_mut_with(&mut precompress_optimizer(&module_info, options, marks));
}
if options.compress.is_some() {
m.visit_mut_with(&mut info_marker(
options.compress.as_ref(),
comments,
marks,
extra.unresolved_mark,
));
}
m.visit_mut_with(&mut unique_scope());
if options.wrap {
// TODO: wrap_common_js
// toplevel = toplevel.wrap_commonjs(options.wrap);
}
if options.enclose {
// TODO: enclose
// toplevel = toplevel.wrap_enclose(options.enclose);
}
// We don't need validation.
if let Some(ref mut _t) = timings {
// TODO: store `rename`
}
// Noop.
// https://github.com/mishoo/UglifyJS2/issues/2794
if options.rename && DISABLE_BUGGY_PASSES {
// toplevel.figure_out_scope(options.mangle);
// TODO: Pass `options.mangle` to name expander.
m.visit_mut_with(&mut name_expander());
}
if let Some(ref mut t) = timings {
t.section("compress");
}
if let Some(options) = &options.compress {
{
let _timer = timer!("compress ast");
GLOBALS.with(|globals| {
m.visit_mut_with(&mut compressor(
globals,
&module_info,
marks,
options,
&Minification,
))
});
if options.wrap {
// TODO: wrap_common_js
// toplevel = toplevel.wrap_commonjs(options.wrap);
}
// Again, we don't need to validate ast
if options.enclose {
// TODO: enclose
// toplevel = toplevel.wrap_enclose(options.enclose);
}
let _timer = timer!("postcompress");
// We don't need validation.
m.visit_mut_with(&mut postcompress_optimizer(options));
m.visit_mut_with(&mut Repeat::new(pure_optimizer(
options,
None,
marks,
PureOptimizerConfig {
force_str_for_tpl: Minification::force_str_for_tpl(),
enable_join_vars: true,
#[cfg(feature = "debug")]
debug_infinite_loop: false,
},
)));
}
if let Some(ref mut _t) = timings {
// TODO: store `rename`
}
if let Some(ref mut _t) = timings {
// TODO: store `scope`
}
if options.mangle.is_some() {
// toplevel.figure_out_scope(options.mangle);
}
// Noop.
// https://github.com/mishoo/UglifyJS2/issues/2794
if options.rename && DISABLE_BUGGY_PASSES {
// toplevel.figure_out_scope(options.mangle);
// TODO: Pass `options.mangle` to name expander.
m.visit_mut_with(&mut name_expander());
}
if let Some(ref mut t) = timings {
t.section("mangle");
}
if let Some(ref mut t) = timings {
t.section("compress");
}
if let Some(options) = &options.compress {
{
let _timer = timer!("compress ast");
if let Some(mangle) = &options.mangle {
let _timer = timer!("mangle names");
// TODO: base54.reset();
GLOBALS.with(|globals| {
m.visit_mut_with(&mut compressor(
globals,
&module_info,
marks,
options,
&Minification,
))
});
}
m.visit_mut_with(&mut name_mangler(mangle.clone(), &m));
}
// Again, we don't need to validate ast
if let Some(property_mangle_options) = options.mangle.as_ref().and_then(|o| o.props.as_ref()) {
mangle_properties(&mut m, &module_info, property_mangle_options.clone());
}
let _timer = timer!("postcompress");
m.visit_mut_with(&mut merge_exports());
m.visit_mut_with(&mut postcompress_optimizer(options));
m.visit_mut_with(&mut Repeat::new(pure_optimizer(
options,
None,
marks,
PureOptimizerConfig {
force_str_for_tpl: Minification::force_str_for_tpl(),
enable_join_vars: true,
#[cfg(feature = "debug")]
debug_infinite_loop: false,
},
)));
}
if let Some(ref mut t) = timings {
t.section("hygiene");
t.end_section();
}
if let Some(ref mut _t) = timings {
// TODO: store `scope`
}
if options.mangle.is_some() {
// toplevel.figure_out_scope(options.mangle);
}
m
if let Some(ref mut t) = timings {
t.section("mangle");
}
if let Some(mangle) = &options.mangle {
let _timer = timer!("mangle names");
// TODO: base54.reset();
m.visit_mut_with(&mut name_mangler(mangle.clone(), &m));
}
if let Some(property_mangle_options) =
options.mangle.as_ref().and_then(|o| o.props.as_ref())
{
mangle_properties(&mut m, &module_info, property_mangle_options.clone());
}
m.visit_mut_with(&mut merge_exports());
if let Some(ref mut t) = timings {
t.section("hygiene");
t.end_section();
}
m
})
}

View File

@ -16,8 +16,11 @@ use anyhow::Error;
use once_cell::sync::Lazy;
use serde::Deserialize;
use swc_common::{
comments::SingleThreadedComments, errors::Handler, sync::Lrc, util::take::Take, EqIgnoreSpan,
FileName, Mark, SourceMap, Spanned,
comments::SingleThreadedComments,
errors::{Handler, HANDLER},
sync::Lrc,
util::take::Take,
EqIgnoreSpan, FileName, Mark, SourceMap, Spanned,
};
use swc_ecma_ast::*;
use swc_ecma_codegen::{
@ -140,120 +143,118 @@ fn run(
mangle: Option<TestMangleOptions>,
skip_hygiene: bool,
) -> Option<Module> {
let _ = rayon::ThreadPoolBuilder::new()
.thread_name(|i| format!("rayon-{}", i + 1))
.build_global();
HANDLER.set(handler, || {
let disable_hygiene = mangle.is_some() || skip_hygiene;
let disable_hygiene = mangle.is_some() || skip_hygiene;
let (_module, config) = parse_compressor_config(cm.clone(), config);
let (_module, config) = parse_compressor_config(cm.clone(), config);
let fm = cm.load_file(input).expect("failed to load input.js");
let comments = SingleThreadedComments::default();
let fm = cm.load_file(input).expect("failed to load input.js");
let comments = SingleThreadedComments::default();
eprintln!("---- {} -----\n{}", Color::Green.paint("Input"), fm.src);
eprintln!("---- {} -----\n{}", Color::Green.paint("Input"), fm.src);
if env::var("SWC_RUN").unwrap_or_default() == "1" {
let stdout = stdout_of(&fm.src);
match stdout {
Ok(stdout) => {
eprintln!(
"---- {} -----\n{}",
Color::Green.paint("Stdout (expected)"),
stdout
);
}
Err(err) => {
eprintln!(
"---- {} -----\n{:?}",
Color::Green.paint("Error (of original source code)"),
err
);
if env::var("SWC_RUN").unwrap_or_default() == "1" {
let stdout = stdout_of(&fm.src);
match stdout {
Ok(stdout) => {
eprintln!(
"---- {} -----\n{}",
Color::Green.paint("Stdout (expected)"),
stdout
);
}
Err(err) => {
eprintln!(
"---- {} -----\n{:?}",
Color::Green.paint("Error (of original source code)"),
err
);
}
}
}
}
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
let minification_start = Instant::now();
let minification_start = Instant::now();
let lexer = Lexer::new(
Syntax::Es(EsConfig {
jsx: true,
..Default::default()
}),
Default::default(),
SourceFileInput::from(&*fm),
Some(&comments),
);
let mut parser = Parser::new_from(lexer);
let program = parser
.parse_module()
.map_err(|err| {
err.into_diagnostic(handler).emit();
})
.map(|module| module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false)));
// Ignore parser errors.
//
// This is typically related to strict mode caused by module context.
let program = match program {
Ok(v) => v,
_ => return None,
};
let optimization_start = Instant::now();
let mut output = optimize(
program.into(),
cm,
Some(&comments),
None,
&MinifyOptions {
compress: Some(config),
mangle: mangle.and_then(|v| match v {
TestMangleOptions::Bool(v) => {
if v {
Some(MangleOptions {
top_level: false,
..Default::default()
})
} else {
None
}
}
TestMangleOptions::Normal(v) => Some(v),
let lexer = Lexer::new(
Syntax::Es(EsConfig {
jsx: true,
..Default::default()
}),
..Default::default()
},
&ExtraOptions {
unresolved_mark,
top_level_mark,
},
)
.expect_module();
let end = Instant::now();
tracing::info!(
"optimize({}) took {:?}",
input.display(),
end - optimization_start
);
Default::default(),
SourceFileInput::from(&*fm),
Some(&comments),
);
if !disable_hygiene {
output.visit_mut_with(&mut hygiene())
}
let mut parser = Parser::new_from(lexer);
let program = parser
.parse_module()
.map_err(|err| {
err.into_diagnostic(handler).emit();
})
.map(|module| module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false)));
let output = output.fold_with(&mut fixer(None));
// Ignore parser errors.
//
// This is typically related to strict mode caused by module context.
let program = match program {
Ok(v) => v,
_ => return None,
};
let end = Instant::now();
tracing::info!(
"process({}) took {:?}",
input.display(),
end - minification_start
);
let optimization_start = Instant::now();
let mut output = optimize(
program.into(),
cm,
Some(&comments),
None,
&MinifyOptions {
compress: Some(config),
mangle: mangle.and_then(|v| match v {
TestMangleOptions::Bool(v) => {
if v {
Some(MangleOptions {
top_level: false,
..Default::default()
})
} else {
None
}
}
TestMangleOptions::Normal(v) => Some(v),
}),
..Default::default()
},
&ExtraOptions {
unresolved_mark,
top_level_mark,
},
)
.expect_module();
let end = Instant::now();
tracing::info!(
"optimize({}) took {:?}",
input.display(),
end - optimization_start
);
Some(output)
if !disable_hygiene {
output.visit_mut_with(&mut hygiene())
}
let output = output.fold_with(&mut fixer(None));
let end = Instant::now();
tracing::info!(
"process({}) took {:?}",
input.display(),
end - minification_start
);
Some(output)
})
}
fn stdout_of(code: &str) -> Result<String, Error> {

View File

@ -6,7 +6,10 @@ use ansi_term::Color;
use anyhow::Error;
use serde::Deserialize;
use swc_common::{
comments::SingleThreadedComments, errors::Handler, sync::Lrc, FileName, Mark, SourceMap,
comments::SingleThreadedComments,
errors::{Handler, HANDLER},
sync::Lrc,
FileName, Mark, SourceMap,
};
use swc_ecma_ast::Module;
use swc_ecma_codegen::{
@ -174,27 +177,29 @@ fn run_exec_test(input_src: &str, config: &str, skip_mangle: bool) {
);
testing::run_test2(false, |cm, handler| {
let _tracing = span!(Level::ERROR, "compress-only").entered();
HANDLER.set(&handler, || {
let _tracing = span!(Level::ERROR, "compress-only").entered();
let output = run(cm.clone(), &handler, input_src, Some(config), None);
let output = output.expect("Parsing in base test should not fail");
let output = print(cm, &[output], false, false);
let output = run(cm.clone(), &handler, input_src, Some(config), None);
let output = output.expect("Parsing in base test should not fail");
let output = print(cm, &[output], false, false);
eprintln!(
"---- {} -----\n{}",
Color::Green.paint("Optimized code"),
output
);
eprintln!(
"---- {} -----\n{}",
Color::Green.paint("Optimized code"),
output
);
let actual_output = stdout_of(&output).expect("failed to execute the optimized code");
assert_ne!(actual_output, "");
let actual_output = stdout_of(&output).expect("failed to execute the optimized code");
assert_ne!(actual_output, "");
assert_eq!(
DebugUsingDisplay(&actual_output),
DebugUsingDisplay(&expected_output)
);
assert_eq!(
DebugUsingDisplay(&actual_output),
DebugUsingDisplay(&expected_output)
);
Ok(())
Ok(())
})
})
.unwrap();

View File

@ -13,7 +13,12 @@ use std::{
use ansi_term::Color;
use anyhow::{anyhow, bail, Context, Error};
use serde::Deserialize;
use swc_common::{comments::SingleThreadedComments, errors::Handler, sync::Lrc, Mark, SourceMap};
use swc_common::{
comments::SingleThreadedComments,
errors::{Handler, HANDLER},
sync::Lrc,
Mark, SourceMap,
};
use swc_ecma_ast::*;
use swc_ecma_codegen::{
text_writer::{omit_trailing_semi, JsWriter, WriteJs},
@ -160,87 +165,85 @@ fn parse_compressor_config(cm: Lrc<SourceMap>, s: &str) -> Result<(bool, Compres
}
fn run(cm: Lrc<SourceMap>, handler: &Handler, input: &Path, config: &str) -> Option<Module> {
let _ = rayon::ThreadPoolBuilder::new()
.thread_name(|i| format!("rayon-{}", i + 1))
.build_global();
HANDLER.set(handler, || {
let (_module, config) = parse_compressor_config(cm.clone(), config).ok()?;
if config.ie8 {
return None;
}
let (_module, config) = parse_compressor_config(cm.clone(), config).ok()?;
if config.ie8 {
return None;
}
let fm = cm.load_file(input).expect("failed to load input.js");
let comments = SingleThreadedComments::default();
let fm = cm.load_file(input).expect("failed to load input.js");
let comments = SingleThreadedComments::default();
eprintln!("---- {} -----\n{}", Color::Green.paint("Input"), fm.src);
eprintln!("---- {} -----\n{}", Color::Green.paint("Input"), fm.src);
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
let minification_start = Instant::now();
let minification_start = Instant::now();
let lexer = Lexer::new(
Syntax::Es(EsConfig {
jsx: true,
..Default::default()
}),
Default::default(),
SourceFileInput::from(&*fm),
Some(&comments),
);
let lexer = Lexer::new(
Syntax::Es(EsConfig {
jsx: true,
..Default::default()
}),
Default::default(),
SourceFileInput::from(&*fm),
Some(&comments),
);
let mut parser = Parser::new_from(lexer);
let program = parser
.parse_module()
.map_err(|err| {
err.into_diagnostic(handler).emit();
})
.map(|module| module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false)));
let mut parser = Parser::new_from(lexer);
let program = parser
.parse_module()
.map_err(|err| {
err.into_diagnostic(handler).emit();
})
.map(|module| module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false)));
// Ignore parser errors.
//
// This is typically related to strict mode caused by module context.
let program = match program {
Ok(v) => v,
_ => return None,
};
// Ignore parser errors.
//
// This is typically related to strict mode caused by module context.
let program = match program {
Ok(v) => v,
_ => return None,
};
let optimization_start = Instant::now();
let mut output = optimize(
program.into(),
cm,
Some(&comments),
None,
&MinifyOptions {
compress: Some(config),
mangle: None,
..Default::default()
},
&ExtraOptions {
unresolved_mark,
top_level_mark,
},
)
.expect_module();
let end = Instant::now();
tracing::info!(
"optimize({}) took {:?}",
input.display(),
end - optimization_start
);
let optimization_start = Instant::now();
let mut output = optimize(
program.into(),
cm,
Some(&comments),
None,
&MinifyOptions {
compress: Some(config),
mangle: None,
..Default::default()
},
&ExtraOptions {
unresolved_mark,
top_level_mark,
},
)
.expect_module();
let end = Instant::now();
tracing::info!(
"optimize({}) took {:?}",
input.display(),
end - optimization_start
);
output.visit_mut_with(&mut hygiene());
output.visit_mut_with(&mut hygiene());
let output = output.fold_with(&mut fixer(None));
let output = output.fold_with(&mut fixer(None));
let end = Instant::now();
tracing::info!(
"process({}) took {:?}",
input.display(),
end - minification_start
);
let end = Instant::now();
tracing::info!(
"process({}) took {:?}",
input.display(),
end - minification_start
);
Some(output)
Some(output)
})
}
fn stdout_of(code: &str, timeout: Duration) -> Result<String, Error> {

View File

@ -69,7 +69,7 @@ where
where
N: Send + Sync + VisitWith<Self>,
{
if nodes.len() >= threshold {
if nodes.len() >= threshold || option_env!("SWC_FORCE_CONCURRENT") == Some("1") {
GLOBALS.with(|globals| {
HELPERS.with(|helpers| {
HANDLER.with(|handler| {
@ -127,7 +127,7 @@ where
where
N: Send + Sync + VisitMutWith<Self>,
{
if nodes.len() >= threshold {
if nodes.len() >= threshold || option_env!("SWC_FORCE_CONCURRENT") == Some("1") {
GLOBALS.with(|globals| {
HELPERS.with(|helpers| {
HANDLER.with(|handler| {
@ -185,7 +185,7 @@ where
where
N: Send + Sync + FoldWith<Self>,
{
if nodes.len() >= threshold {
if nodes.len() >= threshold || option_env!("SWC_FORCE_CONCURRENT") == Some("1") {
use rayon::prelude::*;
let (visitor, nodes) = GLOBALS.with(|globals| {

View File

@ -359,58 +359,43 @@ impl<'a> VisitMut for Operator<'a> {
#[cfg(feature = "concurrent")]
if nodes.len() >= 64 * cpu_count() {
use swc_common::errors::HANDLER;
use crate::helpers::HELPERS;
::swc_common::GLOBALS.with(|globals| {
HELPERS.with(|helpers| {
HANDLER.with(|handler| {
use rayon::prelude::*;
use rayon::prelude::*;
let (visitor, new_nodes) = take(nodes)
.into_par_iter()
.map(|mut node| {
::swc_common::GLOBALS.set(globals, || {
HELPERS.set(helpers, || {
HANDLER.set(handler, || {
let mut visitor = Parallel::create(&*self);
node.visit_mut_with(&mut visitor);
let (visitor, new_nodes) = take(nodes)
.into_par_iter()
.map(|mut node| {
::swc_common::GLOBALS.set(globals, || {
let mut visitor = Parallel::create(&*self);
node.visit_mut_with(&mut visitor);
let mut nodes = Vec::with_capacity(4);
let mut nodes = Vec::with_capacity(4);
ParExplode::after_one_module_item(
&mut visitor,
&mut nodes,
);
ParExplode::after_one_module_item(&mut visitor, &mut nodes);
nodes.push(node);
nodes.push(node);
(visitor, nodes)
})
})
})
})
.reduce(
|| (Parallel::create(&*self), vec![]),
|mut a, b| {
Parallel::merge(&mut a.0, b.0);
a.1.extend(b.1);
a
},
);
Parallel::merge(self, visitor);
{
self.after_module_items(nodes);
}
*nodes = new_nodes;
(visitor, nodes)
})
})
})
.reduce(
|| (Parallel::create(&*self), vec![]),
|mut a, b| {
Parallel::merge(&mut a.0, b.0);
a.1.extend(b.1);
a
},
);
Parallel::merge(self, visitor);
{
self.after_module_items(nodes);
}
*nodes = new_nodes;
});
return;

View File

@ -2,7 +2,6 @@ use swc_atoms::JsWord;
use swc_common::{collections::AHashSet, Spanned};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Parallel;
use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::quote_str;
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
use swc_trace_macro::swc_trace;
@ -22,7 +21,6 @@ impl Parallel for DuplicateKeys {
}
#[swc_trace]
#[parallel]
impl VisitMut for DuplicateKeys {
noop_visit_mut_type!();

View File

@ -1,7 +1,6 @@
use swc_common::util::take::Take;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Parallel;
use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::{private_ident, IdentUsageFinder};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
use swc_trace_macro::swc_trace;
@ -53,7 +52,6 @@ fn prepare(i: Ident) -> Ident {
}
#[swc_trace]
#[parallel]
impl VisitMut for FnName {
noop_visit_mut_type!();

View File

@ -1,7 +1,6 @@
use swc_common::{util::take::Take, Span, Spanned};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::{helper, perf::Parallel};
use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::ExprFactory;
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
use swc_trace_macro::swc_trace;
@ -46,7 +45,6 @@ impl Parallel for InstanceOf {
}
#[swc_trace]
#[parallel]
impl VisitMut for InstanceOf {
noop_visit_mut_type!();

View File

@ -1,7 +1,6 @@
use swc_common::util::take::Take;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Parallel;
use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::quote_ident;
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
use swc_trace_macro::swc_trace;
@ -57,7 +56,6 @@ impl Parallel for Shorthand {
}
#[swc_trace]
#[parallel]
impl VisitMut for Shorthand {
noop_visit_mut_type!();

View File

@ -1,6 +1,5 @@
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Parallel;
use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::{quote_ident, ExprFactory};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
use swc_trace_macro::swc_trace;
@ -35,7 +34,6 @@ impl Parallel for StickyRegex {
}
#[swc_trace]
#[parallel]
impl VisitMut for StickyRegex {
noop_visit_mut_type!();

View File

@ -5,7 +5,6 @@ use swc_atoms::{js_word, JsWord};
use swc_common::{util::take::Take, BytePos, Spanned, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::{helper, perf::Parallel};
use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::{
is_literal, prepend_stmts, private_ident, quote_ident, undefined, ExprFactory, StmtLike,
};
@ -46,7 +45,6 @@ impl Parallel for TemplateLiteral {
}
#[swc_trace]
#[parallel]
impl VisitMut for TemplateLiteral {
noop_visit_mut_type!();

View File

@ -2,7 +2,6 @@ use swc_atoms::js_word;
use swc_common::{util::take::Take, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::{helper, perf::Parallel};
use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::{quote_str, ExprFactory};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
use swc_trace_macro::swc_trace;
@ -25,7 +24,6 @@ impl Parallel for TypeOfSymbol {
}
#[swc_trace]
#[parallel]
impl VisitMut for TypeOfSymbol {
noop_visit_mut_type!();

View File

@ -10,7 +10,7 @@ use swc_ecma_transforms_base::{
helper, helper_expr,
perf::{Check, Parallel},
};
use swc_ecma_transforms_macros::{fast_path, parallel};
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::{
alias_ident_for, alias_if_required, is_literal, private_ident, quote_ident, var::VarCollector,
ExprFactory, StmtLike,
@ -1084,7 +1084,6 @@ impl Parallel for ObjectSpread {
}
#[swc_trace]
#[parallel]
impl VisitMut for ObjectSpread {
noop_visit_mut_type!();

View File

@ -1,7 +1,6 @@
use swc_common::{util::take::Take, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Parallel;
use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::{alias_ident_for, prepend_stmt};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
use swc_trace_macro::swc_trace;
@ -57,7 +56,6 @@ impl Parallel for Operators {
}
#[swc_trace]
#[parallel]
impl VisitMut for Operators {
noop_visit_mut_type!();

View File

@ -1,7 +1,6 @@
use swc_atoms::{js_word, JsWord};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Parallel;
use swc_ecma_transforms_macros::parallel;
use swc_ecma_visit::{
as_folder, noop_visit_mut_type, visit_mut_obj_and_computed, Fold, VisitMut, VisitMutWith,
};
@ -21,7 +20,6 @@ impl Parallel for EsReservedWord {
fn merge(&mut self, _: Self) {}
}
#[parallel]
impl VisitMut for EsReservedWord {
noop_visit_mut_type!();

View File

@ -115,7 +115,8 @@ fn make_par_visit_method(
use swc_ecma_visit::FoldWith;
#[cfg(feature = "rayon")]
if nodes.len() >= threshold {
if nodes.len() >= threshold || option_env!("SWC_FORCE_CONCURRENT") == Some("1")
{
use rayon::prelude::*;
let (visitor, mut nodes) = ::swc_common::GLOBALS.with(|globals| {
@ -204,7 +205,8 @@ fn make_par_visit_method(
use swc_ecma_visit::FoldWith;
#[cfg(feature = "rayon")]
if nodes.len() >= threshold {
if nodes.len() >= threshold || option_env!("SWC_FORCE_CONCURRENT") == Some("1")
{
use rayon::prelude::*;
let (visitor, mut nodes) = ::swc_common::GLOBALS.with(|globals| {
@ -289,7 +291,8 @@ fn make_par_visit_method(
use swc_ecma_visit::VisitMutWith;
#[cfg(feature = "rayon")]
if nodes.len() >= threshold {
if nodes.len() >= threshold || option_env!("SWC_FORCE_CONCURRENT") == Some("1")
{
::swc_common::GLOBALS.with(|globals| {
swc_ecma_transforms_base::helpers::HELPERS.with(|helpers| {
HANDLER.with(|handler| {
@ -380,7 +383,8 @@ fn make_par_visit_method(
use swc_ecma_visit::VisitMutWith;
#[cfg(feature = "rayon")]
if nodes.len() >= threshold {
if nodes.len() >= threshold || option_env!("SWC_FORCE_CONCURRENT") == Some("1")
{
::swc_common::GLOBALS.with(|globals| {
swc_ecma_transforms_base::helpers::HELPERS.with(|helpers| {
HANDLER.with(|handler| {

View File

@ -14,8 +14,12 @@ version = "0.158.0"
bench = false
[features]
concurrent = ["swc_common/concurrent", "rayon"]
debug = []
concurrent = [
"swc_common/concurrent",
"swc_ecma_transforms_base/concurrent",
"rayon",
]
debug = []
[dependencies]
ahash = "0.7.4"

View File

@ -1,7 +1,6 @@
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Parallel;
use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::quote_ident;
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut};
@ -28,7 +27,6 @@ impl Parallel for JsxSelf {
fn merge(&mut self, _: Self) {}
}
#[parallel]
impl VisitMut for JsxSelf {
noop_visit_mut_type!();

View File

@ -1,7 +1,6 @@
use swc_common::{sync::Lrc, SourceMap, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Parallel;
use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::quote_ident;
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut};
@ -27,7 +26,6 @@ impl Parallel for JsxSrc {
fn merge(&mut self, _: Self) {}
}
#[parallel]
impl VisitMut for JsxSrc {
noop_visit_mut_type!();

View File

@ -14,5 +14,5 @@ if [[ "swc" == "$1" ]]; then
fi
if [[ $CRATES == *"$1"* ]]; then
cargo test --color always -p $1 --features "$1/concurrent"
cargo test --color always -p $1 --all-targets --features "$1/concurrent"
fi