← Blog

Text.js 0.1

Text.js 0.1 porta un reconciler React 19, il layout Yoga e un App Router basato sul file system alle app che eseguono il rendering nella console DevTools.

Adam

Text.js 0.1 ha un tag. È la prima versione con tutti e tre i livelli: @textjs/core disegna i frame nella console, @textjs/react renderizza React 19 in quei frame e textjs aggiunge un App Router e una CLI.

L’App Router

Le route sono cartelle in app/ e seguono le convenzioni dell’App Router di Next.js. Questo è il template dashboard di create-text-app:

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

Un layout avvolge ogni pagina al di sotto e mantiene il proprio stato durante la navigazione. <Link> accetta una shortcut, quindi basta un tasto per seguire il link, e activeClassName applica lo stile al link della route corrente:

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>  );}

I segmenti dinamici funzionano come in Next.js. [id] diventa un parametro, [...slug] cattura il resto del percorso e [[...slug]] corrisponde anche al percorso padre. I segmenti statici hanno la precedenza su quelli dinamici e i gruppi di route come (marketing) organizzano i file senza aggiungere parti all’URL.

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 ed error.tsx avvolgono un segmento in un boundary Suspense e in un error boundary. Il componente di errore riceve l’errore e una funzione reset:

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>  );}

I route handler si trovano in app/api e restituiscono una Response standard:

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

Il router mantiene sincronizzato window.location tramite history.pushState, quindi Indietro, Avanti e i deep link funzionano, e scrive metadata.title in document.title. L’app disegna nella console e la pagina ospita un pannello del focus che raccoglie l’input.

La CLI

  • text dev avvia Vite con React Fast Refresh. Salvare un file aggiorna la console sul posto.
  • text build scrive una build di produzione dell’app e dei suoi route handler.
  • text start serve la build e i route handler.

create-text-app crea la struttura di un progetto a partire dal template starter o dashboard.

L’ispettore

Premi Ctrl+` o F2 in un’app in esecuzione per aprire l’ispettore [▼] nella console. Mostra la route corrispondente e i suoi parametri, le dimensioni del frame, il numero di span e i tempi delle fasi, e il numero di frame saltati dal dirty gate. W disegna il layout Yoga come wireframe.

Altre novità della 0.1

@textjs/react include componenti PascalCase (Box, Text, ScrollBox, Input, Textarea, Select, Tabs, Code, Markdown, Diff, Modal, Spinner), utilità className in stile Tailwind che operano sulle celle e temi. Il layout usa Yoga in WebAssembly.

L’elenco completo è nel registro delle modifiche.