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-eslintimport { 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 כאזהרה |
לחלופין, אפשר לבחור כללים בנפרד:
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" }], }, },];קובצי מסוף
רוב הכללים חלים רק על קבצים שמרנדרים למסוף: קבצים שמייבאים רכיב או הוק (Box, Text, useInput, …) מתוך @textjs/app או @textjs/react. קבצים שמייבאים react-dom או @textjs/react/dom הם קוד של דפים והכללים אינם חלים עליהם, כך שאתר יכול לשלב דפי DOM ואפליקציות מסוף בפרויקט אחד. ייבוא כלי עזר כמו tokenize או parseMarkdown אינו הופך קובץ לקובץ מסוף.
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 פועלים לפי שם הקובץ (page.tsx, layout.tsx, route.ts, …) בכל ספריית app/, כולל 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 בזמן הרינדור. הכלל מזהה זאת בעורך במקום זאת, כולל בתוך &&, ביטויים תנאיים משולשים, פרגמנטים ו-.map().
<Text>Status <Box /></Text> // ✗<Box className="flex-row gap-1"><Text>Status</Text><Box /></Box> // ✓<Text>Press <Kbd>⌘K</Kbd> <Spinner /></Text> // ✓ inline componentsno-unsupported-glyphs
Text.js מסדר כל תו ברשת תאים קבועה. גליפים שחסרים ב-Menlo, גופן המסוף של DevTools ב-macOS, מרונדרים בגופן חלופי שרחב מתא, ולכן כל תא אחריהם סוטה ממקומו.
- ברייל (U+2800–U+28FF, הפריימים הקלאסיים של מחוון הפעילות בטרמינל): יש להשתמש בתווי בלוק או רבעים (
▘▝▗▖), ב-<Spinner variant="dots">או ב-marker="quadrant"בתרשימים. לאפליקציות שרצות רק עםtext runבטרמינל, יש להגדיר{ allowBraille: true }. - סמלים וחצים שונים (U+2B00–U+2BFF, כגון הריבוע הקטן U+2B1D): ההצעות מציעות את המקבילה ב-Menlo (
·,●,←). - הצגת אימוג'י בסמלים של תא אחד (
▶או↩ואחריהם U+FE0F): בורר הווריאציה הופך אותם לאימוג'י של שני תאים, בעוד שהרשת מקצה תא אחד. התיקון האוטומטי מסיר אותו.
אימוג'י רחבים (🚀, ✅) תקינים: Text.js מודד אותם כשני תאים.
no-console-in-components
כל פריים הוא קריאת console.log אחת. הודעות היומן של היישום נוחתות בין פריימים, נגללות החוצה עם הפריים הבא ומוסתרות בתצוגה הצמודה לתחתית. console.clear() מוחק את היישום עצמו.
"textjs/no-console-in-components": ["warn", { allow: ["error"], logger: "log" }]להצגת הודעות בממשק יש להשתמש ב-useToast() או ב-<ScrollBox> של יומן, ולבדיקת המצב אפשר להשתמש בשכבת הפיתוח [▼] (Ctrl+`). עם logger, ההצעה משכתבת console.warn(…) ל-log.warn(…). הכלל מדלג על קובצי בדיקות ועל רינדורים של @textjs/app/testing.
prefer-use-interval
שימוש ישיר ב-setInterval ברכיב דורש אפקט, ניקוי ו-ref כדי להימנע ממצב לא עדכני. useInterval מבצע את שלושתם, ו-null עוצר אותו.
useEffect(() => { if (paused) return; const id = setInterval(tick, 1000); return () => clearInterval(id);}, [paused]);useInterval(tick, paused ? null : 1000); // the suggestionבהנפשה, useAnimationFrame פועל לפי קצב הפריימים של מנגנון הרינדור ועוצר כשהפלט מושהה. ראו הוקים של כלי עזר.
no-sub-frame-timers
Text.js מפיק לכל היותר פריים אחד בכל 66 ms (כ-15 fps). מרווח של 16 ms גורם לארבעה רינדורים לכל פריים שמופק; הפריימים הנוספים מאוחדים ורק צורכים משאבי CPU. הכלל מדווח על השהיות של setInterval/useInterval מתחת ל-minInterval (ברירת המחדל 66) ועל useAnimationFrame עם fps מעל 15.
prefer-use-keymap
השוואה ידנית של e.key עובדת, אך קיצורי המקשים אינם גלויים. useKeymap מציב מיפויים עם תוויות בתוך <KeyHelp>, בתוך <KeyHints> (מיפויים עם hint: true) ובלוח הפקודות, מטפל ברצפים כמו g g ועוצר הפצה של אירועים עבור מקשים תואמים. הכלל מדווח על מטפלי useInput שמשווים שני ליטרלים של מקשים או יותר (minKeys) ומדלג על מטפלים שקוראים 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 אינו תומך ב-"error"של Next.js. הערך נקרא מדפים, מקובצי layout וממטפלי נתיב. בדפים ובקובצי layout יש לכתוב אותו כליטרל מחרוזת רגיל:text buildקורא אותו מקוד המקור בלי להריץ את המודול.
valid-metadata
Metadata ממוזג מקובצי layout, ולאחר מכן מהדף (או מ-not-found). הכלל מדווח על מה שהנתב היה מתעלם ממנו ללא הודעה:
metadataאוgenerateMetadataבקובציloadingאוerror- שני הייצואים בקובץ אחד
async generateMetadata: הקריאה אליו סינכרונית, ולכן Promise לעולם אינו מגדיר את הכותרתtitleשאינו מחרוזת או{ default, template?, absolute? }, או מפתח עם שגיאת כתיבtitle.templateללא%s, או בדף, שבו אין לו השפעהdescriptionשאינו מחרוזת
route-handler-exports
קובצי route.ts עונים רק לשיטות ה-HTTP שהם מייצאים: GET, HEAD, POST, PUT, PATCH, DELETE ו-OPTIONS. פונקציה בשם get או Post באותיות קטנות לעולם אינה נקראת (הבקשות מקבלות 405); ההצעה משנה את שמה. הכלל מדווח על ייצואי ברירת מחדל, מטפלים שאינם פונקציות וייצואים אחרים, למעט dynamic ו-revalidate שבתצורת מקטע נתיב. יש להעביר קוד משותף אל lib/, או לציין שמות נוספים באמצעות { allow: [...] }.
export function get() { … } // ✗ never calledexport function GET() { … } // ✓