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: | run: |
rustup target add wasm32-unknown-unknown rustup target add wasm32-unknown-unknown
cargo check -p oxc_wasm --target wasm32-unknown-unknown cargo check -p oxc_wasm --target wasm32-unknown-unknown
- name: Build types
- name: Build
run: | run: |
npx -y wasm-pack build --target web --dev ./crates/oxc_wasm 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 - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
- name: Check output types - 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: typos:
name: Spell Check name: Spell Check
runs-on: ubuntu-latest runs-on: ubuntu-latest

View file

@ -25,7 +25,7 @@ default = ["console_error_panic_hook"]
[dependencies] [dependencies]
oxc = { workspace = true, features = ["serde", "wasm"] } oxc = { workspace = true, features = ["serde", "wasm"] }
serde = { workspace = true } serde = { workspace = true, features = ["derive"] }
wasm-bindgen = { workspace = true } wasm-bindgen = { workspace = true }
serde-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 Source code: https://github.com/oxc-project/oxc/tree/main/wasm/parser
## 🚴 Usage ## Usage
```js ```js
import initWasm, { parseSync } from "@oxc-parser/wasm"; import initWasm, { parseSync } from "@oxc-parser/wasm";
@ -16,9 +16,37 @@ async function main() {
await initWasm(); await initWasm();
const code = "let foo"; const code = "let foo";
const result = parseSync(code, { filename: "test.ts" }); const result = parseSync(code, { sourceFilename: "test.ts" });
console.log(result); console.log(result);
} }
main(); 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)] #![allow(clippy::needless_pass_by_value)]
use serde::Serialize; use serde::{Deserialize, Serialize};
use tsify::Tsify; use tsify::Tsify;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -12,31 +12,33 @@ pub fn main() {
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
} }
/// Babel Parser Options #[derive(Debug, Default, Clone, Deserialize, Tsify)]
/// #[tsify(from_wasm_abi)]
/// <https://github.com/babel/babel/blob/main/packages/babel-parser/typings/babel-parser.d.ts>
#[wasm_bindgen(getter_with_clone)]
#[derive(Default, Tsify)]
pub struct ParserOptions { pub struct ParserOptions {
#[wasm_bindgen(js_name = sourceType)] #[serde(rename = "sourceType")]
#[tsify(optional, type = "\"script\" | \"module\"")]
pub source_type: Option<String>, 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)] #[derive(Default, Tsify)]
#[wasm_bindgen(getter_with_clone)]
pub struct ParseResult { pub struct ParseResult {
#[wasm_bindgen(readonly, skip_typescript)] #[wasm_bindgen(readonly, skip_typescript)]
#[tsify(type = "Program")] #[tsify(type = "Program")]
pub program: JsValue, pub program: JsValue,
#[wasm_bindgen(readonly, skip_typescript)] #[wasm_bindgen(readonly, skip_typescript)]
#[tsify(type = "OxcDiagnostic[]")] #[tsify(type = "Diagnostic[]")]
pub errors: Vec<JsValue>, pub errors: Vec<JsValue>,
} }
#[derive(Default, Tsify, Serialize)] #[derive(Debug, Default, Serialize, Tsify)]
pub struct OxcDiagnostic { pub struct Diagnostic {
pub start: usize, pub start: usize,
pub end: usize, pub end: usize,
pub severity: String, pub severity: String,
@ -61,7 +63,7 @@ pub fn parse_sync(
let allocator = Allocator::default(); let allocator = Allocator::default();
let source_type = options let source_type = options
.filename .source_filename
.as_ref() .as_ref()
.map(|name| SourceType::from_path(name).unwrap()) .map(|name| SourceType::from_path(name).unwrap())
.unwrap_or_default(); .unwrap_or_default();
@ -87,7 +89,7 @@ pub fn parse_sync(
let Some(labels) = error.labels() else { return vec![] }; let Some(labels) = error.labels() else { return vec![] };
labels labels
.map(|label| { .map(|label| {
OxcDiagnostic { Diagnostic {
start: label.offset(), start: label.offset(),
end: label.offset() + label.len(), end: label.offset() + label.len(),
severity: format!("{:?}", error.severity().unwrap_or_default()), severity: format!("{:?}", error.severity().unwrap_or_default()),
@ -96,9 +98,9 @@ pub fn parse_sync(
.serialize(&serializer) .serialize(&serializer)
.unwrap() .unwrap()
}) })
.collect::<Vec<_>>() .collect::<Vec<JsValue>>()
}) })
.collect::<Vec<_>>() .collect::<Vec<JsValue>>()
}; };
Ok(ParseResult { program, errors }) Ok(ParseResult { program, errors })