← Blog

Text.js 0.1

Text.js 0.1 incorpora un reconciliador de React 19, layout con Yoga y un App Router basado en el sistema de archivos para apps que se renderizan en la consola de DevTools.

Adam

Text.js 0.1 ya tiene etiqueta. Es la primera versión con las tres capas: @textjs/core dibuja fotogramas en la consola, @textjs/react renderiza React 19 en esos fotogramas y textjs añade un App Router y una CLI.

El App Router

Las rutas son carpetas en app/ y siguen las convenciones del App Router de Next.js. Esta es la plantilla dashboard de create-text-app:

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

Un layout envuelve todas las páginas que contiene y conserva su estado durante la navegación. <Link> acepta un shortcut, por lo que una sola tecla permite seguir el enlace, y activeClassName aplica estilos al enlace de la ruta actual:

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

Los segmentos dinámicos funcionan igual que en Next.js. [id] se convierte en un parámetro, [...slug] captura el resto de la ruta y [[...slug]] también coincide con la ruta padre. Los segmentos estáticos tienen prioridad sobre los dinámicos, y los grupos de rutas como (marketing) organizan los archivos sin añadir nada a la 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 y error.tsx envuelven un segmento en un límite de Suspense y un límite de errores. El componente de error recibe el error y una función 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>  );}

Los manejadores de ruta se encuentran en app/api y devuelven una Response estándar:

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

El enrutador mantiene window.location sincronizado mediante history.pushState, por lo que funcionan «Atrás», «Adelante» y los enlaces profundos, y metadata.title se escribe en document.title. La app dibuja en la consola y la página aloja un panel de foco que recoge la entrada.

La CLI

  • text dev inicia Vite con React Fast Refresh. Al guardar un archivo, la consola se actualiza en el mismo sitio.
  • text build genera una compilación de producción de la app y sus manejadores de ruta.
  • text start sirve la compilación y los manejadores de ruta.

create-text-app crea la estructura de un proyecto a partir de la plantilla starter o dashboard.

El inspector

Pulsa Ctrl+` o F2 en una app en ejecución para abrir el inspector [▼] dentro de la consola. Muestra la ruta coincidente y sus parámetros, el tamaño del fotograma, el número de spans y los tiempos de cada fase, y el número de fotogramas que omitió el filtro de cambios. W dibuja el layout de Yoga como una estructura de alambre.

También en 0.1

@textjs/react incluye componentes en PascalCase (Box, Text, ScrollBox, Input, Textarea, Select, Tabs, Code, Markdown, Diff, Modal, Spinner), utilidades de className al estilo de Tailwind que funcionan en celdas y temas. El layout se ejecuta con Yoga en WebAssembly.

La lista completa está en el registro de cambios.