From b4ee61702852260da12c4ccbee9fcd89b87bd74d Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Mon, 3 Feb 2025 07:21:18 +0000 Subject: [PATCH] feat(codegen): prefer backquotes over double / single quotes (#8839) When thinking about why #8443 made the gzip size increased, I thought it maybe because the backquotes appear more than before the PR. So I changed the codegen to prefer backquotes than double/single quotes. It seems this separate change have a positive effect with some projects. My guess is that in most projects, back quotes does not appear in string literals than double quotes and single quotes. The gzipped size slightly increases for `bundle.min.js`, `victory.js`, `antd.js`, but those will be decreased by #8443 except for `bundle.min.js`. --- crates/oxc_codegen/src/gen.rs | 32 ++++++--- crates/oxc_codegen/src/lib.rs | 12 ++-- .../tests/integration/snapshots/minify.snap | 69 ++++++++++++------- .../tests/integration/snapshots/ts.snap | 58 +++++++++++++--- crates/oxc_codegen/tests/integration/ts.rs | 8 +++ crates/oxc_codegen/tests/integration/unit.rs | 12 ++-- tasks/minsize/minsize.snap | 20 +++--- 7 files changed, 146 insertions(+), 65 deletions(-) diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 5edbe14f73fed..9e8cd4194e8a1 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -952,7 +952,7 @@ impl Gen for ImportDeclaration<'_> { p.print_str("from"); } p.print_soft_space(); - self.source.print(p, ctx); + p.print_quoted_utf16(&self.source.value, false); if let Some(with_clause) = &self.with_clause { p.print_soft_space(); with_clause.print(p, ctx); @@ -980,16 +980,18 @@ impl Gen for WithClause<'_> { } impl Gen for ImportAttribute<'_> { - fn gen(&self, p: &mut Codegen, ctx: Context) { + fn gen(&self, p: &mut Codegen, _ctx: Context) { match &self.key { ImportAttributeKey::Identifier(identifier) => { p.print_str(identifier.name.as_str()); } - ImportAttributeKey::StringLiteral(literal) => literal.print(p, ctx), + ImportAttributeKey::StringLiteral(literal) => { + p.print_quoted_utf16(&literal.value, false); + } }; p.print_colon(); p.print_soft_space(); - self.value.print(p, ctx); + p.print_quoted_utf16(&self.value.value, false); } } @@ -1055,7 +1057,7 @@ impl Gen for ExportNamedDeclaration<'_> { p.print_soft_space(); p.print_str("from"); p.print_soft_space(); - source.print(p, ctx); + p.print_quoted_utf16(&source.value, false); } p.print_semicolon_after_statement(); } @@ -1111,7 +1113,7 @@ impl Gen for ModuleExportName<'_> { match self { Self::IdentifierName(ident) => ident.print(p, ctx), Self::IdentifierReference(ident) => ident.print(p, ctx), - Self::StringLiteral(literal) => literal.print(p, ctx), + Self::StringLiteral(literal) => p.print_quoted_utf16(&literal.value, false), }; } } @@ -1139,7 +1141,7 @@ impl Gen for ExportAllDeclaration<'_> { p.print_str("from"); p.print_soft_space(); - self.source.print(p, ctx); + p.print_quoted_utf16(&self.source.value, false); if let Some(with_clause) = &self.with_clause { p.print_hard_space(); with_clause.print(p, ctx); @@ -3451,6 +3453,9 @@ impl Gen for TSSignature<'_> { PropertyKey::PrivateIdentifier(key) => { p.print_str(key.name.as_str()); } + PropertyKey::StringLiteral(key) => { + p.print_quoted_utf16(&key.value, false); + } key => { key.to_expression().print_expr(p, Precedence::Comma, ctx); } @@ -3499,6 +3504,9 @@ impl Gen for TSPropertySignature<'_> { PropertyKey::PrivateIdentifier(key) => { p.print_str(key.name.as_str()); } + PropertyKey::StringLiteral(key) => { + p.print_quoted_utf16(&key.value, false); + } key => { key.to_expression().print_expr(p, Precedence::Comma, ctx); } @@ -3585,7 +3593,9 @@ impl Gen for TSImportAttributeName<'_> { fn gen(&self, p: &mut Codegen, ctx: Context) { match self { TSImportAttributeName::Identifier(ident) => ident.print(p, ctx), - TSImportAttributeName::StringLiteral(literal) => literal.print(p, ctx), + TSImportAttributeName::StringLiteral(literal) => { + p.print_quoted_utf16(&literal.value, false); + } } } } @@ -3689,7 +3699,7 @@ impl Gen for TSModuleDeclarationName<'_> { fn gen(&self, p: &mut Codegen, ctx: Context) { match self { Self::Identifier(ident) => ident.print(p, ctx), - Self::StringLiteral(s) => s.print(p, ctx), + Self::StringLiteral(s) => p.print_quoted_utf16(&s.value, false), } } } @@ -3793,7 +3803,7 @@ impl Gen for TSEnumMember<'_> { fn gen(&self, p: &mut Codegen, ctx: Context) { match &self.id { TSEnumMemberName::Identifier(decl) => decl.print(p, ctx), - TSEnumMemberName::String(decl) => decl.print(p, ctx), + TSEnumMemberName::String(decl) => p.print_quoted_utf16(&decl.value, false), } if let Some(init) = &self.initializer { p.print_soft_space(); @@ -3837,7 +3847,7 @@ impl Gen for TSModuleReference<'_> { match self { Self::ExternalModuleReference(decl) => { p.print_str("require("); - decl.expression.print(p, ctx); + p.print_quoted_utf16(&decl.expression.value, false); p.print_str(")"); } match_ts_type_name!(Self) => self.to_ts_type_name().print(p, ctx), diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index dc00fa3e82766..5101c8fe7d596 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -612,13 +612,13 @@ impl<'a> Codegen<'a> { } } let mut quote = b'"'; - if double_cost > single_cost { - quote = b'\''; - if single_cost > backtick_cost && allow_backtick { - quote = b'`'; - } - } else if double_cost > backtick_cost && allow_backtick { + if allow_backtick && double_cost >= backtick_cost { quote = b'`'; + if backtick_cost > single_cost { + quote = b'\''; + } + } else if double_cost > single_cost { + quote = b'\''; } quote } else { diff --git a/crates/oxc_codegen/tests/integration/snapshots/minify.snap b/crates/oxc_codegen/tests/integration/snapshots/minify.snap index d6ef98ab1a54a..c15eb2163df80 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/minify.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/minify.snap @@ -10,43 +10,43 @@ function foo(x: T, y: string, ...restOfParams: Omit): return x; } ---------- -function foo(x:T,y:string,...restOfParams:Omit): T{return x} +function foo(x:T,y:string,...restOfParams:Omit): T{return x} ########## 2 let x: string[] = ['abc', 'def', 'ghi']; ---------- -let x:string[]=["abc","def","ghi"]; +let x:string[]=[`abc`,`def`,`ghi`]; ########## 3 let x: Array = ['abc', 'def', 'ghi',]; ---------- -let x:Array=["abc","def","ghi"]; +let x:Array=[`abc`,`def`,`ghi`]; ########## 4 let x: [string, number] = ['abc', 123]; ---------- -let x:[string,number]=["abc",123]; +let x:[string,number]=[`abc`,123]; ########## 5 let x: string | number = 'abc'; ---------- -let x:string|number="abc"; +let x:string|number=`abc`; ########## 6 let x: string & number = 'abc'; ---------- -let x:string&number="abc"; +let x:string&number=`abc`; ########## 7 let x: typeof String = 'string'; ---------- -let x:typeof String="string"; +let x:typeof String=`string`; ########## 8 let x: keyof string = 'length'; ---------- -let x:keyof string="length"; +let x:keyof string=`length`; ########## 9 let x: keyof typeof String = 'length'; ---------- -let x:keyof typeof String="length"; +let x:keyof typeof String=`length`; ########## 10 let x: string['length'] = 123; ---------- -let x:string["length"]=123; +let x:string[`length`]=123; ########## 11 function isString(value: unknown): asserts value is string { if (typeof value !== 'string') { @@ -54,7 +54,7 @@ function isString(value: unknown): asserts value is string { } } ---------- -function isString(value:unknown): asserts value is string{if(typeof value!=="string"){throw new Error("Not a string")}} +function isString(value:unknown): asserts value is string{if(typeof value!==`string`){throw new Error(`Not a string`)}} ########## 12 import type { Foo } from 'foo'; ---------- @@ -74,7 +74,7 @@ type A={[K in keyof T as K extends string ? B : K]:T[K]}; ########## 16 class A {readonly type = 'frame'} ---------- -class A{readonly type="frame"} +class A{readonly type=`frame`} ########## 17 let foo: { (t: T): void } ---------- @@ -104,34 +104,50 @@ abstract class A {private abstract static readonly prop: string} ---------- abstract class A{private abstract static readonly prop:string} ########## 24 +interface A { a: string, 'b': number, 'c'(): void } +---------- +interface A{a:string;"b":number;"c"():void;} +########## 25 +enum A { a, 'b' } +---------- +enum A {a,"b",} +########## 26 +module 'a' +---------- +module "a" +########## 27 +declare module 'a' +---------- +declare module "a" +########## 28 a = x!; ---------- a=x! ; -########## 25 +########## 29 b = (x as y); ---------- b=x as y; -########## 26 +########## 30 c = foo; ---------- c=foo ; -########## 27 +########## 31 d = x satisfies y; ---------- d=((x) satisfies y); -########## 28 +########## 32 export @x declare abstract class C {} ---------- export @x declare abstract class C{} -########## 29 +########## 33 div`` ---------- div``; -########## 30 +########## 34 export type Component = Foo; ---------- export type Component=Foo; -########## 31 +########## 35 export type Component< Props = any, @@ -147,19 +163,19 @@ export type Component< ---------- export type Component = {},S extends Record = any>=ConcreteComponent|ComponentPublicInstanceConstructor; -########## 32 +########## 36 (a || b) as any ---------- (a||b) as any; -########## 33 +########## 37 (a ** b) as any ---------- (a**b) as any; -########## 34 +########## 38 (function g() {}) as any ---------- (function g(){}) as any; -########## 35 +########## 39 import defaultExport from "module-name"; import * as name from "module-name"; @@ -196,3 +212,10 @@ export { default as name16 } from "module-name"; ---------- import defaultExport from"module-name";import*as name from"module-name";import{export1}from"module-name";import{export1 as alias1}from"module-name";import{default as alias}from"module-name";import{export1,export2}from"module-name";import{export1,export2 as alias2}from"module-name";import{"string name" as alias}from"module-name";import defaultExport,{export1}from"module-name";import defaultExport,*as name from"module-name";import"module-name";import{}from"mod";export let name1,name2;export const name3=1,name4=2;export function functionName(){}export class ClassName{}export function*generatorFunctionName(){}export const{name5,name2:bar}=o;export const[name6,name7]=array;export{name8,name81};export{variable1 as name9,variable2 as name10,name82};export{variable1 as "string name"};export{name1 as default1};export*from"module-name";export*as name11 from"module-name";export{name12,nameN}from"module-name";export{import1 as name13,import2 as name14,name15}from"module-name";export{default}from"module-name";export{default as name16}from"module-name"; +########## 40 + +import a = require("a"); +export import b = require("b"); + +---------- +import a = require("a");export import b = require("b"); diff --git a/crates/oxc_codegen/tests/integration/snapshots/ts.snap b/crates/oxc_codegen/tests/integration/snapshots/ts.snap index e9b95683e23a7..3fe8b318658cb 100644 --- a/crates/oxc_codegen/tests/integration/snapshots/ts.snap +++ b/crates/oxc_codegen/tests/integration/snapshots/ts.snap @@ -156,41 +156,68 @@ abstract class A { } ########## 24 +interface A { a: string, 'b': number, 'c'(): void } +---------- +interface A { + a: string; + 'b': number; + 'c'(): void; +} + +########## 25 +enum A { a, 'b' } +---------- +enum A { + a, + 'b', +} + +########## 26 +module 'a' +---------- +module 'a' + +########## 27 +declare module 'a' +---------- +declare module 'a' + +########## 28 a = x!; ---------- a = x!; -########## 25 +########## 29 b = (x as y); ---------- b = x as y; -########## 26 +########## 30 c = foo; ---------- c = foo; -########## 27 +########## 31 d = x satisfies y; ---------- d = ((x) satisfies y); -########## 28 +########## 32 export @x declare abstract class C {} ---------- export @x declare abstract class C {} -########## 29 +########## 33 div`` ---------- div``; -########## 30 +########## 34 export type Component = Foo; ---------- export type Component = Foo; -########## 31 +########## 35 export type Component< Props = any, @@ -215,22 +242,22 @@ export type Component< S extends Record = any > = ConcreteComponent | ComponentPublicInstanceConstructor; -########## 32 +########## 36 (a || b) as any ---------- (a || b) as any; -########## 33 +########## 37 (a ** b) as any ---------- (a ** b) as any; -########## 34 +########## 38 (function g() {}) as any ---------- (function g() {}) as any; -########## 35 +########## 39 import defaultExport from "module-name"; import * as name from "module-name"; @@ -295,3 +322,12 @@ export { name12, nameN } from 'module-name'; export { import1 as name13, import2 as name14, name15 } from 'module-name'; export { default } from 'module-name'; export { default as name16 } from 'module-name'; + +########## 40 + +import a = require("a"); +export import b = require("b"); + +---------- +import a = require('a'); +export import b = require('b'); diff --git a/crates/oxc_codegen/tests/integration/ts.rs b/crates/oxc_codegen/tests/integration/ts.rs index 1d9daa5857c72..ff7f06f344b7c 100644 --- a/crates/oxc_codegen/tests/integration/ts.rs +++ b/crates/oxc_codegen/tests/integration/ts.rs @@ -33,6 +33,10 @@ fn ts() { "class A {constructor(public readonly a: number) {}}", "abstract class A {private abstract static m() {}}", "abstract class A {private abstract static readonly prop: string}", + "interface A { a: string, 'b': number, 'c'(): void }", + "enum A { a, 'b' }", + "module 'a'", + "declare module 'a'", "a = x!;", "b = (x as y);", "c = foo;", @@ -89,6 +93,10 @@ export { name12, /* …, */ nameN } from "module-name"; export { import1 as name13, import2 as name14, /* …, */ name15 } from "module-name"; export { default, /* …, */ } from "module-name"; export { default as name16 } from "module-name"; +"#, + r#" +import a = require("a"); +export import b = require("b"); "#, ]; diff --git a/crates/oxc_codegen/tests/integration/unit.rs b/crates/oxc_codegen/tests/integration/unit.rs index 884ab3c38f226..c7673fe7b3e97 100644 --- a/crates/oxc_codegen/tests/integration/unit.rs +++ b/crates/oxc_codegen/tests/integration/unit.rs @@ -17,6 +17,10 @@ fn module_decl() { test("export * from './foo.js' with {}", "export * from \"./foo.js\" with {};\n"); test_minify("export { '☿' } from 'mod';", "export{\"☿\"}from\"mod\";"); test_minify("export { '☿' as '☿' } from 'mod';", "export{\"☿\"}from\"mod\";"); + test_minify( + "import x from './foo.custom' with { 'type': 'json' }", + "import x from\"./foo.custom\"with{\"type\":\"json\"};", + ); } #[test] @@ -27,20 +31,20 @@ fn expr() { test("1000000000000000128.0.toFixed(0)", "0xde0b6b3a7640080.toFixed(0);\n"); test_minify("1000000000000000128.0.toFixed(0)", "0xde0b6b3a7640080.toFixed(0);"); - test_minify("throw 'foo'", "throw\"foo\";"); - test_minify("return 'foo'", "return\"foo\";"); + test_minify("throw 'foo'", "throw`foo`;"); + test_minify("return 'foo'", "return`foo`;"); test_minify("return class {}", "return class{};"); test_minify("return async function foo() {}", "return async function foo(){};"); test_minify_same("return super();"); test_minify_same("return new.target;"); test_minify_same("throw await 1;"); - test_minify_same("await import(\"\");"); + test_minify_same("await import(``);"); test("delete 2e308", "delete (0, Infinity);\n"); test_minify("delete 2e308", "delete(1/0);"); test_minify_same(r#"({"http://a\r\" \n<'b:b@c\r\nd/e?f":{}});"#); - test_minify_same("new(import(\"\"),function(){});"); + test_minify_same("new(import(``),function(){});"); } #[test] diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 3c615bd4e2a0b..32f8eb3e910e3 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -1,27 +1,27 @@ | Oxc | ESBuild | Oxc | ESBuild | Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- -72.14 kB | 23.56 kB | 23.70 kB | 8.51 kB | 8.54 kB | react.development.js +72.14 kB | 23.56 kB | 23.70 kB | 8.50 kB | 8.54 kB | react.development.js -173.90 kB | 59.55 kB | 59.82 kB | 19.19 kB | 19.33 kB | moment.js +173.90 kB | 59.55 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js 287.63 kB | 89.47 kB | 90.07 kB | 30.97 kB | 31.95 kB | jquery.js -342.15 kB | 117.67 kB | 118.14 kB | 43.57 kB | 44.37 kB | vue.js +342.15 kB | 117.67 kB | 118.14 kB | 43.48 kB | 44.37 kB | vue.js -544.10 kB | 71.42 kB | 72.48 kB | 25.87 kB | 26.20 kB | lodash.js +544.10 kB | 71.42 kB | 72.48 kB | 25.86 kB | 26.20 kB | lodash.js 555.77 kB | 271.24 kB | 270.13 kB | 88.34 kB | 90.80 kB | d3.js -1.01 MB | 440.93 kB | 458.89 kB | 122.50 kB | 126.71 kB | bundle.min.js +1.01 MB | 440.93 kB | 458.89 kB | 122.53 kB | 126.71 kB | bundle.min.js -1.25 MB | 650.33 kB | 646.76 kB | 161.01 kB | 163.73 kB | three.js +1.25 MB | 650.33 kB | 646.76 kB | 160.96 kB | 163.73 kB | three.js -2.14 MB | 718.69 kB | 724.14 kB | 162.14 kB | 181.07 kB | victory.js +2.14 MB | 718.69 kB | 724.14 kB | 162.18 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 324.45 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 324.41 kB | 331.56 kB | echarts.js -6.69 MB | 2.30 MB | 2.31 MB | 468.52 kB | 488.28 kB | antd.js +6.69 MB | 2.30 MB | 2.31 MB | 468.57 kB | 488.28 kB | antd.js -10.95 MB | 3.36 MB | 3.49 MB | 862.12 kB | 915.50 kB | typescript.js +10.95 MB | 3.36 MB | 3.49 MB | 862.07 kB | 915.50 kB | typescript.js