feat(wasm/parser): improve FFI (#2232)

This commit is contained in:
Boshen 2024-01-31 15:53:32 +08:00 committed by GitHub
parent 5fb5d8a01e
commit 9c6c17b9aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 58 additions and 22 deletions

View file

@ -63,15 +63,21 @@ jobs:
run: |
rustup target add wasm32-unknown-unknown
cargo check -p oxc_wasm --target wasm32-unknown-unknown
- name: Build types
- name: Build
run: |
npx -y wasm-pack build --target web --dev ./crates/oxc_wasm
npx -y wasm-pack build --target web --dev ./wasm/parser
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Check output types
run: npx -y -p typescript tsc --lib es2020,dom crates/oxc_wasm/pkg/oxc_wasm.d.ts
run: |
npx -y -p typescript tsc --lib es2020,dom crates/oxc_wasm/pkg/oxc_wasm.d.ts
typos:
name: Spell Check
runs-on: ubuntu-latest

View file

@ -25,7 +25,7 @@ default = ["console_error_panic_hook"]
[dependencies]
oxc = { workspace = true, features = ["serde", "wasm"] }
serde = { workspace = true }
serde = { workspace = true, features = ["derive"] }
wasm-bindgen = { workspace = true }
serde-wasm-bindgen = { workspace = true }

View file

@ -7,7 +7,7 @@ Checkout [oxc-parser](https://www.npmjs.com/package/oxc-parser) for usage in nod
Source code: https://github.com/oxc-project/oxc/tree/main/wasm/parser
## 🚴 Usage
## Usage
```js
import initWasm, { parseSync } from "@oxc-parser/wasm";
@ -16,9 +16,37 @@ async function main() {
await initWasm();
const code = "let foo";
const result = parseSync(code, { filename: "test.ts" });
const result = parseSync(code, { sourceFilename: "test.ts" });
console.log(result);
}
main();
```
## Notes
### UTF8 vs UTF16 byte offsets
The `span` value returned from the ASTs and diagnostics is in UTF8 byte offsets. Converting to UTF16 byte offsets:
```js
let sourceTextUtf8 = new TextEncoder().encode(sourceText);
const convertToUtf8 = (sourceTextUtf8, d) => {
return new TextDecoder().decode(sourceTextUtf8.slice(0, d)).length;
}
const diagnostics = result.errors.map((d) => ({
from: convertToUtf8(sourceTextUtf8, d.start),
to: convertToUtf8(sourceTextUtf8, d.end),
severity: d.severity.toLowerCase(),
message: d.message,
}));
```
### Vite
`wasm-pack build --target web` is used for the wasm build.
You may need something like https://github.com/nshen/vite-plugin-wasm-pack to get it working with vite,
otherwise vite will load the wasm file as a HTML file causing a `CompileError: WebAssembly.instantiate(): expected magic word` error.

View file

@ -1,6 +1,6 @@
#![allow(clippy::needless_pass_by_value)]
use serde::Serialize;
use serde::{Deserialize, Serialize};
use tsify::Tsify;
use wasm_bindgen::prelude::*;
@ -12,31 +12,33 @@ pub fn main() {
console_error_panic_hook::set_once();
}
/// Babel Parser Options
///
/// <https://github.com/babel/babel/blob/main/packages/babel-parser/typings/babel-parser.d.ts>
#[wasm_bindgen(getter_with_clone)]
#[derive(Default, Tsify)]
#[derive(Debug, Default, Clone, Deserialize, Tsify)]
#[tsify(from_wasm_abi)]
pub struct ParserOptions {
#[wasm_bindgen(js_name = sourceType)]
#[serde(rename = "sourceType")]
#[tsify(optional, type = "\"script\" | \"module\"")]
pub source_type: Option<String>,
#[wasm_bindgen]
pub filename: Option<String>,
/// "module" and "jsx" will be inferred from `sourceFilename`.
#[serde(rename = "sourceFilename")]
#[tsify(optional)]
pub source_filename: Option<String>,
}
#[wasm_bindgen(getter_with_clone)]
#[derive(Default, Tsify)]
#[wasm_bindgen(getter_with_clone)]
pub struct ParseResult {
#[wasm_bindgen(readonly, skip_typescript)]
#[tsify(type = "Program")]
pub program: JsValue,
#[wasm_bindgen(readonly, skip_typescript)]
#[tsify(type = "OxcDiagnostic[]")]
#[tsify(type = "Diagnostic[]")]
pub errors: Vec<JsValue>,
}
#[derive(Default, Tsify, Serialize)]
pub struct OxcDiagnostic {
#[derive(Debug, Default, Serialize, Tsify)]
pub struct Diagnostic {
pub start: usize,
pub end: usize,
pub severity: String,
@ -61,7 +63,7 @@ pub fn parse_sync(
let allocator = Allocator::default();
let source_type = options
.filename
.source_filename
.as_ref()
.map(|name| SourceType::from_path(name).unwrap())
.unwrap_or_default();
@ -87,7 +89,7 @@ pub fn parse_sync(
let Some(labels) = error.labels() else { return vec![] };
labels
.map(|label| {
OxcDiagnostic {
Diagnostic {
start: label.offset(),
end: label.offset() + label.len(),
severity: format!("{:?}", error.severity().unwrap_or_default()),
@ -96,9 +98,9 @@ pub fn parse_sync(
.serialize(&serializer)
.unwrap()
})
.collect::<Vec<_>>()
.collect::<Vec<JsValue>>()
})
.collect::<Vec<_>>()
.collect::<Vec<JsValue>>()
};
Ok(ParseResult { program, errors })