KN-001Mục đích: Công cụ

Biome + Prettier — Polyms default

#typescript#polyms-default#biome#prettier
Kiểm mục
  • Install devDependencies: @biomejs/biome@2.5.5, prettier@~3.9.4
  • Copy biome.json, .prettierrc.yml, .prettierignore, .vscode/settings.json into the target repo root
  • Wire package.json scripts: check
  • Install editor extensions: biomejs.biome, esbenp.prettier-vscode, bradlc.vscode-tailwindcss
  • Verify: pnpm check auto-fixes (biome check --write . && prettier -l --write .)
  • Verify CI runs pnpm check on pull requests

Setup checklist

Kiểm mục

Verify steps for applying the Biome + Prettier Polyms default to a target repo.

  • Install devDependencies: @biomejs/biome@2.5.5, prettier@~3.9.4
  • Copy biome.json, .prettierrc.yml, .prettierignore, .vscode/settings.json into the target repo root
  • Wire package.json scripts: check
  • Install editor extensions: biomejs.biome, esbenp.prettier-vscode, bradlc.vscode-tailwindcss
  • Verify: pnpm check auto-fixes (biome check --write . && prettier -l --write .)
  • Verify CI runs pnpm check on pull requests

Rationale

Văn bản

Biome handles JS/TS/CSS/HTML/JSON/JSONC formatting and linting — one fast binary, no plugin ecosystem needed for these languages. Prettier handles MD/MDX/YAML — file types Biome does not format.

This is a deliberate split, not redundancy: each tool owns file extensions the other never touches. .prettierignore excludes every extension Biome owns (see the ignore-file chunk); Biome's files.includes only matches its own language set. Never let both tools claim the same file type.

Indentation: tabs, width 2 — Prettier useTabs: true + tabWidth: 2; Biome indentStyle: "tab" + indentWidth: 2; editor insertSpaces: false + tabSize: 2.

Run check (biome check --write . && prettier -l --write .) — always auto-fix both tools' domains in one command. In CI, run it and fail if the working tree is dirty (uncommitted fixes).

Biome 2.5+: use "preset": "recommended" under linter.rules (and under rule groups like a11y). The boolean "recommended": true field is deprecated and will be removed in the next major.

biome.json

biome.jsonCấu hình
{
  "$schema": "node_modules/@biomejs/biome/configuration_schema.json",
  "assist": {
    "actions": {
      "source": {
        "organizeImports": "on",
        "useSortedAttributes": "on",
        "useSortedKeys": "on",
        "useSortedProperties": "on"
      }
    },
    "enabled": true,
    "includes": [
      "apps/{project}/**/*",
      ".vscode/settings.json",
      "biome.json",
      "!**/tsconfig*",
      "!**/package.json",
      "!**/project.json",
      "!**/nx.json"
    ]
  },
  "css": {
    "assist": { "enabled": false },
    "formatter": {
      "enabled": true,
      "indentWidth": 2,
      "quoteStyle": "single"
    },
    "linter": { "enabled": false },
    "parser": {
      "tailwindDirectives": true
    }
  },
  "files": {
    "includes": [
      "**/*.{js,mjs,jsx,ts,mts,tsx,json,css,svg,html,webmanifest}",
      "!**/_root.css",
      "!**/routeTree.gen.ts",
      "!**/.nx",
      "!**/.tanstack",
      "!**/node_modules",
      "!**/.pnpm-store",
      "!**/.venv-rembg",
      "!**/.agents/skills",
      "!**/build",
      "!**/dist"
    ]
  },
  "formatter": {
    "attributePosition": "auto",
    "bracketSameLine": true,
    "bracketSpacing": true,
    "enabled": true,
    "expand": "auto",
    "formatWithErrors": false,
    "indentStyle": "tab",
    "indentWidth": 2,
    "lineEnding": "lf",
    "lineWidth": 110
  },
  "html": {
    "formatter": {
      "attributePosition": "auto",
      "bracketSameLine": true,
      "enabled": true,
      "indentScriptAndStyle": true,
      "indentStyle": "tab",
      "indentWidth": 2,
      "lineEnding": "lf",
      "lineWidth": 110,
      "whitespaceSensitivity": "css"
    },
    "parser": {
      "interpolation": true
    }
  },
  "javascript": {
    "experimentalEmbeddedSnippetsEnabled": true,
    "formatter": {
      "arrowParentheses": "asNeeded",
      "bracketSameLine": false,
      "bracketSpacing": true,
      "jsxQuoteStyle": "single",
      "quoteProperties": "asNeeded",
      "quoteStyle": "single",
      "semicolons": "asNeeded",
      "trailingCommas": "es5"
    }
  },
  "json": {
    "formatter": {
      "enabled": true,
      "indentWidth": 2,
      "lineWidth": 110
    }
  },
  "linter": {
    "enabled": true,
    "rules": {
      "a11y": {
        "noSvgWithoutTitle": "off",
        "preset": "recommended",
        "useKeyWithClickEvents": "error",
        "useValidAnchor": "error"
      },
      "complexity": {
        "noForEach": "off",
        "noImportantStyles": "off",
        "useOptionalChain": "error"
      },
      "correctness": {
        "noUndeclaredVariables": "error",
        "noUnusedImports": "warn",
        "noUnusedVariables": "warn",
        "useExhaustiveDependencies": "off",
        "useParseIntRadix": "off"
      },
      "nursery": {
        "useSortedClasses": {
          "fix": "safe",
          "level": "error",
          "options": {
            "attributes": ["classList"],
            "functions": ["clsx"]
          }
        }
      },
      "preset": "recommended",
      "security": {
        "noDangerouslySetInnerHtml": "off"
      },
      "style": {
        "noDescendingSpecificity": "off",
        "noNonNullAssertion": "off",
        "noUnusedTemplateLiteral": "warn",
        "useBlockStatements": "off",
        "useConsistentArrayType": "error",
        "useShorthandFunctionType": "error",
        "useTemplate": "warn"
      },
      "suspicious": {
        "noArrayIndexKey": "warn",
        "noEmptyBlockStatements": "error",
        "noExplicitAny": "warn",
        "noUnknownAtRules": "off"
      }
    }
  },
  "overrides": [
    {
      "assist": {
        "actions": {
          "source": {
            "useSortedKeys": "off"
          }
        }
      },
      "includes": ["**/*.ts"]
    },
    {
      "assist": {
        "actions": {
          "source": {
            "useSortedKeys": "off"
          }
        }
      },
      "includes": ["**/*.tsx", "**/*.jsx"],
      "linter": {
        "rules": {
          "a11y": {
            "useAltText": "error",
            "useButtonType": "error"
          },
          "style": {
            "useNamingConvention": {
              "level": "error",
              "options": {
                "conventions": [],
                "requireAscii": true,
                "strictCase": false
              }
            }
          }
        }
      }
    },
    {
      "includes": ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"],
      "linter": {
        "rules": {
          "suspicious": {
            "noExplicitAny": "off"
          }
        }
      }
    },
    {
      "includes": ["**/*.d.ts"],
      "linter": {
        "rules": {
          "correctness": {
            "noUnusedVariables": "off"
          }
        }
      }
    }
  ],
  "vcs": {
    "clientKind": "git",
    "defaultBranch": "main",
    "enabled": true,
    "useIgnoreFile": true
  }
}

