Biome + Prettier — Polyms default
- 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ụcVerify 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ảnBiome 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ìnhprintWidth: 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
*.webmanifestpackage.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.
Thứ tự đọc · 8