@dom-expressions/jsx-compiler 0.50.0-next.17 → 0.50.0-next.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +33 -9
  2. package/index.js +59 -9
  3. package/package.json +23 -9
package/README.md CHANGED
@@ -15,6 +15,16 @@ matching your platform automatically through `optionalDependencies`. On other
15
15
  platforms, build from source with `pnpm run build` inside
16
16
  `packages/jsx-compiler` (requires a Rust toolchain).
17
17
 
18
+ ### WebAssembly and StackBlitz
19
+
20
+ The compiler also ships a WASI fallback for environments such as StackBlitz
21
+ WebContainers, where Node.js reports a native platform but cannot load `.node`
22
+ addons. Package managers install the WASI binding as an optional dependency
23
+ without requiring architecture configuration. The normal package entry point
24
+ prefers a native binding and falls back to
25
+ `@dom-expressions/jsx-compiler-wasm32-wasi` when native addons are unavailable.
26
+ Set `NAPI_RS_FORCE_WASI=error` to require the WASI binding for testing.
27
+
18
28
  ## Usage
19
29
 
20
30
  This package exposes a compiler backend API. It is not a Vite, Rollup, or Babel
@@ -136,8 +146,9 @@ Supported options track the Babel plugin where currently implemented:
136
146
  - `omitQuotes`
137
147
  - `omitAttributeSpacing`
138
148
  - `inlineStyles`
139
- - `effectWrapper`: `"effect"` or `false`
140
- - paired wrapperless mode: `wrapConditionals: false` with `memoWrapper: false`
149
+ - `effectWrapper`: custom import name string, or `false` to disable
150
+ - `memoWrapper`: custom import name string, or `false` to disable
151
+ - `wrapConditionals`
141
152
  - `staticMarker`
142
153
  - `validate`
143
154
  - `omitNestedClosingTags`
@@ -146,6 +157,22 @@ Supported options track the Babel plugin where currently implemented:
146
157
  - `requireImportSource`
147
158
  - `renderers`
148
159
 
160
+ ## Performance
161
+
162
+ Compared against `@dom-expressions/babel-plugin-jsx` compiling identical
163
+ sources under identical options (Apple Silicon, release build, in-process,
164
+ median of 7 iterations after warmup — run `pnpm bench` in this package to
165
+ reproduce on your machine):
166
+
167
+ | Workload | babel-plugin-jsx | jsx-compiler | Speedup |
168
+ | ----------------------------------------------- | ---------------: | -----------: | ------: |
169
+ | Fixture corpus (88 files, 174 KB, all 10 modes) | 151 ms | 8.4 ms | 18x |
170
+ | 129 KB single module | 230 ms | 4.6 ms | 50x |
171
+ | 1 MB single module | 10,164 ms | 39 ms | 258x |
172
+
173
+ Native throughput stays flat at ~20–27 MB/s as input grows, while Babel's
174
+ per-file cost grows super-linearly — so the gap widens with file size.
175
+
149
176
  ## Current Scope
150
177
 
151
178
  This package is the AST-native compiler backend. It currently has checked fixture coverage for
@@ -159,10 +186,10 @@ the DOM, hydratable DOM, dev hydratable DOM, SSR, hydratable SSR, universal, dyn
159
186
  attribute handling covered by the checked fixture suites for those targets
160
187
  - Solid-compatible defaults such as `contextToCustomElements: true`
161
188
  - option coverage for `hydratable`, `dev`, `delegateEvents`, `delegatedEvents`,
162
- `omitQuotes`, `omitAttributeSpacing`, `inlineStyles`, `effectWrapper: false`,
163
- paired `wrapConditionals: false` / `memoWrapper: false`, `requireImportSource`,
164
- `staticMarker`, `validate`, `omitNestedClosingTags`, `omitLastClosingTag`,
165
- `builtIns`, and dynamic `renderers`
189
+ `omitQuotes`, `omitAttributeSpacing`, `inlineStyles`, `effectWrapper`,
190
+ `memoWrapper`, `wrapConditionals`, `requireImportSource`, `staticMarker`,
191
+ `validate`, `omitNestedClosingTags`, `omitLastClosingTag`, `builtIns`, and
192
+ dynamic `renderers`
166
193
  - source maps for the implemented path
