feat(playground): add transform and minify (#993)

This commit is contained in:
Boshen 2023-10-14 19:49:58 +08:00 committed by GitHub
parent 801d78a3c6
commit 2e2b7587ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 277 additions and 172 deletions

11
Cargo.lock generated
View file

@ -1428,13 +1428,16 @@ version = "0.2.0"
dependencies = [
"oxc_allocator",
"oxc_ast",
"oxc_codegen",
"oxc_diagnostics",
"oxc_formatter",
"oxc_index",
"oxc_minifier",
"oxc_parser",
"oxc_semantic",
"oxc_span",
"oxc_syntax",
"oxc_transformer",
]
[[package]]
@ -1863,15 +1866,9 @@ name = "oxc_wasm"
version = "0.0.0"
dependencies = [
"console_error_panic_hook",
"oxc_allocator",
"oxc_ast",
"oxc_diagnostics",
"oxc_formatter",
"oxc",
"oxc_linter",
"oxc_parser",
"oxc_query",
"oxc_semantic",
"oxc_span",
"oxc_type_synthesis",
"serde",
"serde-wasm-bindgen",

View file

@ -26,16 +26,18 @@ oxc_parser = { version = "0.2.0", path = "crates/oxc_parser" }
oxc_semantic = { version = "0.2.0", path = "crates/oxc_semantic" }
oxc_span = { version = "0.2.0", path = "crates/oxc_span" }
oxc_syntax = { version = "0.2.0", path = "crates/oxc_syntax" }
oxc_transformer = { version = "0.2.0", path = "crates/oxc_transformer" }
oxc_codegen = { version = "0.2.0", path = "crates/oxc_codegen" }
# publish = false
oxc_macros = { path = "crates/oxc_macros" }
oxc_linter = { path = "crates/oxc_linter" }
oxc_type_synthesis = { path = "crates/oxc_type_synthesis" }
oxc_resolver = { path = "crates/oxc_resolver" }
oxc_query = { path = "crates/oxc_query" }
oxc_linter_plugin = { path = "crates/oxc_linter_plugin" }
oxc_transformer = { path = "crates/oxc_transformer" }
oxc_codegen = { path = "crates/oxc_codegen" }
# published by its own
oxc_resolver = { path = "crates/oxc_resolver" }
oxc_tasks_common = { path = "tasks/common" }
oxc_vscode = { path = "editor/vscode/server" }

View file

@ -25,9 +25,14 @@ oxc_span = { workspace = true }
oxc_syntax = { workspace = true }
oxc_semantic = { workspace = true, optional = true }
oxc_formatter = { workspace = true, optional = true }
# oxc_minifier = { workspace = true, optional = true }
oxc_transformer = { workspace = true, optional = true }
oxc_minifier = { workspace = true, optional = true }
oxc_codegen = { workspace = true, optional = true }
[features]
formatter = ["oxc_formatter"]
semantic = ["oxc_semantic"]
# minifier = ["oxc_minifier"]
serde = ["oxc_ast/serde"]
semantic = ["oxc_semantic"]
formatter = ["oxc_formatter"]
transformer = ["oxc_transformer"]
minifier = ["oxc_minifier"]
codegen = ["oxc_codegen"]

View file

@ -17,34 +17,16 @@ pub mod diagnostics {
pub use oxc_diagnostics::*;
}
#[cfg(feature = "formatter")]
pub mod formatter {
#[doc(inline)]
pub use oxc_formatter::*;
}
pub mod index {
#[doc(inline)]
pub use oxc_index::*;
}
// #[cfg(feature = "minifier")]
// pub mod minifier {
// #[doc(inline)]
// pub use oxc_minifier::*;
// }
pub mod parser {
#[doc(inline)]
pub use oxc_parser::*;
}
#[cfg(feature = "semantic")]
pub mod semantic {
#[doc(inline)]
pub use oxc_semantic::*;
}
pub mod span {
#[doc(inline)]
pub use oxc_span::*;
@ -54,3 +36,33 @@ pub mod syntax {
#[doc(inline)]
pub use oxc_syntax::*;
}
#[cfg(feature = "semantic")]
pub mod semantic {
#[doc(inline)]
pub use oxc_semantic::*;
}
#[cfg(feature = "formatter")]
pub mod formatter {
#[doc(inline)]
pub use oxc_formatter::*;
}
#[cfg(feature = "transformer")]
pub mod transformer {
#[doc(inline)]
pub use oxc_transformer::*;
}
#[cfg(feature = "minifier")]
pub mod minifier {
#[doc(inline)]
pub use oxc_minifier::*;
}
#[cfg(feature = "codegen")]
pub mod codegen {
#[doc(inline)]
pub use oxc_codegen::*;
}

View file

@ -1,7 +1,7 @@
[package]
name = "oxc_codegen"
version = "0.2.0"
publish = false
publish = true
authors.workspace = true
description.workspace = true
edition.workspace = true

View file

@ -2,6 +2,7 @@
mod ast_util;
mod fold;
mod options;
mod prepass;
mod util;
@ -15,61 +16,9 @@ use oxc_syntax::{
NumberBase,
};
pub use self::options::CompressOptions;
use self::prepass::Prepass;
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Copy)]
pub struct CompressOptions {
/// Various optimizations for boolean context, for example `!!a ? b : c` → `a ? b : c`.
///
/// Default `true`
pub booleans: bool,
/// Remove `debugger;` statements.
///
/// Default `true`
pub drop_debugger: bool,
/// Remove `console.*` statements.
///
/// Default `false`
pub drop_console: bool,
/// Attempt to evaluate constant expressions
///
/// Default `true`
pub evaluate: bool,
/// Join consecutive var statements.
///
/// Default `true`
pub join_vars: bool,
/// Optimizations for do, while and for loops when we can statically determine the condition
///
/// Default `true`
pub loops: bool,
/// Transforms `typeof foo == "undefined" into `foo === void 0`
///
/// Default `true`
pub typeofs: bool,
}
impl Default for CompressOptions {
fn default() -> Self {
Self {
booleans: true,
drop_debugger: true,
drop_console: false,
evaluate: true,
join_vars: true,
loops: true,
typeofs: true,
}
}
}
pub struct Compressor<'a> {
ast: AstBuilder<'a>,
options: CompressOptions,

View file

@ -0,0 +1,78 @@
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Copy)]
pub struct CompressOptions {
/// Various optimizations for boolean context, for example `!!a ? b : c` → `a ? b : c`.
///
/// Default `true`
pub booleans: bool,
/// Remove `debugger;` statements.
///
/// Default `true`
pub drop_debugger: bool,
/// Remove `console.*` statements.
///
/// Default `false`
pub drop_console: bool,
/// Attempt to evaluate constant expressions
///
/// Default `true`
pub evaluate: bool,
/// Join consecutive var statements.
///
/// Default `true`
pub join_vars: bool,
/// Optimizations for do, while and for loops when we can statically determine the condition
///
/// Default `true`
pub loops: bool,
/// Transforms `typeof foo == "undefined" into `foo === void 0`
///
/// Default `true`
pub typeofs: bool,
}
impl Default for CompressOptions {
fn default() -> Self {
Self {
booleans: true,
drop_debugger: true,
drop_console: false,
evaluate: true,
join_vars: true,
loops: true,
typeofs: true,
}
}
}
impl CompressOptions {
pub fn all_true() -> Self {
Self {
booleans: true,
drop_debugger: true,
drop_console: true,
evaluate: true,
join_vars: true,
loops: true,
typeofs: true,
}
}
pub fn all_false() -> Self {
Self {
booleans: false,
drop_debugger: false,
drop_console: false,
evaluate: false,
join_vars: false,
loops: false,
typeofs: false,
}
}
}

