refactor(es/minifier): Use swc_timer (#3087)

swc_bundler:
 - Add `Config.disable_fixer`.
 - Add `Config.disable_hygiene`.

swc_ecma_minifier:
 - Use `swc_timer`.
This commit is contained in:
Donny/강동윤 2021-12-21 18:19:33 +09:00 committed by GitHub
parent f1b81266cf
commit 0e4dce694d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 112 additions and 35 deletions

1
Cargo.lock generated
View File

@ -2935,6 +2935,7 @@ dependencies = [
"swc_ecma_utils", "swc_ecma_utils",
"swc_ecma_visit", "swc_ecma_visit",
"swc_node_base", "swc_node_base",
"swc_timer",
"testing", "testing",
"tracing", "tracing",
"unicode-xid", "unicode-xid",

View File

@ -1,4 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -eu set -eu
cargo profile instruments --release -t time --bench typescript --features concurrent,tracing/release_max_level_info $@ cargo profile instruments --release -t time --bench typescript --features concurrent,tracing/release_max_level_info $@
# MINIFY=1 cargo profile instruments --release -t time --bench typescript --features concurrent,tracing/release_max_level_info $@

View File

@ -7,36 +7,50 @@ use std::{
collections::HashMap, collections::HashMap,
env, fs, env, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
time::Instant, time::{Duration, Instant},
}; };
use swc_atoms::js_word; use swc_atoms::js_word;
use swc_bundler::{Bundle, Bundler, Load, ModuleData, ModuleRecord, Resolve}; use swc_bundler::{Bundle, Bundler, Load, ModuleData, ModuleRecord, Resolve};
use swc_common::{ use swc_common::{
errors::{ColorConfig, Handler}, errors::{ColorConfig, Handler},
sync::Lrc, sync::Lrc,
FileName, Globals, SourceMap, Span, FileName, Globals, Mark, SourceMap, Span, GLOBALS,
}; };
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; use swc_ecma_codegen::{
text_writer::{omit_trailing_semi, JsWriter, WriteJs},
Emitter,
};
use swc_ecma_loader::{ use swc_ecma_loader::{
resolvers::{lru::CachingResolver, node::NodeModulesResolver}, resolvers::{lru::CachingResolver, node::NodeModulesResolver},
TargetEnv, TargetEnv,
}; };
use swc_ecma_minifier::option::{
CompressOptions, ExtraOptions, MangleOptions, MinifyOptions, TopLevelOptions,
};
use swc_ecma_parser::{lexer::Lexer, EsConfig, Parser, StringInput, Syntax}; use swc_ecma_parser::{lexer::Lexer, EsConfig, Parser, StringInput, Syntax};
use swc_ecma_transforms_base::fixer::fixer;
use swc_ecma_visit::VisitMutWith;
fn print_bundles(cm: Lrc<SourceMap>, modules: Vec<Bundle>) { fn print_bundles(cm: Lrc<SourceMap>, modules: Vec<Bundle>, minify: bool) {
for bundled in modules { for bundled in modules {
let code = { let code = {
let mut buf = vec![]; let mut buf = vec![];
{ {
let wr = JsWriter::new(cm.clone(), "\n", &mut buf, None);
let mut emitter = Emitter { let mut emitter = Emitter {
cfg: swc_ecma_codegen::Config { cfg: swc_ecma_codegen::Config {
minify,
..Default::default() ..Default::default()
}, },
cm: cm.clone(), cm: cm.clone(),
comments: None, comments: None,
wr: JsWriter::new(cm.clone(), "\n", &mut buf, None), wr: if minify {
Box::new(omit_trailing_semi(wr)) as Box<dyn WriteJs>
} else {
Box::new(wr) as Box<dyn WriteJs>
},
}; };
emitter.emit_module(&bundled.module).unwrap(); emitter.emit_module(&bundled.module).unwrap();
@ -48,11 +62,12 @@ fn print_bundles(cm: Lrc<SourceMap>, modules: Vec<Bundle>) {
#[cfg(feature = "concurrent")] #[cfg(feature = "concurrent")]
rayon::spawn(move || drop(bundled)); rayon::spawn(move || drop(bundled));
println!("Created output.js ({}kb)", code.len() / 1024);
fs::write("output.js", &code).unwrap(); fs::write("output.js", &code).unwrap();
} }
} }
fn do_test(_entry: &Path, entries: HashMap<String, FileName>, inline: bool) { fn do_test(_entry: &Path, entries: HashMap<String, FileName>, inline: bool, minify: bool) {
testing::run_test2(false, |cm, _| { testing::run_test2(false, |cm, _| {
let start = Instant::now(); let start = Instant::now();
@ -69,12 +84,14 @@ fn do_test(_entry: &Path, entries: HashMap<String, FileName>, inline: bool) {
require: true, require: true,
disable_inliner: !inline, disable_inliner: !inline,
external_modules: Default::default(), external_modules: Default::default(),
disable_fixer: minify,
disable_hygiene: minify,
module: Default::default(), module: Default::default(),
}, },
Box::new(Hook), Box::new(Hook),
); );
let modules = bundler let mut modules = bundler
.bundle(entries) .bundle(entries)
.map_err(|err| println!("{:?}", err))?; .map_err(|err| println!("{:?}", err))?;
println!("Bundled as {} modules", modules.len()); println!("Bundled as {} modules", modules.len());
@ -86,14 +103,50 @@ fn do_test(_entry: &Path, entries: HashMap<String, FileName>, inline: bool) {
{ {
let dur = start.elapsed(); let dur = start.elapsed();
println!("Bundler.bundle() took {:?}", dur); println!("Bundler.bundle() took {}", to_ms(dur));
} }
let error = false; let error = false;
if minify {
let start = Instant::now();
modules = modules
.into_iter()
.map(|mut b| {
GLOBALS.set(&globals, || {
b.module = swc_ecma_minifier::optimize(
b.module,
cm.clone(),
None,
None,
&MinifyOptions {
compress: Some(CompressOptions {
top_level: Some(TopLevelOptions { functions: true }),
..Default::default()
}),
mangle: Some(MangleOptions {
top_level: true,
..Default::default()
}),
..Default::default()
},
&ExtraOptions {
top_level_mark: Mark::fresh(Mark::root()),
},
);
b.module.visit_mut_with(&mut fixer(None));
b
})
})
.collect();
let dur = start.elapsed();
println!("Minification took {}", to_ms(dur));
}
{ {
let cm = cm.clone(); let cm = cm.clone();
print_bundles(cm, modules); print_bundles(cm, modules, minify);
} }
if error { if error {
@ -105,15 +158,21 @@ fn do_test(_entry: &Path, entries: HashMap<String, FileName>, inline: bool) {
.expect("failed to process a module"); .expect("failed to process a module");
} }
fn to_ms(dur: Duration) -> String {
format!("{}ms", dur.as_millis())
}
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
let minify = env::var("MINIFY").unwrap_or_else(|_| "0".to_string()) == "1";
let main_file = env::args().nth(1).unwrap(); let main_file = env::args().nth(1).unwrap();
let mut entries = HashMap::default(); let mut entries = HashMap::default();
entries.insert("main".to_string(), FileName::Real(main_file.clone().into())); entries.insert("main".to_string(), FileName::Real(main_file.clone().into()));
let start = Instant::now(); let start = Instant::now();
do_test(Path::new(&main_file), entries, false); do_test(Path::new(&main_file), entries, false, minify);
let dur = start.elapsed(); let dur = start.elapsed();
println!("Took {:?}", dur); println!("Took {}", to_ms(dur));
Ok(()) Ok(())
} }

View File

@ -32,11 +32,15 @@ where
for mut bundle in bundles { for mut bundle in bundles {
bundle.module = self.optimize(bundle.module); bundle.module = self.optimize(bundle.module);
bundle.module = bundle.module.fold_with(&mut hygiene()); if !self.config.disable_hygiene {
bundle.module = bundle.module.fold_with(&mut hygiene());
}
bundle.module = self.may_wrap_with_iife(bundle.module); bundle.module = self.may_wrap_with_iife(bundle.module);
bundle.module = bundle.module.fold_with(&mut fixer(None)); if !self.config.disable_fixer {
bundle.module = bundle.module.fold_with(&mut fixer(None));
}
{ {
// Inject swc helpers // Inject swc helpers

View File

@ -31,6 +31,11 @@ pub struct Config {
/// temporary variables, it's really hard to see what's going on. /// temporary variables, it's really hard to see what's going on.
pub disable_inliner: bool, pub disable_inliner: bool,
/// Useful if you are going to minify the code.
pub disable_hygiene: bool,
pub disable_fixer: bool,
/// List of modules which should be preserved. /// List of modules which should be preserved.
pub external_modules: Vec<JsWord>, pub external_modules: Vec<JsWord>,

View File

@ -132,6 +132,8 @@ impl TestBuilder {
Config { Config {
require: true, require: true,
disable_inliner: true, disable_inliner: true,
disable_hygiene: false,
disable_fixer: false,
external_modules: vec![], external_modules: vec![],
module: Default::default(), module: Default::default(),
}, },

View File

@ -35,6 +35,7 @@ fn do_test(entry: &Path, entries: HashMap<String, FileName>, inline: bool) {
disable_inliner: !inline, disable_inliner: !inline,
external_modules: NODE_BUILTINS.to_vec().into_iter().map(From::from).collect(), external_modules: NODE_BUILTINS.to_vec().into_iter().map(From::from).collect(),
module: Default::default(), module: Default::default(),
..Default::default()
}, },
Box::new(Hook), Box::new(Hook),
); );

View File

@ -37,6 +37,7 @@ swc_ecma_transforms = {version = "0.103.0", path = "../swc_ecma_transforms/", fe
swc_ecma_transforms_base = {version = "0.49.0", path = "../swc_ecma_transforms_base"} swc_ecma_transforms_base = {version = "0.49.0", path = "../swc_ecma_transforms_base"}
swc_ecma_utils = {version = "0.56.0", path = "../swc_ecma_utils"} swc_ecma_utils = {version = "0.56.0", path = "../swc_ecma_utils"}
swc_ecma_visit = {version = "0.46.0", path = "../swc_ecma_visit"} swc_ecma_visit = {version = "0.46.0", path = "../swc_ecma_visit"}
swc_timer = {version = "0.2.0", path = "../swc_timer"}
tracing = "0.1.28" tracing = "0.1.28"
unicode-xid = "0.2.2" unicode-xid = "0.2.2"

View File

@ -4,7 +4,7 @@
//! //!
//! ## `debug` //! ## `debug`
//! //!
//! If you enable this cargo feature and set the environemnt variable named //! If you enable this cargo feature and set the environment variable named
//! `SWC_RUN` to `1`, the minifier will validate the code using node before each //! `SWC_RUN` to `1`, the minifier will validate the code using node before each
//! step. //! step.
//! //!
@ -23,14 +23,13 @@ use crate::{
mangle_names::name_mangler, mangle_props::mangle_properties, mangle_names::name_mangler, mangle_props::mangle_properties,
precompress::precompress_optimizer, precompress::precompress_optimizer,
}, },
util::now,
}; };
use mode::Minification; use mode::Minification;
use pass::postcompress::postcompress_optimizer; use pass::postcompress::postcompress_optimizer;
use std::time::Instant;
use swc_common::{comments::Comments, sync::Lrc, SourceMap, GLOBALS}; use swc_common::{comments::Comments, sync::Lrc, SourceMap, GLOBALS};
use swc_ecma_ast::Module; use swc_ecma_ast::Module;
use swc_ecma_visit::{FoldWith, VisitMutWith}; use swc_ecma_visit::{FoldWith, VisitMutWith};
use swc_timer::timer;
use timing::Timings; use timing::Timings;
mod analyzer; mod analyzer;
@ -57,14 +56,16 @@ pub fn optimize(
options: &MinifyOptions, options: &MinifyOptions,
extra: &ExtraOptions, extra: &ExtraOptions,
) -> Module { ) -> Module {
let _timer = timer!("minify");
let marks = Marks::new(); let marks = Marks::new();
let start = now();
if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) { if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) {
let _timer = timer!("inline global defs");
// Apply global defs. // Apply global defs.
// //
// As terser treats `CONFIG['VALUE']` and `CONFIG.VALUE` differently, we don't // As terser treats `CONFIG['VALUE']` and `CONFIG.VALUE` differently, we don't
// have to see if optimized code matches global definition and wecan run // have to see if optimized code matches global definition and we can run
// this at startup. // this at startup.
if !defs.is_empty() { if !defs.is_empty() {
@ -72,16 +73,11 @@ pub fn optimize(
m.visit_mut_with(&mut global_defs::globals_defs(defs, extra.top_level_mark)); m.visit_mut_with(&mut global_defs::globals_defs(defs, extra.top_level_mark));
} }
} }
if let Some(start) = start {
tracing::info!("global_defs took {:?}", Instant::now() - start);
}
if let Some(options) = &options.compress { if let Some(options) = &options.compress {
let start = now(); let _timer = timer!("precompress");
m.visit_mut_with(&mut precompress_optimizer(options, marks)); m.visit_mut_with(&mut precompress_optimizer(options, marks));
if let Some(start) = start {
tracing::info!("precompress took {:?}", Instant::now() - start);
}
} }
m.visit_mut_with(&mut info_marker(comments, marks, extra.top_level_mark)); m.visit_mut_with(&mut info_marker(comments, marks, extra.top_level_mark));
@ -115,19 +111,19 @@ pub fn optimize(
t.section("compress"); t.section("compress");
} }
if let Some(options) = &options.compress { if let Some(options) = &options.compress {
let start = now(); {
m = GLOBALS let _timer = timer!("compress ast");
.with(|globals| m.fold_with(&mut compressor(globals, marks, &options, &Minification)));
if let Some(start) = start { m = GLOBALS.with(|globals| {
tracing::info!("compressor took {:?}", Instant::now() - start); m.fold_with(&mut compressor(globals, marks, &options, &Minification))
});
} }
// Again, we don't need to validate ast // Again, we don't need to validate ast
let start = now(); let _timer = timer!("postcompress");
m.visit_mut_with(&mut postcompress_optimizer(options)); m.visit_mut_with(&mut postcompress_optimizer(options));
if let Some(start) = start {
tracing::info!("postcompressor took {:?}", Instant::now() - start);
}
} }
if let Some(ref mut _t) = timings { if let Some(ref mut _t) = timings {
@ -142,6 +138,7 @@ pub fn optimize(
} }
if let Some(mangle) = &options.mangle { if let Some(mangle) = &options.mangle {
let _timer = timer!("mangle names");
// TODO: base54.reset(); // TODO: base54.reset();
let char_freq_info = compute_char_freq(&m); let char_freq_info = compute_char_freq(&m);

View File

@ -76,6 +76,7 @@ fn pass(input_dir: PathBuf) {
disable_inliner: true, disable_inliner: true,
module: Default::default(), module: Default::default(),
external_modules: NODE_BUILTINS.to_vec().into_iter().map(From::from).collect(), external_modules: NODE_BUILTINS.to_vec().into_iter().map(From::from).collect(),
..Default::default()
}, },
Box::new(Hook), Box::new(Hook),
); );

View File

@ -35,6 +35,10 @@ impl Drop for Timer {
/// Creates a timer. For input arguments, see [tracing::span]. /// Creates a timer. For input arguments, see [tracing::span].
/// ///
/// # Convention
///
/// The string passed to `timer!` should start with a verb.
///
/// # Example usage /// # Example usage
/// ///
/// ``` /// ```
@ -56,6 +60,7 @@ macro_rules! timer {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
let span = $crate::tracing::span!($crate::tracing::Level::INFO, $($args)*).entered(); let span = $crate::tracing::span!($crate::tracing::Level::INFO, $($args)*).entered();
#[cfg(not(target_arch = "wasm32"))]
$crate::Timer::new(span) $crate::Timer::new(span)
}}; }};
} }