167
194
 
168
195
  ## Not Implemented Yet
@@ -172,8 +199,6 @@ The compiler intentionally rejects unsupported features instead of pretending to
172
199
  - DOM `namespaceElements` sections that the current Oxc parser rejects before transform
173
200
  (for example, hyphenated JSX member segments)
174
201
  - arbitrary custom renderer names beyond dynamic DOM renderer override plus universal fallback
175
- - custom `effectWrapper` / `memoWrapper` helper names
176
- - unpaired `wrapConditionals: false` or `memoWrapper: false`
177
202
  - unknown/custom namespaced DOM attributes outside known runtime namespaces such as `xlink`
178
203
 
179
204
  ## Architecture
@@ -198,4 +223,3 @@ The module layout mirrors the Babel plugin shape where possible:
198
223
  - `src/ssr/transform.rs`
199
224
  - `src/universal/mod.rs`
200
225
  - `src/universal/transform.rs`
201
-
package/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
5
 
6
- const native = requireNative();
6
+ const native = requireBinding();
7
7
 
8
8
  function transform(code, options) {
9
9
  if (typeof code !== "string") {
@@ -152,10 +152,20 @@ function platformArchSuffix() {
152
152
  return null;
153
153
  }
154
154
 
155
- function requireNative() {
155
+ function requireBinding() {
156
156
  const explicit = process.env.JSX_DOM_EXPRESSIONS_COMPILER_NATIVE;
157
157
  if (explicit) return require(explicit);
158
158
 
159
+ const forceWasi = process.env.NAPI_RS_FORCE_WASI;
160
+ if (forceWasi === "true" || forceWasi === "error") {
161
+ const wasi = requireWasi();
162
+ if (wasi) return wasi;
163
+ if (forceWasi === "error") {
164
+ throw new Error("WASI binding not found and NAPI_RS_FORCE_WASI is set to error");
165
+ }
166
+ }
167
+
168
+ let nativeError;
159
169
  const suffix = platformArchSuffix();
160
170
 
161
171
  // Local builds (napi build output) take precedence for development.
@@ -164,23 +174,63 @@ function requireNative() {
164
174
  localCandidates.push("jsx-compiler.node");
165
175
  for (const file of localCandidates) {
166
176
  const full = path.join(__dirname, file);
167
- if (fs.existsSync(full)) return require(full);
177
+ if (fs.existsSync(full)) {
178
+ try {
179
+ return require(full);
180
+ } catch (error) {
181
+ nativeError = error;
182
+ break;
183
+ }
184
+ }
168
185
  }
169
186
 
170
- if (suffix) {
187
+ if (!nativeError && suffix) {
188
+ const packageName = `@dom-expressions/jsx-compiler-${suffix}`;
171
189
  try {
172
- return require(`@dom-expressions/jsx-compiler-${suffix}`);
190
+ return require(packageName);
173
191
  } catch (error) {
174
- if (error.code !== "MODULE_NOT_FOUND") throw error;
192
+ if (!isMissingPackage(error, packageName)) nativeError = error;
175
193
  }
176
194
  }
177
195
 
196
+ const wasi = requireWasi();
197
+ if (wasi) return wasi;
198
+
199
+ if (nativeError) {
200
+ nativeError.message +=
201
+ "\nThe native binding could not be loaded and the optional " +
202
+ "@dom-expressions/jsx-compiler-wasm32-wasi fallback is not installed.";
203
+ throw nativeError;
204
+ }
205
+
178
206
  throw new Error(
179
- `Could not find the native @dom-expressions/jsx-compiler binary for ${process.platform}-${process.arch}` +
207
+ `Could not find an @dom-expressions/jsx-compiler binding for ${process.platform}-${process.arch}` +
180
208
  (suffix
181
- ? ` (expected the @dom-expressions/jsx-compiler-${suffix} package or a local build)`
209
+ ? ` (expected @dom-expressions/jsx-compiler-${suffix}, @dom-expressions/jsx-compiler-wasm32-wasi, or a local build)`
182
210
  : " (no prebuilt binary is published for this platform)") +
183
- ". Run `pnpm run build` in packages/jsx-compiler to build from source."
211
+ ". Install with WASM support or run `pnpm run build` in packages/jsx-compiler."
212
+ );
213
+ }
214
+
215
+ function requireWasi() {
216
+ const localWasi = path.join(__dirname, "jsx-compiler.wasi.cjs");
217
+ if (fs.existsSync(localWasi)) return require(localWasi);
218
+
219
+ const packageName = "@dom-expressions/jsx-compiler-wasm32-wasi";
220
+ try {
221
+ return require(packageName);
222
+ } catch (error) {
223
+ if (!isMissingPackage(error, packageName)) throw error;
224
+ return null;
225
+ }
226
+ }
227
+
228
+ function isMissingPackage(error, packageName) {
229
+ return (
230
+ error &&
231
+ error.code === "MODULE_NOT_FOUND" &&
232
+ typeof error.message === "string" &&
233
+ error.message.includes(`'${packageName}'`)
184
234
  );
185
235
  }
186
236
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dom-expressions/jsx-compiler",
3
3
  "description": "A JSX to DOM Expressions compiler implemented with Oxc",
4
- "version": "0.50.0-next.17",
4
+ "version": "0.50.0-next.19",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -31,22 +31,36 @@
31
31
  "aarch64-apple-darwin",
32
32
  "x86_64-unknown-linux-gnu",
33
33
  "aarch64-unknown-linux-gnu",
34
- "x86_64-pc-windows-msvc"
35
- ]
34
+ "x86_64-pc-windows-msvc",
35
+ "wasm32-wasip1-threads"
36
+ ],
37
+ "wasm": {
38
+ "browser": {
39
+ "fs": false,
40
+ "asyncInit": false,
41
+ "buffer": false,
42
+ "errorEvent": true
43
+ }
44
+ }
36
45
  },
37
46
  "devDependencies": {
38
- "@napi-rs/cli": "^3.6.2"
47
+ "@emnapi/core": "^1.11.2",
48
+ "@emnapi/runtime": "^1.11.2",
49
+ "@napi-rs/cli": "^3.7.2",
50
+ "@napi-rs/wasm-runtime": "^1.1.6"
39
51
  },
40
52
  "optionalDependencies": {
41
- "@dom-expressions/jsx-compiler-darwin-x64": "0.50.0-next.17",
42
- "@dom-expressions/jsx-compiler-darwin-arm64": "0.50.0-next.17",
43
- "@dom-expressions/jsx-compiler-linux-x64-gnu": "0.50.0-next.17",
44
- "@dom-expressions/jsx-compiler-linux-arm64-gnu": "0.50.0-next.17",
45
- "@dom-expressions/jsx-compiler-win32-x64-msvc": "0.50.0-next.17"
53
+ "@dom-expressions/jsx-compiler-darwin-x64": "0.50.0-next.19",
54
+ "@dom-expressions/jsx-compiler-darwin-arm64": "0.50.0-next.19",
55
+ "@dom-expressions/jsx-compiler-linux-x64-gnu": "0.50.0-next.19",
56
+ "@dom-expressions/jsx-compiler-linux-arm64-gnu": "0.50.0-next.19",
57
+ "@dom-expressions/jsx-compiler-win32-x64-msvc": "0.50.0-next.19",
58
+ "@dom-expressions/jsx-compiler-wasm32-wasi": "0.50.0-next.19"
46
59
  },
47
60
  "scripts": {
48
61
  "build": "napi build --release --strip --manifest-path ./Cargo.toml",
49
62
  "build:debug": "napi build --manifest-path ./Cargo.toml",
63
+ "bench": "pnpm run build && node scripts/bench.mjs",
50
64
  "lint": "cargo clippy --manifest-path ./Cargo.toml -- -D warnings",
51
65
  "test": "pnpm run build:debug && jest --no-cache",
52
66
  "artifacts": "napi artifacts",