mirror of
https://github.com/swc-project/swc.git
synced 2024-12-29 00:23:10 +03:00
296 lines
8.9 KiB
Rust
296 lines
8.9 KiB
Rust
//! Javascript minifier implemented in rust.
|
|
//!
|
|
//! # Debugging
|
|
//!
|
|
//! In debug build, if you set an environment variable `SWC_CHECK` to `1`, the
|
|
//! minifier will check the validity of the syntax using `node --check`
|
|
//!
|
|
//! # Cargo features
|
|
//!
|
|
//! ## `debug`
|
|
//!
|
|
//! 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
|
|
//! step.
|
|
#![deny(clippy::all)]
|
|
#![allow(clippy::blocks_in_if_conditions)]
|
|
#![allow(clippy::collapsible_else_if)]
|
|
#![allow(clippy::collapsible_if)]
|
|
#![allow(clippy::ptr_arg)]
|
|
#![allow(clippy::vec_box)]
|
|
#![allow(clippy::logic_bug)]
|
|
#![allow(unstable_name_collisions)]
|
|
#![allow(clippy::match_like_matches_macro)]
|
|
|
|
use once_cell::sync::Lazy;
|
|
use swc_common::{
|
|
comments::Comments,
|
|
pass::{Repeat, Repeated},
|
|
sync::Lrc,
|
|
SourceMap, SyntaxContext,
|
|
};
|
|
use swc_ecma_ast::*;
|
|
use swc_ecma_transforms_optimization::debug_assert_valid;
|
|
use swc_ecma_visit::VisitMutWith;
|
|
use swc_timer::timer;
|
|
|
|
pub use self::analyzer::dump_snapshot;
|
|
pub use crate::pass::unique_scope::unique_scope;
|
|
use crate::{
|
|
analyzer::ModuleInfo,
|
|
compress::{compressor, pure_optimizer, PureOptimizerConfig},
|
|
marks::Marks,
|
|
metadata::info_marker,
|
|
mode::{Minification, Mode},
|
|
option::{ExtraOptions, MinifyOptions},
|
|
pass::{
|
|
expand_names::name_expander, global_defs, mangle_names::name_mangler,
|
|
mangle_props::mangle_properties, merge_exports::merge_exports,
|
|
postcompress::postcompress_optimizer, precompress::precompress_optimizer,
|
|
},
|
|
timing::Timings,
|
|
};
|
|
|
|
#[macro_use]
|
|
mod macros;
|
|
mod alias;
|
|
mod analyzer;
|
|
mod compress;
|
|
mod debug;
|
|
pub mod eval;
|
|
pub mod marks;
|
|
mod metadata;
|
|
mod mode;
|
|
pub mod option;
|
|
mod pass;
|
|
pub mod timing;
|
|
mod util;
|
|
|
|
const DISABLE_BUGGY_PASSES: bool = true;
|
|
|
|
pub(crate) static CPU_COUNT: Lazy<usize> = Lazy::new(num_cpus::get);
|
|
pub(crate) static HEAVY_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 8);
|
|
pub(crate) static LIGHT_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 100);
|
|
|
|
pub fn optimize(
|
|
mut m: Program,
|
|
_cm: Lrc<SourceMap>,
|
|
comments: Option<&dyn Comments>,
|
|
mut timings: Option<&mut Timings>,
|
|
options: &MinifyOptions,
|
|
extra: &ExtraOptions,
|
|
) -> Program {
|
|
let _timer = timer!("minify");
|
|
|
|
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 !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());
|
|
}
|
|
|
|
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);
|
|
}
|
|
if let Some(ref mut t) = timings {
|
|
t.section("compress");
|
|
}
|
|
if let Some(options) = &options.compress {
|
|
{
|
|
let _timer = timer!("compress ast");
|
|
{
|
|
if options.unused {
|
|
let _timer = timer!("remove dead code");
|
|
|
|
let mut visitor = swc_ecma_transforms_optimization::simplify::dce::dce(
|
|
swc_ecma_transforms_optimization::simplify::dce::Config {
|
|
module_mark: None,
|
|
top_level: options.top_level(),
|
|
top_retain: options.top_retain.clone(),
|
|
},
|
|
extra.unresolved_mark,
|
|
);
|
|
|
|
loop {
|
|
#[cfg(feature = "debug")]
|
|
let start = crate::debug::dump(&m, false);
|
|
|
|
m.visit_mut_with(&mut visitor);
|
|
|
|
#[cfg(feature = "debug")]
|
|
if visitor.changed() {
|
|
let src = crate::debug::dump(&m, false);
|
|
tracing::debug!(
|
|
"===== Before DCE =====\n{}\n===== After DCE =====\n{}",
|
|
start,
|
|
src
|
|
);
|
|
}
|
|
|
|
if !visitor.changed() {
|
|
break;
|
|
}
|
|
|
|
visitor.reset();
|
|
}
|
|
|
|
debug_assert_valid(&m);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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");
|
|
|
|
m.visit_mut_with(&mut compressor(&module_info, marks, options, &Minification))
|
|
}
|
|
|
|
// Again, we don't need to validate ast
|
|
|
|
let _timer = timer!("postcompress");
|
|
|
|
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 `scope`
|
|
}
|
|
if options.mangle.is_some() {
|
|
// toplevel.figure_out_scope(options.mangle);
|
|
}
|
|
|
|
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,
|
|
SyntaxContext::empty().apply_mark(extra.unresolved_mark),
|
|
));
|
|
}
|
|
|
|
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
|
|
}
|