use std::path::PathBuf;
use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType;
use crate::{externs::ExternMap, property_names::LocalPropertyMap};
pub use globals::GlobalCategory;
pub use property_names::PropertyMap;
pub mod annotation;
pub(crate) mod chunk;
pub(crate) mod context;
pub mod externs;
pub(crate) mod globals;
pub(crate) mod module;
pub(crate) mod property_names;
pub(crate) mod statements;
#[derive(Default, Debug)]
pub struct OptimizerOptions {
pub hoist: bool,
pub dedupe: bool,
pub globals: GlobalsOptions,
pub rename_properties: bool,
pub url: Option<String>,
}
#[derive(Default, Debug)]
pub struct GlobalsOptions {
pub include: GlobalCategory,
pub hoist: bool,
pub singletons: bool,
}
pub struct OptimizerOutput {
pub code: String,
pub map: String,
}
#[derive(Debug, thiserror::Error)]
pub enum OptimizerError {
#[error("Invalid module type: {0}")]
ModuleType(String),
#[error("Unable to parse javascript file: {0}")]
SyntaxError(String),
#[error("Unable to parse javascript file: {0}")]
SemanticError(String),
#[error("Unable to optimize javascript file: {0}")]
OptimizerError(String),
#[error("Unable to parse property map: {0}")]
PropertyMapParseError(String),
}
pub fn optimize_module(
source_text: &str,
module_type: &str,
options: &OptimizerOptions,
externs: &ExternMap,
) -> Result<OptimizerOutput, OptimizerError> {
let allocator = Allocator::default();
let source_type = match module_type {
"js" => SourceType::mjs(),
"jsx" => SourceType::jsx(),
"ts" => SourceType::ts(),
"tsx" => SourceType::tsx(),
_ => return Err(OptimizerError::ModuleType(module_type.to_string())),
};
let ret = Parser::new(&allocator, source_text, source_type).parse();
if let Some(err) = ret.errors.first() {
return Err(OptimizerError::SyntaxError(err.to_string()));
}
let mut program = ret.program;
let ret = SemanticBuilder::new().with_excess_capacity(0.1).build(&program);
if let Some(err) = ret.errors.first() {
return Err(OptimizerError::SemanticError(err.to_string()));
}
let scoping = ret.semantic.into_scoping();
module::optimize_module(&mut program, options, externs, &allocator, scoping);
let result = Codegen::new()
.with_options(CodegenOptions {
source_map_path: Some(PathBuf::new()),
..Default::default()
})
.build(&program);
Ok(OptimizerOutput {
code: result.code,
map: result.map.map_or_else(String::default, |v| v.to_json_string()),
})
}
pub fn optimize_chunk(
source_text: &str,
options: &OptimizerOptions,
property_map: &PropertyMap,
) -> Result<OptimizerOutput, OptimizerError> {
let allocator = Allocator::default();
let source_type = SourceType::mjs();
let ret = Parser::new(&allocator, source_text, source_type).parse();
if let Some(err) = ret.errors.first() {
return Err(OptimizerError::SyntaxError(err.to_string()));
}
let mut program = ret.program;
let ret = SemanticBuilder::new().with_excess_capacity(0.1).build(&program);
if let Some(err) = ret.errors.first() {
return Err(OptimizerError::SemanticError(err.to_string()));
}
let scoping = ret.semantic.into_scoping();
chunk::optimize_chunk(
&mut program,
options,
LocalPropertyMap::new(property_map),
&allocator,
scoping,
);
let result = Codegen::new()
.with_options(CodegenOptions {
source_map_path: Some(PathBuf::new()),
..Default::default()
})
.build(&program);
Ok(OptimizerOutput {
code: result.code,
map: result.map.map_or_else(String::default, |v| v.to_json_string()),
})
}