Browse learning paths

Taskboard 1. Local TSX project

Start the cumulative Taskboard as a TSX and ESM browser project. Every later stage extends the same TaskboardState, TaskboardApp and files.

Table of contents

Stage 1. Build a local TSX project

Starting state
Start with an empty local folder.
Applied change
Install Valyrian.js and the Node types, configure automatic TSX, generate the browser entry with inline and mount TaskboardApp on body with serializable TaskboardState data.
Observable result
The browser displays Taskboard with one task from the initial state.
Next step
Continue with Stage 2 and add interaction to this mounted application.

Understand the starting model

Functional component
TaskboardApp is a function that returns the view rendered by Valyrian.js.
TSX
TSX expresses the element tree passed to the Valyrian.js runtime. The configured jsxImportSource resolves that syntax through Valyrian.js.
Props
TaskboardApp receives its state object through props.
Simple state
createState returns the plain TaskboardState object used by this first render.
inline and raw
inline resolves an object with raw, map and file. build.mjs writes raw, the executable bundle, to public/client-entry.js.
Mounting
mount resolves "body", hydrates its existing DOM into a vnode tree, renders TaskboardApp and returns an empty string in the browser. The resulting DOM shows the Taskboard heading and "1 task".

tsconfig.json

json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "valyrian.js",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "skipLibCheck": true,
    "strict": true,
    "target": "ES2022",
    "types": ["node"]
  },
  "include": ["src/**/*.ts", "src/**/*.tsx"]
}

build.mjs

js
import fs from "node:fs";
import { inline } from "valyrian.js/node";

const client = await inline("./src/client-entry.tsx");
fs.mkdirSync("./public", { recursive: true });
fs.writeFileSync("./public/client-entry.js", client.raw);
fs.writeFileSync(
  "./public/index.html",
  '<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Taskboard</title></head><body><script src="/client-entry.js"></script></body></html>',
);

src/state.ts

ts
export type Task = { id: number; title: string };

export type TaskboardState = {
  tasks: Task[];
  showDetails: boolean;
  nextId: number;
  loaded: boolean;
};

export function createState(
  initial: Partial<TaskboardState> = {},
): TaskboardState {
  return {
    tasks: initial.tasks ?? [{ id: 1, title: "Learn Valyrian.js" }],
    showDetails: initial.showDetails ?? false,
    nextId: initial.nextId ?? 2,
    loaded: initial.loaded ?? false,
  };
}

src/app.tsx

tsx
import type { TaskboardState } from "./state";

export function TaskboardApp({ state }: { state: TaskboardState }) {
  return (
    <main>
      <h1>Taskboard</h1>
      <p>{state.tasks.length} task</p>
    </main>
  );
}

src/client-entry.tsx

tsx
import { mount } from "valyrian.js";
import { TaskboardApp } from "./app";
import { createState } from "./state";

const state = createState();
mount("body", <TaskboardApp state={state} />);

Run the project

Install Valyrian.js, build the TSX entry and serve the generated public directory.

bash
npm install valyrian.js@9.1.13 @types/node@22.15.3
node build.mjs
npx --yes serve@14.2.5 public --listen 8000

Open Taskboard

Open http://localhost:8000. The page displays Taskboard with one task.

text
http://localhost:8000