ESLint

eslint-plugin-textjs: JSX an toàn cho console, ký tự theo chiều rộng ô, bộ hẹn giờ theo nhịp frame và kiểm tra App Router.

eslint-plugin-textjs phát hiện lỗi Text.js trong trình soạn thảo: thẻ DOM trong component console, hộp bên trong văn bản, ký tự mà Menlo không có, lệnh gọi console.log ngoài ý muốn và các export App Router bị bỏ qua mà không có thông báo.

Thiết lập

bun add -d eslint eslint-plugin-textjs typescript-eslint
eslint.config.jsjs
import { defineConfig } from "eslint/config";import tseslint from "typescript-eslint";import textjs from "eslint-plugin-textjs";export default defineConfig(tseslint.configs.recommended, textjs.configs.recommended);

Plugin hỗ trợ ESLint 9 và 10 với cấu hình phẳng. Các quy tắc kiểm tra cú pháp và không cần thông tin kiểu hay parserOptions.project. Mọi bộ phân tích cú pháp hiểu TypeScript và JSX đều dùng được.

Cấu hình

Cấu hìnhNội dung bật
textjs.configs.recommendedMọi quy tắc kiểm tra tính đúng đắn ở mức lỗi; quy tắc về console, bộ hẹn giờ và nhịp frame ở mức cảnh báo
textjs.configs.strictTất cả ở mức lỗi, thêm prefer-use-keymap ở mức cảnh báo

Hoặc chọn từng quy tắc:

eslint.config.jsjs
import textjs from "eslint-plugin-textjs";export default [  {    plugins: { textjs },    rules: {      "textjs/no-box-in-text": "error",      "textjs/no-console-in-components": ["warn", { allow: ["error"], logger: "log" }],    },  },];

Tệp console

Hầu hết quy tắc chỉ áp dụng cho tệp render vào console: tệp import component hoặc hook (Box, Text, useInput, …) từ @textjs/app hoặc @textjs/react. Tệp import react-dom hoặc @textjs/react/dom là mã trang và không bị kiểm tra, nên một website có thể kết hợp trang DOM và ứng dụng console trong cùng dự án. Import tiện ích như tokenize hoặc parseMarkdown không biến tệp thành tệp console.

