npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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 canvas and 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:

  1. 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.
  2. A VFS service URL

    • The frontend only knows an appId and an HTTP VFS endpoint.
    • This is a good fit when assets live on the server and the browser fetches them on demand.

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/ink

If you also want to use the VFS flow, prepare an HTTP endpoint that exposes:

GET /apps/:appId/manifest
GET /apps/:appId/files/:path

Core 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 | Request
    • moduleOrPath?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module

What it does

  • Initializes the Ink wasm runtime
  • Creates the underlying InkWebView
  • Returns the higher-level InkView wrapper
  • Automatically calls bindCanvas() if a canvas is provided

Returns

  • Promise<InkView>
    • Resolves to an InkView instance that can then call bindDomEvents(), openBundle(), and openFromVfs()

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 proxyUrl is 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() or initInk()

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 window when 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:
    • string
    • Uint8Array
    • ArrayBuffer
    • ArrayBufferView
  • 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 fetch implementation
  • 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: string
    • manifest: VfsManifest
    • files: 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 requestAnimationFrame internally 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/manifest
  • https://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/ink

Step 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 files and appId as 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/ink

Step 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 files as a stable reference when possible, so the view is not rebuilt on every render
  • If you need to switch apps, reinitialize when appId or a wrapping key changes
  • If the host size changes significantly, use ResizeObserver and call view.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:

  • string
  • Uint8Array
  • ArrayBuffer

4. When should I use openBundle() vs openFromVfs()?

  • If you already have the full resource contents, use openBundle()
  • If you only know the appId and a server endpoint, use openFromVfs()

Local Development

Build

npm run build

Test

npm test

Package Shape

  • dist/index.js
    • SDK entry point
  • dist/pkg/*
    • wasm-bindgen output
  • dist/env.js
    • runtime import shim