Walkthrough: Vite

See this example on GitHub, where you can find the instructions to run it yourself.

Tip

Check out the standalone example first to understand the basics, if you are not already familiar with Vite and React. Otherwise you might get lost pretty fast!

Setup

The standalone example shows a scenario where the app calls the worker through workex. In this example, let's go a step further. The app will call the worker, and the worker needs to call back to the app to get some data.

Tip

This pattern is very useful if there are some very large data that doesn't make sense to send to the worker. Instead, it is required that the worker should compute what it needs, then asks for it.

Workex Setup

Similar to the standalone example, let's define the messaging interfaces first

// src/msg/proto.ts
import { WorkexPromise as Promise } from "./workex";

/**
 * Functions the app can call on the worker
 *
 * @workex:send app
 * @workex:recv worker
 */
export interface GreetWorker {
    /** Ask the app for a name and greet the person! */
    greet(): Promise<string>;
}

/**
 * Functions the worker can call back to the app to get something
 *
 * @workex:send worker
 * @workex:recv app
 */
export interface GreetApp {
    /** Get the name of the person to greet */
    getName(): Promise<string>;
}

Now run workex to generate the interfaces and workex library

workex --protocol greet src/msg/proto.ts

Info

Run inside the examples/vite directory. If you don't have workex installed yet, run npm run workex instead.

The Worker Side

See the comments in the code that walks through the implementation

// src/worker.ts
import { WorkexPromise } from "./msg/workex";
import { GreetAppClient, bindGreetWorkerHost } from "./msg/sides/worker.ts";
import type { GreetWorker } from "./msg/proto.ts";

const options = { worker: self };

// Create the client used to call back to the app
const client = new GreetAppClient(options);

// Create the handler to handle the messages sent by app
// Note that the standalone case we used Delegate,
// here we are showing how to implement the host directly
class Handler implements GreetWorker {
    async greet(): WorkexPromise<string> {
        // get the person's name from the app
        const name = await client.getName();
        // handle potential communication errors
        if (name.err) {
            return name;
        }
        // greet the person
        const greeting = `Hello, ${name.val}!`;
        // return it back to the app
        return { val: greeting };
    }
}

// similar to the standalone example, we will let the worker
// initiate the handshake
const handshake = bindGreetWorkerHost(new Handler(), options);
handshake.initiate();

The App Side

In the React app, we will make a button that will call the worker when clicked.

// src/App.tsx
import { useState } from 'react'
import './App.css'

// Use the vite ?worker syntax to import the module as a worker directly!
import GreetWorker from './worker.ts?worker'
import { GreetWorkerClient, bindGreetAppHost } from './msg/sides/app.ts'
import { GreetApp } from './msg/proto';
import { hostFromDelegate, type Delegate } from './msg/workex';

async function createWorker(): Promise<GreetWorkerClient> {
    // just some example data
    const names = ["Alice", "Bob", "Charlie", "David", "Eve"];
    const randomName = () => names[Math.floor(Math.random() * names.length)];

    // note this setup is very similar to what we are doing in the worker
    const worker = new GreetWorker();
    const client = new GreetWorkerClient({ worker });

    // here we use a delegate to bind the handler
    const handler = {
        async getName(): Promise<string> {
            return randomName();
        }
    } satisfies Delegate<GreetApp>;
    const handshake = bindGreetAppHost(hostFromDelegate(handler), { worker });

    // note the worker side calls initiate() and the app side
    // calls established()
    await handshake.established();

    return client;
}

// make sure you are handling the worker lifecycle correctly
// so you don't have resource leaks, especially if you need 
// to terminate() the worker
//
// here we are just using a simple global variable
const greetClient = createWorker();

function App() {
    const [message, setMessage] = useState("Press the button to greet someone!");

    return (
        <>
            <h1>Workex Example with Vite</h1>
            <div className="card">
                <button onClick={async () => {
                    const client = await greetClient;
                    const message = await client.greet();
                    if (message.val) {
                        setMessage(message.val);
                        return
                    }
                    console.error(message.err);
                }}>
                    Greet
                </button>
                <p>
                    {message}
                </p>
            </div>
        </>
    )
}

export default App

Run the Example

npm run dev