Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/oxc_minifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ oxc_codegen = { workspace = true }
oxc_ecmascript = { workspace = true }
oxc_mangler = { workspace = true }
oxc_parser = { workspace = true }
oxc_regular_expression = { workspace = true }
oxc_semantic = { workspace = true }
oxc_span = { workspace = true }
oxc_syntax = { workspace = true }
Expand Down
93 changes: 82 additions & 11 deletions crates/oxc_minifier/src/peephole/replace_known_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ use std::borrow::Cow;

use cow_utils::CowUtils;

use oxc_allocator::TakeIn;
use oxc_allocator::{Box, TakeIn};
use oxc_ast::ast::*;
use oxc_ecmascript::{
StringCharAt, StringCharAtResult, ToBigInt, ToIntegerIndex,
constant_evaluation::{ConstantEvaluation, DetermineValueType},
side_effects::MayHaveSideEffects,
};
use oxc_regular_expression::{
RegexUnsupportedPatterns, has_unsupported_regular_expression_pattern,
};
use oxc_span::SPAN;
use oxc_syntax::es_target::ESTarget;
use oxc_traverse::Ancestor;
Expand Down Expand Up @@ -367,11 +370,15 @@ impl<'a> PeepholeOptimizations {

let (name, object, span) = match node {
Expression::StaticMemberExpression(member) if !member.optional => {
(member.property.name.as_str(), &member.object, member.span)
let span = member.span;
(member.property.name.as_str(), &mut member.object, span)
}
Expression::ComputedMemberExpression(member) if !member.optional => {
match &member.expression {
Expression::StringLiteral(s) => (s.value.as_str(), &member.object, member.span),
Expression::StringLiteral(s) => {
let span = member.span;
(s.value.as_str(), &mut member.object, span)
}
Expression::NumericLiteral(n) => {
if let Some(integer_index) = n.value.to_integer_index() {
let span = member.span;
Expand Down Expand Up @@ -411,15 +418,60 @@ impl<'a> PeepholeOptimizations {
}
_ => return,
};
let Expression::Identifier(ident) = object else { return };

if !ctx.is_global_reference(ident) {
return;
}

let replacement = match ident.name.as_str() {
"Number" => Self::try_fold_number_constants(name, span, ctx),
_ => None,
let replacement = match object {
Expression::Identifier(ident) => {
if !ctx.is_global_reference(ident) {
return;
}
match ident.name.as_str() {
"Number" => Self::try_fold_number_constants(name, span, ctx),
_ => None,
}
}
Expression::RegExpLiteral(regex) => match name {
"source" => {
const ES2015_UNSUPPORTED_FLAGS: RegExpFlags = RegExpFlags::G
.union(RegExpFlags::I)
.union(RegExpFlags::M)
.union(RegExpFlags::S)
.union(RegExpFlags::Y)
.complement();
const ES2015_UNSUPPORTED_PATTERNS: RegexUnsupportedPatterns =
RegexUnsupportedPatterns {
look_behind_assertions: true,
named_capture_groups: true,
unicode_property_escapes: true,
pattern_modifiers: true,
};

if regex.regex.pattern.pattern.is_none()
&& let Ok(pattern) = regex.parse_pattern(ctx.ast.allocator)
{
regex.regex.pattern.pattern = Some(Box::new_in(pattern, ctx.ast.allocator));
}
if let Some(pattern) = &regex.regex.pattern.pattern
// for now, only replace regexes that are supported by ES2015 to preserve the syntax error
// we can check whether each feature is supported for the target range to improve this
&& regex.regex.flags.intersection(ES2015_UNSUPPORTED_FLAGS).is_empty()
&& !has_unsupported_regular_expression_pattern(
pattern,
&ES2015_UNSUPPORTED_PATTERNS,
)
{
Some(ctx.ast.expression_string_literal(
span,
regex.regex.pattern.text,
None,
))
} else {
// the pattern might be invalid, keep it as-is to preserve the error
None
}
}
_ => None,
},
_ => return,
};
if let Some(replacement) = replacement {
ctx.state.changed = true;
Expand Down Expand Up @@ -556,15 +608,18 @@ mod test {
tester::{test, test_options, test_same},
};

#[track_caller]
fn test_es2015(code: &str, expected: &str) {
let options = CompressOptions { target: ESTarget::ES2015, ..CompressOptions::default() };
test_options(code, expected, &options);
}

#[track_caller]
fn test_value(code: &str, expected: &str) {
test(format!("x = {code}").as_str(), format!("x = {expected}").as_str());
}

#[track_caller]
fn test_same_value(code: &str) {
test_same(format!("x = {code}").as_str());
}
Expand Down Expand Up @@ -1778,4 +1833,20 @@ mod test {
test_same_value("isFinite(unknown)");
test_same_value("isFinite((foo, 0))"); // foo may have sideeffect
}

#[test]
fn test_fold_regex_source() {
test_value("/abc def/.source", "'abc def'");
test_value("/\\d+/.source", "'\\\\d+'");
test_value("/[a-z]/.source", "'[a-z]'");
test_value("/a|b/.source", "'a|b'");
test_value("/^test$/.source", "'^test$'");
test_value("/./.source", "'.'");
test_value("/.*/.source", "'.*'");

test_value("/abc def/i.source", "'abc def'");
test_same_value("/(/.source"); // this regex is invalid
test_value("/\\u{}/.source", "'\\\\u{}'");
test_same_value("/\\u{}/u.source"); // this regex is invalid, also u flag is not supported by ES2015
}
}
8 changes: 4 additions & 4 deletions tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
-------------------------------------------------------------------------------------
72.14 kB | 23.21 kB | 23.70 kB | 8.38 kB | 8.54 kB | 2 | react.development.js

173.90 kB | 59.45 kB | 59.82 kB | 19.16 kB | 19.33 kB | 2 | moment.js
173.90 kB | 59.44 kB | 59.82 kB | 19.16 kB | 19.33 kB | 2 | moment.js

287.63 kB | 89.29 kB | 90.07 kB | 30.95 kB | 31.95 kB | 2 | jquery.js
287.63 kB | 89.28 kB | 90.07 kB | 30.94 kB | 31.95 kB | 2 | jquery.js

342.15 kB | 117.01 kB | 118.14 kB | 43.19 kB | 44.37 kB | 2 | vue.js

Expand All @@ -15,13 +15,13 @@ Original | minified | minified | gzip | gzip | Iterations | Fi

1.01 MB | 439.58 kB | 458.89 kB | 122.15 kB | 126.71 kB | 2 | bundle.min.js

1.25 MB | 645.65 kB | 646.76 kB | 159.55 kB | 163.73 kB | 2 | three.js
1.25 MB | 645.63 kB | 646.76 kB | 159.54 kB | 163.73 kB | 2 | three.js

2.14 MB | 713.54 kB | 724.14 kB | 161.00 kB | 181.07 kB | 2 | victory.js

3.20 MB | 1.00 MB | 1.01 MB | 323.12 kB | 331.56 kB | 3 | echarts.js

6.69 MB | 2.22 MB | 2.31 MB | 459.29 kB | 488.28 kB | 4 | antd.js

10.95 MB | 3.34 MB | 3.49 MB | 855.41 kB | 915.50 kB | 4 | typescript.js
10.95 MB | 3.34 MB | 3.49 MB | 855.39 kB | 915.50 kB | 4 | typescript.js

2 changes: 1 addition & 1 deletion tasks/track_memory_allocations/allocs_minifier.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ RadixUIAdoptionSection.jsx | 2.52 kB || 75 | 4 |

pdf.mjs | 567.30 kB || 19577 | 2898 || 47403 | 7782 | 1.624 MB

antd.js | 6.69 MB || 99857 | 13518 || 331723 | 70354 | 17.408 MB
antd.js | 6.69 MB || 99857 | 13518 || 331724 | 70354 | 17.408 MB

Loading