mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(napi/minify): implement napi (#8478)
This commit is contained in:
parent
9d550aacaf
commit
4ad695dcfb
11 changed files with 290 additions and 41 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1870,7 +1870,9 @@ dependencies = [
|
|||
"oxc_codegen",
|
||||
"oxc_minifier",
|
||||
"oxc_parser",
|
||||
"oxc_sourcemap",
|
||||
"oxc_span",
|
||||
"oxc_syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ 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,
|
||||
|
|
@ -279,7 +279,7 @@ impl Oxc {
|
|||
}
|
||||
} else {
|
||||
CompressOptions::all_false()
|
||||
},
|
||||
}),
|
||||
};
|
||||
Minifier::new(options).build(&allocator, &mut program).mangler
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
69
napi/minify/index.d.ts
vendored
69
napi/minify/index.d.ts
vendored
|
|
@ -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>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
let mangler = Minifier::new(minifier_options).build(&allocator, &mut program).mangler;
|
||||
|
||||
Codegen::new()
|
||||
.with_options(CodegenOptions { minify: true, ..CodegenOptions::default() })
|
||||
.with_mangler(mangler)
|
||||
.build(&program)
|
||||
.code
|
||||
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
napi/minify/src/options.rs
Normal file
123
napi/minify/src/options.rs
Normal file
|
|
@ -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>,
|
||||
}
|
||||
34
napi/minify/test/minify.test.ts
Normal file
34
napi/minify/test/minify.test.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { minify } from '../index';
|
||||
|
||||
describe('simple', () => {
|
||||
const code = 'function foo() { var bar; bar(undefined) } foo();';
|
||||
|
||||
it('matches output', () => {
|
||||
const ret = minify('test.js', code, { sourcemap: true });
|
||||
expect(ret).toStrictEqual({
|
||||
'code': 'function foo(){var b;b(void 0)}foo();',
|
||||
'map': {
|
||||
'mappings': 'AAAA,SAAS,KAAM,CAAE,IAAIA,EAAK,SAAc,AAAE,CAAC,KAAK',
|
||||
'names': [
|
||||
'bar',
|
||||
],
|
||||
'sources': [
|
||||
'test.js',
|
||||
],
|
||||
'sourcesContent': [
|
||||
code,
|
||||
],
|
||||
'version': 3,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('can turn off everything', () => {
|
||||
const ret = minify('test.js', code, { compress: false, mangle: false, codegen: { whitespace: false } });
|
||||
expect(ret).toStrictEqual({
|
||||
'code': 'function foo() {\n\tvar bar;\n\tbar(undefined);\n}\nfoo();\n',
|
||||
});
|
||||
});
|
||||
});
|
||||
11
napi/transform/index.d.ts
vendored
11
napi/transform/index.d.ts
vendored
|
|
@ -205,17 +205,6 @@ export declare const enum Severity {
|
|||
Advice = 'Advice'
|
||||
}
|
||||
|
||||
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>
|
||||
}
|
||||
|
||||
/**
|
||||
* Transpile a JavaScript or TypeScript into a target ECMAScript version.
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in a new issue