← 博客

Text.js 0.1

Text.js 0.1 为在 DevTools 控制台中渲染的应用带来 React 19 协调器、Yoga 布局和基于文件系统的 App Router。

Adam

Text.js 0.1 已打标签。这是首个包含全部三层的版本:@textjs/core 将帧绘制到控制台,@textjs/react 将 React 19 渲染到这些帧中,textjs 则添加 App Router 和 CLI。

App Router

路由是 app/ 中的文件夹,遵循 Next.js App Router 的约定。这是 create-text-app 的 dashboard 模板:

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

布局包裹其下的每个页面,并在导航时保留状态。<Link> 接受 shortcut,按一个键即可跳转,activeClassName 则为指向当前路由的链接设置样式:

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

动态路由段的工作方式与 Next.js 相同。[id] 成为参数,[...slug] 匹配路径的其余部分,[[...slug]] 还会匹配父路径。静态路由段优先于动态路由段,(marketing) 这样的路由分组可用于组织文件,不会增加 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 和 error.tsx 分别用 Suspense 边界和错误边界包裹路由段。错误组件接收错误和一个 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>  );}

路由处理程序位于 app/api 中,返回标准的 Response:

app/api/metrics/route.tsts
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 启动 Vite 并启用 React Fast Refresh。保存文件会原地更新控制台。
  • text build 输出应用及其路由处理程序的生产构建。
  • text start 提供构建产物和路由处理程序的服务。

create-text-app 根据 starter 或 dashboard 模板生成项目骨架。

检查器

在运行中的应用里按 Ctrl+` 或 F2,即可在控制台内打开 [▼] 检查器。检查器显示匹配的路由及其参数、帧尺寸、span 数量、各阶段耗时,以及脏检查门控跳过的帧数。W 将 Yoga 布局绘制为线框图。

0.1 的其他内容

@textjs/react 提供 PascalCase 命名的组件(Box、Text、ScrollBox、Input、Textarea、Select、Tabs、Code、Markdown、Diff、Modal、Spinner)、以单元格为单位的 Tailwind 风格 className 工具类和主题。布局由 WebAssembly 中的 Yoga 处理。

完整列表见更新日志。