diff --git a/Cargo.lock b/Cargo.lock index 8bdf2f54741e0..16ba468cef009 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2088,7 +2088,6 @@ dependencies = [ name = "oxc_minifier" version = "0.82.2" dependencies = [ - "base64", "cow-utils", "insta", "javascript-globals", @@ -2100,6 +2099,7 @@ dependencies = [ "oxc_mangler", "oxc_parser", "oxc_semantic", + "oxc_sourcemap", "oxc_span", "oxc_syntax", "oxc_traverse", @@ -2469,6 +2469,7 @@ dependencies = [ "oxc_tasks_common", "oxc_transformer", "oxc_traverse", + "pico-args", "rustc-hash", "similar", ] diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 4cba6b821bad7..8407445d47798 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -923,7 +923,6 @@ impl Gen for WithClause<'_> { p.print_soft_space(); } p.print_ascii_byte(b'}'); - p.add_source_mapping_end(self.span); } } @@ -1474,7 +1473,6 @@ impl Gen for ArrayExpression<'_> { p.print_indent(); } p.print_ascii_byte(b']'); - p.add_source_mapping_end(self.span); } } @@ -1517,7 +1515,6 @@ impl GenExpr for ObjectExpression<'_> { p.print_soft_space(); } p.print_ascii_byte(b'}'); - p.add_source_mapping_end(self.span); }); } } @@ -1905,7 +1902,6 @@ impl Gen for ArrayAssignmentTarget<'_> { target.print(p, ctx); } p.print_ascii_byte(b']'); - p.add_source_mapping_end(self.span); } } @@ -1922,7 +1918,6 @@ impl Gen for ObjectAssignmentTarget<'_> { target.print(p, ctx); } p.print_ascii_byte(b'}'); - p.add_source_mapping_end(self.span); } } @@ -2789,7 +2784,6 @@ impl Gen for ObjectPattern<'_> { p.print_soft_space(); } p.print_ascii_byte(b'}'); - p.add_source_mapping_end(self.span); } } @@ -2863,7 +2857,6 @@ impl Gen for ArrayPattern<'_> { rest.print(p, ctx); } p.print_ascii_byte(b']'); - p.add_source_mapping_end(self.span); } } diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index c1ec1bc36f019..908eb801b2c2f 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -514,7 +514,6 @@ impl<'a> Codegen<'a> { self.dedent(); self.print_indent(); } - self.add_source_mapping_end(span); self.print_ascii_byte(b'}'); } @@ -525,10 +524,9 @@ impl<'a> Codegen<'a> { self.indent(); } - fn print_block_end(&mut self, span: Span) { + fn print_block_end(&mut self, _span: Span) { self.dedent(); self.print_indent(); - self.add_source_mapping_end(span); self.print_ascii_byte(b'}'); } @@ -845,14 +843,6 @@ impl<'a> Codegen<'a> { } } - fn add_source_mapping_end(&mut self, span: Span) { - if let Some(sourcemap_builder) = self.sourcemap_builder.as_mut() { - if !span.is_empty() { - sourcemap_builder.add_source_mapping(self.code.as_bytes(), span.end, None); - } - } - } - fn add_source_mapping_for_name(&mut self, span: Span, name: &str) { if let Some(sourcemap_builder) = self.sourcemap_builder.as_mut() { if !span.is_empty() { diff --git a/crates/oxc_minifier/Cargo.toml b/crates/oxc_minifier/Cargo.toml index 470ff2aefbe8e..485685f772de9 100644 --- a/crates/oxc_minifier/Cargo.toml +++ b/crates/oxc_minifier/Cargo.toml @@ -38,8 +38,8 @@ rustc-hash = { workspace = true } [dev-dependencies] oxc_parser = { workspace = true } +oxc_sourcemap = { workspace = true } -base64 = { workspace = true } insta = { workspace = true } javascript-globals = { workspace = true } pico-args = { workspace = true } diff --git a/crates/oxc_minifier/examples/minifier.rs b/crates/oxc_minifier/examples/minifier.rs index 0c437d0faec57..591cbd12811b7 100644 --- a/crates/oxc_minifier/examples/minifier.rs +++ b/crates/oxc_minifier/examples/minifier.rs @@ -20,7 +20,6 @@ use std::path::{Path, PathBuf}; -use base64::{Engine, prelude::BASE64_STANDARD}; use pico_args::Arguments; use oxc_allocator::Allocator; @@ -28,6 +27,7 @@ use oxc_codegen::{Codegen, CodegenOptions, CodegenReturn, CommentOptions}; use oxc_mangler::MangleOptions; use oxc_minifier::{CompressOptions, Minifier, MinifierOptions}; use oxc_parser::Parser; +use oxc_sourcemap::SourcemapVisualizer; use oxc_span::SourceType; // Instruction: @@ -53,16 +53,10 @@ fn main() -> std::io::Result<()> { 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 let Some(map) = ret.map { + let visualizer = SourcemapVisualizer::new(&printed, &map); + println!("{}", visualizer.get_url()); + println!("{}", visualizer.get_text()); } if twice { diff --git a/crates/oxc_transformer_plugins/Cargo.toml b/crates/oxc_transformer_plugins/Cargo.toml index 4e02f928b2c2a..279c942b185b4 100644 --- a/crates/oxc_transformer_plugins/Cargo.toml +++ b/crates/oxc_transformer_plugins/Cargo.toml @@ -38,10 +38,13 @@ rustc-hash = { workspace = true } [dev-dependencies] insta = { workspace = true } +pico-args = { workspace = true } +similar = { workspace = true } + oxc_codegen = { workspace = true } oxc_minifier = { workspace = true } oxc_parser = { workspace = true } +oxc_semantic = { workspace = true } oxc_sourcemap = { workspace = true } oxc_tasks_common = { workspace = true } oxc_transformer = { workspace = true } -similar = { workspace = true } diff --git a/crates/oxc_transformer_plugins/examples/define.rs b/crates/oxc_transformer_plugins/examples/define.rs new file mode 100644 index 0000000000000..8052ac3a9927c --- /dev/null +++ b/crates/oxc_transformer_plugins/examples/define.rs @@ -0,0 +1,87 @@ +#![expect(clippy::print_stdout)] +//! # Define Plugin Example +//! +//! This example demonstrates how to use the Oxc code generator to convert an AST +//! back into JavaScript code. It supports minification and idempotency testing. +//! +//! ## Usage +//! +//! Create a `test.js` file and run: +//! ```bash +//! cargo run -p oxc_transformer_plugins --example define -- [filename] +//! ``` + +use std::path::Path; + +use pico_args::Arguments; + +use oxc_allocator::Allocator; +use oxc_ast::ast::Program; +use oxc_codegen::{Codegen, CodegenOptions, CodegenReturn}; +use oxc_parser::{ParseOptions, Parser}; +use oxc_semantic::SemanticBuilder; +use oxc_sourcemap::SourcemapVisualizer; +use oxc_span::SourceType; +use oxc_transformer_plugins::{ReplaceGlobalDefines, ReplaceGlobalDefinesConfig}; + +fn main() -> std::io::Result<()> { + let mut args = Arguments::from_env(); + + let defines = [("process.env.NODE_ENV", "development")]; + + let sourcemap = args.contains("--sourcemap"); + + let name = args.free_from_str().unwrap_or_else(|_| "test.js".to_string()); + let path = Path::new(&name); + let sourcemap = sourcemap.then_some(path); + + let source_text = std::fs::read_to_string(path)?; + let source_type = SourceType::from_path(path).unwrap(); + let allocator = Allocator::default(); + + let mut program = parse(&allocator, &source_text, source_type); + let scoping = SemanticBuilder::new().build(&program).semantic.into_scoping(); + let config = ReplaceGlobalDefinesConfig::new(&defines).unwrap(); + let _ = ReplaceGlobalDefines::new(&allocator, config).build(scoping, &mut program); + let printed = codegen(&program, sourcemap); + + println!("{printed}"); + + Ok(()) +} + +/// Parse JavaScript/TypeScript source code into an AST +fn parse<'a>( + allocator: &'a Allocator, + source_text: &'a str, + source_type: SourceType, +) -> Program<'a> { + let ret = Parser::new(allocator, source_text, source_type) + .with_options(ParseOptions { + allow_return_outside_function: true, + ..ParseOptions::default() + }) + .parse(); + for error in ret.errors { + println!("{:?}", error.with_source_code(source_text.to_string())); + } + ret.program +} + +/// Generate JavaScript code from an AST +fn codegen(program: &Program<'_>, source_map_path: Option<&Path>) -> String { + let options = CodegenOptions { + source_map_path: source_map_path.map(Path::to_path_buf), + ..CodegenOptions::default() + }; + + let CodegenReturn { code, map, .. } = Codegen::new().with_options(options).build(program); + + if let Some(map) = map { + let visualizer = SourcemapVisualizer::new(&code, &map); + println!("{}", visualizer.get_url()); + println!("{}", visualizer.get_text()); + } + + code +} diff --git a/napi/minify/test/minify.test.ts b/napi/minify/test/minify.test.ts index 033d3b0239c7b..a3814b5d224ac 100644 --- a/napi/minify/test/minify.test.ts +++ b/napi/minify/test/minify.test.ts @@ -8,22 +8,19 @@ describe('simple', () => { it('matches output', () => { const ret = minify('test.js', code, { sourcemap: true }); - expect(ret).toStrictEqual({ - 'code': 'function foo(){var e;e(void 0)}foo();', - 'map': { - 'mappings': 'AACA,SAAS,KAAM,CAAE,IAAIA,EAAKA,EAAI,OAAY,CAAC', - 'names': [ - 'bar', - ], - 'sources': [ - 'test.js', - ], - 'sourcesContent': [ - code, - ], - 'version': 3, - }, - 'errors': [], + expect(ret.code).toEqual('function foo(){var e;e(void 0)}foo();'); + expect(ret.errors.length).toBe(0); + expect(ret.map).toMatchObject({ + 'names': [ + 'bar', + ], + 'sources': [ + 'test.js', + ], + 'sourcesContent': [ + code, + ], + 'version': 3, }); }); diff --git a/napi/transform/test/id.test.ts b/napi/transform/test/id.test.ts index daeb61e8e904e..8ac53a9a881c4 100644 --- a/napi/transform/test/id.test.ts +++ b/napi/transform/test/id.test.ts @@ -19,7 +19,7 @@ describe('isolated declaration', () => { it('matches output', () => { const ret = oxc.isolatedDeclaration('test.ts', code, { sourcemap: true }); - expect(ret).toStrictEqual({ + expect(ret).toMatchObject({ code: '/**\n' + '* jsdoc 1\n' + '*/\n' + @@ -31,7 +31,6 @@ describe('isolated declaration', () => { '}\n' + 'export declare class B {}\n', map: { - mappings: ';;;AAIE,OAAO,cAAM,EAAE;;;;CAIb;AACD;AAED,OAAO,cAAM,EAAE,CAAE', names: [], sources: ['test.ts'], sourcesContent: [code], diff --git a/napi/transform/test/transform.test.ts b/napi/transform/test/transform.test.ts index 57c078db662af..afd87510598ca 100644 --- a/napi/transform/test/transform.test.ts +++ b/napi/transform/test/transform.test.ts @@ -8,12 +8,11 @@ describe('simple', () => { it('matches output', () => { const ret = transform('test.ts', code, { sourcemap: true }); - expect(ret).toStrictEqual({ + expect(ret).toMatchObject({ code: 'export class A {}\n', errors: [], helpersUsed: {}, map: { - mappings: 'AAAA,OAAO,MAAM,EAAK,CAAE', names: [], sources: ['test.ts'], sourcesContent: ['export class A {}'], @@ -37,8 +36,7 @@ describe('simple', () => { typescript: { declaration: {} }, sourcemap: true, }); - expect(ret.declarationMap).toStrictEqual({ - mappings: 'AAAA,OAAO,cAAM,EAAE,GAAG,CAAE', + expect(ret.declarationMap).toMatchObject({ names: [], sources: ['test.ts'], sourcesContent: ['export class A {}'],