Text.js 0.1
Text.js 0.1은 DevTools 콘솔에서 렌더링하는 앱에 React 19 리컨실러, Yoga 레이아웃, 파일 시스템 기반 App Router를 제공해요.
Text.js 0.1에 태그가 붙었어요. 세 계층을 모두 갖춘 첫 버전이에요: @textjs/core는 콘솔에 프레임을 그리고, @textjs/react는 그 프레임에 React 19를 렌더링하며, textjs는 App Router와 CLI를 추가해요.
App Router
라우트는 Next.js App Router 규칙을 따르는 app/ 안의 폴더예요. 다음은 create-text-app의 dashboard 템플릿이에요:
app/├── layout.tsx├── page.tsx├── not-found.tsx├── deployments/│ └── [id]/│ └── page.tsx└── api/ └── metrics/ └── route.ts레이아웃은 하위의 모든 페이지를 감싸고 탐색 중에도 상태를 유지해요. <Link>는 shortcut을 받아 키 하나로 링크를 따라 이동할 수 있고, activeClassName은 현재 라우트로 향하는 링크에 스타일을 적용해요:
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> );}동적 세그먼트는 Next.js와 같은 방식으로 작동해요. [id]는 매개변수가 되고, [...slug]는 경로의 나머지를 포착하며, [[...slug]]는 상위 경로에도 일치해요. 정적 세그먼트는 동적 세그먼트보다 우선하고, (marketing) 같은 라우트 그룹은 URL에 경로를 추가하지 않고 파일을 정리해요.
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와 error.tsx는 세그먼트를 각각 Suspense 경계와 에러 바운더리로 감싸요. 오류 컴포넌트는 오류와 reset 함수를 받아요:
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> );}라우트 핸들러는 app/api에 있고 표준 Response를 반환해요:
export function GET(): Response { return Response.json({ cpu: Math.random(), memory: Math.random(), at: new Date().toISOString() });}라우터는 history.pushState로 window.location을 동기화하므로 뒤로 가기, 앞으로 가기, 딥 링크가 작동하고, metadata.title은 document.title에 기록돼요. 앱은 콘솔에 화면을 그리고, 페이지에는 입력을 수집하는 포커스 패드가 있어요.
CLI
text dev는 React Fast Refresh와 함께 Vite를 시작해요. 파일을 저장하면 현재 콘솔 화면이 갱신돼요.text build는 앱과 라우트 핸들러의 프로덕션 빌드를 생성해요.text start는 빌드와 라우트 핸들러를 제공해요.
create-text-app은 starter 또는 dashboard 템플릿으로 프로젝트의 기본 구조를 만들어요.
검사기
실행 중인 앱에서 Ctrl+` 또는 F2를 눌러 콘솔 안의 [▼] 검사기를 여세요. 일치하는 라우트와 매개변수, 프레임 크기, 스팬 수와 단계별 소요 시간, dirty gate가 건너뛴 프레임 수를 보여 줘요. W는 Yoga 레이아웃을 와이어프레임으로 그려요.
0.1의 추가 기능
@textjs/react는 PascalCase 컴포넌트(Box, Text, ScrollBox, Input, Textarea, Select, Tabs, Code, Markdown, Diff, Modal, Spinner), 셀 단위로 작동하는 Tailwind 스타일의 className 유틸리티, 테마를 제공해요. 레이아웃은 WebAssembly의 Yoga에서 실행돼요.
전체 목록은 변경 기록에 있어요.