ESLint

eslint-plugin-textjs:適用於主控台的 JSX、儲存格寬度字形、配合影格節奏的計時器與 App Router 檢查。

eslint-plugin-textjs 會在編輯器中找出 Text.js 錯誤:主控台元件中的 DOM 標籤、文字中的方塊、Menlo 不支援的字形、多餘的 console.log 呼叫,以及遭到無聲忽略的 App Router 匯出。

設定

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

此外掛支援採用扁平設定的 ESLint 9 和 10。規則以語法為依據,不需要型別資訊或 parserOptions.project。任何能解析 TypeScript 和 JSX 的剖析器都適用。

設定組合

設定組合啟用項目
textjs.configs.recommended所有正確性規則設為錯誤;主控台、計時器和影格節奏規則設為警告
textjs.configs.strict所有規則設為錯誤,另將 prefer-use-keymap 設為警告

也可以個別選用規則:

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" }],    },  },];

主控台檔案

大多數規則只適用於渲染至主控台的檔案:也就是從 @textjs/app 或 @textjs/react 匯入元件或 Hook(Box、Text、useInput 等)的檔案。匯入 react-dom 或 @textjs/react/dom 的檔案屬於網頁程式碼,不會受到檢查,因此網站可以在同一個專案中混用 DOM 頁面和主控台應用程式。匯入 tokenize 或 parseMarkdown 之類的工具函式,不會讓檔案成為主控台檔案。

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"      },    },  },];

App Router 規則依據任何 app/ 目錄中的檔名(page.tsx、layout.tsx、route.ts 等)判斷,也包括 src/app/。如果你的目錄使用其他名稱,請將 { appDir: "routes" } 傳給 valid-route-module、valid-metadata 和 route-handler-exports。

規則

規則建議設定修正
no-dom-elements錯誤建議修正
no-box-in-text錯誤
no-unsupported-glyphs錯誤自動修正、建議修正
no-console-in-components警告建議修正
prefer-use-interval警告建議修正
no-sub-frame-timers警告
prefer-use-keymap僅限嚴格設定
valid-route-module錯誤
valid-metadata錯誤
route-handler-exports錯誤建議修正

no-dom-elements

主控台中不存在 <div>、<span>、自訂元素及其他 DOM 標籤。協調器只辨識自己的宿主節點,遇到其他節點就會拋出 TX003。

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

建議修正會將標籤替換成最接近的元件(div → Box、span/p → Text、strong → Text bold、a → Link、input → Input、hr → Divider 等),並加入匯入陳述式。

no-box-in-text

<Text> 是一連串帶有樣式的 span,不能包含方塊。巢狀放入 <Box> 或任何以方塊為根節點的元件(<ScrollBox>、<Input>、<Code>、<ProgressBar>、<Divider> 等),會在渲染時拋出 TX004。這條規則會提前在編輯器中找出問題,包括 &&、三元運算式、Fragment 和 .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 將每個字元排列在固定的儲存格網格中。macOS 上的 DevTools 主控台字型 Menlo 若不支援某個字形,就會改用比儲存格更寬的後備字型渲染,導致後方所有儲存格偏移。

  • 點字(U+2800–U+28FF,經典終端機旋轉指示器影格):使用區塊或象限字元(▘▝▗▖)、<Spinner variant="dots">,或在圖表上設定 marker="quadrant"。只在終端機中透過 text run 執行的應用程式,可設定 { allowBraille: true }。
  • 雜項符號和箭頭(U+2B00–U+2BFF,例如小方塊 U+2B1D):建議修正會提供 Menlo 中的對應字元(·、●、←)。
  • 單一儲存格符號的 Emoji 呈現(▶ 或 ↩ 後接 U+FE0F):變體選擇符會將符號變成佔兩個儲存格的 emoji,但網格只預留一格。自動修正會移除變體選擇符。

寬版 emoji(🚀、✅)沒有問題:Text.js 會將其計為兩個儲存格。

no-console-in-components

每個影格都是一次 console.log。你自己的記錄會插在影格之間,下一個影格會將記錄捲走,並隱藏在固定於底部的檢視之外。console.clear() 會清除應用程式本身。

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

使用 useToast() 或記錄用的 <ScrollBox> 在 UI 中顯示訊息,或在 [▼] 開發覆蓋面板(Ctrl+`)中檢查狀態。設定 logger 後,建議修正會將 console.warn(…) 改寫為 log.warn(…)。測試檔案和 @textjs/app/testing 的渲染會略過檢查。

prefer-use-interval

在元件中直接使用 setInterval,需要 effect、清理函式和 ref,才能避免使用過期的狀態。useInterval 會處理這三項工作,傳入 null 即可停止。

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

製作動畫時,useAnimationFrame 會配合渲染器的影格節奏,並在輸出暫停時停止。請參閱工具 Hook。

no-sub-frame-timers

Text.js 每 66 ms 最多輸出一個影格(約 15 fps)。16 ms 的間隔會在每次輸出影格前渲染四次;多餘的影格會合併,只會消耗 CPU。這條規則會回報延遲低於 minInterval(預設為 66)的 setInterval/useInterval,以及 fps 高於 15 的 useAnimationFrame。

prefer-use-keymap

手動比較 e.key 可行,但使用者看不到這些快速鍵。useKeymap 會將帶有標籤的按鍵繫結放入 <KeyHelp>、<KeyHints>(具有 hint: true 的繫結)和命令選單,處理 g g 之類的按鍵序列,並阻止相符按鍵的事件繼續傳播。這條規則會回報比較兩個以上按鍵字面值(minKeys)的 useInput 處理常式,並略過讀取 e.input 以處理文字輸入的處理常式。

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

請參閱鍵盤快速鍵。

valid-route-module

  • page、layout、loading、error 和 not-found 檔案必須以預設匯出提供元件(TX015)。
  • export const dynamic 必須是 "auto"、"force-static" 或 "force-dynamic"(TX031)。Text.js 不支援 Next.js 的 "error"。此設定會從頁面、版面配置和路由處理常式讀取。在頁面和版面配置中,請寫成純字串字面值:text build 會直接從原始碼讀取,不會執行模組。

valid-metadata

Metadata 會先從版面配置合併,再合併頁面(或 not-found)的內容。這條規則會回報路由器原本會無聲忽略的項目:

  • loading 或 error 檔案中的 metadata 或 generateMetadata
  • 同一個檔案中的兩種匯出
  • async generateMetadata:呼叫採同步方式,因此 Promise 無法設定標題
  • 不是字串或 { default, template?, absolute? } 的 title,或拼錯的鍵名
  • 缺少 %s 的 title.template,或位於頁面中而不會生效的 title.template
  • 非字串的 description

route-handler-exports

route.ts 檔案只回應其匯出的 HTTP 方法:GET、HEAD、POST、PUT、PATCH、DELETE 和 OPTIONS。小寫的 get 或 Post 永遠不會被呼叫(請求會收到 405);修正建議會將其重新命名。預設匯出、非函式的處理常式及其他匯出都會被回報,但 dynamic 和 revalidate 路由區段設定 除外。將共用程式碼移至 lib/,或使用 { allow: [...] } 列出額外允許的名稱。

app/api/health/route.tsts
export function get() { … }   // ✗ never calledexport function GET() { … }   // ✓
在 GitHub 編輯此頁