Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion crates/oxc_mangler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ pub(crate) mod base54;

#[derive(Default, Debug, Clone, Copy)]
pub struct MangleOptions {
/// Also mangle exported variables.
/// Pass true to mangle names declared in the top level scope.
///
/// Default: `false`
pub top_level: bool,

/// Use more readable mangled names
/// (e.g. `slot_0`, `slot_1`, `slot_2`, ...) for debugging.
///
Expand Down
14 changes: 9 additions & 5 deletions crates/oxc_minifier/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

A JavaScript minifier has three components:

1. printer
1. compressor
2. mangler
3. compressor
3. printer

## Compressor

The compressor is responsible for rewriting statements and expressions for minimal text output.
[Terser](https://github.com/terser/terser) is a good place to start for learning the fundamentals.

## Mangler

Expand All @@ -13,10 +18,9 @@ It is responsible for shortening variables. Its algorithm should be gzip friendl

The printer is also responsible for printing out the shortened variable names.

## Compressor
## Printer

The compressor is responsible for rewriting statements and expressions for minimal text output.
[Terser](https://github.com/terser/terser) is a good place to start for learning the fundamentals.
The printer is responsible for removing whitespace from the source text.

### Assumptions

Expand Down
5 changes: 4 additions & 1 deletion crates/oxc_minifier/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ use oxc_syntax::es_target::ESTarget;

#[derive(Debug, Clone, Copy)]
pub struct CompressOptions {
/// Enable features that are targeted above.
/// Set desired EcmaScript standard version for output.
///
/// e.g.
///
/// * catch optional binding when >= es2019
/// * `??` operator >= es2020
///
/// Default `ESTarget::ESNext`
pub target: ESTarget,

/// Remove `debugger;` statements.
Expand Down
46 changes: 46 additions & 0 deletions napi/minify/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
# Oxc Minify

This is alpha software and may yield incorrect results, feel free to [submit a bug report](https://github.com/oxc-project/oxc/issues/new?assignees=&labels=C-bug&projects=&template=bug_report.md).

### Performance and Compression Size

See [minification-benchmarks](https://github.com/privatenumber/minification-benchmarks) for details.

The current version already outperforms `esbuild`,
but it still lacks a few key minification techniques
such as constant inlining and dead code removal,
which we plan to implement next.

## Caveats

To maximize performance, `oxc-minify` assumes the input code is semantically correct.
It uses `oxc-parser`'s fast mode to parse the input code,
which does not check for semantic errors related to symbols and scopes.

## API

```javascript
import { minify } from 'oxc-minify';

const filename = 'test.js';
const code = "const x = 'a' + 'b'; console.log(x);";
const options = {
compress: {
target: 'esnext',
},
mangle: {
toplevel: false,
},
codegen: {
removeWhitespace: true,
},
sourcemap: true,
};
const result = minify(filename, code, options);

console.log(result.code);
console.log(result.map);
```

## Assumptions

`oxc-minify` makes some assumptions about the source code.

See https://github.com/oxc-project/oxc/blob/main/crates/oxc_minifier/README.md#assumptions for details.
29 changes: 17 additions & 12 deletions napi/minify/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,23 @@ export interface CodegenOptions {
*
* @default true
*/
whitespace?: boolean
removeWhitespace?: boolean
}

export interface CompressOptions {
/**
* Enables optional catch or nullish-coalescing operator if targeted higher.
* Set desired EcmaScript standard version for output.
*
* @default 'es2015'
* Set `esnext` to enable all target highering.
*
* e.g.
*
* * catch optional binding when >= es2019
* * `??` operator >= es2020
*
* @default 'esnext'
*/
target?: string
target?: 'esnext' | 'es2015' | 'es2016' | 'es2017' | 'es2018' | 'es2019' | 'es2020' | 'es2021' | 'es2022' | 'es2023' | 'es2024'
/**
* Pass true to discard calls to `console.*`.
*
Expand All @@ -31,19 +38,17 @@ export interface CompressOptions {
}

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

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

export interface MinifyOptions {
Expand Down
8 changes: 2 additions & 6 deletions napi/minify/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![expect(clippy::needless_pass_by_value)]
#![expect(clippy::needless_pass_by_value, clippy::missing_errors_doc)]

mod options;

Expand All @@ -16,10 +16,6 @@ use oxc_span::SourceType;
use crate::options::{MinifyOptions, MinifyResult};

/// Minify synchronously.
///
/// # Errors
///
/// * Fails to parse the options.
#[napi]
pub fn minify(
filename: String,
Expand All @@ -35,7 +31,7 @@ pub fn minify(

let allocator = Allocator::default();

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

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

Expand Down
40 changes: 29 additions & 11 deletions napi/minify/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@ use oxc_syntax::es_target::ESTarget;

#[napi(object)]
pub struct CompressOptions {
/// Enables optional catch or nullish-coalescing operator if targeted higher.
/// Set desired EcmaScript standard version for output.
///
/// @default 'es2015'
/// Set `esnext` to enable all target highering.
///
/// e.g.
///
/// * catch optional binding when >= es2019
/// * `??` operator >= es2020
///
/// @default 'esnext'
#[napi(
ts_type = "'esnext' | 'es2015' | 'es2016' | 'es2017' | 'es2018' | 'es2019' | 'es2020' | 'es2021' | 'es2022' | 'es2023' | 'es2024'"
)]
pub target: Option<String>,

/// Pass true to discard calls to `console.*`.
Expand All @@ -33,23 +43,26 @@ impl Default for CompressOptions {
impl TryFrom<&CompressOptions> for oxc_minifier::CompressOptions {
type Error = String;
fn try_from(o: &CompressOptions) -> Result<Self, Self::Error> {
let default = oxc_minifier::CompressOptions::default();
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),
.unwrap_or(default.target),
drop_console: o.drop_console.unwrap_or(default.drop_console),
drop_debugger: o.drop_debugger.unwrap_or(default.drop_debugger),
})
}
}

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

/// Debug mangled names.
Expand All @@ -58,7 +71,11 @@ pub struct MangleOptions {

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) }
let default = oxc_minifier::MangleOptions::default();
Self {
top_level: o.toplevel.unwrap_or(default.top_level),
debug: o.debug.unwrap_or(default.debug),
}
}
}

Expand All @@ -67,20 +84,21 @@ pub struct CodegenOptions {
/// Remove whitespace.
///
/// @default true
pub whitespace: Option<bool>,
pub remove_whitespace: Option<bool>,
}

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

impl From<&CodegenOptions> for oxc_codegen::CodegenOptions {
fn from(o: &CodegenOptions) -> Self {
let default = oxc_codegen::CodegenOptions::default();
oxc_codegen::CodegenOptions {
minify: o.whitespace.unwrap_or(true),
..oxc_codegen::CodegenOptions::default()
minify: o.remove_whitespace.unwrap_or(default.minify),
..default
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion napi/minify/test/minify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,17 @@ describe('simple', () => {
});

it('can turn off everything', () => {
const ret = minify('test.js', code, { compress: false, mangle: false, codegen: { whitespace: false } });
const ret = minify('test.js', code, { compress: false, mangle: false, codegen: { removeWhitespace: false } });
expect(ret).toStrictEqual({
'code': 'function foo() {\n\tvar bar;\n\tbar(undefined);\n}\nfoo();\n',
});
});

it('defaults to esnext', () => {
const code = 'try { foo } catch (e) {}';
const ret = minify('test.js', code);
expect(ret.code).toBe('try{foo}catch{}');
});
});

describe('worker', () => {
Expand Down
16 changes: 4 additions & 12 deletions napi/parser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

### ESTree

The returned JavaScript AST follows the [ESTree](https://github.com/estree/estree) specification.
When parsing `.js` or `.jsx` files, the AST returned is fully conformant with the
[ESTree standard](https://github.com/estree/estree).

It is fully aligned with Acorn's AST, and any deviation would be considered a bug.

The returned TypeScript AST will conform to [@typescript-eslint/typescript-estree](https://www.npmjs.com/package/@typescript-eslint/typescript-estree) in the near future.

If you need all ASTs in the same with-TS-properties format, use the `astType: 'ts'` option.

### AST Types

[@oxc-project/types](https://www.npmjs.com/package/@oxc-project/types) can be used. For example:
Expand Down Expand Up @@ -41,17 +44,6 @@ Fast mode is best suited for parser plugins, where other parts of your build pip

Please note that turning off fast mode ​incurs​ a small performance overhead.

### ESTree compatibility

When parsing JS or JSX files, the AST returned is fully conformant with the
[ESTree standard](https://github.com/estree/estree).

When parsing TS or TSX files, the AST has additional properties related to TypeScript syntax.
These extra properties are broadly (but not entirely) in line with
[TypeScript ESLint](https://typescript-eslint.io/packages/parser/)'s AST.

If you need all ASTs in the same with-TS-properties format, use the `astType: 'ts'` option.

### Returns ESM information.

It is likely that you are writing a parser plugin that requires ESM information.
Expand Down
Loading