diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 13f6f7d8e5e34..6e08e72f28f84 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -1375,31 +1375,27 @@ fn print_unquoted_str(s: &str, quote: u8, p: &mut Codegen) { } '\'' => { if quote == b'\'' { - p.print_str("\\'"); - } else { - p.print_str("'"); + p.print_ascii_byte(b'\\'); } + p.print_ascii_byte(b'\''); } '\"' => { if quote == b'"' { - p.print_str("\\\""); - } else { - p.print_str("\""); + p.print_ascii_byte(b'\\'); } + p.print_ascii_byte(b'"'); } '`' => { if quote == b'`' { - p.print_str("\\`"); - } else { - p.print_str("`"); + p.print_ascii_byte(b'\\'); } + p.print_ascii_byte(b'`'); } '$' => { - if chars.peek().is_some_and(|&next| next == '{') { - p.print_str("\\$"); - } else { - p.print_str("$"); + if chars.peek() == Some(&'{') { + p.print_ascii_byte(b'\\'); } + p.print_ascii_byte(b'$'); } // Allow `U+2028` and `U+2029` in string literals // @@ -1424,22 +1420,40 @@ impl Gen for StringLiteral<'_> { let quote = if p.options.minify { let mut single_cost: u32 = 0; let mut double_cost: u32 = 0; - for b in s.as_bytes() { + let mut backtick_cost: u32 = 0; + let mut bytes = s.as_bytes().iter().peekable(); + while let Some(b) = bytes.next() { match b { + b'\n' if p.options.minify => { + backtick_cost = backtick_cost.saturating_sub(1); + } b'\'' => { single_cost += 1; } b'"' => { double_cost += 1; } + b'`' => { + backtick_cost += 1; + } + b'$' => { + if bytes.peek() == Some(&&b'{') { + backtick_cost += 1; + } + } _ => {} } } + let mut quote = b'"'; if double_cost > single_cost { - b'\'' - } else { - b'"' + quote = b'\''; + if single_cost > backtick_cost { + quote = b'`'; + } + } else if double_cost > backtick_cost { + quote = b'`'; } + quote } else { p.quote }; diff --git a/crates/oxc_codegen/tests/integration/unit.rs b/crates/oxc_codegen/tests/integration/unit.rs index d5f5d9f933def..ac467ede2e8b8 100644 --- a/crates/oxc_codegen/tests/integration/unit.rs +++ b/crates/oxc_codegen/tests/integration/unit.rs @@ -38,6 +38,11 @@ fn expr() { test("delete 2e308", "delete (0, Infinity);\n"); test_minify("delete 2e308", "delete(1/0);"); + + test_minify( + r#";'eval("\'\\vstr\\ving\\v\'") === "\\vstr\\ving\\v"'"#, + r#";`eval("'\\vstr\\ving\\v'") === "\\vstr\\ving\\v"`;"#, + ); } #[test] diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 2bafb223e0892..d5f589c8b3033 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -5,7 +5,7 @@ Original | minified | minified | gzip | gzip | Fixture 173.90 kB | 61.55 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js -287.63 kB | 92.60 kB | 90.07 kB | 32.27 kB | 31.95 kB | jquery.js +287.63 kB | 92.59 kB | 90.07 kB | 32.28 kB | 31.95 kB | jquery.js 342.15 kB | 121.55 kB | 118.14 kB | 44.64 kB | 44.37 kB | vue.js @@ -21,7 +21,7 @@ Original | minified | minified | gzip | gzip | Fixture 3.20 MB | 1.02 MB | 1.01 MB | 332.00 kB | 331.56 kB | echarts.js -6.69 MB | 2.39 MB | 2.31 MB | 495.63 kB | 488.28 kB | antd.js +6.69 MB | 2.39 MB | 2.31 MB | 495.62 kB | 488.28 kB | antd.js -10.95 MB | 3.54 MB | 3.49 MB | 909.72 kB | 915.50 kB | typescript.js +10.95 MB | 3.54 MB | 3.49 MB | 909.73 kB | 915.50 kB | typescript.js