mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(codegen): drop redundant space after export default when minifying (#23024)
## What
Found with a self-written whitespace analyzer (for each space in oxc's minified output, remove it and check the result still canonicalizes to the same AST — if so the space was non-optimal). The most common hit:
`ExportDefaultDeclaration` printed `"export default "` with an unconditional trailing space, kept before non-identifier values when minifying:
| Input | Before | After |
| --- | --- | --- |
| `export default {a:1}` | `export default {a:1}` | `export default{a:1}` |
| `export default [1]` | `export default [1]` | `export default[1]` |
| `export default (a,b)` | `export default (a,b)` | `export default(a,b)` |
| `export default "s"` | `export default "s"` | ``export default`s` `` |
## Fix
Use a soft space (kept when pretty-printing) and let the value insert a hard space only before an identifier-like token — matching how `return` already works, and esbuild's `SExportDefault` printer (`print("export default")` + `printSpace()`).
Spaces that are required are preserved: `export default function(){}`, `export default 5`, `export default foo`, `export default class C{}`.
### TypeScript
Exposed a latent bug: `TSInterfaceDeclaration` printed `interface` without a `print_space_before_identifier()` guard, so `export default interface I {}` would have minified to `export defaultinterface`. Added the guard (same fix as #23013 for enums). `export default interface`, `export default abstract class`, and `export default <const>x` all verified to round-trip.
Pretty-print output is unchanged. Codegen (115) and minifier (506) suites pass.
This commit is contained in:
3 files changed
+21
-1
No files matched your search
@@ -1216,7 +1216,8 @@ impl Gen for ExportDefaultDeclaration<'_> {
|
||||
}
|
||||
p.print_indent();
|
||||
p.add_source_mapping(self.span);
|
||||
p.print_str("export default ");
|
||||
p.print_str("export default");
|
||||
p.print_soft_space();
|
||||
self.declaration.print(p, ctx);
|
||||
}
|
||||
}
|
||||
@@ -3930,6 +3931,7 @@ impl Gen for TSTypeAliasDeclaration<'_> {
|
||||
|
||||
impl Gen for TSInterfaceDeclaration<'_> {
|
||||
fn r#gen(&self, p: &mut Codegen, ctx: Context) {
|
||||
p.print_space_before_identifier();
|
||||
if self.declare {
|
||||
p.print_str("declare ");
|
||||
}
|
||||
|
||||
@@ -45,6 +45,13 @@ fn module_decl() {
|
||||
"export { default } from './foo.js' with { type: 'json' }",
|
||||
"export{default}from\"./foo.js\"with{type:\"json\"};",
|
||||
);
|
||||
|
||||
test_minify("export default {a:1}", "export default{a:1};");
|
||||
test_minify("export default [1]", "export default[1];");
|
||||
test_minify("export default (a,b)", "export default(a,b);");
|
||||
test_minify("export default 5", "export default 5;");
|
||||
test_minify("export default foo", "export default foo;");
|
||||
test_minify("export default function(){}", "export default function(){}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -181,6 +181,17 @@ export import b = require("b");
|
||||
snapshot_options("minify", &cases, &CodegenOptions::minify());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minify_export_default() {
|
||||
let min = |src: &str, expected: &str| {
|
||||
test_options_with_source_type(src, expected, SourceType::ts(), CodegenOptions::minify());
|
||||
};
|
||||
// A leading `interface`/`abstract`/keyword needs the space; `{`/`<` does not.
|
||||
min("export default interface I { x: number }", "export default interface I{x:number;}");
|
||||
min("export default abstract class {}", "export default abstract class{}");
|
||||
min("export default <const>x;", "export default<const>x;");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ts_as_expression_in_binary_expr() {
|
||||
test_idempotency("key in (that as object)");
|
||||
|
||||
Reference in new issue
Block a user