Browse learning paths

1. Local TSX project

Start Taskboard as a browser project using TSX and ESM. Every later stage extends the state, main component, and files in this same project.

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 bundle with inline, and mount TaskboardApp with serializable state.
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
At this stage, mount finds "body", renders TaskboardApp, and returns an empty string in the browser. The result shows Taskboard with one task.

package.json

json
{
  "name": "valyrian-taskboard",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "node build.mjs"
  },
  "dependencies": {
    "valyrian.js": "9.1.13"
  },
  "devDependencies": {
    "@types/node": "22.15.3"
  }
}

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>',
);

public/index.html

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