feat: setup wasm parser for npm (#2221)

This commit is contained in:
Boshen 2024-01-30 21:40:10 +08:00 committed by GitHub
parent f673e41539
commit 5ac61f09a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 492 additions and 29 deletions

View file

@ -57,7 +57,7 @@ jobs:
uses: ./.github/actions/rustup
with:
shared-key: 'wasm'
# cache is saved from the website workflow
save-cache: ${{ github.ref_name == 'main' }}
- name: Check
run: |

71
.github/workflows/release_wasm.yml vendored Normal file
View file

@ -0,0 +1,71 @@
name: Release WASM
on:
workflow_dispatch:
push:
branches:
- main
paths:
- wasm/parser/package.json # Please only commit this file, so we don't need to wait for all the other CI jobs to finish.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check:
name: Check version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
version_changed: ${{ steps.version.outputs.changed }}
steps:
- uses: actions/checkout@v4
- name: Check version changes
uses: EndBug/version-check@v2
id: version
with:
static-checking: localIsNew
file-url: https://unpkg.com/@oxc-parser@wasm/package.json
file-name: wasm/parser/package.json
- name: Set version name
if: steps.version.outputs.changed == 'true'
run: |
echo "Version change found! New version: ${{ steps.version.outputs.version }} (${{ steps.version.outputs.version_type }})"
build:
needs: check
if: needs.check.outputs.version_changed == 'true'
name: Release WASM
runs-on: ubuntu-latest
permissions:
id-token: write # for `npm publish --provenance`
steps:
- uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
- name: Install Rust Toolchain
uses: ./.github/actions/rustup
with:
shared-key: 'wasm'
- name: Build
working-directory: wasm/parser
run: |
rustup target add wasm32-unknown-unknown
corepack enable
pnpm install
pnpm run build
- name: Publish
working-directory: npm/parser-wasm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --tag latest --provenance --access public

View file

@ -25,7 +25,6 @@ jobs:
- name: Install Rust Toolchain
uses: ./.github/actions/rustup
with:
save-cache: ${{ github.ref_name == 'main' }}
shared-key: 'wasm'
- name: Install pnpm

44
Cargo.lock generated
View file

@ -1615,7 +1615,26 @@ dependencies = [
]
[[package]]
name = "oxc_napi_parser"
name = "oxc_parser"
version = "0.5.0"
dependencies = [
"assert-unchecked",
"bitflags 2.4.2",
"miette",
"num-bigint",
"ouroboros",
"oxc_allocator",
"oxc_ast",
"oxc_diagnostics",
"oxc_index",
"oxc_span",
"oxc_syntax",
"rustc-hash",
"serde_json",
]
[[package]]
name = "oxc_parser_napi"
version = "0.0.0"
dependencies = [
"flexbuffers",
@ -1633,22 +1652,15 @@ dependencies = [
]
[[package]]
name = "oxc_parser"
version = "0.5.0"
name = "oxc_parser_wasm"
version = "0.0.1"
dependencies = [
"assert-unchecked",
"bitflags 2.4.2",
"miette",
"num-bigint",
"ouroboros",
"oxc_allocator",
"oxc_ast",
"oxc_diagnostics",
"oxc_index",
"oxc_span",
"oxc_syntax",
"rustc-hash",
"serde_json",
"console_error_panic_hook",
"oxc",
"serde",
"serde-wasm-bindgen",
"tsify",
"wasm-bindgen",
]
[[package]]

View file

@ -1,12 +1,12 @@
[workspace]
resolver = "2"
members = ["crates/*", "tasks/*", "napi/*"]
members = ["crates/*", "tasks/*", "napi/*", "wasm/*"]
exclude = ["tasks/lint_rules"]
[workspace.package]
authors = ["Boshen <boshenc@gmail.com>", "Oxc contributors"]
categories = ["development-tools", "web-programming", "compilers"]
description = "Oxc is a JavaScript / TypeScript tooling suite."
description = "A collection of JavaScript tools written in Rust."
edition = "2021"
homepage = "https://oxc-project.github.io"
keywords = ["JavaScript", "TypeScript", "parser", "linter", "minifier"]

View file

@ -1,5 +1,5 @@
[package]
name = "oxc_napi_parser"
name = "oxc_parser_napi"
version = "0.0.0"
publish = false
authors.workspace = true

4
wasm/parser/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
bin/
pkg/
node_modules/
wasm-pack.log

38
wasm/parser/Cargo.toml Normal file
View file

@ -0,0 +1,38 @@
[package]
name = "oxc_parser_wasm"
version = "0.0.1"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
categories.workspace = true
[lints]
workspace = true
[lib]
crate-type = ["cdylib", "rlib"]
test = false
doctest = false
[features]
default = ["console_error_panic_hook"]
[dependencies]
oxc = { workspace = true, features = ["serde", "wasm"] }
serde = { workspace = true }
wasm-bindgen = { workspace = true }
serde-wasm-bindgen = { workspace = true }
tsify = { workspace = true }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.7", optional = true }

24
wasm/parser/README.md Normal file
View file

@ -0,0 +1,24 @@
## About
Experimental wasm package for the oxc parser, with full TypeScript typings support.
This package is built with `wasm-pack build --release --target web` for bundler (webpack / vite) consumption.
Checkout [oxc-parser](https://www.npmjs.com/package/oxc-parser) for usage in node.js.
Source code: https://github.com/oxc-project/oxc/tree/main/wasm/parser
## 🚴 Usage
```js
import initWasm, { parseSync } from "@oxc-parser/wasm";
async function main() {
await initWasm();
const code = "let foo";
const result = parseSync(code, { filename: "test.ts" });
console.log(result);
}
main();
```

36
wasm/parser/package.json Normal file
View file

@ -0,0 +1,36 @@
{
"name": "@oxc-parser/wasm",
"version": "0.0.1",
"description": "Wasm target for the oxc parser.",
"keywords": [
"JavaScript",
"TypeScript",
"parser"
],
"author": "Boshen and oxc contributors",
"license": "MIT",
"homepage": "https://oxc-project.github.io",
"repository": {
"type": "git",
"url": "https://github.com/oxc-project/oxc",
"directory": "wasm/parser"
},
"funding": {
"url": "https://github.com/sponsors/Boshen"
},
"files": [
"oxc_parser_wasm.d.ts",
"oxc_parser_wasm.js",
"oxc_parser_wasm_bg.wasm",
"oxc_parser_wasm_bg.wasm.d.ts",
"README.md"
],
"module": "oxc_parser_wasm.js",
"types": "oxc_parser_wasm.d.ts",
"scripts": {
"build": "wasm-pack build --release --no-pack --target web --out-dir ../../npm/parser-wasm . && cp ./package.json ../../npm/parser-wasm/package.json"
},
"devDependencies": {
"wasm-pack": "^0.12.1"
}
}

174
wasm/parser/pnpm-lock.yaml Normal file
View file

@ -0,0 +1,174 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
devDependencies:
wasm-pack:
specifier: ^0.12.1
version: 0.12.1
packages:
/axios@0.26.1:
resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==}
dependencies:
follow-redirects: 1.15.5
transitivePeerDependencies:
- debug
dev: true
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/binary-install@1.1.0:
resolution: {integrity: sha512-rkwNGW+3aQVSZoD0/o3mfPN6Yxh3Id0R/xzTVBVVpGNlVz8EGwusksxRlbk/A5iKTZt9zkMn3qIqmAt3vpfbzg==}
engines: {node: '>=10'}
dependencies:
axios: 0.26.1
rimraf: 3.0.2
tar: 6.2.0
transitivePeerDependencies:
- debug
dev: true
/brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true
/chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
dev: true
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/follow-redirects@1.15.5:
resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: true
/fs-minipass@2.1.0:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
engines: {node: '>= 8'}
dependencies:
minipass: 3.3.6
dev: true
/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true
/glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
dev: true
/minipass@3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'}
dependencies:
yallist: 4.0.0
dev: true
/minipass@5.0.0:
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
engines: {node: '>=8'}
dev: true
/minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
dependencies:
minipass: 3.3.6
yallist: 4.0.0
dev: true
/mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
dev: true
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
wrappy: 1.0.2
dev: true
/path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
dev: true
/rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
dependencies:
glob: 7.2.3
dev: true
/tar@6.2.0:
resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==}
engines: {node: '>=10'}
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 5.0.0
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
dev: true
/wasm-pack@0.12.1:
resolution: {integrity: sha512-dIyKWUumPFsGohdndZjDXRFaokUT/kQS+SavbbiXVAvA/eN4riX5QNdB6AhXQx37zNxluxQkuixZUgJ8adKjOg==}
hasBin: true
requiresBuild: true
dependencies:
binary-install: 1.1.0
transitivePeerDependencies:
- debug
dev: true
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
dev: true

105
wasm/parser/src/lib.rs Normal file
View file

@ -0,0 +1,105 @@
#![allow(clippy::needless_pass_by_value)]
use serde::Serialize;
use tsify::Tsify;
use wasm_bindgen::prelude::*;
use oxc::{allocator::Allocator, parser::Parser, span::SourceType};
#[wasm_bindgen(start)]
pub fn main() {
#[cfg(feature = "console_error_panic_hook")]
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)]
pub struct ParserOptions {
#[wasm_bindgen(js_name = sourceType)]
pub source_type: Option<String>,
#[wasm_bindgen]
pub filename: Option<String>,
}
#[wasm_bindgen(getter_with_clone)]
#[derive(Default, Tsify)]
pub struct ParseResult {
#[wasm_bindgen(readonly, skip_typescript)]
#[tsify(type = "Program")]
pub program: JsValue,
#[wasm_bindgen(readonly, skip_typescript)]
#[tsify(type = "OxcDiagnostic[]")]
pub errors: Vec<JsValue>,
}
#[derive(Default, Tsify, Serialize)]
pub struct OxcDiagnostic {
pub start: usize,
pub end: usize,
pub severity: String,
pub message: String,
}
/// # Errors
///
/// * wasm bindgen serialization failed
///
/// # Panics
///
/// * File extension is invalid
/// * Serde JSON serialization
#[wasm_bindgen(js_name = parseSync)]
pub fn parse_sync(
source_text: String,
options: Option<ParserOptions>,
) -> Result<ParseResult, serde_wasm_bindgen::Error> {
let options = options.unwrap_or_default();
let allocator = Allocator::default();
let source_type = options
.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,
};
let ret = Parser::new(&allocator, &source_text, source_type).parse();
let serializer = serde_wasm_bindgen::Serializer::json_compatible();
let program = ret.program.serialize(&serializer)?;
let errors = if ret.errors.is_empty() {
vec![]
} else {
ret.errors
.iter()
.flat_map(|error| {
let Some(labels) = error.labels() else { return vec![] };
labels
.map(|label| {
OxcDiagnostic {
start: label.offset(),
end: label.offset() + label.len(),
severity: format!("{:?}", error.severity().unwrap_or_default()),
message: format!("{error}"),
}
.serialize(&serializer)
.unwrap()
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
};
Ok(ParseResult { program, errors })
}

View file

@ -5,9 +5,9 @@
"type": "module",
"scripts": {
"dev": "pnpm run wasm-dev && concurrently 'vite' 'cd .. && cargo watch --workdir website -s \"pnpm run wasm-dev\"'",
"wasm-dev": "wasm-pack build --out-dir ../../npm/wasm-web --target web --dev --scope oxc ../crates/oxc_wasm",
"wasm-dev": "wasm-pack build --out-dir ../../npm/oxc-wasm --target web --dev --scope oxc ../crates/oxc_wasm",
"build": "pnpm run wasm-build && vite build --base=https://oxc-project.github.io/oxc/",
"wasm-build": "wasm-pack build --out-dir ../../npm/wasm-web --target web --release --scope oxc ../crates/oxc_wasm"
"wasm-build": "wasm-pack build --out-dir ../../npm/oxc-wasm --target web --release --scope oxc ../crates/oxc_wasm"
},
"dependencies": {
"@codemirror/autocomplete": "^6.12.0",
@ -28,7 +28,7 @@
},
"devDependencies": {
"@lezer/common": "^1.2.1",
"@oxc/wasm-web": "link:../npm/wasm-web",
"@oxc/oxc_wasm": "link:../npm/oxc-wasm",
"concurrently": "^8.2.2",
"vite": "^5.0.12",
"wasm-pack": "^0.12.1"

View file

@ -33,7 +33,7 @@ import initWasm, {
OxcLinterOptions,
OxcMinifierOptions,
OxcCodegenOptions,
} from "@oxc/wasm-web";
} from "@oxc/oxc_wasm";
import { getSymbolAndReferencesSpan, renderSymbols } from "./symbols.js";
const placeholderText = `

View file

@ -55,9 +55,9 @@ devDependencies:
'@lezer/common':
specifier: ^1.2.1
version: 1.2.1
'@oxc/wasm-web':
specifier: link:../npm/wasm-web
version: link:../npm/wasm-web
'@oxc/oxc_wasm':
specifier: link:../npm/oxc-wasm
version: link:../npm/oxc-wasm
concurrently:
specifier: ^8.2.2
version: 8.2.2

View file

@ -4,7 +4,7 @@ import { defineConfig } from 'vite'
export default defineConfig({
server: {
fs: {
allow: [__dirname, "../npm/wasm-web"],
allow: [__dirname, "../npm/oxc-wasm"],
},
},
build: {