diff --git a/Cargo.lock b/Cargo.lock index abfe30b68d7a7..02fc94ea40f11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2030,6 +2030,7 @@ dependencies = [ name = "oxc_minifier" version = "0.77.2" dependencies = [ + "base64", "cow-utils", "insta", "oxc_allocator", diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 96404e0456426..a16d274786a58 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -188,7 +188,6 @@ impl Gen for Statement<'_> { impl Gen for ExpressionStatement<'_> { fn r#gen(&self, p: &mut Codegen, _ctx: Context) { p.print_comments_at(self.span.start); - p.add_source_mapping(self.span); p.print_indent(); p.start_of_stmt = p.code_len(); p.print_expression(&self.expression); @@ -1388,7 +1387,7 @@ impl GenExpr for CallExpression<'_> { let is_export_default = p.start_of_default_export == p.code_len(); let mut wrap = precedence >= Precedence::New || ctx.intersects(Context::FORBID_CALL); let pure = self.pure && p.options.print_annotation_comment(); - if precedence >= Precedence::Postfix && pure { + if !wrap && pure && precedence >= Precedence::Postfix { wrap = true; } @@ -1401,7 +1400,6 @@ impl GenExpr for CallExpression<'_> { } else if is_statement { p.start_of_stmt = p.code_len(); } - p.add_source_mapping(self.span); self.callee.print_expr(p, Precedence::Postfix, Context::empty()); if self.optional { p.print_str("?."); @@ -1727,10 +1725,12 @@ impl GenExpr for UnaryExpression<'_> { let operator = self.operator.as_str(); if self.operator.is_keyword() { p.print_space_before_identifier(); + p.add_source_mapping(self.span); p.print_str(operator); p.print_soft_space(); } else { p.print_space_before_operator(self.operator.into()); + p.add_source_mapping(self.span); p.print_str(operator); p.prev_op = Some(self.operator.into()); p.prev_op_end = p.code().len(); diff --git a/crates/oxc_codegen/src/sourcemap_builder.rs b/crates/oxc_codegen/src/sourcemap_builder.rs index d2497e334001c..456e8fe759b96 100644 --- a/crates/oxc_codegen/src/sourcemap_builder.rs +++ b/crates/oxc_codegen/src/sourcemap_builder.rs @@ -119,7 +119,7 @@ impl<'a> SourcemapBuilder<'a> { } pub fn add_source_mapping(&mut self, output: &[u8], position: u32, name: Option<&str>) { - if matches!(self.last_position, Some(last_position) if last_position == position) { + if self.last_position == Some(position) { return; } let (original_line, original_column) = self.search_original_line_and_column(position); diff --git a/crates/oxc_minifier/Cargo.toml b/crates/oxc_minifier/Cargo.toml index 3c33df317f22a..62bed90abd85a 100644 --- a/crates/oxc_minifier/Cargo.toml +++ b/crates/oxc_minifier/Cargo.toml @@ -40,5 +40,6 @@ rustc-hash = { workspace = true } [dev-dependencies] oxc_parser = { workspace = true } +base64 = { workspace = true } insta = { workspace = true } pico-args = { workspace = true } diff --git a/crates/oxc_minifier/examples/minifier.rs b/crates/oxc_minifier/examples/minifier.rs index 2c0270124f935..c487a6fd9d547 100644 --- a/crates/oxc_minifier/examples/minifier.rs +++ b/crates/oxc_minifier/examples/minifier.rs @@ -1,13 +1,15 @@ #![expect(clippy::print_stdout)] -use std::path::Path; +use std::path::{Path, PathBuf}; + +use base64::{Engine, prelude::BASE64_STANDARD}; +use pico_args::Arguments; use oxc_allocator::Allocator; -use oxc_codegen::{Codegen, CodegenOptions, CommentOptions}; +use oxc_codegen::{Codegen, CodegenOptions, CodegenReturn, CommentOptions}; use oxc_mangler::MangleOptions; use oxc_minifier::{CompressOptions, Minifier, MinifierOptions}; use oxc_parser::Parser; use oxc_span::SourceType; -use pico_args::Arguments; // Instruction: // create a `test.js`, @@ -19,19 +21,34 @@ fn main() -> std::io::Result<()> { let mangle = args.contains("--mangle"); let nospace = args.contains("--nospace"); let twice = args.contains("--twice"); + let sourcemap = args.contains("--sourcemap"); let name = args.free_from_str().unwrap_or_else(|_| "test.js".to_string()); let path = Path::new(&name); let source_text = std::fs::read_to_string(path)?; let source_type = SourceType::from_path(path).unwrap(); + let source_map_path = sourcemap.then(|| path.to_path_buf()); let mut allocator = Allocator::default(); - let printed = minify(&allocator, &source_text, source_type, mangle, nospace); + let ret = minify(&allocator, &source_text, source_type, source_map_path, mangle, nospace); + let printed = ret.code; println!("{printed}"); + if let Some(source_map) = ret.map { + let result = source_map.to_json_string(); + let hash = BASE64_STANDARD.encode(format!( + "{}\0{}{}\0{}", + printed.len(), + printed, + result.len(), + result + )); + println!("https://evanw.github.io/source-map-visualization/#{hash}"); + } + if twice { allocator.reset(); - let printed2 = minify(&allocator, &printed, source_type, mangle, nospace); + let printed2 = minify(&allocator, &printed, source_type, None, mangle, nospace).code; println!("{printed2}"); println!("same = {}", printed == printed2); } @@ -43,9 +60,10 @@ fn minify( allocator: &Allocator, source_text: &str, source_type: SourceType, + source_map_path: Option, mangle: bool, nospace: bool, -) -> String { +) -> CodegenReturn { let ret = Parser::new(allocator, source_text, source_type).parse(); let mut program = ret.program; let options = MinifierOptions { @@ -55,11 +73,11 @@ fn minify( let ret = Minifier::new(options).build(allocator, &mut program); Codegen::new() .with_options(CodegenOptions { + source_map_path, minify: nospace, comments: CommentOptions::disabled(), ..CodegenOptions::default() }) .with_scoping(ret.scoping) .build(&program) - .code } diff --git a/crates/oxc_transformer_plugins/tests/integrations/snapshots/integrations__replace_global_defines__test_sourcemap.snap b/crates/oxc_transformer_plugins/tests/integrations/snapshots/integrations__replace_global_defines__test_sourcemap.snap index 4afd585ccd8dd..484cef6b9160f 100644 --- a/crates/oxc_transformer_plugins/tests/integrations/snapshots/integrations__replace_global_defines__test_sourcemap.snap +++ b/crates/oxc_transformer_plugins/tests/integrations/snapshots/integrations__replace_global_defines__test_sourcemap.snap @@ -4,9 +4,7 @@ expression: snapshot --- - test.js.map (0:0) "1;\n" --> (0:0) "1;\n" -(1:0) "__OBJECT__;\n" --> (1:0) "({ 'hello': 'test' });\n" (2:0) "2;\n" --> (2:0) "2;\n" -(3:0) "__STRING__;\n" --> (3:0) "'development';\n" (4:0) "3;\n" --> (4:0) "3;\n" (5:0) "log(__OBJECT__)" --> (5:0) "log({ 'hello': 'test' })" (5:15) ";\n" --> (5:24) ";\n" @@ -14,7 +12,6 @@ expression: snapshot (7:0) "log(__STRING__)" --> (7:0) "log('development')" (7:15) ";\n" --> (7:18) ";\n" (8:0) "5;\n" --> (8:0) "5;\n" -(9:0) "__OBJECT__." --> (9:0) "({ 'hello': 'test' })." (9:11) "hello;\n" --> (9:22) "hello;\n" (10:0) "6;\n" --> (10:0) "6;\n" (11:0) "log(__MEMBER__)" --> (11:0) "log(xx.yy.zz)" diff --git a/napi/minify/test/minify.test.ts b/napi/minify/test/minify.test.ts index 2cce28d64842d..db996673ace3c 100644 --- a/napi/minify/test/minify.test.ts +++ b/napi/minify/test/minify.test.ts @@ -11,7 +11,7 @@ describe('simple', () => { expect(ret).toStrictEqual({ 'code': 'function foo(){var e;e(void 0)}foo();', 'map': { - 'mappings': 'AACA,SAAS,KAAM,CAAE,IAAIA,EAAK,SAAc,AAAE,CAAC,KAAK', + 'mappings': 'AACA,SAAS,KAAM,CAAE,IAAIA,EAAKA,EAAI,OAAU,AAAE,CAAC,KAAK', 'names': [ 'bar', ], diff --git a/napi/transform/test/moduleRunnerTransform.test.ts b/napi/transform/test/moduleRunnerTransform.test.ts index c052bc2cab906..b7fd9d66a4083 100644 --- a/napi/transform/test/moduleRunnerTransform.test.ts +++ b/napi/transform/test/moduleRunnerTransform.test.ts @@ -32,7 +32,7 @@ describe('moduleRunnerTransform', () => { expect(map).toMatchInlineSnapshot(` { - "mappings": "AAAA;;;;;;;AAAO,MAAM,IAAI", + "mappings": ";;;;;;;AAAO,MAAM,IAAI", "names": [], "sources": [ "index.js",