mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(minifier): try fold typeof (#408)
This commit is contained in:
parent
224624bf89
commit
7c79fbc026
5 changed files with 145 additions and 4 deletions
77
crates/oxc_hir/src/hir_util.rs
Normal file
77
crates/oxc_hir/src/hir_util.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
use crate::hir::{
|
||||||
|
ArrayExpressionElement, Expression, ObjectProperty, ObjectPropertyKind, PropertyKey,
|
||||||
|
SpreadElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Code ported from [closure-compiler](https://github.com/google/closure-compiler/blob/f3ce5ed8b630428e311fe9aa2e20d36560d975e2/src/com/google/javascript/jscomp/NodeUtil.java#LL836C6-L836C6)
|
||||||
|
/// Returns true if this is a literal value. We define a literal value as any node that evaluates
|
||||||
|
/// to the same thing regardless of when or where it is evaluated. So `/xyz/` and `[3, 5]` are
|
||||||
|
/// literals, but the name a is not.
|
||||||
|
///
|
||||||
|
/// Function literals do not meet this definition, because they lexically capture variables. For
|
||||||
|
/// example, if you have `function() { return a; }`.
|
||||||
|
/// If it is evaluated in a different scope, then it captures a different variable. Even if
|
||||||
|
/// the function did not read any captured variables directly, it would still fail this definition,
|
||||||
|
/// because it affects the lifecycle of variables in the enclosing scope.
|
||||||
|
///
|
||||||
|
/// However, a function literal with respect to a particular scope is a literal.
|
||||||
|
/// If `include_functions` is true, all function expressions will be treated as literals.
|
||||||
|
pub trait IsLiteralValue<'a, 'b> {
|
||||||
|
fn is_literal_value(&self, include_functions: bool) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> IsLiteralValue<'a, 'b> for Expression<'a> {
|
||||||
|
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::FunctionExpression(_) | Self::ArrowExpression(_) => include_functions,
|
||||||
|
Self::ArrayExpression(expr) => {
|
||||||
|
expr.elements.iter().all(|element| element.is_literal_value(include_functions))
|
||||||
|
}
|
||||||
|
Self::ObjectExpression(expr) => {
|
||||||
|
expr.properties.iter().all(|property| property.is_literal_value(include_functions))
|
||||||
|
}
|
||||||
|
_ => self.is_immutable_value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> IsLiteralValue<'a, 'b> for ArrayExpressionElement<'a> {
|
||||||
|
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::SpreadElement(element) => element.is_literal_value(include_functions),
|
||||||
|
Self::Expression(expr) => expr.is_literal_value(include_functions),
|
||||||
|
Self::Elision(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> IsLiteralValue<'a, 'b> for SpreadElement<'a> {
|
||||||
|
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||||
|
self.argument.is_literal_value(include_functions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> IsLiteralValue<'a, 'b> for ObjectPropertyKind<'a> {
|
||||||
|
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::ObjectProperty(method) => method.is_literal_value(include_functions),
|
||||||
|
Self::SpreadProperty(property) => property.is_literal_value(include_functions),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> IsLiteralValue<'a, 'b> for ObjectProperty<'a> {
|
||||||
|
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||||
|
self.key.is_literal_value(include_functions)
|
||||||
|
&& self.value.is_literal_value(include_functions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> IsLiteralValue<'a, 'b> for PropertyKey<'a> {
|
||||||
|
fn is_literal_value(&self, include_functions: bool) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Identifier(_) | Self::PrivateIdentifier(_) => false,
|
||||||
|
Self::Expression(expr) => expr.is_literal_value(include_functions),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ mod serialize;
|
||||||
|
|
||||||
pub mod hir;
|
pub mod hir;
|
||||||
mod hir_builder;
|
mod hir_builder;
|
||||||
|
pub mod hir_util;
|
||||||
pub mod precedence;
|
pub mod precedence;
|
||||||
mod visit_mut;
|
mod visit_mut;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@
|
||||||
|
|
||||||
#[allow(clippy::wildcard_imports)]
|
#[allow(clippy::wildcard_imports)]
|
||||||
use oxc_hir::hir::*;
|
use oxc_hir::hir::*;
|
||||||
use oxc_span::Span;
|
use oxc_hir::hir_util::IsLiteralValue;
|
||||||
use oxc_syntax::operator::BinaryOperator;
|
use oxc_span::{Atom, Span};
|
||||||
|
use oxc_syntax::operator::{BinaryOperator, UnaryOperator};
|
||||||
|
|
||||||
use super::Compressor;
|
use super::Compressor;
|
||||||
|
|
||||||
|
|
@ -63,6 +64,12 @@ impl<'a> Compressor<'a> {
|
||||||
),
|
),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
|
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
|
||||||
|
UnaryOperator::Typeof => {
|
||||||
|
self.try_fold_typeof(unary_expr.span, &unary_expr.argument)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
if let Some(folded_expr) = folded_expr {
|
if let Some(folded_expr) = folded_expr {
|
||||||
|
|
@ -136,4 +143,41 @@ impl<'a> Compressor<'a> {
|
||||||
}
|
}
|
||||||
Tri::Unknown
|
Tri::Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Folds 'typeof(foo)' if foo is a literal, e.g.
|
||||||
|
/// typeof("bar") --> "string"
|
||||||
|
/// typeof(6) --> "number"
|
||||||
|
fn try_fold_typeof<'b>(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
argument: &'b Expression<'a>,
|
||||||
|
) -> Option<Expression<'a>> {
|
||||||
|
if argument.is_literal_value(true) {
|
||||||
|
let type_name = match argument {
|
||||||
|
Expression::FunctionExpression(_) | Expression::ArrowExpression(_) => {
|
||||||
|
Some("function")
|
||||||
|
}
|
||||||
|
Expression::StringLiteral(_) | Expression::TemplateLiteral(_) => Some("string"),
|
||||||
|
Expression::NumberLiteral(_) => Some("number"),
|
||||||
|
Expression::BooleanLiteral(_) => Some("boolean"),
|
||||||
|
Expression::NullLiteral(_)
|
||||||
|
| Expression::ObjectExpression(_)
|
||||||
|
| Expression::ArrayExpression(_) => Some("object"),
|
||||||
|
Expression::Identifier(_) if argument.is_undefined() => Some("undefined"),
|
||||||
|
Expression::UnaryExpression(unary_expr)
|
||||||
|
if unary_expr.operator == UnaryOperator::Void =>
|
||||||
|
{
|
||||||
|
Some("undefined")
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(type_name) = type_name {
|
||||||
|
let string_literal = self.hir.string_literal(span, Atom::from(type_name));
|
||||||
|
return Some(self.hir.literal_string_expression(string_literal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,27 @@
|
||||||
//! <https://github.com/google/closure-compiler/blob/master/test/com/google/javascript/jscomp/PeepholeFoldConstantsTest.java>
|
//! <https://github.com/google/closure-compiler/blob/master/test/com/google/javascript/jscomp/PeepholeFoldConstantsTest.java>
|
||||||
|
|
||||||
use crate::test;
|
use crate::{test, test_same};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn undefined_comparison1() {
|
fn undefined_comparison1() {
|
||||||
test("undefined == undefined", "!0");
|
test("undefined == undefined", "!0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn js_typeof() {
|
||||||
|
test("x = typeof 1", "x='number'");
|
||||||
|
test("x = typeof 'foo'", "x='string'");
|
||||||
|
test("x = typeof true", "x='boolean'");
|
||||||
|
test("x = typeof false", "x='boolean'");
|
||||||
|
test("x = typeof null", "x='object'");
|
||||||
|
test("x = typeof undefined", "x='undefined'");
|
||||||
|
test("x = typeof void 0", "x='undefined'");
|
||||||
|
test("x = typeof []", "x='object'");
|
||||||
|
test("x = typeof [1]", "x='object'");
|
||||||
|
test("x = typeof [1,[]]", "x='object'");
|
||||||
|
test("x = typeof {}", "x='object'");
|
||||||
|
test("x = typeof function() {}", "x='function'");
|
||||||
|
|
||||||
|
test_same("x=typeof [1,[foo()]]");
|
||||||
|
test_same("x=typeof {bathwater:baby()}");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Original -> Minified -> Gzip Brotli
|
||||||
|
|
||||||
555.77 kB -> 274.97 kB -> 91.38 kB 77.41 kB d3.js
|
555.77 kB -> 274.97 kB -> 91.38 kB 77.41 kB d3.js
|
||||||
|
|
||||||
977.19 kB -> 456.46 kB -> 123.82 kB 107.39 kB bundle.min.js
|
977.19 kB -> 456.45 kB -> 123.82 kB 107.40 kB bundle.min.js
|
||||||
|
|
||||||
1.25 MB -> 677.77 kB -> 166.79 kB 135.23 kB three.js
|
1.25 MB -> 677.77 kB -> 166.79 kB 135.23 kB three.js
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue