Skip to content

Commit

Permalink
feat(napi/minify): implement napi (#8478)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Jan 14, 2025
1 parent 9d550aa commit 4ad695d
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 42 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/oxc_minifier/examples/minifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ fn minify(
let mut program = ret.program;
let options = MinifierOptions {
mangle: mangle.then(MangleOptions::default),
compress: CompressOptions::default(),
compress: Some(CompressOptions::default()),
};
let ret = Minifier::new(options).build(allocator, &mut program);
CodeGenerator::new()
Expand Down
21 changes: 13 additions & 8 deletions crates/oxc_minifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod tester;
use oxc_allocator::Allocator;
use oxc_ast::ast::Program;
use oxc_mangler::Mangler;
use oxc_semantic::SemanticBuilder;
use oxc_semantic::{SemanticBuilder, Stats};

pub use oxc_mangler::MangleOptions;

Expand All @@ -21,12 +21,12 @@ pub use crate::{ast_passes::CompressorPass, compressor::Compressor, options::Com
#[derive(Debug, Clone, Copy)]
pub struct MinifierOptions {
pub mangle: Option<MangleOptions>,
pub compress: CompressOptions,
pub compress: Option<CompressOptions>,
}

impl Default for MinifierOptions {
fn default() -> Self {
Self { mangle: Some(MangleOptions::default()), compress: CompressOptions::default() }
Self { mangle: Some(MangleOptions::default()), compress: Some(CompressOptions::default()) }
}
}

Expand All @@ -44,11 +44,16 @@ impl Minifier {
}

pub fn build<'a>(self, allocator: &'a Allocator, program: &mut Program<'a>) -> MinifierReturn {
let semantic = SemanticBuilder::new().build(program).semantic;
let stats = semantic.stats();
let (symbols, scopes) = semantic.into_symbol_table_and_scope_tree();
Compressor::new(allocator, self.options.compress)
.build_with_symbols_and_scopes(symbols, scopes, program);
let stats = if let Some(compress) = self.options.compress {
let semantic = SemanticBuilder::new().build(program).semantic;
let stats = semantic.stats();
let (symbols, scopes) = semantic.into_symbol_table_and_scope_tree();
Compressor::new(allocator, compress)
.build_with_symbols_and_scopes(symbols, scopes, program);
stats
} else {
Stats::default()
};
let mangler = self.options.mangle.map(|options| {
let semantic = SemanticBuilder::new().with_stats(stats).build(program).semantic;
let (symbols, scopes) = semantic.into_symbol_table_and_scope_tree();
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,15 +271,15 @@ impl Oxc {
let compress_options = minifier_options.compress_options.unwrap_or_default();
let options = MinifierOptions {
mangle: minifier_options.mangle.unwrap_or_default().then(MangleOptions::default),
compress: if minifier_options.compress.unwrap_or_default() {
compress: Some(if minifier_options.compress.unwrap_or_default() {
CompressOptions {
drop_console: compress_options.drop_console,
drop_debugger: compress_options.drop_debugger,
..CompressOptions::all_false()
}
} else {
CompressOptions::all_false()
},
}),
};
Minifier::new(options).build(&allocator, &mut program).mangler
} else {
Expand Down
2 changes: 2 additions & 0 deletions napi/minify/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ oxc_allocator = { workspace = true }
oxc_codegen = { workspace = true }
oxc_minifier = { workspace = true }
oxc_parser = { workspace = true }
oxc_sourcemap = { workspace = true, features = ["napi", "rayon"] }
oxc_span = { workspace = true }
oxc_syntax = { workspace = true }

napi = { workspace = true }
napi-derive = { workspace = true }
Expand Down
69 changes: 68 additions & 1 deletion napi/minify/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,71 @@
/* auto-generated by NAPI-RS */
/* eslint-disable */
export declare function minify(filename: string, sourceText: string): string
export interface CodegenOptions {
/**
* Remove whitespace.
*
* @default true
*/
whitespace?: boolean
}

export interface CompressOptions {
/**
* Enables optional catch or nullish-coalescing operator if targeted higher.
*
* @default 'es2015'
*/
target?: string
/**
* Pass true to discard calls to `console.*`.
*
* @default false
*/
dropConsole?: boolean
/**
* Remove `debugger;` statements.
*
* @default true
*/
dropDebugger?: boolean
}

export interface MangleOptions {
/** Pass true to mangle names declared in the top level scope. */
toplevel?: boolean
/** Debug mangled names. */
debug?: boolean
}

/**
* Minify synchronously.
*
* # Errors
*
* * Fails to parse the options.
*/
export declare function minify(filename: string, sourceText: string, options?: MinifyOptions | undefined | null): MinifyResult

export interface MinifyOptions {
compress?: boolean | CompressOptions
mangle?: boolean | MangleOptions
codegen?: boolean | CodegenOptions
sourcemap?: boolean
}

export interface MinifyResult {
code: string
map?: SourceMap
}

export interface SourceMap {
file?: string
mappings: string
names: Array<string>
sourceRoot?: string
sources: Array<string>
sourcesContent?: Array<string>
version: number
x_google_ignoreList?: Array<number>
}

5 changes: 1 addition & 4 deletions napi/minify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
"scripts": {
"build-dev": "napi build --platform",
"build": "napi build --platform --release",
"test": "echo 'skip'"
},
"engines": {
"node": ">=14.*"
"test": "vitest --typecheck run ./test"
},
"napi": {
"binaryName": "minify",
Expand Down
60 changes: 45 additions & 15 deletions napi/minify/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,59 @@
#![allow(clippy::needless_pass_by_value)]

mod options;

use std::path::PathBuf;

use napi::Either;
use napi_derive::napi;

use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_minifier::{CompressOptions, MangleOptions, Minifier, MinifierOptions};
use oxc_minifier::Minifier;
use oxc_parser::Parser;
use oxc_span::SourceType;

#[allow(clippy::needless_pass_by_value)]
use crate::options::{MinifyOptions, MinifyResult};

/// Minify synchronously.
///
/// # Errors
///
/// * Fails to parse the options.
#[napi]
pub fn minify(filename: String, source_text: String) -> String {
pub fn minify(
filename: String,
source_text: String,
options: Option<MinifyOptions>,
) -> napi::Result<MinifyResult> {
let options = options.unwrap_or_default();

let minifier_options = match oxc_minifier::MinifierOptions::try_from(&options) {
Ok(options) => options,
Err(error) => return Err(napi::Error::from_reason(&error)),
};

let allocator = Allocator::default();

let source_type = SourceType::from_path(&filename).unwrap_or_default().with_typescript(true);

let mut program = Parser::new(&allocator, &source_text, source_type).parse().program;

let mangler = Minifier::new(MinifierOptions {
mangle: Some(MangleOptions::default()),
compress: CompressOptions::default(),
})
.build(&allocator, &mut program)
.mangler;

Codegen::new()
.with_options(CodegenOptions { minify: true, ..CodegenOptions::default() })
.with_mangler(mangler)
.build(&program)
.code
let mangler = Minifier::new(minifier_options).build(&allocator, &mut program).mangler;

let mut codegen_options = match &options.codegen {
Some(Either::A(false)) => CodegenOptions { minify: false, ..CodegenOptions::default() },
None | Some(Either::A(true)) => {
CodegenOptions { minify: true, ..CodegenOptions::default() }
}
Some(Either::B(o)) => CodegenOptions::from(o),
};

if options.sourcemap == Some(true) {
codegen_options.source_map_path = Some(PathBuf::from(filename));
}

let ret = Codegen::new().with_options(codegen_options).with_mangler(mangler).build(&program);

Ok(MinifyResult { code: ret.code, map: ret.map.map(oxc_sourcemap::napi::SourceMap::from) })
}
123 changes: 123 additions & 0 deletions napi/minify/src/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use std::str::FromStr;

use napi::Either;
use napi_derive::napi;

use oxc_sourcemap::napi::SourceMap;
use oxc_syntax::es_target::ESTarget;

#[napi(object)]
pub struct CompressOptions {
/// Enables optional catch or nullish-coalescing operator if targeted higher.
///
/// @default 'es2015'
pub target: Option<String>,

/// Pass true to discard calls to `console.*`.
///
/// @default false
pub drop_console: Option<bool>,

/// Remove `debugger;` statements.
///
/// @default true
pub drop_debugger: Option<bool>,
}

impl Default for CompressOptions {
fn default() -> Self {
Self { target: None, drop_console: None, drop_debugger: Some(true) }
}
}

impl TryFrom<&CompressOptions> for oxc_minifier::CompressOptions {
type Error = String;
fn try_from(o: &CompressOptions) -> Result<Self, Self::Error> {
Ok(oxc_minifier::CompressOptions {
target: o
.target
.as_ref()
.map(|s| ESTarget::from_str(s))
.transpose()?
.unwrap_or(ESTarget::ES2015),
drop_debugger: o.drop_debugger.unwrap_or(false),
drop_console: o.drop_console.unwrap_or(true),
})
}
}

#[napi(object)]
#[derive(Default)]
pub struct MangleOptions {
/// Pass true to mangle names declared in the top level scope.
pub toplevel: Option<bool>,

/// Debug mangled names.
pub debug: Option<bool>,
}

impl From<&MangleOptions> for oxc_minifier::MangleOptions {
fn from(o: &MangleOptions) -> Self {
Self { top_level: o.toplevel.unwrap_or(false), debug: o.debug.unwrap_or(false) }
}
}

#[napi(object)]
pub struct CodegenOptions {
/// Remove whitespace.
///
/// @default true
pub whitespace: Option<bool>,
}

impl Default for CodegenOptions {
fn default() -> Self {
Self { whitespace: Some(true) }
}
}

impl From<&CodegenOptions> for oxc_codegen::CodegenOptions {
fn from(o: &CodegenOptions) -> Self {
oxc_codegen::CodegenOptions {
minify: o.whitespace.unwrap_or(true),
..oxc_codegen::CodegenOptions::default()
}
}
}

#[napi(object)]
#[derive(Default)]
pub struct MinifyOptions {
pub compress: Option<Either<bool, CompressOptions>>,

pub mangle: Option<Either<bool, MangleOptions>>,

pub codegen: Option<Either<bool, CodegenOptions>>,

pub sourcemap: Option<bool>,
}

impl TryFrom<&MinifyOptions> for oxc_minifier::MinifierOptions {
type Error = String;

fn try_from(o: &MinifyOptions) -> Result<Self, Self::Error> {
let compress = match &o.compress {
Some(Either::A(false)) => None,
None | Some(Either::A(true)) => Some(oxc_minifier::CompressOptions::default()),
Some(Either::B(o)) => Some(oxc_minifier::CompressOptions::try_from(o)?),
};
let mangle = match &o.mangle {
Some(Either::A(false)) => None,
None | Some(Either::A(true)) => Some(oxc_minifier::MangleOptions::default()),
Some(Either::B(o)) => Some(oxc_minifier::MangleOptions::from(o)),
};
Ok(oxc_minifier::MinifierOptions { compress, mangle })
}
}

#[napi(object)]
pub struct MinifyResult {
pub code: String,

pub map: Option<SourceMap>,
}
Loading

0 comments on commit 4ad695d

Please sign in to comment.