mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 20:32:10 +00:00
Hi! I have created a proof of concept of improving using oxc in JavaScript. The method is not polished but it provides valuable insights for future direction! Feel free to close~ It is for reference only :) # Context This is a proof of concept implementation of passing binary AST to JavaScript. JavaScript can selectively read flexbuffers-based AST nodes on demand to avoid the deserialization toll. More context [here](https://dev.to/herrington_darkholme/benchmark-typescript-parsers-demystify-rust-tooling-performance-2go8). # Changes * Add a `parseSyncBuffer` napi method to return a binary AST from Rust to JavaScript. The AST is in flexbuffer format. * Add a `test_buffer.js` to test usage of flexbuffers in JavaScript. It is in cjs format because flexbuffers does not support ESM :/ # Result Some preliminary results, for reference only. ``` ~ node test_buffer.js testJSON: 4.043s testBuffer: 2.395s ``` Buffer based API is 100% faster than JSON. # Future Ideas * Flexbuffers itself is slow. A better binary protocol is desired! * Using binary reader to traverse AST is undesirable. A proxy-based API to emulate object behavior will be nice.
117 lines
3.5 KiB
Rust
117 lines
3.5 KiB
Rust
#![allow(clippy::trailing_empty_array)]
|
|
|
|
use std::sync::Arc;
|
|
|
|
use flexbuffers::FlexbufferSerializer;
|
|
use miette::NamedSource;
|
|
use napi::bindgen_prelude::Buffer;
|
|
use napi_derive::napi;
|
|
use oxc_allocator::Allocator;
|
|
pub use oxc_ast::ast::Program;
|
|
use oxc_parser::{Parser, ParserReturn};
|
|
use oxc_span::SourceType;
|
|
use serde::Serialize;
|
|
|
|
/// Babel Parser Options
|
|
///
|
|
/// <https://github.com/babel/babel/blob/main/packages/babel-parser/typings/babel-parser.d.ts>
|
|
#[napi(object)]
|
|
#[derive(Default)]
|
|
pub struct ParserOptions {
|
|
#[napi(ts_type = "'script' | 'module' | 'unambiguous' | undefined")]
|
|
pub source_type: Option<String>,
|
|
pub source_filename: Option<String>,
|
|
}
|
|
|
|
#[napi(object)]
|
|
pub struct ParseResult {
|
|
pub program: String,
|
|
pub errors: Vec<String>,
|
|
}
|
|
|
|
fn parse<'a>(
|
|
allocator: &'a Allocator,
|
|
source_text: &'a str,
|
|
options: &ParserOptions,
|
|
) -> ParserReturn<'a> {
|
|
let source_type = options
|
|
.source_filename
|
|
.as_ref()
|
|
.map(|name| SourceType::from_path(name).unwrap())
|
|
.unwrap_or_default();
|
|
let source_type = match options.source_type.as_deref() {
|
|
Some("script") => source_type.with_script(true),
|
|
Some("module") => source_type.with_module(true),
|
|
_ => source_type,
|
|
};
|
|
Parser::new(allocator, source_text, source_type).parse()
|
|
}
|
|
|
|
/// Parse without returning anything.
|
|
/// This is for benchmark purposes such as measuring napi communication overhead.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// * File extension is invalid
|
|
/// * Serde JSON serialization
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
#[napi]
|
|
pub fn parse_without_return(source_text: String, options: Option<ParserOptions>) {
|
|
let options = options.unwrap_or_default();
|
|
let allocator = Allocator::default();
|
|
parse(&allocator, &source_text, &options);
|
|
}
|
|
|
|
/// # Panics
|
|
///
|
|
/// * File extension is invalid
|
|
/// * Serde JSON serialization
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
#[napi]
|
|
pub fn parse_sync(source_text: String, options: Option<ParserOptions>) -> ParseResult {
|
|
let options = options.unwrap_or_default();
|
|
|
|
let allocator = Allocator::default();
|
|
let ret = parse(&allocator, &source_text, &options);
|
|
let program = serde_json::to_string(&ret.program).unwrap();
|
|
|
|
let errors = if ret.errors.is_empty() {
|
|
vec![]
|
|
} else {
|
|
let file_name = options.source_filename.unwrap_or_default();
|
|
let source = Arc::new(NamedSource::new(file_name, source_text.to_string()));
|
|
ret.errors
|
|
.into_iter()
|
|
.map(|diagnostic| diagnostic.with_source_code(Arc::clone(&source)))
|
|
.map(|error| format!("{error:?}"))
|
|
.collect()
|
|
};
|
|
|
|
ParseResult { program, errors }
|
|
}
|
|
|
|
/// Returns a binary AST in flexbuffers format.
|
|
/// This is a POC API. Error handling is not done yet.
|
|
/// # Panics
|
|
///
|
|
/// * File extension is invalid
|
|
/// * FlexbufferSerializer serialization error
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
#[napi]
|
|
pub fn parse_sync_buffer(source_text: String, options: Option<ParserOptions>) -> Buffer {
|
|
let options = options.unwrap_or_default();
|
|
let allocator = Allocator::default();
|
|
let ret = parse(&allocator, &source_text, &options);
|
|
let mut serializer = FlexbufferSerializer::new();
|
|
ret.program.serialize(&mut serializer).unwrap();
|
|
serializer.take_buffer().into()
|
|
}
|
|
|
|
/// # Panics
|
|
///
|
|
/// * Tokio crashes
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
#[napi]
|
|
pub async fn parse_async(source_text: String, options: Option<ParserOptions>) -> ParseResult {
|
|
tokio::spawn(async move { parse_sync(source_text, options) }).await.unwrap()
|
|
}
|