diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ddd776..2884123 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,11 +23,14 @@ jobs: steps: - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable + - uses: actions/setup-node@v6 with: - targets: wasm32-unknown-unknown - - run: cargo install wasm-bindgen-cli --version 0.2.128 + node-version: '24' + - run: npm install - run: npm run build working-directory: wasm + - run: npm test + working-directory: wasm build: needs: test diff --git a/DEV.md b/DEV.md index 1b67bd2..03e013d 100644 --- a/DEV.md +++ b/DEV.md @@ -40,14 +40,19 @@ The repository is a Cargo workspace with one published crate and one binding cra `wasm/` is `mdhtml-wasm`, the `wasm-bindgen` glue for the browser, and `wasm/package.json` is the npm package `@answerdotai/mdhtml` around it. The package is private until its first publish. Its `version` field is a copy that `ship-bump` keeps in step through `[tool.fastship].version-files`. -Building needs two one-time installs. The CLI version must match the crate version pinned in `wasm/Cargo.toml`. +WASM builds require Rust managed by rustup and Node/npm. The project's npm dependency `wasm-pack` installs the WASM target when missing and downloads or builds the CLI matching Cargo's resolved `wasm-bindgen` version. No separate target or CLI installation is needed. + +In an aai-ws workspace, `ws-sync` installs the npm dependencies, links local packages, and runs the WASM build. For a standalone checkout, install and build from the repository root: ```bash -rustup target add wasm32-unknown-unknown -cargo install wasm-bindgen-cli --version 0.2.128 +npm install +npm run build --workspace wasm +npm test --workspace wasm ``` -`npm run build` in `wasm/` compiles with the `wasm` profile (`dist` at `opt-level = "z"`, for size) and runs `wasm-bindgen` into the ignored `wasm/pkg/`: the `.wasm`, the JavaScript glue, and type declarations. That is the `maturin develop` of the JavaScript side. In the browser the output of `md2mdhtml` goes straight into the DOM, and the browser's own parser does the tree construction that fast5ever does for Python. +`npm run build` in `wasm/` compiles with the `wasm` profile (`dist` at `opt-level = "z"`, for size) and generates the ignored `wasm/pkg/`: the `.wasm`, the JavaScript glue, and type declarations. It keeps the hand-maintained npm manifest and does not run an additional wasm-opt pass. That is the `maturin develop` of the JavaScript side. In the browser the output of `md2mdhtml` goes straight into the DOM, and the browser's own parser does the tree construction that fast5ever does for Python. + +`npm test` in `wasm/` runs Node's built-in test runner against that generated browser-targeted package, checking rendering and Unicode string transfer through the real WASM module. Build first after Rust changes; the test does not rebuild. CI installs, builds, and tests through the same npm commands. A binding crate can only reach the library's public surface, so anything a binding needs is exported from `src/lib.rs`. The Python glue needs six items beyond the documented API (`render_inlines`, `plain`, `code_block_open`, `CODE_BLOCK_CLOSE`, `trailing_attr_span`, `highlight_md`), exported by name so the modules that hold them stay private. diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 3fb48bd..6509b1c 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -15,4 +15,4 @@ crate-type = ["cdylib"] [dependencies] mdhtml-crate = { path = ".." } -wasm-bindgen = "=0.2.128" +wasm-bindgen = ">=0.2.128" diff --git a/wasm/package.json b/wasm/package.json index ab35009..665a1b2 100644 --- a/wasm/package.json +++ b/wasm/package.json @@ -9,7 +9,11 @@ "main": "pkg/mdhtml_wasm.js", "types": "pkg/mdhtml_wasm.d.ts", "files": ["pkg"], + "devDependencies": { + "wasm-pack": ">=0.15.0" + }, "scripts": { - "build": "cargo build -p mdhtml-wasm --target wasm32-unknown-unknown --profile wasm && wasm-bindgen --target web --out-dir pkg ../target/wasm32-unknown-unknown/wasm/mdhtml_wasm.wasm" + "build": "wasm-pack build --target web --profile wasm --no-pack --no-opt", + "test": "node --test test.js" } } diff --git a/wasm/test.js b/wasm/test.js new file mode 100644 index 0000000..3c9b9c8 --- /dev/null +++ b/wasm/test.js @@ -0,0 +1,17 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; +import init, { md2mdhtml } from '@answerdotai/mdhtml'; + +await init({ module_or_path: readFileSync(new URL('./pkg/mdhtml_wasm_bg.wasm', import.meta.url)) }); + +test('renders Markdown through the generated JavaScript bindings', () => { + assert.equal(md2mdhtml('# Hello\n\n**world**'), '
world
\n'); + assert.equal(md2mdhtml(''), ''); +}); + +test('preserves Unicode across repeated calls and memory growth', () => { + for (const text of ['café 日本語 🦀', '🦀'.repeat(100000), 'café 日本語 🦀']) { + assert.equal(md2mdhtml(text), `${text}
\n`); + } +});