| Crates.io | relay-hook-transpiler |
| lib.rs | relay-hook-transpiler |
| version | 1.3.31 |
| created_at | 2025-12-19 02:07:30.119859+00 |
| updated_at | 2026-01-05 14:27:25.352352+00 |
| description | Minimal JSX/TSX transpiler for Relay hooks with dynamic import() rewrite to context.helpers.loadModule and friendly errors |
| homepage | https://relaynet.online |
| repository | https://github.com/clevertree/hook-transpiler |
| max_upload_size | |
| id | 1994017 |
| size | 301,763 |
A minimal JSX/TSX transpiler specifically designed for Relay hooks. This library provides a unified interface for code transpilation across Web (via WASM) and Android (JavaScriptCore + JNI).
graph TD
subgraph "Input"
JSX[JSX/TSX Source]
end
subgraph "Transpiler Core (Rust)"
Parser[SWC Parser]
TS[TypeScript Stripping]
React[React Transform]
Import[Import Rewriting]
CJS[CommonJS Conversion]
end
subgraph "Output"
Web[WASM / ESM]
Android[JNI / CommonJS]
end
JSX --> Parser
Parser --> TS
TS --> React
React --> Import
Import --> CJS
CJS --> Web
CJS --> Android
Following the Relay project vision, Relay aims to provide a seamless, high-performance developer experience across all platforms. Since Relay hooks often involve dynamic code generation or transformation that must be extremely fast and lightweight, a standard heavy transpiler like Babel or SWC (which are bloated and unnecessary for this use case) would be overkill for runtime use in resource-constrained environments like mobile apps.
@clevertree/hook-transpiler uses a specialized Rust core, compiled to WASM for web and natively for Android/iOS, ensuring consistent and rapid transpilation regardless of the host environment.
Install from npm:
npm install @clevertree/hook-transpiler
# or
yarn add @clevertree/hook-transpiler
The package includes prebuilt WASM files in the wasm/ directory, so no additional build step is required during installation.
When you run npm install, the WASM files are automatically included:
node_modules/@clevertree/hook-transpiler/wasm/relay_hook_transpiler.js (WASM wrapper)node_modules/@clevertree/hook-transpiler/wasm/relay_hook_transpiler_bg.wasm (WASM binary, ~4.4MB)These are bundled and published with every version update. When you upgrade the package, the WASM files are updated automatically—no manual copy needed.
Initialize the WASM module in your app startup:
import { initHookTranspiler, HookRenderer } from '@clevertree/hook-transpiler';
async function startApp() {
// Initialize the WASM transpiler
await initHookTranspiler();
// Now globalThis.__hook_transpile_jsx is available
// You can transpile JSX on demand or use HookRenderer for drop-in rendering
}
// Later, when rendering a hook
import React from 'react';
const MyApp = () => (
<HookRenderer
host="http://localhost:8002"
hookPath="/hooks/client/get-client.jsx"
/>
);
If you omit hookPath, HookRenderer will issue an OPTIONS request to the provided host to discover the hook path. The response can provide the path via an x-hook-path (or x-relay-hook-path) header, or as JSON { hookPath: "/hooks/client/get-client.jsx" }. When hookPath is supplied, no discovery OPTIONS call is made.
The WASM files are loaded dynamically by initHookTranspiler(). Here are key points for different bundlers:
For Vite:
.wasm files are not excluded from assets.For esbuild:
--loader:.wasm=file to emit WASM as external files.For webpack:
file-loader or asset/resource is configured for .wasm files.If you see console errors like "Failed to load WASM" or network 404 errors for relay_hook_transpiler_bg.wasm:
Step 1: Verify WASM files exist
ls node_modules/@clevertree/hook-transpiler/wasm/
# Should output:
# relay_hook_transpiler.js
# relay_hook_transpiler_bg.wasm
Step 2: Clear and reinstall if missing
rm -rf node_modules
npm install
Step 3: Rebuild your bundle
npm run build
Step 4: Check bundler output Verify that your bundle includes the WASM files in the dist directory:
ls dist/ # or dist/assets/ depending on your bundler
# Should see relay_hook_transpiler*.wasm files
Step 5: For custom HTTP servers If using a non-Vite HTTP server, ensure it serves WASM with correct MIME type:
// Express.js example
app.use('/node_modules/@clevertree/hook-transpiler/wasm', express.static(
path.join(__dirname, 'node_modules/@clevertree/hook-transpiler/wasm'),
{
setHeaders: (res, path) => {
if (path.endsWith('.wasm')) {
res.setHeader('Content-Type', 'application/wasm');
}
}
}
));
Step 6: Check browser DevTools
relay_hook_transpiler_bg.wasm requestStep 7: After upgrading the package
rm -rf distnpm run buildAndroid builds should expose globalThis.__hook_transpile_jsx via the host JNI module. The Android entrypoint provides utilities and a renderer that work with the native transpiler binding.
import { assertTranspilerReady, HookRenderer } from '@clevertree/hook-transpiler/android';
async function startApp() {
// Verify the native transpiler binding is available
try {
assertTranspilerReady();
} catch (e) {
console.error('Native transpiler not linked:', e);
return;
}
// The transpiler is ready to use via globalThis.__hook_transpile_jsx
}
// Use HookRenderer with your native fetch binding
const MyApp = () => (
<HookRenderer
host="http://localhost:8002"
hookPath="/hooks/client/get-client.jsx"
fetchImpl={globalThis.__nativeFetch} // your native fetch binding
/>
);
For Android, ensure your app's native code has the Relay JNI module registered:
RustTranspilerModule is linked in your Gradle buildglobalThis.__nativeFetch that calls your platform HTTP stack (e.g., OkHttp)Use platform-specific entry points to avoid bundling unnecessary code:
// Web
import { initHookTranspiler, HookRenderer } from '@clevertree/hook-transpiler';
// Android/Android/iOS Native
import { assertTranspilerReady, HookRenderer } from '@clevertree/hook-transpiler/android';
HookRenderer: A drop-in React component that fetches a remote JSX hook, transpiles it, and renders the result.FileRenderer: Renders .md, .txt, and .html files with error boundaries.MarkdownRenderer: Specialized renderer for Markdown content.transpileCode(code, options): Lower-level API for manual transpilation.initHookTranspiler() / initTranspiler(): Initialize the WASM transpiler for web.assertTranspilerReady() (Android): Verify native transpiler binding is available.The transpiler provides both code output and import metadata to help the runtime safely rewrite special imports and resolve modules consistently across platforms.
transpile_jsx(source, filename, is_typescript?): Returns { code, error }.transpile_jsx_with_metadata(source, filename, is_typescript?): Returns { code, metadata, error }, where metadata includes:
imports: Array of { source, kind, bindings }
kind.type: One of Builtin | SpecialPackage | Modulebindings: Array of { binding_type, name, alias? }
binding_type.type: Default | Named | Namespacehas_jsx: Whether the source contains JSXhas_dynamic_import: Whether the source uses dynamic import()version: Transpiler version stringThe runtime rewrites imports for certain packages to platform-provided globals so hooks run without bundler-specific resolution:
Recognized special packages:
reactreact-dom@clevertree/meta@clevertree/file-renderer@clevertree/helpers@clevertree/markdownThese are mapped to runtime globals (examples):
globalThis.__hook_jsx_runtimefilename, dirname, url) → globalThis.__relay_metaNote: Do not add import React from 'react' in hooks. The transpiler uses the automatic JSX runtime and injects the necessary helpers.
On web, the WASM init exposes both functions and the runtime will prefer metadata when available:
import { initHookTranspiler } from '@clevertree/hook-transpiler'
await initHookTranspiler()
// Provided by init:
// globalThis.__hook_transpile_jsx
// globalThis.__hook_transpile_jsx_with_metadata (if supported)
const src = "import { dirname } from '@clevertree/meta'\n<div>Hello</div>"
const res = globalThis.__hook_transpile_jsx_with_metadata
? globalThis.__hook_transpile_jsx_with_metadata(src, 'example.jsx')
: globalThis.__hook_transpile_jsx(src, 'example.jsx')
// res.code -> transpiled JS using automatic JSX runtime
// res.metadata -> imports, feature flags, version
The runtime will always apply a lightweight import rewrite to special packages and JSX runtime helpers to ensure hooks execute consistently in production builds.
If modifying the Rust core or TypeScript sources:
# Install dependencies
npm install
# Compile TypeScript and build WASM
npm run build
# The build script automatically:
# 1. Runs `wasm-pack build --release --target web --features wasm`
# 2. Copies WASM files to `wasm/` directory
# 3. Compiles TypeScript to `dist/web/`, `dist/android/`, and `dist/shared/`
# Verify package contents before publishing
npm pack --dry-run
--target webThe WASM must be built with wasm-pack build --release --target web --features wasm:
--target bundler as it creates embedded imports that fail with "Import #0 not an object or function"Run the test suite:
# Unit/self-check tests
npm run build
npm test
# Web Cypress e2e tests
cd tests/web
npm install
npm run test:e2e
Symptom: "Failed to initialize WASM transpiler" or window.__hook_transpile_jsx is undefined.
Solution:
ls node_modules/@clevertree/hook-transpiler/wasm/relay_hook_transpiler_bg.wasm (see Avoiding 404 Errors section above)rm -rf dist && npm run buildrm -rf node_modules && npm installSymptom: assertTranspilerReady() throws "Android transpiler not ready: __hook_transpile_jsx missing".
Solution:
RustTranspilerModule is registered in your Android/iOS Native TurboModule setupadb logcat | grep -i transpilerSymptom: Components don't render or show transpilation errors.
Solution:
initHookTranspiler() completes before rendering (web)assertTranspilerReady() (Android)hookPathThe following features are planned for future releases:
date, time, and datetime-local with native Android pickers (currently only changes keyboard type).<input type="file"> with native file picker integration.<input type="color"> with a native color picker dialog.<input type="range"> mapping to SeekBar.required, pattern, min, max).Contributions are welcome! Please ensure:
npm run buildtsc --noEmitnpm test and npm run test:e2e (web)See LICENSE file in the repository.