.prettierrc.yml

.prettierrc.ymlCấu hình
printWidth: 110
singleQuote: true
jsxSingleQuote: true
useTabs: true
tabWidth: 2
semi: false
trailingComma: es5

.prettierignore

.prettierignoreCấu hình
# Prettier: markdown, MDX, YAML. JS/TS/CSS/HTML/JSON → Biome.

# Build / deps
node_modules/
.pnpm-store/
dist/
coverage/
.nx/
tmp/
out-tsc/
doc_build/
.tanstack/
.agents/skills/

# Lock / generated
pnpm-lock.yaml
skills-lock.json
next-env.d.ts
**/routeTree.gen.ts
vite.config.*.timestamp*
vitest.config.*.timestamp*

# No Prettier parser
*.mdc

# Biome handles these — Prettier skip
*.js
*.mjs
*.cjs
*.jsx
*.ts
*.mts
*.tsx
*.json
*.jsonc
*.css
*.html
*.svg
*.webmanifest

package.json scripts

package.jsonCấu hình
{
  "scripts": {
    "check": "biome check --write . && prettier -l --write ."
  },
  "devDependencies": {
    "@biomejs/biome": "2.5.5",
    "prettier": "~3.9.4"
  }
}

VS Code settings

.vscode/settings.jsonCấu hình
{
  "files.associations": {
    "*.css": "tailwindcss",
    "*.md": "markdown",
    "*.mdx": "markdown"
  },
  "[javascript][javascriptreact][typescript][typescriptreact][tailwindcss][css][html][json][jsonc]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[mdx][markdown][yaml]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "biome.suggestInstallingGlobally": false,
  "editor.codeActionsOnSave": {
    "source.action.useSortedAttributes.biome": "explicit",
    "source.action.useSortedProperties.biome": "explicit",
    "source.fixAll.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  },
  "editor.defaultFormatter": "biomejs.biome",
  "editor.formatOnSave": true,
  "editor.quickSuggestions": {
    "strings": "on"
  },
  "editor.tabSize": 2,
  "editor.insertSpaces": false,
  "editor.wordWrapColumn": 110,
  "tailwindCSS.classFunctions": ["clsx"],
  "tailwindCSS.experimental.configFile": "{your tailwindCSS configFile}",
  "js/ts.preferences.quoteStyle": "single",
  "js/ts.tsdk.path": "node_modules/typescript/lib"
}

recommended field deprecated — use preset

Sự cố

Biome warns that boolean recommended under linter.rules (or under a rule group such as a11y) is deprecated.

Nguyên nhân
  • biome.json still uses `"recommended": true` under `linter.rules` or a group like `a11y` — Biome 2.5+ renamed this to `preset`.
Cách sửa
  • Replace `"recommended": true` with `"preset": "recommended"` at `linter.rules` and at any group that had the boolean (e.g. `a11y`).
  • Do not keep both keys.
Xác minh
  • Open biome.json — editor shows no deprecation warning on those lines.
  • Run `biome check` on a sample file — config loads without the recommended-field warning.