chore: switch code formatter from Prettier to Biome (#4223)

Replace Prettier with Biome for formatting JS/TS/JSX/CSS/JSON. The CI
format check drops from ~23s to ~0.4s.

- Unify config into a single root biome.json (formatter + linter); the
  former apps/readest-app/biome.json was linter-only
- Mirror the old .prettierrc.json style: 100 line width, 2-space indent,
  LF, single quotes, trailing commas
- Enable the CSS tailwindDirectives parser for @apply in globals.css
- Convert // prettier-ignore comments to // biome-ignore format:
- Root scripts and lint-staged now run biome; apps/readest-app lint runs
  `biome lint` (lint-only) so formatting stays a separate CI step
- Drop prettier + prettier-plugin-tailwindcss dependencies

Markdown/YAML are no longer format-checked (Biome does not format them)
and Tailwind class sorting is no longer enforced.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-05-19 14:13:36 +08:00
committed by GitHub
parent 05da6bdf43
commit fe41c42ec5
19 changed files with 199 additions and 243 deletions
+174
View File
@@ -0,0 +1,174 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
"files": {
"ignoreUnknown": true,
"includes": [
"**",
"!**/node_modules/**",
"!**/packages/**",
"!**/.claude/**",
"!**/gen/**",
"!**/autogenerated/**",
"!**/schemas/**"
]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf",
"includes": [
"**",
"!**/.next/**",
"!**/.open-next/**",
"!**/.build/**",
"!**/.tauri/**",
"!**/.wrangler/**",
"!**/.vercel/**",
"!**/out/**",
"!**/build/**",
"!**/dist/**",
"!**/target/**",
"!**/fastlane/**",
"!apps/readest-app/public/*.js",
"!apps/readest-app/public/vendor/**",
"!apps/readest-app/src-tauri/plugins/tauri-plugin-turso/**",
"!apps/readest-app/src-tauri/plugins/tauri-plugin-webview-upgrade/**",
"!**/*.mjs",
"!**/*.cjs",
"!**/*.mts",
"!pnpm-lock.yaml"
]
},
"assist": { "enabled": false },
"css": {
"parser": { "tailwindDirectives": true },
"linter": { "enabled": false },
"formatter": { "enabled": true, "quoteStyle": "single" }
},
"linter": {
"enabled": true,
"includes": [
"**",
"!apps/readest-app/.next/**",
"!apps/readest-app/.open-next/**",
"!apps/readest-app/.wrangler/**",
"!apps/readest-app/.claude/**",
"!apps/readest-app/dist/**",
"!apps/readest-app/out/**",
"!apps/readest-app/build/**",
"!apps/readest-app/public/**",
"!apps/readest-app/src-tauri/**",
"!apps/readest-app/next-env.d.ts",
"!apps/readest-app/i18next-scanner.config.cjs"
],
"rules": {
"recommended": true,
"a11y": {
"recommended": true,
"useKeyWithClickEvents": "off",
"useKeyWithMouseEvents": "off",
"noSvgWithoutTitle": "off",
"noLabelWithoutControl": "off",
"useSemanticElements": "off",
"noAriaHiddenOnFocusable": "off",
"noInteractiveElementToNoninteractiveRole": "off",
"noNoninteractiveElementToInteractiveRole": "off",
"noNoninteractiveElementInteractions": "off",
"noStaticElementInteractions": "off",
"noNoninteractiveTabindex": "off",
"useButtonType": "off",
"useAriaPropsSupportedByRole": "off",
"useFocusableInteractive": "off",
"noAutofocus": "off"
},
"complexity": {
"noForEach": "off",
"noStaticOnlyClass": "off",
"noUselessSwitchCase": "off",
"noUselessFragments": "off",
"noUselessCatch": "off",
"useLiteralKeys": "off",
"useOptionalChain": "off",
"noThisInStatic": "off",
"useArrowFunction": "off",
"noUselessEscapeInRegex": "off"
},
"correctness": {
"noUnusedVariables": "warn",
"noUnusedImports": "error",
"useExhaustiveDependencies": "off",
"useHookAtTopLevel": "error",
"useJsxKeyInIterable": "error",
"noChildrenProp": "error",
"noNextAsyncClientComponent": "warn",
"noSwitchDeclarations": "off",
"noUndeclaredVariables": "off",
"noEmptyCharacterClassInRegex": "off",
"useParseIntRadix": "off",
"noEmptyPattern": "off"
},
"nursery": {
"noBeforeInteractiveScriptOutsideDocument": "warn",
"noDuplicateEnumValues": "error",
"noSyncScripts": "warn",
"useInlineScriptId": "error"
},
"performance": {
"noImgElement": "off",
"noUnwantedPolyfillio": "warn",
"useGoogleFontPreconnect": "warn"
},
"security": {
"noBlankTarget": "off",
"noDangerouslySetInnerHtmlWithChildren": "off",
"noDangerouslySetInnerHtml": "off"
},
"style": {
"noNonNullAssertion": "off",
"useImportType": "off",
"noParameterAssign": "off",
"useDefaultParameterLast": "off",
"noUselessElse": "off",
"noHeadElement": "warn",
"noCommonJs": "off",
"useFilenamingConvention": "off",
"useNamingConvention": "off",
"noUnusedTemplateLiteral": "off",
"useTemplate": "off",
"useExponentiationOperator": "off",
"useNodejsImportProtocol": "off"
},
"suspicious": {
"noExplicitAny": "error",
"noArrayIndexKey": "off",
"noAssignInExpressions": "off",
"noDoubleEquals": "off",
"noDocumentImportInPage": "error",
"noHeadImportInDocument": "error",
"useGoogleFontDisplay": "warn",
"noCommentText": "error",
"noDuplicateJsxProps": "error",
"noAsyncPromiseExecutor": "off",
"noImplicitAnyLet": "off",
"noControlCharactersInRegex": "off",
"noEmptyBlockStatements": "off",
"useIterableCallbackReturn": "off",
"noGlobalIsNan": "off",
"noConfusingVoidType": "off",
"noConstEnum": "off"
}
}
},
"javascript": {
"globals": ["React"],
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "single",
"trailingCommas": "all",
"semicolons": "always"
}
}
}