hook-transpiler — Rust Crate for Cross‑Platform JSX/TSX Transpiling and Loading
Last updated: 2025-12-12 10:02 (local)
Purpose
Minimal, reliable transpiler used by Relay clients (web and React Native) to:
- Transpile JSX/TSX → executable JavaScript
- Rewrite dynamic
import()→context.helpers.loadModule(spec)for lazy loading - Offer friendly diagnostics suitable for non‑devs
- Provide fetch helpers (with TLS) for server/native contexts (future work)
This crate is the foundation for client‑side (WASM) and server‑side transpilation. Client‑web uses the WASM build first; server /api/transpile acts as a fallback. RN initially uses the server endpoint, with room to evolve to on‑device transpilation.
Current Status Summary
- Core Rust crate exists and compiles in workspace
- Core transforms implemented (TS strip, React classic runtime,
import()rewrite) - Friendly error types implemented (parse/transform/codegen)
- Initial unit tests included (JSX basics, dynamic import rewrite, minimal get‑client snippet)
- Server fallback route implemented and calling this crate:
POST /api/transpile - Client‑web wired for strict crate‑WASM usage (no SWC/Babel/server fallback while validating) — confirmed working
- RN integration next: choose native binding approach (JSI) or use server fallback initially
- A separate isolated WASM crate for the browser is aligned to
swc_core v50.xAPIs (in progress; usable subset already powering client‑web)
Task List and Status
-
Core crate (this directory)
- Create crate and add to workspace
- Implement parser and transforms (TS strip, React classic w/ pragma, dynamic
import()→ loader) - Error types for parse/transform/codegen with filename and positions
- Optional CommonJS output flag (for RN if needed)
- Initial unit tests (basic JSX, dynamic import rewrite, minimal get‑client)
- Expand unit tests
- Full
template/hooks/client/get-client.jsxtranspilation (integration test) - TSX inter‑module async imports (A lazily imports B)
- Negative cases with user‑friendly diagnostics (parse error locations)
- Full
-
Web/WASM build (client‑first)
- Add build script to emit wasm + JS glue to client‑web public assets (
scripts/build-hook-wasm.sh) - Client‑web loader that initializes the WASM and exposes
globalThis.__hook_transpile_jsx - [*] Align and finalize WASM build against
swc_core v50.x(isolated browser crate) — in progress - Minimal wasm‑exposed API shape:
transpile_jsx(source, filename) -> { code, map?, diagnostics? } - Document WASM loading behavior and troubleshooting — see Release Validation doc
- Add build script to emit wasm + JS glue to client‑web public assets (
-
Server fallback (apps/server)
- Implement
POST /api/transpileendpoint invoking this crate - Map errors to friendly diagnostics for clients
- Add source map support (optional, nice‑to‑have)
- Integration test covering endpoint with typical inputs
- Implement
-
Client‑web integration (apps/client‑web)
- Strict mode using only crate‑WASM during bring‑up (no SWC/Babel/server fallback)
- Settings option to allow server fallback or force server‑only
- [*] Re‑enable server fallback after WASM is green (client‑first strategy) — available via Settings
- Surface diagnostics in the UI where hooks are rendered
- E2E test: load
get-client.jsx, verify lazy imports route viahelpers.loadModule
-
Client‑React‑Native integration (apps/client‑react‑native)
- Decide native integration path (modern & efficient):
- Phase 1: Use server endpoint for reliability on all devices
- Phase 2: Add native JSI/TurboModule binding to Rust (C++ shim), compile crate as shared lib (.so/.a) via NDK/clang
- Android: cargo-ndk build, Gradle packaging, Hermes compatibility
- iOS: Xcode build settings, CocoaPods or Swift Package linking
- Expose minimal API to JS:
transpileJsx(source, filename) -> string | { code } - Optionally request CommonJS output until ESM path is uniform
- Replace Debug tab tests with a single “Client Transpiler Test”
- Settings toggle to choose Client vs Server transpiler (default: Client)
- Decide native integration path (modern & efficient):
-
Fetch handling utilities
- Server/native:
reqwestTLS‑enabled fetch helper (optional feature) - Web: WASM build provides a stub delegating to JS
fetch - Tests for basic fetch scenarios (HTTPS + cert validation)
- Server/native:
-
Documentation
- This README with tasks and status
- Crate API examples and diagnostics format
- Update READMEs: hook‑transpiler, client‑react‑native, client‑web, and project root with transpiler details
- Release Validation guide:
docs/RELEASE_VALIDATION.md - Migration notes for client‑web and RN
-
Server GET fallback behavior (integration & tests)
- Implement fallback option in server for GET that need transpile (route available)
- Ensure middleware/handlers invoke transpiler where appropriate
- Unit/integration tests for fallback path (success and diagnostics)
-
Release & Distribution
- Android APK (release) build with RN client using the latest transpiler path
- Install on device and smoke‑test: load
get-client.jsx, verify lazyimport()viahelpers.loadModule - Document release steps and troubleshooting
Build (Server/Native)
This crate is part of the Cargo workspace. To build and run tests:
Build (Web/WASM)
Artifacts are generated into the web app’s source folder so Vite can bundle them. Run the cross-platform helper from the repo root:
# From repo root (cross-platform)
# Dev cycle (build then start web dev server)
Expected outputs (canonical location under the web app source):
apps/client-web/src/wasm/hook_transpiler.jsapps/client-web/src/wasm/hook_transpiler_bg.wasm
Client‑web loads these at startup via apps/client-web/src/wasmEntry.ts (shim) and exposes:
globalThis.__hook_transpile_jsx(source: string, filename: string) => string | { code: string }
See also: Release validation steps in docs/RELEASE_VALIDATION.md.
Minimal Public API (Rust)
The crate exposes a Rust API consumed by the server and unit tests:
;
Error types are user‑facing and include filename and (when available) locations:
ParseError { filename, line, col, message }TransformError(filename, source_err)CodegenError(filename, source_err)
Dynamic import() rewrite
All import(spec) calls are rewritten to context.helpers.loadModule(spec) so hooks lazily load peer modules through the runtime’s loader. This is essential for TSX/JSX modules importing each other asynchronously.
Testing
Run unit tests:
Planned tests to add:
- End‑to‑end transpilation for
template/hooks/client/get-client.jsx - Cross‑file lazy imports (A → import("./B"))
- Friendly diagnostics on malformed JSX/TSX
Notes
- Source maps are currently omitted; can be enabled later.
- RN can initially rely on the server endpoint and optionally request CommonJS output (
to_commonjs: true). - Web client should attempt WASM first, then server fallback (once strict bring‑up completes).
Publishing (crates.io + npm)
- The Rust crate is the single source of truth; the npm package compiles its WASM/JS from this crate.
- Keep versions in
Cargo.tomlandpackage.jsonaligned (e.g., 0.2.x) before releasing. - Publish flow:
cargo publishnpm run build(regenerates WASM/JS from the crate) →npm publish
- No duplicate Rust code is shipped to npm; only the generated WASM/JS artifacts built from this crate.