View file

@ -1,7 +1,7 @@
[package]
name = "oxc_transformer"
version = "0.2.0"
publish = false
publish = true
authors.workspace = true
description.workspace = true
edition.workspace = true

View file

@ -17,20 +17,14 @@ doctest = false
default = ["console_error_panic_hook"]
[dependencies]
oxc_allocator = { workspace = true }
oxc_diagnostics = { workspace = true }
oxc_ast = { workspace = true, features = ["serde"] }
oxc_parser = { workspace = true }
oxc_semantic = { workspace = true }
oxc = { workspace = true, features = ["serde", "semantic", "formatter", "transformer", "minifier", "codegen"] }
oxc_linter = { workspace = true }
oxc_formatter = { workspace = true }
oxc_type_synthesis = { workspace = true }
# oxc_minifier = { workspace = true }
oxc_span = { workspace = true }
oxc_query = { workspace = true }
serde_json = { workspace = true }
trustfall = { workspace = true }
serde = { workspace = true }
oxc_query = { workspace = true }
serde_json = { workspace = true }
trustfall = { workspace = true }
serde = { workspace = true }
wasm-bindgen = { version = "0.2" }
serde-wasm-bindgen = "0.6.0"

View file

@ -2,23 +2,27 @@ mod options;
use std::{cell::RefCell, collections::BTreeMap, path::PathBuf, rc::Rc, sync::Arc};
use oxc_allocator::Allocator;
use oxc_diagnostics::Error;
use oxc_formatter::{Formatter, FormatterOptions};
use oxc::{
allocator::Allocator,
codegen::{Codegen, CodegenOptions},
diagnostics::Error,
formatter::{Formatter, FormatterOptions},
minifier::{CompressOptions, Minifier, MinifierOptions},
parser::{Parser, ParserReturn},
semantic::{SemanticBuilder, SemanticBuilderReturn},
span::SourceType,
transformer::{TransformOptions, TransformTarget, Transformer},
};
use oxc_linter::{LintContext, Linter};
// use oxc_minifier::{CompressOptions, Compressor, ManglerBuilder, Printer, PrinterOptions};
use oxc_parser::{Parser, ParserReturn};
use oxc_query::{schema, Adapter, SCHEMA_TEXT};
use oxc_semantic::{SemanticBuilder, SemanticBuilderReturn};
use oxc_span::SourceType;
use oxc_type_synthesis::{synthesize_program, Diagnostic as TypeCheckDiagnostic};
use serde::Serialize;
use trustfall::{execute_query, TransparentValue};
use wasm_bindgen::prelude::*;
use crate::options::{
OxcFormatterOptions, OxcLinterOptions, /*OxcMinifierOptions, */ OxcParserOptions,
OxcRunOptions, OxcTypeCheckingOptions,
OxcFormatterOptions, OxcLinterOptions, OxcMinifierOptions, OxcParserOptions, OxcRunOptions,
OxcTypeCheckingOptions,
};
#[wasm_bindgen(start)]
@ -40,8 +44,8 @@ pub struct Oxc {
ast: JsValue,
ir: JsValue,
codegen_text: String,
formatted_text: String,
minified_text: String,
diagnostics: RefCell<Vec<Error>>,
@ -92,9 +96,9 @@ impl Oxc {
self.formatted_text.clone()
}
#[wasm_bindgen(getter = minifiedText)]
pub fn minified_text(&self) -> String {
self.minified_text.clone()
#[wasm_bindgen(getter = codegenText)]
pub fn codegen_text(&self) -> String {
self.codegen_text.clone()
}
/// Returns Array of String
@ -152,7 +156,7 @@ impl Oxc {
parser_options: &OxcParserOptions,
_linter_options: &OxcLinterOptions,
formatter_options: &OxcFormatterOptions,
// minifier_options: &OxcMinifierOptions,
minifier_options: &OxcMinifierOptions,
_type_checking_options: &OxcTypeCheckingOptions,
) -> Result<(), serde_wasm_bindgen::Error> {
self.diagnostics = RefCell::default();
@ -200,27 +204,36 @@ impl Oxc {
self.formatted_text = printed;
}
// if run_options.minify() {
// let ast_lower_ret = AstLower::new(&allocator, source_text, source_type).build(program);
// let semantic = ast_lower_ret.semantic;
// let mut printer = Printer::new(self.source_text.len(), PrinterOptions);
// let _semantic =
// Compressor::new(&allocator, semantic, CompressOptions::default()).build(hir);
// if minifier_options.mangle() {
// let mangler = ManglerBuilder::new(source_text, source_type).build(hir);
// printer.with_mangler(mangler);
// }
// self.minified_text = printer.build(hir);
// }
if run_options.type_check() {
let (diagnostics, ..) = synthesize_program(program, |_: &std::path::Path| None);
*self.type_check_diagnostics.borrow_mut() = diagnostics.get_diagnostics();
}
if run_options.transform() {
let options = TransformOptions { target: TransformTarget::ES2015, react: None };
Transformer::new(&allocator, source_type, options).build(program);
}
let program = allocator.alloc(program);
if minifier_options.compress() || minifier_options.mangle() {
let options = MinifierOptions {
mangle: minifier_options.mangle(),
compress: if minifier_options.compress() {
CompressOptions::all_true()
} else {
CompressOptions::all_false()
},
};
Minifier::new(options).build(&allocator, program);
}
self.codegen_text = if minifier_options.whitespace() {
Codegen::<true>::new(source_text.len(), CodegenOptions).build(program)
} else {
Codegen::<false>::new(source_text.len(), CodegenOptions).build(program)
};
Ok(())
}

View file

@ -7,7 +7,7 @@ pub struct OxcRunOptions {
syntax: bool,
lint: bool,
format: bool,
minify: bool,
transform: bool,
type_check: bool,
}
@ -49,13 +49,13 @@ impl OxcRunOptions {
}
#[wasm_bindgen(getter)]
pub fn minify(self) -> bool {
self.minify
pub fn transform(self) -> bool {
self.transform
}
#[wasm_bindgen(setter)]
pub fn set_minify(&mut self, yes: bool) {
self.minify = yes;
pub fn set_transform(&mut self, yes: bool) {
self.transform = yes;
}
#[wasm_bindgen(getter)]
@ -113,7 +113,9 @@ impl OxcFormatterOptions {
#[wasm_bindgen]
#[derive(Default, Clone, Copy)]
pub struct OxcMinifierOptions {
whitespace: bool,
mangle: bool,
compress: bool,
}
#[wasm_bindgen]
@ -123,6 +125,16 @@ impl OxcMinifierOptions {
Self::default()
}
#[wasm_bindgen(getter)]
pub fn whitespace(self) -> bool {
self.whitespace
}
#[wasm_bindgen(setter)]
pub fn set_whitespace(&mut self, yes: bool) {
self.whitespace = yes;
}
#[wasm_bindgen(getter)]
pub fn mangle(self) -> bool {
self.mangle
@ -132,6 +144,16 @@ impl OxcMinifierOptions {
pub fn set_mangle(&mut self, yes: bool) {
self.mangle = yes;
}
#[wasm_bindgen(getter)]
pub fn compress(self) -> bool {
self.compress
}
#[wasm_bindgen(setter)]
pub fn set_compress(&mut self, yes: bool) {
self.compress = yes;
}
}
#[wasm_bindgen]

View file

@ -19,10 +19,15 @@
#editor { flex: 1; overflow-y: auto; }
#panel { height: 20%; overflow-y: auto; padding: 1em; color: #d1d5da; background-color: #24292e; border-top: 1px solid #444!important; }
#right { flex: 1; display:flex; flex-direction: column; min-width: 0; }
.controls { color: white; background: #24292e; padding: .8em 1em; border-bottom: 1px solid #444; display: flex; align-items: center; justify-content: space-between; }
.header { color: white; background: #24292e; border-bottom: 1px solid #444; }
.left-container { padding: .8em 1em; display: flex; align-items: center; justify-content: space-between; }
.controls { padding: .6em; }
.controls > div { padding: .2em 1em; display: flex; align-items: center; }
.controls > div > button { margin-right: 4px; }
.controls > div > label { margin-right: 8px; }
.controls label { font-size: 14px; }
#duration { margin-left: auto; }
#viewer { flex: 1; overflow-y: auto; }
#mangle { display: inline-flex; align-items: center }
.query-button-green { background-color: #32CD59; border-color: #15c541; }
.query-button-red { background-color: #e74c3c; border-color: #c0392b; }
#query-results-viewer { height: 50%; border-top: 1px solid #444; display: none; }
@ -36,11 +41,11 @@
<div id="container">
<div id="left">
<div class="controls">
<div class="header left-container">
<div id="logo"><img height="100%" width="20" src="https://raw.githubusercontent.com/Boshen/oxc-assets/main/oxc.ico" alt="logo"></img><span>Oxc</span></div>
<div>
<label id="type-check">Type Check (experimental)<input id="type-check-checkbox" type="checkbox"></label>
<label id="syntax">Check Syntax<input id="syntax-checkbox" type="checkbox" checked></label>
<label id="syntax">Syntax Check<input id="syntax-checkbox" type="checkbox" checked></label>
<label id="lint">Lint<input id="lint-checkbox" type="checkbox" checked></label>
</div>
</div>
@ -49,18 +54,24 @@
</div>
<div id="divider"></div>
<div id="right">
<div class="controls">
<div class="header controls">
<div>
<button type="button" id="ast">AST</button>
<button type="button" id="codegen">Codegen</button>
<button type="button" id="ir">IR</button>
<!-- The formatter is WIP <button type="button" id="format">Format</button> -->
<!-- <button type="button" id="minify">Minify</button> -->
<button type="button" id="query">Query Playground</button>
<button type="button" id="query-args-or-outputs" class="query-button-red">Show Query Arguments</button>
<button type="button" id="ir-copy">Copy IR to clipboard</button>
<label id="mangle">Mangle<input id="mangle-checkbox" type="checkbox"></label>
</div>
<span id="duration"></span>
<div>
<!-- Format is experimental <button type="button" id="format">Format</button> -->
<label id="transform">Transform<input id="transform-checkbox" type="checkbox"></label>
<label>| Minify: </label>
<label id="whitespace">whitespace<input id="whitespace-checkbox" type="checkbox"></label>
<label id="mangle">mangle<input id="mangle-checkbox" type="checkbox"></label>
<label id="compress">compress<input id="compress-checkbox" type="checkbox"></label>
<div id="duration" title="Execution Time"></div>
</div>
</div>
<div id="viewer"></div>
<div id="query-results-viewer"></div>

View file

@ -106,6 +106,7 @@ class Playground {
this.viewer = this.initViewer();
this.queryResultsViewer = this.initQueryResultsViewer();
this.showingQueryResultsOrArguments = "results";
}
initOxc() {
@ -117,7 +118,11 @@ class Playground {
this.minifierOptions = new OxcMinifierOptions();
this.typeCheckOptions = new OxcTypeCheckingOptions();
this.runOptions.syntax = true;
this.runOptions.lint = true;
this.runOxc(this.editor.state.doc.toString());
this.updateDiagnostics();
}
runOxc(text) {
@ -154,16 +159,7 @@ class Playground {
autocompletion(),
linter(
() => {
const diagnostics = this.oxc
? this.oxc.getDiagnostics().map((d) => ({
from: d.start,
to: d.end,
severity: d.severity.toLowerCase(),
message: d.message,
}))
: [];
this.updatePanel(diagnostics);
return diagnostics;
return this.updateDiagnostics();
},
{ delay: 0 }
),
@ -177,6 +173,17 @@ class Playground {
});
}
updateDiagnostics() {
const diagnostics = (this.oxc ? this.oxc.getDiagnostics() : []).map((d) => ({
from: d.start,
to: d.end,
severity: d.severity.toLowerCase(),
message: d.message,
}));
this.updatePanel(diagnostics);
return diagnostics;
}
runQuery() {
if (
// run query, and put results in query result viewer pane
@ -412,8 +419,7 @@ class Playground {
this.parserOptions,
this.linterOptions,
this.formatterOptions,
// TODO: minifierOptions is not used temporarily, see: #917
// this.minifierOptions,
this.minifierOptions,
this.typeCheckOptions
);
const elapsed = new Date() - start;
@ -452,7 +458,6 @@ class Playground {
view = view || this.currentView;
this.currentView = view;
document.getElementById("mangle").style.visibility = "hidden";
document.getElementById("ir-copy").style.display = "none";
document.getElementById("query-args-or-outputs").style.display = "none";
document.getElementById("query-results-viewer").style.display = "none";
@ -468,6 +473,10 @@ class Playground {
this.run();
text = JSON.stringify(this.oxc.ast, null, 2);
break;
case "codegen":
this.run();
text = this.oxc.codegenText;
break;
case "ir":
document.getElementById("ir-copy").style.display = "inline";
this.runOptions.ir = true;
@ -479,12 +488,6 @@ class Playground {
this.run();
text = this.oxc.formattedText;
break;
case "minify":
document.getElementById("mangle").style.visibility = "visible";
this.runOptions.minify = true;
this.run();
text = this.oxc.minifiedText;
break;
case "query":
document.getElementById("query-args-or-outputs").style.display =
"inline";
@ -734,6 +737,10 @@ async function main() {
playground.updateView("ast");
};
document.getElementById("codegen").onclick = () => {
playground.updateView("codegen");
};
document.getElementById("ir").onclick = () => {
playground.updateView("ir");
};
@ -746,9 +753,30 @@ async function main() {
// playground.updateView("format");
// };
// document.getElementById("minify").onclick = function () {
// playground.updateView("minify");
// };
document.getElementById("transform").onchange = function () {
const checked = document.getElementById("transform-checkbox").checked;
playground.runOptions.transform = checked;
playground.updateView("codegen");
};
document.getElementById("whitespace").onchange = function () {
const checked = document.getElementById("whitespace-checkbox").checked;
playground.minifierOptions.whitespace = checked;
playground.updateView("codegen");
};
document.getElementById("mangle").onchange = function () {
const checked = document.getElementById("mangle-checkbox").checked;
playground.minifierOptions.mangle = checked;
playground.updateView("codegen");
};
document.getElementById("compress").onchange = function () {
const checked = document.getElementById("compress-checkbox").checked;
playground.minifierOptions.compress = checked;
playground.updateView("codegen");
};
document.getElementById("query").onclick = () => {
playground.updateView("query");
@ -785,12 +813,6 @@ async function main() {
playground.updateEditorText(playground.editor, sourceText);
};
document.getElementById("mangle").onchange = function () {
const checked = document.getElementById("mangle-checkbox").checked;
playground.minifierOptions.mangle = checked;
playground.updateView("minify");
};
document.getElementById("type-check").onchange = function () {
const checked = document.getElementById("type-check-checkbox").checked;
playground.runOptions.type_check = checked;
@ -798,7 +820,7 @@ async function main() {
};
}
// port from https://github.com/fkling/astexplorer/blob/541552fe45885c225fbb67d54dc4c6d6107b65b5/website/src/components/SplitPane.js#L26-L55
// port from https://github.com/fkling/astexplorer/blob/541552fe45885c225fbb67d54dc4c6d6107b65b5/website/src/components/SplitPane.js#L26-L55
function addHorizontalResize() {
const container = document.getElementById("container");
const left = document.getElementById("left");