feat(es/minifier): Implement static evaluator (#2176)

swc_ecma_minifier:
 - Add an api to evaluate constants statically.
This commit is contained in:
강동윤 2021-08-30 14:21:38 +09:00 committed by GitHub
parent ee16139a19
commit 11fe35dbd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1034 additions and 105 deletions

4
Cargo.lock generated
View File

@ -2628,7 +2628,7 @@ dependencies = [
[[package]]
name = "swc_ecma_minifier"
version = "0.23.0"
version = "0.23.1"
dependencies = [
"ansi_term 0.12.1",
"anyhow",
@ -3400,7 +3400,7 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm"
version = "1.2.83"
version = "1.2.84"
dependencies = [
"anyhow",
"console_error_panic_hook",

View File

@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs", "src/lists/*.json"]
license = "Apache-2.0/MIT"
name = "swc_ecma_minifier"
repository = "https://github.com/swc-project/swc.git"
version = "0.23.0"
version = "0.23.1"
[features]
debug = []

View File

@ -1,3 +1,4 @@
pub(crate) use self::pure::pure_optimizer;
use self::{
drop_console::drop_console,
hoist_decls::DeclHoisterConfig,
@ -5,9 +6,10 @@ use self::{
};
use crate::{
analyzer::{analyze, ProgramData, UsageAnalyzer},
compress::{hoist_decls::decl_hoister, pure::pure_optimizer},
compress::hoist_decls::decl_hoister,
debug::{dump, invoke},
marks::Marks,
mode::Mode,
option::CompressOptions,
util::{now, unit::CompileUnit, Optional},
MAX_PAR_DEPTH,
@ -41,11 +43,15 @@ mod optimize;
mod pure;
mod util;
pub(crate) fn compressor<'a>(
pub(crate) fn compressor<'a, M>(
globals: &'a Globals,
marks: Marks,
options: &'a CompressOptions,
) -> impl 'a + JsPass {
mode: &'a M,
) -> impl 'a + JsPass
where
M: Mode,
{
let console_remover = Optional {
enabled: options.drop_console,
visitor: drop_console(),
@ -59,12 +65,16 @@ pub(crate) fn compressor<'a>(
data: None,
optimizer_state: Default::default(),
left_parallel_depth: 0,
mode,
};
chain!(console_remover, as_folder(compressor), expr_simplifier())
}
struct Compressor<'a> {
struct Compressor<'a, M>
where
M: Mode,
{
globals: &'a Globals,
marks: Marks,
options: &'a CompressOptions,
@ -74,15 +84,23 @@ struct Compressor<'a> {
optimizer_state: OptimizerState,
/// `0` means we should not create more threads.
left_parallel_depth: u8,
mode: &'a M,
}
impl CompilerPass for Compressor<'_> {
impl<M> CompilerPass for Compressor<'_, M>
where
M: Mode,
{
fn name() -> Cow<'static, str> {
"compressor".into()
}
}
impl Compressor<'_> {
impl<M> Compressor<'_, M>
where
M: Mode,
{
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike,
@ -114,7 +132,7 @@ impl Compressor<'_> {
/// Optimize a bundle in a parallel.
fn visit_par<N>(&mut self, nodes: &mut Vec<N>)
where
N: Send + Sync + for<'aa> VisitMutWith<Compressor<'aa>>,
N: Send + Sync + for<'aa> VisitMutWith<Compressor<'aa, M>>,
{
log::debug!("visit_par(left_depth = {})", self.left_parallel_depth);
@ -129,6 +147,7 @@ impl Compressor<'_> {
data: None,
optimizer_state: Default::default(),
left_parallel_depth: 0,
mode: self.mode,
};
node.visit_mut_with(&mut v);
@ -148,6 +167,7 @@ impl Compressor<'_> {
data: None,
optimizer_state: Default::default(),
left_parallel_depth: self.left_parallel_depth - 1,
mode: self.mode,
};
node.visit_mut_with(&mut v);
@ -164,7 +184,7 @@ impl Compressor<'_> {
fn optimize_unit_repeatedly<N>(&mut self, n: &mut N)
where
N: CompileUnit + VisitWith<UsageAnalyzer> + for<'aa> VisitMutWith<Compressor<'aa>>,
N: CompileUnit + VisitWith<UsageAnalyzer> + for<'aa> VisitMutWith<Compressor<'aa, M>>,
{
if cfg!(feature = "debug") {
log::debug!(
@ -210,7 +230,7 @@ impl Compressor<'_> {
/// Optimize a module. `N` can be [Module] or [FnExpr].
fn optimize_unit<N>(&mut self, n: &mut N)
where
N: CompileUnit + VisitWith<UsageAnalyzer> + for<'aa> VisitMutWith<Compressor<'aa>>,
N: CompileUnit + VisitWith<UsageAnalyzer> + for<'aa> VisitMutWith<Compressor<'aa, M>>,
{
self.data = Some(analyze(&*n, Some(self.marks)));
@ -282,7 +302,7 @@ impl Compressor<'_> {
let start_time = now();
let mut visitor = pure_optimizer(&self.options, self.marks);
let mut visitor = pure_optimizer(&self.options, self.marks, self.mode);
n.apply(&mut visitor);
self.changed |= visitor.changed();
@ -312,6 +332,7 @@ impl Compressor<'_> {
self.options,
self.data.as_ref().unwrap(),
&mut self.optimizer_state,
self.mode,
);
n.apply(&mut visitor);
self.changed |= visitor.changed();
@ -368,7 +389,10 @@ impl Compressor<'_> {
}
}
impl VisitMut for Compressor<'_> {
impl<M> VisitMut for Compressor<'_, M>
where
M: Mode,
{
noop_visit_mut_type!();
fn visit_mut_fn_expr(&mut self, n: &mut FnExpr) {

View File

@ -1,5 +1,5 @@
use super::Optimizer;
use crate::compress::optimize::is_left_access_to_arguments;
use crate::{compress::optimize::is_left_access_to_arguments, mode::Mode};
use std::iter::repeat_with;
use swc_atoms::js_word;
use swc_common::DUMMY_SP;
@ -8,7 +8,10 @@ use swc_ecma_utils::{find_ids, ident::IdentLike, private_ident, Id};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
/// Methods related to the option `arguments`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
///
/// - `arguments['foo']` => `arguments.foo`
pub(super) fn optimize_str_access_to_arguments(&mut self, e: &mut Expr) {

View File

@ -2,6 +2,7 @@ use super::Optimizer;
use crate::{
compress::{optimize::Ctx, util::negate_cost},
debug::dump,
mode::Mode,
};
use swc_atoms::js_word;
use swc_common::Spanned;
@ -10,7 +11,10 @@ use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ident::IdentLike, undefined, ExprExt, Type, Value::Known};
/// Methods related to the options `bools` and `bool_as_ints`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// **This negates bool**.
///
/// Returns true if it's negated.
@ -115,7 +119,7 @@ impl Optimizer<'_> {
///
/// - `"undefined" == typeof value;` => `void 0 === value`
pub(super) fn compress_typeof_undefined(&mut self, e: &mut BinExpr) {
fn opt(o: &mut Optimizer, l: &mut Expr, r: &mut Expr) -> bool {
fn opt<M>(o: &mut Optimizer<M>, l: &mut Expr, r: &mut Expr) -> bool {
match (&mut *l, &mut *r) {
(
Expr::Lit(Lit::Str(Str {

View File

@ -1,11 +1,15 @@
use super::Optimizer;
use crate::mode::Mode;
use fxhash::FxHashMap;
use swc_ecma_ast::*;
use swc_ecma_utils::{ident::IdentLike, Id};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
/// Methods related to the option `collapse_vars`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
pub(super) fn collapse_assignment_to_vars(&mut self, e: &mut Expr) {
if !self.options.collapse_vars {
return;

View File

@ -4,6 +4,7 @@ use crate::{
optimize::Ctx,
util::{always_terminates, negate_cost},
},
mode::Mode,
util::SpanExt,
DISABLE_BUGGY_PASSES,
};
@ -15,7 +16,10 @@ use swc_ecma_utils::{ident::IdentLike, ExprExt, ExprFactory, StmtLike};
/// Methods related to the option `conditionals`. All methods are noop if
/// `conditionals` is false.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// Negates the condition of a `if` statement to reduce body size.
pub(super) fn negate_if_stmt(&mut self, stmt: &mut IfStmt) {
let alt = match stmt.alt.as_deref_mut() {

View File

@ -1,10 +1,14 @@
use super::Optimizer;
use crate::mode::Mode;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::ident::IdentLike;
/// Methods related to option `dead_code`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// Optimize return value or argument of throw.
///
/// This methods removes some useless assignments.

View File

@ -1,5 +1,5 @@
use super::Optimizer;
use crate::{compress::util::eval_as_number, DISABLE_BUGGY_PASSES};
use crate::{compress::util::eval_as_number, mode::Mode, DISABLE_BUGGY_PASSES};
use std::num::FpCategory;
use swc_atoms::js_word;
use swc_common::{Spanned, SyntaxContext, DUMMY_SP};
@ -8,7 +8,10 @@ use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ident::IdentLike, undefined, ExprExt, Value::Known};
/// Methods related to the option `evaludate`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// Evaludate expression if possible.
///
/// This method call apppropriate methods for each ast types.

View File

@ -1,6 +1,7 @@
use super::Optimizer;
use crate::{
compress::util::is_directive,
mode::Mode,
util::{sort::is_sorted_by, MoudleItemExt},
DISABLE_BUGGY_PASSES,
};
@ -8,7 +9,10 @@ use std::cmp::Ordering;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// Calls `reorder_stmts_inner` after splitting stmts.
pub(super) fn reorder_stmts<T>(&mut self, stmts: &mut Vec<T>)
where

View File

@ -1,9 +1,13 @@
use super::Optimizer;
use crate::mode::Mode;
use swc_ecma_ast::*;
use swc_ecma_utils::ident::IdentLike;
/// Methods related to the option `hoist_props`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// Store values of properties so we can replace property accesses with the
/// values.
pub(super) fn store_var_for_prop_hoisting(&mut self, n: &mut VarDeclarator) {

View File

@ -2,6 +2,7 @@ use super::Optimizer;
use crate::{
compress::{optimize::Ctx, util::is_pure_undefined},
debug::dump,
mode::Mode,
util::ExprOptExt,
};
use swc_common::{Spanned, DUMMY_SP};
@ -12,7 +13,10 @@ use swc_ecma_visit::{noop_visit_type, Node, Visit, VisitWith};
/// Methods related to the option `if_return`. All methods are noop if
/// `if_return` is false.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// # Input
///
/// ```js

View File

@ -1,6 +1,7 @@
use super::Optimizer;
use crate::{
compress::optimize::Ctx,
mode::Mode,
util::{idents_used_by, make_number},
};
use fxhash::FxHashMap;
@ -16,7 +17,10 @@ use swc_ecma_utils::{ident::IdentLike, undefined, ExprFactory, Id};
use swc_ecma_visit::VisitMutWith;
/// Methods related to the option `negate_iife`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// Negates iife, while ignore return value.
pub(super) fn negate_iife_ignoring_ret(&mut self, e: &mut Expr) {
if !self.options.negate_iife || self.ctx.in_bang_arg || self.ctx.dont_use_negated_iife {
@ -116,7 +120,10 @@ impl Optimizer<'_> {
}
/// Methods related to iife.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// # Exmaple
///
/// ## Input

View File

@ -2,6 +2,7 @@ use super::Optimizer;
use crate::{
compress::optimize::util::{class_has_side_effect, is_valid_for_lhs},
debug::dump,
mode::Mode,
util::idents_used_by,
};
use swc_atoms::js_word;
@ -11,7 +12,10 @@ use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ident::IdentLike, ExprExt, UsageFinder};
/// Methods related to option `inline`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// Stores the value of a variable to inline it.
///
/// This method may remove value of initializer. It mean that the value will
@ -135,6 +139,11 @@ impl Optimizer<'_> {
_ => {}
}
}
if !usage.mutated {
self.mode.store(i.to_id(), &*init);
}
// No use => doppred
if usage.ref_count == 0 {
if init.may_have_side_effects() {
@ -165,6 +174,8 @@ impl Optimizer<'_> {
_ => false,
}
{
self.mode.store(i.to_id(), &*init);
if self.options.inline != 0
&& !should_preserve
&& match &**init {

View File

@ -1,12 +1,14 @@
use crate::compress::util::is_directive;
use super::Optimizer;
use crate::{compress::util::is_directive, mode::Mode};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::StmtLike;
/// Methods related to option `join_vars`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// Join variables.
///
/// This method may move variables to head of for statements like

View File

@ -1,11 +1,17 @@
use crate::compress::optimize::{unused::UnreachableHandler, Optimizer};
use crate::{
compress::optimize::{unused::UnreachableHandler, Optimizer},
mode::Mode,
};
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, Value::Known};
/// Methods related to the option `loops`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// `for(a;b;c;) break;` => `a;b;`
pub(super) fn optimize_loops_with_break(&mut self, s: &mut Stmt) {
if !self.options.loops {

View File

@ -1,8 +1,10 @@
use self::util::replace_id_with_expr;
use crate::{
analyzer::{ProgramData, UsageAnalyzer},
compress::util::is_pure_undefined,
debug::dump,
marks::Marks,
mode::Mode,
option::CompressOptions,
util::{contains_leaping_yield, MoudleItemExt},
};
@ -20,8 +22,6 @@ use swc_ecma_utils::{
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
use Value::Known;
use self::util::replace_id_with_expr;
mod arguments;
mod bools;
mod collapse_vars;
@ -49,12 +49,16 @@ pub(super) struct OptimizerState {
}
/// This pass is simillar to `node.optimize` of terser.
pub(super) fn optimizer<'a>(
pub(super) fn optimizer<'a, M>(
marks: Marks,
options: &'a CompressOptions,
data: &'a ProgramData,
state: &'a mut OptimizerState,
) -> impl 'a + VisitMut + Repeated {
mode: &'a M,
) -> impl 'a + VisitMut + Repeated
where
M: Mode,
{
assert!(
options.top_retain.iter().all(|s| s.trim() != ""),
"top_retain should not contain empty string"
@ -79,6 +83,7 @@ pub(super) fn optimizer<'a>(
done,
done_ctxt,
label: Default::default(),
mode,
}
}
@ -178,7 +183,7 @@ impl Ctx {
}
}
struct Optimizer<'a> {
struct Optimizer<'a, M> {
marks: Marks,
changed: bool,
@ -213,9 +218,11 @@ struct Optimizer<'a> {
/// Closest label.
label: Option<Id>,
mode: &'a M,
}
impl Repeated for Optimizer<'_> {
impl<M> Repeated for Optimizer<'_, M> {
fn changed(&self) -> bool {
self.changed
}
@ -225,7 +232,10 @@ impl Repeated for Optimizer<'_> {
}
}
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike + ModuleItemLike + MoudleItemExt + VisitMutWith<Self>,
@ -1427,7 +1437,10 @@ impl Optimizer<'_> {
}
}
impl VisitMut for Optimizer<'_> {
impl<M> VisitMut for Optimizer<'_, M>
where
M: Mode,
{
noop_visit_mut_type!();
fn visit_mut_arrow_expr(&mut self, n: &mut ArrowExpr) {

View File

@ -1,6 +1,7 @@
use super::Optimizer;
use crate::{
compress::util::negate,
mode::Mode,
util::{make_bool, ValueExt},
};
use swc_atoms::js_word;
@ -10,7 +11,10 @@ use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ident::IdentLike, ExprExt, Type, Value};
use Value::Known;
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
///
/// - `'12' === `foo` => '12' == 'foo'`
pub(super) fn optimize_bin_operator(&mut self, e: &mut BinExpr) {

View File

@ -5,6 +5,7 @@ use crate::{
util::{get_lhs_ident, get_lhs_ident_mut, is_directive, is_ident_used_by},
},
debug::dump,
mode::Mode,
option::CompressOptions,
util::{idents_used_by, idents_used_by_ignoring_nested, ExprOptExt, MoudleItemExt},
};
@ -19,7 +20,10 @@ use swc_ecma_visit::{noop_visit_type, Node, Visit, VisitWith};
/// Methods related to the option `sequences`. All methods are noop if
/// `sequences` is false.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
///
/// # Exmaple
///

View File

@ -1,11 +1,15 @@
use super::Optimizer;
use crate::mode::Mode;
use swc_atoms::js_word;
use swc_common::Spanned;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ident::IdentLike, ExprExt, Value::Known};
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
pub(super) fn optimize_expr_in_str_ctx_unsafely(&mut self, e: &mut Expr) {
if !self.options.unsafe_passes {
return;

View File

@ -1,7 +1,6 @@
use std::mem::take;
use super::Optimizer;
use crate::util::ExprOptExt;
use crate::{mode::Mode, util::ExprOptExt};
use std::mem::take;
use swc_common::{EqIgnoreSpan, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
@ -9,7 +8,10 @@ use swc_ecma_utils::{ident::IdentLike, prepend, ExprExt, StmtExt, Type, Value::K
use swc_ecma_visit::{noop_visit_type, Node, Visit, VisitWith};
/// Methods related to option `switches`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
/// Handle switches in the case where we can know which branch will be
/// taken.
pub(super) fn optimize_const_switches(&mut self, s: &mut Stmt) {

View File

@ -1,6 +1,7 @@
use super::Optimizer;
use crate::{
compress::optimize::util::class_has_side_effect, debug::dump, option::PureGetterOption,
compress::optimize::util::class_has_side_effect, debug::dump, mode::Mode,
option::PureGetterOption,
};
use swc_atoms::js_word;
use swc_common::{Span, DUMMY_SP};
@ -10,7 +11,10 @@ use swc_ecma_utils::{contains_ident_ref, ident::IdentLike};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
/// Methods related to the option `unused`.
impl Optimizer<'_> {
impl<M> Optimizer<'_, M>
where
M: Mode,
{
pub(super) fn drop_unused_var_declarator(&mut self, var: &mut VarDeclarator) {
match &mut var.init {
Some(init) => match &**init {

View File

@ -1,3 +1,5 @@
use crate::mode::Mode;
use super::{Ctx, Optimizer};
use std::ops::{Deref, DerefMut};
use swc_atoms::JsWord;
@ -6,7 +8,10 @@ use swc_ecma_ast::*;
use swc_ecma_utils::{prop_name_eq, ExprExt, Id};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
impl<'b> Optimizer<'b> {
impl<'b, M> Optimizer<'b, M>
where
M: Mode,
{
pub(super) fn access_property<'e>(
&mut self,
expr: &'e mut Expr,
@ -77,7 +82,7 @@ impl<'b> Optimizer<'b> {
/// RAII guard to change context temporarically
#[inline]
pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx<'_, 'b> {
pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx<'_, 'b, M> {
let orig_ctx = self.ctx;
self.ctx = ctx;
WithCtx {
@ -87,26 +92,26 @@ impl<'b> Optimizer<'b> {
}
}
pub(super) struct WithCtx<'a, 'b> {
reducer: &'a mut Optimizer<'b>,
pub(super) struct WithCtx<'a, 'b, M> {
reducer: &'a mut Optimizer<'b, M>,
orig_ctx: Ctx,
}
impl<'b> Deref for WithCtx<'_, 'b> {
type Target = Optimizer<'b>;
impl<'b, M> Deref for WithCtx<'_, 'b, M> {
type Target = Optimizer<'b, M>;
fn deref(&self) -> &Self::Target {
&self.reducer
}
}
impl DerefMut for WithCtx<'_, '_> {
impl<M> DerefMut for WithCtx<'_, '_, M> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.reducer
}
}
impl Drop for WithCtx<'_, '_> {
impl<M> Drop for WithCtx<'_, '_, M> {
fn drop(&mut self) {
self.reducer.ctx = self.orig_ctx;
}

View File

@ -1,11 +1,15 @@
use super::Pure;
use crate::mode::Mode;
use swc_common::Spanned;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::contains_this_expr;
/// Methods related to the option `arrows`.
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
pub(super) fn optimize_arrow_body(&mut self, b: &mut BlockStmtOrExpr) {
if !self.options.arrows {
return;

View File

@ -1,6 +1,7 @@
use super::Pure;
use crate::{
compress::util::{is_pure_undefined, negate, negate_cost},
mode::Mode,
util::make_bool,
};
use std::mem::swap;
@ -10,7 +11,10 @@ use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, Type, Value};
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
pub(super) fn negate_twice(&mut self, e: &mut Expr) {
self.changed = true;
negate(e, false);

View File

@ -1,12 +1,15 @@
use super::Pure;
use crate::{compress::util::negate_cost, debug::dump, util::make_bool};
use crate::{compress::util::negate_cost, debug::dump, mode::Mode, util::make_bool};
use std::mem::swap;
use swc_common::{EqIgnoreSpan, Spanned};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, Type, Value};
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
///
/// - `foo ? bar : false` => `!!foo && bar`
/// - `!foo ? true : bar` => `!foo || bar`

View File

@ -23,10 +23,10 @@ pub(super) struct Ctx {
pub in_first_expr: bool,
}
impl<'b> Pure<'b> {
impl<'b, M> Pure<'b, M> {
/// RAII guard to change context temporarically
#[inline]
pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx<'_, 'b> {
pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx<'_, 'b, M> {
let orig_ctx = self.ctx;
self.ctx = ctx;
WithCtx {
@ -36,26 +36,26 @@ impl<'b> Pure<'b> {
}
}
pub(super) struct WithCtx<'a, 'b> {
pass: &'a mut Pure<'b>,
pub(super) struct WithCtx<'a, 'b, M> {
pass: &'a mut Pure<'b, M>,
orig_ctx: Ctx,
}
impl<'b> Deref for WithCtx<'_, 'b> {
type Target = Pure<'b>;
impl<'b, M> Deref for WithCtx<'_, 'b, M> {
type Target = Pure<'b, M>;
fn deref(&self) -> &Self::Target {
&self.pass
}
}
impl DerefMut for WithCtx<'_, '_> {
impl<M> DerefMut for WithCtx<'_, '_, M> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.pass
}
}
impl Drop for WithCtx<'_, '_> {
impl<M> Drop for WithCtx<'_, '_, M> {
fn drop(&mut self) {
self.pass.ctx = self.orig_ctx;
}

View File

@ -1,5 +1,5 @@
use super::Pure;
use crate::compress::util::always_terminates;
use crate::{compress::util::always_terminates, mode::Mode};
use swc_atoms::js_word;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
@ -7,7 +7,10 @@ use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, StmtLike, Value};
/// Methods related to option `dead_code`.
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
pub(super) fn drop_useless_blocks<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike,

View File

@ -1,12 +1,18 @@
use super::Pure;
use crate::compress::util::{eval_as_number, is_pure_undefined_or_null};
use crate::{
compress::util::{eval_as_number, is_pure_undefined_or_null},
mode::Mode,
};
use swc_atoms::js_word;
use swc_common::{Spanned, SyntaxContext};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{undefined, ExprExt, Value};
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
pub(super) fn eval_array_method_call(&mut self, e: &mut Expr) {
if !self.options.evaluate {
return;
@ -351,7 +357,10 @@ impl Pure<'_> {
}
/// Evaluation of strings.
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
/// Handle calls on string literals, like `'foo'.toUpperCase()`.
pub(super) fn eval_str_method_call(&mut self, e: &mut Expr) {
if !self.options.evaluate {

View File

@ -1,10 +1,14 @@
use super::Pure;
use crate::mode::Mode;
use swc_common::Spanned;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, Value};
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
///
/// - `while(test);` => `for(;;test);
/// - `do; while(true)` => `for(;;);

View File

@ -1,8 +1,11 @@
use super::Pure;
use crate::compress::util::is_pure_undefined;
use crate::{compress::util::is_pure_undefined, mode::Mode};
use swc_ecma_ast::*;
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
pub(super) fn drop_undefined_from_return_arg(&mut self, s: &mut ReturnStmt) {
match s.arg.as_deref() {
Some(e) => {

View File

@ -1,5 +1,7 @@
use self::ctx::Ctx;
use crate::{marks::Marks, option::CompressOptions, util::MoudleItemExt, MAX_PAR_DEPTH};
use crate::{
marks::Marks, mode::Mode, option::CompressOptions, util::MoudleItemExt, MAX_PAR_DEPTH,
};
use rayon::prelude::*;
use swc_common::{pass::Repeated, DUMMY_SP};
use swc_ecma_ast::*;
@ -20,26 +22,32 @@ mod strings;
mod unsafes;
mod vars;
pub(super) fn pure_optimizer<'a>(
pub(crate) fn pure_optimizer<'a, M>(
options: &'a CompressOptions,
marks: Marks,
) -> impl 'a + VisitMut + Repeated {
mode: &'a M,
) -> impl 'a + VisitMut + Repeated
where
M: Mode,
{
Pure {
options,
marks,
ctx: Default::default(),
changed: Default::default(),
mode,
}
}
struct Pure<'a> {
struct Pure<'a, M> {
options: &'a CompressOptions,
marks: Marks,
ctx: Ctx,
changed: bool,
mode: &'a M,
}
impl Repeated for Pure<'_> {
impl<M> Repeated for Pure<'_, M> {
fn changed(&self) -> bool {
self.changed
}
@ -50,7 +58,10 @@ impl Repeated for Pure<'_> {
}
}
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
where
T: MoudleItemExt,
@ -70,7 +81,7 @@ impl Pure<'_> {
/// Visit `nodes`, maybe in parallel.
fn visit_par<N>(&mut self, nodes: &mut Vec<N>)
where
N: for<'aa> VisitMutWith<Pure<'aa>> + Send + Sync,
N: for<'aa> VisitMutWith<Pure<'aa, M>> + Send + Sync,
{
if self.ctx.par_depth >= MAX_PAR_DEPTH * 2 || cfg!(target_arch = "wasm32") {
for node in nodes {
@ -79,6 +90,7 @@ impl Pure<'_> {
marks: self.marks,
ctx: self.ctx,
changed: false,
mode: self.mode,
};
node.visit_mut_with(&mut v);
@ -96,6 +108,7 @@ impl Pure<'_> {
..self.ctx
},
changed: false,
mode: self.mode,
};
node.visit_mut_with(&mut v);
@ -110,7 +123,10 @@ impl Pure<'_> {
}
}
impl VisitMut for Pure<'_> {
impl<M> VisitMut for Pure<'_, M>
where
M: Mode,
{
noop_visit_mut_type!();
fn visit_mut_assign_expr(&mut self, e: &mut AssignExpr) {

View File

@ -1,8 +1,12 @@
use super::Pure;
use crate::mode::Mode;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
pub(super) fn optimize_expr_in_num_ctx(&mut self, e: &mut Expr) {
match e {
Expr::Lit(Lit::Str(Str { span, value, .. })) => {

View File

@ -1,11 +1,14 @@
use super::Pure;
use crate::{compress::util::is_valid_identifier, util::deeply_contains_this_expr};
use crate::{compress::util::is_valid_identifier, mode::Mode, util::deeply_contains_this_expr};
use swc_atoms::js_word;
use swc_common::SyntaxContext;
use swc_ecma_ast::*;
use swc_ecma_utils::{prop_name_eq, ExprExt};
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
pub(super) fn optimize_property_of_member_expr(&mut self, e: &mut MemberExpr) {
if !e.computed {
return;

View File

@ -1,12 +1,14 @@
use crate::compress::util::get_lhs_ident;
use super::Pure;
use crate::{compress::util::get_lhs_ident, mode::Mode};
use swc_common::{SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, ExprFactory};
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
pub(super) fn drop_useless_ident_ref_in_seq(&mut self, seq: &mut SeqExpr) {
if !self.options.collapse_vars {
return;

View File

@ -1,13 +1,16 @@
use std::mem::take;
use super::Pure;
use crate::mode::Mode;
use std::mem::take;
use swc_atoms::{js_word, JsWord};
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ExprExt, Type, Value};
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
/// Converts template literals to string if `exprs` of [Tpl] is empty.
pub(super) fn convert_tpl_to_str(&mut self, e: &mut Expr) {
match e {
@ -15,6 +18,7 @@ impl Pure<'_> {
if let Some(c) = &t.quasis[0].cooked {
if c.value.chars().all(|c| match c {
'\u{0020}'..='\u{007e}' => true,
'\n' | '\r' => M::force_str_for_tpl(),
_ => false,
}) {
*e = Expr::Lit(Lit::Str(c.clone()));

View File

@ -1,9 +1,13 @@
use super::Pure;
use crate::mode::Mode;
use swc_atoms::js_word;
use swc_ecma_ast::*;
use swc_ecma_utils::ExprExt;
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
/// Drop arguments of `Symbol()` call.
pub(super) fn drop_arguemtns_of_symbol_call(&mut self, e: &mut CallExpr) {
if !self.options.unsafe_symbols {

View File

@ -1,3 +1,5 @@
use super::Pure;
use crate::mode::Mode;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::MapWithMut;
@ -6,9 +8,10 @@ use swc_ecma_visit::{
noop_visit_mut_type, noop_visit_type, Node, Visit, VisitMut, VisitMutWith, VisitWith,
};
use super::Pure;
impl Pure<'_> {
impl<M> Pure<'_, M>
where
M: Mode,
{
/// Collapse single-use non-constant variables, side effects permitting.
///
/// This merges all variables to first variable declartion with an

View File

@ -0,0 +1,251 @@
use crate::{
compress::{compressor, pure_optimizer},
marks::Marks,
mode::Mode,
};
use fxhash::FxHashMap;
use std::sync::{Arc, Mutex};
use swc_atoms::js_word;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms::optimization::simplify::expr_simplifier;
use swc_ecma_transforms_base::ext::MapWithMut;
use swc_ecma_utils::{ident::IdentLike, undefined, ExprExt, ExprFactory, Id};
use swc_ecma_visit::{FoldWith, VisitMutWith};
pub struct Evaluator {
module: Module,
marks: Marks,
data: Eval,
/// We run minification only once.
done: bool,
}
impl Evaluator {
pub fn new(module: Module, marks: Marks) -> Self {
Evaluator {
module,
marks,
data: Default::default(),
done: Default::default(),
}
}
}
#[derive(Default, Clone)]
struct Eval {
store: Arc<Mutex<EvalStore>>,
}
#[derive(Default)]
struct EvalStore {
cache: FxHashMap<Id, Box<Expr>>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EvalResult {
Lit(Lit),
Undefined,
}
impl Mode for Eval {
fn store(&self, id: Id, value: &Expr) {
let mut w = self.store.lock().unwrap();
w.cache.insert(id, Box::new(value.clone()));
}
fn force_str_for_tpl() -> bool {
true
}
}
impl Evaluator {
fn run(&mut self) {
if !self.done {
self.done = true;
let marks = self.marks;
let data = self.data.clone();
self.module.map_with_mut(|m| {
//
swc_common::GLOBALS.with(|globals| {
//
m.fold_with(&mut compressor(
&globals,
marks,
&serde_json::from_str("{}").unwrap(),
&data,
))
})
});
}
}
pub fn eval(&mut self, e: &Expr) -> Option<EvalResult> {
match e {
Expr::Seq(s) => return self.eval(s.exprs.last()?),
Expr::Lit(l @ Lit::Null(..))
| Expr::Lit(l @ Lit::Num(..) | l @ Lit::Str(..) | l @ Lit::BigInt(..)) => {
return Some(EvalResult::Lit(l.clone()))
}
Expr::Tpl(t) => {
return self.eval_tpl(&t);
}
Expr::TaggedTpl(t) => {
// Handle `String.raw`
match &*t.tag {
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(tag_obj),
prop,
computed: false,
..
}) if tag_obj.is_ident_ref_to("String".into())
&& prop.is_ident_ref_to("raw".into()) =>
{
return self.eval_tpl(&t.tpl);
}
_ => {}
}
}
Expr::Cond(c) => {
let test = self.eval(&c.test)?;
if is_truthy(&test)? {
return self.eval(&c.cons);
} else {
return self.eval(&c.alt);
}
}
// TypeCastExpression, ExpressionStatement etc
Expr::TsTypeAssertion(e) => {
return self.eval(&e.expr);
}
Expr::TsConstAssertion(e) => {
return self.eval(&e.expr);
}
// "foo".length
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) if obj.is_lit() && prop.is_ident_ref_to("length".into()) => {}
Expr::Unary(UnaryExpr {
op: op!("void"), ..
}) => return Some(EvalResult::Undefined),
Expr::Unary(UnaryExpr {
op: op!("!"), arg, ..
}) => {
let arg = self.eval(&arg)?;
if is_truthy(&arg)? {
return Some(EvalResult::Lit(Lit::Bool(Bool {
span: DUMMY_SP,
value: false,
})));
} else {
return Some(EvalResult::Lit(Lit::Bool(Bool {
span: DUMMY_SP,
value: true,
})));
}
}
_ => {}
}
Some(EvalResult::Lit(self.eval_as_expr(e)?.lit()?))
}
fn eval_as_expr(&mut self, e: &Expr) -> Option<Box<Expr>> {
match e {
Expr::Ident(i) => {
self.run();
let lock = self.data.store.lock().ok()?;
let val = lock.cache.get(&i.to_id())?;
return Some(val.clone());
}
Expr::Member(MemberExpr {
span,
obj: ExprOrSuper::Expr(obj),
prop,
computed: false,
..
}) => {
let obj = self.eval_as_expr(&obj)?;
let mut e = Expr::Member(MemberExpr {
span: *span,
obj: obj.as_obj(),
prop: prop.clone(),
computed: false,
});
e.visit_mut_with(&mut expr_simplifier());
return Some(Box::new(e));
}
_ => {}
}
None
}
pub fn eval_tpl(&mut self, q: &Tpl) -> Option<EvalResult> {
self.run();
let mut exprs = vec![];
for expr in &q.exprs {
let res = self.eval(expr)?;
exprs.push(match res {
EvalResult::Lit(v) => Box::new(Expr::Lit(v)),
EvalResult::Undefined => undefined(DUMMY_SP),
});
}
let mut e = Expr::Tpl(Tpl {
span: q.span,
exprs,
quasis: q.quasis.clone(),
});
{
let data = self.data.clone();
e.visit_mut_with(&mut pure_optimizer(
&serde_json::from_str("{}").unwrap(),
self.marks,
&data,
));
}
Some(EvalResult::Lit(e.lit()?))
}
}
fn is_truthy(lit: &EvalResult) -> Option<bool> {
match lit {
EvalResult::Lit(v) => match v {
Lit::Str(v) => Some(v.value != js_word!("")),
Lit::Bool(v) => Some(v.value),
Lit::Null(_) => Some(false),
Lit::Num(v) => Some(v.value != 0.0 && v.value != -0.0),
_ => None,
},
EvalResult::Undefined => Some(false),
}
}

View File

@ -28,6 +28,7 @@ use crate::{
},
util::now,
};
use mode::Minification;
use pass::postcompress::postcompress_optimizer;
use std::time::Instant;
use swc_common::{comments::Comments, sync::Lrc, SourceMap, GLOBALS};
@ -38,8 +39,10 @@ use timing::Timings;
mod analyzer;
mod compress;
mod debug;
pub mod eval;
pub mod marks;
mod metadata;
mod mode;
pub mod option;
mod pass;
pub mod timing;
@ -105,7 +108,7 @@ pub fn optimize(
// Noop.
// https://github.com/mishoo/UglifyJS2/issues/2794
if options.rename && false {
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());
@ -116,7 +119,8 @@ pub fn optimize(
}
if let Some(options) = &options.compress {
let start = now();
m = GLOBALS.with(|globals| m.fold_with(&mut compressor(globals, marks, &options)));
m = GLOBALS
.with(|globals| m.fold_with(&mut compressor(globals, marks, &options, &Minification)));
if let Some(start) = start {
log::info!("compressor took {:?}", Instant::now() - start);
}

View File

@ -0,0 +1,20 @@
use swc_ecma_ast::*;
use swc_ecma_utils::Id;
pub(crate) trait Mode: Send + Sync {
fn store(&self, id: Id, value: &Expr);
/// If this returns true, template literals with `\n` or `\r` will be
/// converted to [Lit::Str].
fn force_str_for_tpl() -> bool;
}
pub struct Minification;
impl Mode for Minification {
fn store(&self, _: Id, _: &Expr) {}
fn force_str_for_tpl() -> bool {
false
}
}

View File

@ -0,0 +1,468 @@
use swc_common::{input::SourceFileInput, sync::Lrc, FileName, SourceMap};
use swc_ecma_ast::*;
use swc_ecma_codegen::{text_writer::JsWriter, Emitter};
use swc_ecma_minifier::{
eval::{EvalResult, Evaluator},
marks::Marks,
};
use swc_ecma_parser::{lexer::Lexer, EsConfig, Parser, Syntax};
use swc_ecma_transforms::resolver;
use swc_ecma_utils::ExprExt;
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
use testing::{assert_eq, DebugUsingDisplay};
fn eval(module: &str, expr: &str) -> Option<String> {
testing::run_test2(false, |cm, _handler| {
let fm = cm.new_source_file(FileName::Anon, module.to_string());
let marks = Marks::new();
let module_ast = {
let lexer = Lexer::new(
Default::default(),
EsVersion::latest(),
SourceFileInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
parser.parse_module().unwrap()
};
let expr_ast = {
let fm = cm.new_source_file(FileName::Anon, expr.to_string());
let lexer = Lexer::new(
Default::default(),
EsVersion::latest(),
SourceFileInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
parser.parse_expr().unwrap()
};
let mut evaluator = Evaluator::new(module_ast, marks);
let res = evaluator.eval(&expr_ast);
match res {
Some(res) => match res {
EvalResult::Lit(l) => match l {
swc_ecma_ast::Lit::Str(v) => Ok(Some(v.value.to_string())),
swc_ecma_ast::Lit::Bool(v) => Ok(Some(v.value.to_string())),
swc_ecma_ast::Lit::Num(v) => Ok(Some(v.value.to_string())),
swc_ecma_ast::Lit::Null(_) => Ok(Some("null".into())),
_ => unreachable!(),
},
EvalResult::Undefined => Ok(Some("undefined".into())),
},
None => Ok(None),
}
})
.unwrap()
}
#[test]
fn simple() {
assert_eq!(eval("const foo = 4", "foo").unwrap(), "4");
}
struct PartialInliner {
marks: Marks,
eval: Option<Evaluator>,
}
impl PartialInliner {
fn run_test<F>(src: &str, op: F)
where
F: FnOnce(Lrc<SourceMap>, Module, &mut PartialInliner),
{
testing::run_test2(false, |cm, _handler| {
let fm = cm.new_source_file(FileName::Anon, src.to_string());
let marks = Marks::new();
let mut module = {
let lexer = Lexer::new(
Syntax::Es(EsConfig {
jsx: true,
..Default::default()
}),
EsVersion::latest(),
SourceFileInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
parser.parse_module().unwrap()
};
module.visit_mut_with(&mut resolver());
let mut inliner = PartialInliner {
marks,
eval: Default::default(),
};
op(cm.clone(), module, &mut inliner);
Ok(())
})
.unwrap();
}
fn expect(src: &str, expected: &str) {
PartialInliner::run_test(src, |cm, mut module, inliner| {
//
module.visit_mut_with(inliner);
let expected_module = {
let fm = cm.new_source_file(FileName::Anon, expected.to_string());
let lexer = Lexer::new(
Syntax::Es(EsConfig {
jsx: true,
..Default::default()
}),
EsVersion::latest(),
SourceFileInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
parser.parse_module().unwrap()
};
let expected = {
let mut buf = vec![];
{
let mut emitter = Emitter {
cfg: Default::default(),
cm: cm.clone(),
comments: None,
wr: Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None)),
};
emitter.emit_module(&expected_module).unwrap();
}
String::from_utf8(buf).unwrap()
};
let actual = {
let mut buf = vec![];
{
let mut emitter = Emitter {
cfg: Default::default(),
cm: cm.clone(),
comments: None,
wr: Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None)),
};
emitter.emit_module(&module).unwrap();
}
String::from_utf8(buf).unwrap()
};
assert_eq!(DebugUsingDisplay(&expected), DebugUsingDisplay(&actual));
});
}
}
impl VisitMut for PartialInliner {
noop_visit_mut_type!();
fn visit_mut_expr(&mut self, e: &mut Expr) {
e.visit_mut_children_with(self);
if let Some(evaluator) = self.eval.as_mut() {
match e {
Expr::TaggedTpl(tt) => {
if tt.tag.is_ident_ref_to("css".into()) {
let res = evaluator.eval_tpl(&tt.tpl);
let res = match res {
Some(v) => v,
None => return,
};
match res {
EvalResult::Lit(Lit::Str(s)) => {
let el = TplElement {
span: s.span,
tail: true,
cooked: Some(s.clone()),
raw: s,
};
tt.tpl = Tpl {
span: el.span,
exprs: Default::default(),
quasis: vec![el],
};
}
_ => {
unreachable!()
}
}
}
}
_ => {}
}
}
}
fn visit_mut_module(&mut self, module: &mut Module) {
self.eval = Some(Evaluator::new(module.clone(), self.marks));
module.visit_mut_children_with(self);
}
}
#[test]
fn partial_1() {
PartialInliner::expect(
"
const color = 'red'
export const foo = css`
div {
color: ${color};
}
`
",
"
const color = 'red'
export const foo = css`
div {
color: red;
}
`
",
);
}
#[test]
fn partial_2() {
PartialInliner::expect(
"
export default css`
div {
font-size: 3em;
}
p {
color: ${props.color};
}
`
",
"
export default css`
div {
font-size: 3em;
}
p {
color: ${props.color};
}
`
",
);
}
#[test]
fn partial_3() {
PartialInliner::expect(
"
const darken = c => c
const color = 'red'
const otherColor = 'green'
const mediumScreen = '680px'
const animationDuration = '200ms'
const animationName = 'my-cool-animation'
const obj = { display: 'block' }
export const s1 = css`
p.${color} {
color: ${otherColor};
display: ${obj.display};
}
`;
",
"
const darken = c => c
const color = 'red'
const otherColor = 'green'
const mediumScreen = '680px'
const animationDuration = '200ms'
const animationName = 'my-cool-animation'
const obj = { display: 'block' }
export const s1 = css`
p.red {
color: green;
display: block;
}
`
",
);
}
#[test]
#[ignore]
fn partial_4() {
PartialInliner::expect(
"
const darken = c => c
const color = 'red'
const otherColor = 'green'
const mediumScreen = '680px'
const animationDuration = '200ms'
const animationName = 'my-cool-animation'
const obj = { display: 'block' }
export const s2 = css`
p.${color} {
color: ${darken(color)};
}
`;
",
"
const darken = c => c
const color = 'red'
const otherColor = 'green'
const mediumScreen = '680px'
const animationDuration = '200ms'
const animationName = 'my-cool-animation'
const obj = { display: 'block' }
export const s2 = css`
p.red {
color: 'red';
}
`
",
);
}
#[test]
#[ignore]
fn partial_5() {
PartialInliner::expect(
"
const darken = c => c
const color = 'red'
const otherColor = 'green'
const mediumScreen = '680px'
const animationDuration = '200ms'
const animationName = 'my-cool-animation'
const obj = { display: 'block' }
export const s3 = css`
p.${color} {
color: ${darken(color) + 2};
}
`;
",
"
const darken = c => c
const color = 'red'
const otherColor = 'green'
const mediumScreen = '680px'
const animationDuration = '200ms'
const animationName = 'my-cool-animation'
const obj = { display: 'block' }
export const s3 = css`
p.red {
color: 'red2';
}
`;
",
);
}
#[test]
fn partial_6() {
PartialInliner::expect(
"
const darken = c => c
const color = 'red'
const otherColor = 'green'
const mediumScreen = '680px'
const animationDuration = '200ms'
const animationName = 'my-cool-animation'
const obj = { display: 'block' }
export const s4 = css`
@media (min-width: ${mediumScreen}) {
p {
color: green;
}
p {
color: ${`red`};
}
}
p {
color: red;
}
`;
",
"
const darken = c => c
const color = 'red'
const otherColor = 'green'
const mediumScreen = '680px'
const animationDuration = '200ms'
const animationName = 'my-cool-animation'
const obj = { display: 'block' }
export const s4 = css`
@media (min-width: 680px) {
p {
color: green;
}
p {
color: red;
}
}
p {
color: red;
}
`;
",
);
}
#[test]
fn partial_7() {
PartialInliner::expect(
"
const darken = c => c
const color = 'red'
const otherColor = 'green'
const mediumScreen = '680px'
const animationDuration = '200ms'
const animationName = 'my-cool-animation'
const obj = { display: 'block' }
export default ({ display }) => (
css`
span {
display: ${display ? 'block' : 'none'};
}
`
)
",
"
const darken = c => c
const color = 'red'
const otherColor = 'green'
const mediumScreen = '680px'
const animationDuration = '200ms'
const animationName = 'my-cool-animation'
const obj = { display: 'block' }
export default ({ display }) => (
css`
span {
display: ${display ? 'block' : 'none'};
}
`
)
",
);
}

View File

@ -13,7 +13,7 @@ mod tests;
const LOG: bool = false;
/// See [resolver_with_mark] for docs.
pub fn resolver() -> impl 'static + Fold {
pub fn resolver() -> impl 'static + Fold + VisitMut {
resolver_with_mark(Mark::fresh(Mark::root()))
}

View File

@ -1,6 +1,6 @@
{
"name": "@swc/core",
"version": "1.2.83",
"version": "1.2.84",
"description": "Super-fast alternative for babel",
"homepage": "https://swc.rs",
"main": "./index.js",

View File

@ -6,7 +6,7 @@ license = "Apache-2.0 AND MIT"
name = "wasm"
publish = false
repository = "https://github.com/swc-project/swc.git"
version = "1.2.83"
version = "1.2.84"
[lib]
crate-type = ["cdylib"]