← Blog

Text.js 0.1

Text.js 0.1 bringt einen React 19-Reconciler, Yoga-Layout und einen dateisystembasierten App Router für Apps, die in der DevTools-Konsole rendern.

Adam

Text.js 0.1 ist getaggt. Es ist die erste Version mit allen drei Schichten: @textjs/core zeichnet Frames in die Konsole, @textjs/react rendert React 19 in diese Frames, und textjs ergänzt einen App Router und eine CLI.

Der App Router

Routen sind Ordner in app/ und folgen den Konventionen des Next.js App Router. Das ist die dashboard-Vorlage aus create-text-app:

app/├── layout.tsx├── page.tsx├── not-found.tsx├── deployments/│   └── [id]/│       └── page.tsx└── api/    └── metrics/        └── route.ts

Ein Layout umschließt jede untergeordnete Seite und behält seinen Zustand beim Navigieren bei. <Link> nimmt einen shortcut entgegen, sodass ein einzelner Tastendruck dem Link folgt, und activeClassName gestaltet den Link zur aktuellen Route:

app/layout.tsxtsx
import type { ReactNode } from "react";import { Box, KeyHint, Text, useTheme, type Metadata } from "textjs";import Link from "textjs/link";export const metadata: Metadata = { title: { default: "Acme", template: "%s · Acme" } };export default function RootLayout({ children }: { children?: ReactNode }): ReactNode {  const theme = useTheme();  return (    <Box className="flex-col w-full h-full">      <Box className="flex-row h-1 px-1 gap-3 bg-surface shrink-0">        <Text bold color={theme.fg} wrap="nowrap">          ▼ Acme        </Text>        <Link href="/" shortcut="1" className="text-muted no-underline" activeClassName="text-fg bold">          1 Metrics        </Link>        <Link href="/deployments/dpl_a1" shortcut="2" className="text-muted no-underline" activeClassName="text-fg bold">          2 Latest deploy        </Link>      </Box>      <Box className="grow flex-col px-2 pt-1">{children}</Box>      <Box className="flex-row h-1 gap-2 pl-6 shrink-0 bg-surface">        <KeyHint keys="1-2" label="navigate" />        <KeyHint keys="tab" label="focus" />        <KeyHint keys="F2" label="devtools" />      </Box>    </Box>  );}

Dynamische Segmente funktionieren genauso wie in Next.js. [id] wird zu einem Parameter, [...slug] erfasst den Rest des Pfads und [[...slug]] erfasst auch das übergeordnete Segment. Statische Segmente haben Vorrang vor dynamischen, und Routengruppen wie (marketing) organisieren Dateien, ohne die URL zu erweitern.

app/deployments/[id]/page.tsxtsx
import { Box, Text, useInput } from "textjs";import { notFound, useParams, useRouter } from "textjs/navigation";import { DEPLOYMENTS } from "../../../lib/data";export default function DeploymentPage() {  const router = useRouter();  const { id } = useParams<{ id: string }>();  const deployment = DEPLOYMENTS.find((d) => d.id === id);  useInput((e) => {    if (e.key === "escape") router.back();  });  if (!deployment) notFound();  return (    <Box className="flex-col gap-1">      <Text bold>{deployment.id}</Text>      <Text>{deployment.branch}</Text>    </Box>  );}

loading.tsx und error.tsx umschließen ein Segment mit einer Suspense-Boundary und einer Error-Boundary. Die Fehlerkomponente erhält den Fehler und eine reset-Funktion:

app/deployments/error.tsxtsx
import { Box, Text, useInput, type ErrorComponentProps } from "textjs";export default function DeploymentsError({ error, reset }: ErrorComponentProps) {  useInput((e) => {    if (e.key === "r") reset();  });  return (    <Box className="flex-col grow items-center justify-center gap-1">      <Text bold>Something went wrong loading deployments.</Text>      <Text>{error.message}</Text>    </Box>  );}

Route-Handler liegen in app/api und geben eine standardmäßige Response zurück:

app/api/metrics/route.tsts
export function GET(): Response {  return Response.json({ cpu: Math.random(), memory: Math.random(), at: new Date().toISOString() });}

Der Router hält window.location über history.pushState synchron, sodass Zurück, Vorwärts und Deep-Links funktionieren, und schreibt metadata.title in document.title. Die App zeichnet in der Konsole, und die Seite enthält ein Fokus-Pad, das Eingaben erfasst.

Die CLI

  • text dev startet Vite mit React Fast Refresh. Das Speichern einer Datei aktualisiert die Konsole an Ort und Stelle.
  • text build erstellt einen Produktions-Build der App und ihrer Route-Handler.
  • text start stellt den Build und die Route-Handler bereit.

create-text-app erstellt ein Projektgerüst aus der Vorlage starter oder dashboard.

Der Inspektor

Drücke in einer laufenden App Ctrl+` oder F2, um den [▼]-Inspektor in der Konsole zu öffnen. Er zeigt die passende Route und ihre Parameter, die Größe des Frames, die Anzahl der Spans und die Phasenlaufzeiten sowie die Anzahl der Frames, die das Dirty Gate übersprungen hat. W zeichnet das Yoga-Layout als Drahtgittermodell.

Außerdem in 0.1

@textjs/react enthält PascalCase-Komponenten (Box, Text, ScrollBox, Input, Textarea, Select, Tabs, Code, Markdown, Diff, Modal, Spinner), className-Hilfsklassen im Tailwind-Stil, die in Zellen arbeiten, und Themes. Das Layout läuft auf Yoga in WebAssembly.

Die vollständige Liste steht im Änderungsprotokoll.