@yodaos-pkg/ink
v0.3.0
Published
Ink Web SDK for browser rendering and VFS-backed app loading
Readme
@yodaos-pkg/ink
@yodaos-pkg/ink is the browser-facing Ink SDK for third-party frontend
projects.
It wraps the low-level wasm runtime and InkWebView into a stable JavaScript
and TypeScript API so you can:
- initialize the Ink wasm runtime
- create and destroy an
InkView - bind a
canvasand forward DOM events such as keyboard and pointer input - open an Ink bundle directly from memory
- fetch and render an Ink bundle from an HTTP VFS service
Use Cases
This package is designed for:
- plain browser-based frontend applications
- React applications embedding Ink inside a component
- Vue applications embedding Ink inside a page or component
- frontend apps that fetch Ink resources from a backend VFS service
What You Need Before Starting
Before integrating the SDK, make sure you have one of these two inputs:
An in-memory bundle
- You already have the contents of
app.json, page scripts, styles, images, and other files. - This is a good fit when your build pipeline or backend has already prepared the bundle.
- You already have the contents of
A VFS service URL
- The frontend only knows an
appIdand an HTTP VFS endpoint. - This is a good fit when assets live on the server and the browser fetches them on demand.
- The frontend only knows an
If you use the VFS flow, provide your own HTTP VFS endpoint on the server side.
Installation
Step 1: Install the SDK
npm install @yodaos-pkg/inkIf you also want to use the VFS flow, prepare an HTTP endpoint that exposes:
GET /apps/:appId/manifest
GET /apps/:appId/files/:pathCore API
These are the most commonly used APIs:
createInkView(options)
Creates an InkView instance and initializes the wasm runtime internally.
Parameters
options.width: number- Required
- The logical width of the Ink view
options.height: number- Required
- The logical height of the Ink view
options.scaleFactor?: number- Optional
- The device pixel ratio, defaulting to
window.devicePixelRatio
options.canvas?: HTMLCanvasElement- Optional
- If provided, the view is automatically bound to this canvas
options.wasm?: InitInkOptions- Optional
- Lets you customize wasm initialization
wasmUrl?: string | URL | RequestmoduleOrPath?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module
What it does
- Initializes the Ink wasm runtime
- Creates the underlying
InkWebView - Returns the higher-level
InkViewwrapper - Automatically calls
bindCanvas()if acanvasis provided
Returns
Promise<InkView>- Resolves to an
InkViewinstance that can then callbindDomEvents(),openBundle(), andopenFromVfs()
- Resolves to an
configureNetwork(options)
Configures host-controlled request interception for Ink network traffic.
Parameters
options.interceptor- Optional
- A function that receives the outgoing request and returns an action
options.proxyUrl- Optional
- A convenience option for rewriting requests through a proxy endpoint
options.targetSearchParam- Optional
- The query parameter name used when
proxyUrlis provided
options.clear- Optional
- Clears the previously registered interceptor
What it does
- Registers a global Ink request interceptor
- Lets hosts continue, rewrite, or block outgoing requests
- Mirrors the shape of a WebView-style request interception hook
Returns
void
createProxyUrlResolver(options)
Creates a request interceptor that rewrites outgoing requests to a host-managed
proxy endpoint such as '/__proxy?url=...'.
Parameters
options.proxyUrl- Required
- The proxy endpoint to call
options.targetSearchParam- Optional
- The query parameter name that receives the original target URL
Returns
InkRequestInterceptor- A reusable interceptor for
configureNetwork()orinitInk()
- A reusable interceptor for
view.bindDomEvents(options?)
Binds common DOM events to the current InkView.
Parameters
options.canvas?: HTMLCanvasElement- Optional
- Overrides the currently bound canvas
options.keyboardTarget?: EventTarget | null- Optional
- The keyboard event target, defaulting to
windowwhen available
options.focusTarget?: HTMLElement | HTMLCanvasElement | null- Optional
- The target used for focus and blur events, defaulting to the canvas
options.preventWheelDefault?: boolean- Optional
- Controls whether wheel default behavior is prevented
- Defaults to
true
What it does
- Binds
pointerdown/pointermove/pointerup/pointercancel - Binds
wheel - Binds
keydown/keyup - Binds
focus/blur - Automatically forwards these browser events into the Ink runtime
Returns
() => void- Returns a cleanup function
- Calling it removes every listener created by this binding call
view.openBundle(options)
Opens an Ink bundle that already exists in memory.
Parameters
options.appId: string- Required
- The application ID of the Ink app to open
options.files: BundleFiles- Required
- Supports three formats:
Map<string, BundleFileInput>Array<[string, BundleFileInput]>Record<string, BundleFileInput>
BundleFileInput- Supported input types:
stringUint8ArrayArrayBufferArrayBufferView
options.initialPage?: string | null- Optional
- Sets the first page to open
options.query?: string | Record<string, unknown> | null- Optional
- Initial query payload
- If an object is provided, the SDK serializes it into JSON
What it does
- Normalizes the file set into the bundle shape expected by the runtime
- Calls the underlying
openBundle - Opens the requested Ink app
- Automatically triggers a render request
Returns
InkView- Returns the current instance so it can be chained
view.openFromVfs(options)
Fetches an Ink bundle from a remote HTTP VFS service and opens it.
Parameters
options.appId: string- Required
- The application ID to open
options.baseUrl: string- Required
- The VFS service base URL, for example
https://example.com/ink-vfs
options.fetch?: typeof globalThis.fetch- Optional
- Lets you provide a custom
fetchimplementation
options.signal?: AbortSignal- Optional
- Used to cancel the request flow
options.headers?: HeadersInit- Optional
- Extra request headers
options.requestInterceptor?: InkRequestInterceptor- Optional
- Overrides the global interceptor for this VFS load only
options.initialPage?: string | null- Optional
- Sets the first page to open
options.query?: string | Record<string, unknown> | null- Optional
- Initial query payload
What it does
- Fetches the VFS manifest first
- Fetches each file listed in the manifest
- Reconstructs the bundle in the browser
- Applies the request interceptor when one is configured
- Calls
openBundle()automatically
Returns
Promise<LoadedVfsBundle>- Resolves to the fetched bundle data:
appId: stringmanifest: VfsManifestfiles: Map<string, Uint8Array>
initInk(options)
Initializes the wasm runtime and optionally installs a host networking policy.
Parameters
options.wasmUrl?: string | URL | Request- Optional
- Custom URL for the wasm binary
options.moduleOrPath?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module- Optional
- Custom wasm module input supported by
wasm-bindgen
options.requestInterceptor?: InkRequestInterceptor- Optional
- Installs a host request interceptor before Ink networking starts
options.proxyUrl?: string- Optional
- Shorthand for a proxy-based interceptor
Returns
Promise<{ InkWebView, getInkVersion }>- Resolves to the wasm bindings
view.startRendering()
Starts the continuous render loop.
Parameters
- None
What it does
- Enables automatic rendering mode
- Uses
requestAnimationFrameinternally to keep rendering - Works well for animations, interaction-heavy apps, and continuously updating UIs
Returns
InkView- Returns the current instance so it can be chained
view.destroy()
Destroys the current InkView and releases its resources.
Parameters
- None
What it does
- Stops the render loop
- Removes event listeners created by
bindDomEvents() - Calls the underlying
InkWebView.destroy() - Releases runtime resources held by the current view
Returns
void
Plain JavaScript Integration
This is the most direct way to integrate Ink into a plain JavaScript app.
Step 1: Prepare a container element
Start with a canvas element:
<canvas id="ink-root" style="width: 375px; height: 667px;"></canvas>Step 2: Import the SDK
import { createInkView } from '@yodaos-pkg/ink';Step 3: Prepare the bundle files
If your assets are already available in memory, organize them as an object or a
Map:
const files = {
'app.json': JSON.stringify({
pages: ['pages/index/index'],
}),
'pages/index/index.js': `
export default {
data: {
title: 'Hello Ink'
}
};
`,
};Step 4: Create the view and bind the canvas
const canvas = document.getElementById('ink-root');
const view = await createInkView({
width: canvas.clientWidth,
height: canvas.clientHeight,
scaleFactor: window.devicePixelRatio,
canvas,
});Step 5: Bind DOM events
This wires common pointer, keyboard, focus, and blur events automatically:
view.bindDomEvents();Step 6: Open the Ink bundle
view.openBundle({
appId: 'demo-app',
files,
});Step 7: Start the render loop
view.startRendering();Step 8: Clean up when the page is destroyed
window.addEventListener('beforeunload', () => {
view.destroy();
});Full Plain JavaScript Example
import { createInkView } from '@yodaos-pkg/ink';
const files = {
'app.json': JSON.stringify({
pages: ['pages/index/index'],
}),
'pages/index/index.js': `
export default {
data: {
title: 'Hello Ink'
}
};
`,
};
const canvas = document.getElementById('ink-root');
const view = await createInkView({
width: canvas.clientWidth,
height: canvas.clientHeight,
scaleFactor: window.devicePixelRatio,
canvas,
});
view.bindDomEvents();
view.openBundle({
appId: 'demo-app',
files,
});
view.startRendering();
window.addEventListener('beforeunload', () => {
view.destroy();
});Opening an App Through VFS
If you do not want to embed the full bundle in the browser and instead prefer
to fetch resources from the server, use openFromVfs().
Step 1: Prepare an accessible VFS service
For example, the server may expose:
https://example.com/ink-vfs/apps/demo-app/manifesthttps://example.com/ink-vfs/apps/demo-app/files/...
Step 2: Create the view
import { createInkView } from '@yodaos-pkg/ink';
const canvas = document.getElementById('ink-root');
const view = await createInkView({
width: canvas.clientWidth,
height: canvas.clientHeight,
scaleFactor: window.devicePixelRatio,
canvas,
});Step 3: Open the remote app
view.bindDomEvents();
await view.openFromVfs({
appId: 'demo-app',
baseUrl: 'https://example.com/ink-vfs',
});
view.startRendering();Step 4: Release resources when done
view.destroy();Vue.js Integration
The recommended approach is to wrap Ink inside a Vue component and manage it
through onMounted and onBeforeUnmount.
Step 1: Install the dependency
npm install @yodaos-pkg/inkStep 2: Create a Vue component
Below is a minimal InkCanvas.vue example.
Step 3: Initialize in onMounted
- Get the
canvas - Create the
InkView - Bind DOM events
- Open the bundle
- Start rendering
Step 4: Clean up in onBeforeUnmount
- Call
view.destroy()
Full Vue Example
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { createInkView } from '@yodaos-pkg/ink';
const props = defineProps<{
appId: string;
files: Record<string, string | Uint8Array>;
}>();
const canvasRef = ref<HTMLCanvasElement | null>(null);
let cleanup = () => {};
onMounted(async () => {
const canvas = canvasRef.value;
if (!canvas) {
return;
}
const view = await createInkView({
width: canvas.clientWidth,
height: canvas.clientHeight,
scaleFactor: window.devicePixelRatio,
canvas,
});
view.bindDomEvents();
view.openBundle({
appId: props.appId,
files: props.files,
});
view.startRendering();
cleanup = () => view.destroy();
});
onBeforeUnmount(() => {
cleanup();
});
</script>
<template>
<canvas ref="canvasRef" style="width: 375px; height: 667px;" />
</template>Vue Integration Tips
- Treat
filesandappIdas component inputs - If the bundle changes, destroy the old view before creating a new one
- If the component size changes, call
view.resize()from the host layer
React.js Integration
The recommended approach is to wrap Ink in a React component and manage its
lifecycle inside useEffect.
Step 1: Install the dependency
npm install @yodaos-pkg/inkStep 2: Create a React component
The component keeps a canvas ref and creates or destroys the Ink view inside
useEffect.
Step 3: Initialize inside useEffect
- Get the
canvas - Create the
InkView - Bind DOM events
- Open the bundle
- Start rendering
Step 4: Destroy it in the effect cleanup
- Call
view.destroy()
Full React Example
import { useEffect, useRef } from 'react';
import { createInkView } from '@yodaos-pkg/ink';
type InkCanvasProps = {
appId: string;
files: Record<string, string | Uint8Array>;
};
export function InkCanvas({ appId, files }: InkCanvasProps) {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
useEffect(() => {
let disposed = false;
let cleanup = () => {};
async function mount() {
const canvas = canvasRef.current;
if (!canvas) {
return;
}
const view = await createInkView({
width: canvas.clientWidth,
height: canvas.clientHeight,
scaleFactor: window.devicePixelRatio,
canvas,
});
view.bindDomEvents();
view.openBundle({
appId,
files,
});
view.startRendering();
cleanup = () => view.destroy();
if (disposed) {
cleanup();
}
}
mount();
return () => {
disposed = true;
cleanup();
};
}, [appId, files]);
return <canvas ref={canvasRef} style={{ width: 375, height: 667 }} />;
}React Integration Tips
- Keep
filesas a stable reference when possible, so the view is not rebuilt on every render - If you need to switch apps, reinitialize when
appIdor a wrappingkeychanges - If the host size changes significantly, use
ResizeObserverand callview.resize()
Frequently Asked Questions
1. The page renders, but input does not work
The usual cause is missing this step:
view.bindDomEvents();2. Resources are still retained after the page is destroyed
Make sure you call this when the component unmounts or the page exits:
view.destroy();3. Should bundle files be strings or binary?
Both are supported:
stringUint8ArrayArrayBuffer
4. When should I use openBundle() vs openFromVfs()?
- If you already have the full resource contents, use
openBundle() - If you only know the
appIdand a server endpoint, useopenFromVfs()
Local Development
Build
npm run buildTest
npm testPackage Shape
dist/index.js- SDK entry point
dist/pkg/*- wasm-bindgen output
dist/env.js- runtime import shim