eslint.config.jsjs
import textjs from "eslint-plugin-textjs";export default [  textjs.configs.recommended,  {    settings: {      textjs: {        packages: ["@acme/console-ui"], // also re-exports Text.js components        consoleFiles: "imports", // or "all"      },    },  },];

Các quy tắc App Router dựa vào tên tệp (page.tsx, layout.tsx, route.ts, …) trong mọi thư mục app/, kể cả src/app/. Nếu thư mục của bạn có tên khác, truyền { appDir: "routes" } cho valid-route-module, valid-metadata và route-handler-exports.

Quy tắc

Quy tắcKhuyến nghịSửa lỗi
no-dom-elementslỗigợi ý
no-box-in-textlỗi
no-unsupported-glyphslỗitự sửa, gợi ý
no-console-in-componentscảnh báogợi ý
prefer-use-intervalcảnh báogợi ý
no-sub-frame-timerscảnh báo
prefer-use-keymapchỉ trong strict
valid-route-modulelỗi
valid-metadatalỗi
route-handler-exportslỗigợi ý

no-dom-elements

<div>, <span>, phần tử tùy chỉnh và các thẻ DOM khác không tồn tại trong console. Bộ đối soát chỉ biết các nút host của nó và ném lỗi TX003 với mọi thứ khác.

<div className="flex-col">       // ✗<Box className="flex-col">       // ✓<strong>Deployed</strong>        // ✗<Text bold>Deployed</Text>       // ✓

Gợi ý thay thẻ bằng component gần nhất (div → Box, span/p → Text, strong → Text bold, a → Link, input → Input, hr → Divider, …) và thêm import.

no-box-in-text

<Text> là một chuỗi span có định dạng; nó không thể chứa hộp. Lồng <Box> hoặc bất kỳ component nào có gốc là hộp (<ScrollBox>, <Input>, <Code>, <ProgressBar>, <Divider>, …) sẽ ném lỗi TX004 khi render. Quy tắc này phát hiện lỗi ngay trong trình soạn thảo, kể cả bên trong &&, biểu thức ba ngôi, fragment và .map().

<Text>Status <Box /></Text>                     // ✗<Box className="flex-row gap-1"><Text>Status</Text><Box /></Box>  // ✓<Text>Press <Kbd>⌘K</Kbd> <Spinner /></Text>     // ✓ inline components

no-unsupported-glyphs

Text.js bố trí mọi ký tự trên lưới ô cố định. Ký tự mà Menlo, phông chữ console DevTools trên macOS, không có sẽ render bằng phông dự phòng rộng hơn một ô, khiến mọi ô phía sau bị lệch.

  • Chữ nổi Braille (U+2800–U+28FF, các frame spinner terminal cổ điển): dùng ký tự khối hoặc góc phần tư (▘▝▗▖), <Spinner variant="dots"> hoặc marker="quadrant" trên biểu đồ. Đặt { allowBraille: true } cho ứng dụng chỉ chạy bằng text run trong terminal.
  • Ký hiệu và mũi tên khác (U+2B00–U+2BFF, như hình vuông nhỏ U+2B1D): gợi ý đưa ra ký tự tương đương trong Menlo (·, ●, ←).
  • Hiển thị emoji cho ký hiệu một ô (▶ hoặc ↩ theo sau bởi U+FE0F): ký tự chọn biến thể biến chúng thành emoji hai ô trong khi lưới dành một ô. Tính năng tự sửa sẽ xóa ký tự chọn biến thể.

Emoji rộng (🚀, ✅) không có vấn đề: Text.js đo chúng là hai ô.

no-console-in-components

Mỗi frame là một console.log. Nhật ký riêng của bạn nằm giữa các frame, bị frame tiếp theo cuộn đi và khuất khỏi chế độ xem bám đáy. console.clear() xóa chính ứng dụng.

"textjs/no-console-in-components": ["warn", { allow: ["error"], logger: "log" }]

Hiển thị thông báo trong giao diện bằng useToast() hoặc <ScrollBox> nhật ký, hoặc kiểm tra trạng thái trong Dev Overlay [▼] (Ctrl+`). Với logger, gợi ý viết lại console.warn(…) thành log.warn(…). Quy tắc bỏ qua tệp kiểm thử và các lần render bằng @textjs/app/testing.

prefer-use-interval

Dùng trực tiếp setInterval trong component cần effect, hàm dọn dẹp và ref để tránh trạng thái cũ. useInterval làm cả ba, và null dừng nó.

useEffect(() => {  if (paused) return;  const id = setInterval(tick, 1000);  return () => clearInterval(id);}, [paused]);useInterval(tick, paused ? null : 1000); // the suggestion

Với hoạt ảnh, useAnimationFrame theo nhịp frame của bộ render và dừng khi đầu ra tạm dừng. Xem Hook tiện ích.

no-sub-frame-timers

Text.js xuất tối đa một frame mỗi 66 ms (khoảng 15 fps). Khoảng lặp 16 ms render bốn lần cho mỗi frame xuất ra; các frame thừa gộp lại và chỉ tiêu tốn CPU. Quy tắc báo cáo độ trễ setInterval/useInterval dưới minInterval (mặc định 66) và useAnimationFrame có fps trên 15.

prefer-use-keymap

So sánh e.key thủ công vẫn hoạt động, nhưng phím tắt không hiển thị. useKeymap đưa các liên kết phím có nhãn vào <KeyHelp>, <KeyHints> (liên kết có hint: true) và bảng lệnh, xử lý chuỗi như g g và ngăn lan truyền sự kiện cho phím khớp. Quy tắc báo cáo handler useInput so sánh từ hai giá trị phím dạng literal trở lên (minKeys) và bỏ qua handler đọc e.input để nhập văn bản.

useKeymap({  "up, k": { label: "Increment", run: inc, hint: true },  "down, j": { label: "Decrement", run: dec, hint: true },});

Xem Phím tắt.

valid-route-module

  • Tệp page, layout, loading, error và not-found phải export mặc định một component (TX015).
  • export const dynamic phải là "auto", "force-static" hoặc "force-dynamic" (TX031). Text.js không hỗ trợ "error" của Next.js. Text.js đọc giá trị này từ trang, layout và route handler. Trong trang và layout, viết giá trị dưới dạng chuỗi literal thuần: text build đọc nó từ mã nguồn mà không chạy mô-đun.

valid-metadata

Metadata hợp nhất từ các layout, rồi đến trang (hoặc not-found). Quy tắc báo cáo những gì router sẽ bỏ qua mà không có thông báo:

  • metadata hoặc generateMetadata trong tệp loading hoặc error
  • cả hai export trong cùng một tệp
  • async generateMetadata: hàm chạy đồng bộ, nên Promise không bao giờ đặt tiêu đề
  • title không phải chuỗi hay { default, template?, absolute? }, hoặc khóa viết sai
  • title.template không có %s, hoặc nằm trong trang, nơi nó không có tác dụng
  • description không phải chuỗi

route-handler-exports

Tệp route.ts chỉ phản hồi các phương thức HTTP mà chúng export: GET, HEAD, POST, PUT, PATCH, DELETE và OPTIONS. get viết thường hoặc Post không bao giờ được gọi (yêu cầu nhận 405); gợi ý đổi tên chúng. Quy tắc báo cáo export mặc định, handler không phải hàm và các export khác, ngoại trừ cấu hình phân đoạn route dynamic và revalidate. Chuyển mã dùng chung vào lib/, hoặc liệt kê tên bổ sung bằng { allow: [...] }.

app/api/health/route.tsts
export function get() { … }   // ✗ never calledexport function GET() { … }   // ✓
Sửa trang này trên GitHub