Skip to content

Prettier 配置详解

深入解析项目的 Prettier 配置和格式化规则。

🎯 配置文件

文件位置

plus-ui/
├── .prettierrc.js     # Prettier 配置
└── .prettierignore    # 忽略文件配置

完整配置

javascript
// .prettierrc.js
export default {
  printWidth: 150,
  tabWidth: 2,
  useTabs: false,
  semi: false,
  singleQuote: true,
  quoteProps: 'preserve',
  jsxSingleQuote: false,
  bracketSameLine: false,
  trailingComma: 'none',
  bracketSpacing: true,
  embeddedLanguageFormatting: 'auto',
  arrowParens: 'always',
  requirePragma: false,
  insertPragma: false,
  proseWrap: 'preserve',
  htmlWhitespaceSensitivity: 'css',
  vueIndentScriptAndStyle: false,
  endOfLine: 'auto'
}

📋 配置项详解

printWidth: 150

单行最大宽度

typescript
// ✅ 不超过 150 字符,保持单行
const result = apiService.getData().then(data => processData(data)).catch(err => handleError(err))

// ❌ 超过 150 字符会自动换行
const veryLongFunctionName = anotherLongFunctionName()
  .then(data => processData(data))
  .catch(err => handleError(err))

设置原因

  • 适合宽屏显示器(1920px+)
  • 减少不必要的换行
  • 提高代码阅读效率

标准值比较

  • Prettier 默认:80
  • 项目配置:150
  • 推荐范围:80-120

tabWidth: 2

缩进宽度为 2 个空格

typescript
// ✅ 2 个空格缩进
function example() {
··const name = 'John'  // 2 spaces
··if (true) {
····console.log(name)  // 4 spaces
··}
}

优点

  • 节省水平空间
  • Vue/JavaScript 社区标准
  • 嵌套层级更清晰

useTabs: false

使用空格而非制表符

typescript
// ✅ 空格缩进
function demo() {
  return true
}

// ❌ 制表符缩进(会被转换为空格)
function demo() {
return true
}

原因

  • 不同编辑器显示一致
  • 避免混合缩进问题
  • 团队协作友好

semi: false

不使用分号

typescript
// ✅ 不使用分号
const name = 'John'
const age = 30
export default { name, age }

// ❌ 使用分号(格式化后会被移除)
const name = 'John';
const age = 30;

现代 JavaScript 趋势

  • 依赖 ASI(自动分号插入)
  • 代码更简洁
  • Vue 3、Vite 官方示例风格

注意事项

  • [ ( ` + - 开头的语句需要前置分号
typescript
;[1, 2, 3].forEach(n => console.log(n))
;(function() { /* ... */ })()

singleQuote: true

使用单引号

typescript
// ✅ 单引号
const message = 'Hello World'
import { ref } from 'vue'

// ❌ 双引号(格式化后会被替换)
const message = "Hello World"

例外情况

  • 字符串包含单引号时使用双引号
typescript
const message = "It's a beautiful day"  // 保留双引号

quoteProps: 'preserve'

保持对象属性引号原样

typescript
// ✅ 保持原样
const obj1 = { name: 'John', age: 30 }
const obj2 = { 'user-name': 'John', 'user-age': 30 }

// 不会统一添加或移除引号

可选值

  • 'as-needed' - 仅在需要时加引号
  • 'consistent' - 保持一致性
  • 'preserve' - 保持原样(项目配置)

jsxSingleQuote: false

JSX 中使用双引号

tsx
// ✅ JSX 属性使用双引号
<Button type="primary" />
<Input placeholder="请输入" />

// ✅ JavaScript 使用单引号
const type = 'primary'

原因:遵循 HTML 属性习惯。

bracketSameLine: false

标签的闭合括号另起一行

vue
<!-- ✅ 闭合括号另起一行 -->
<Button
  type="primary"
  size="large"
>
  点击
</Button>

<!-- ❌ 闭合括号与属性同行 -->
<Button
  type="primary"
  size="large">
  点击
</Button>

trailingComma: 'none'

不使用尾随逗号

typescript
// ✅ 不使用尾随逗号
const obj = {
  name: 'John',
  age: 30
}

const arr = [
  1,
  2,
  3
]

// ❌ 尾随逗号(格式化后会被移除)
const obj = {
  name: 'John',
  age: 30,
}

可选值

  • 'none' - 不使用(项目配置)
  • 'es5' - ES5 允许的地方使用
  • 'all' - 所有地方使用

'all' 的优点

  • Git diff 更清晰
  • 添加新项不影响上一行

bracketSpacing: true

对象括号内添加空格

typescript
// ✅ 括号内有空格
const obj = { name: 'John', age: 30 }

// ❌ 括号内无空格(格式化后会添加)
const obj = {name: 'John', age: 30}

arrowParens: 'always'

箭头函数参数总是使用括号

typescript
// ✅ 单参数也使用括号
const double = (x) => x * 2
const greet = (name) => `Hello ${name}`

// ❌ 单参数省略括号(格式化后会添加)
const double = x => x * 2

可选值

  • 'always' - 总是使用(项目配置)
  • 'avoid' - 尽量避免

vueIndentScriptAndStyle: false

不缩进 Vue 文件的 script 和 style 标签

vue
<!-- ✅ 标签内代码不缩进 -->
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
</script>

<style scoped>
.container {
  padding: 20px;
}
</style>

<!-- ❌ 标签内代码缩进 -->
<script setup lang="ts">
  import { ref } from 'vue'
  const count = ref(0)
</script>

原因:节省缩进层级。

endOfLine: 'auto'

自动识别换行符

LF (\n)    - Linux/Mac
CRLF (\r\n) - Windows
CR (\r)    - 旧 Mac

'auto' 行为

  • 保留文件原有的换行符类型
  • 避免跨平台协作时的换行符冲突

可选值

  • 'auto' - 自动(项目配置)
  • 'lf' - 强制 LF
  • 'crlf' - 强制 CRLF
  • 'cr' - 强制 CR

🚫 忽略配置

.prettierignore

# 构建输出
/dist/*

# 临时文件
.local
.output.js

# 依赖目录
/node_modules/**

# 特殊文件
**/*.svg          # SVG 文件
**/*.sh           # Shell 脚本

# 静态资源
/public/*

忽略原因

  • 构建产物无需格式化
  • 第三方文件不应修改
  • 二进制/特殊格式文件

🛠️ 使用命令

格式化所有文件

bash
pnpm prettier
# 等同于
npx prettier --write .

格式化指定文件

bash
# 单个文件
npx prettier --write src/views/Home.vue

# 指定目录
npx prettier --write "src/**/*.{js,ts,vue}"

# 指定类型
npx prettier --write "**/*.vue"

检查格式(不修改)

bash
# 检查所有文件
npx prettier --check .

# 检查指定文件
npx prettier --check "src/**/*.vue"

输出

  • 无输出:所有文件格式正确
  • 列出文件:需要格式化的文件

与其他工具配合

bash
# 格式化后再 ESLint 检查
npx prettier --write . && npx eslint .

# Git 提交前自动格式化(lint-staged)

🔧 IDE 集成

VS Code

安装扩展

  • Prettier - Code formatter: esbenp.prettier-vscode

工作区配置

.vscode/settings.json

json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "editor.formatOnPaste": false,

  "[vue]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[css]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[scss]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

效果

  • 保存文件时自动格式化
  • 所有支持的文件类型使用 Prettier

快捷键

  • Windows/Linux: Shift + Alt + F
  • Mac: Shift + Option + F

WebStorm

  1. SettingsLanguages & FrameworksJavaScriptPrettier
  2. 选择 Prettier package: node_modules/prettier
  3. 勾选 On saveOn Reformat Code
  4. 设置 Run for files: {**/*,*}.{js,ts,jsx,tsx,vue,css,scss,json}

🎯 最佳实践

保存时自动格式化

推荐:开启 IDE 保存时自动格式化,保持代码一致。

Git Hooks 强制格式化

json
{
  "lint-staged": {
    "*.{js,ts,vue}": [
      "prettier --write",
      "eslint --fix"
    ],
    "*.{css,scss,json,md}": [
      "prettier --write"
    ]
  }
}

团队协作

  1. 统一配置:所有成员使用相同的 .prettierrc.js
  2. IDE 集成:强制安装 Prettier 扩展
  3. CI 检查:PR 提交前运行 prettier --check

忽略特定代码

typescript
// prettier-ignore
const matrix = [
  1, 0, 0,
  0, 1, 0,
  0, 0, 1
]
vue
<!-- prettier-ignore -->
<div   class="custom-spacing"     id="demo">
  特殊格式
</div>

使用场景

  • 手动对齐的代码
  • 特殊格式的数据结构
  • 需要保持原样的代码

🔄 与 ESLint 协同

规则冲突解决

项目使用 @vue/eslint-config-prettier/skip-formatting 避免冲突:

typescript
// eslint.config.ts
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'

export default defineConfigWithVueTs(
  // ...
  skipFormatting  // 跳过 ESLint 的格式化规则
)

工作流程

编写代码 → ESLint 检查语法 → Prettier 格式化 → 提交代码

规则分工

工具职责示例
ESLint代码质量未使用变量、类型错误
Prettier代码风格缩进、引号、换行

📊 配置对比

项目配置 vs 默认配置

配置项项目值默认值原因
printWidth15080适合宽屏显示器
semifalsetrue现代 JS 趋势
singleQuotetruefalseVue 生态习惯
trailingComma'none''all'简洁风格

社区流行配置

Standard 风格

  • semi: false
  • singleQuote: true
  • printWidth: 80

Airbnb 风格

  • semi: true
  • singleQuote: true
  • trailingComma: 'all'

项目风格:介于两者之间,更宽松灵活。

🐛 常见问题

格式化破坏代码

问题:格式化后代码运行出错

排查

  1. 检查是否忽略了关键空格/换行
  2. 使用 // prettier-ignore 保护特殊代码
  3. 调整相关配置项

与 ESLint 冲突

问题:ESLint 报格式化错误

解决

  1. 确保使用了 skipFormatting
  2. 检查 ESLint 配置是否有格式化规则
  3. 运行顺序:Prettier → ESLint

IDE 不自动格式化

检查

  1. 是否安装 Prettier 扩展
  2. 是否设置为默认格式化工具
  3. 是否启用保存时格式化
  4. 是否在 .prettierignore

格式化慢

优化

  1. 减少检查文件数量
  2. 使用 .prettierignore
  3. 升级 Prettier 到最新版本

常见问题

1. 格式化后代码功能异常或语法错误

问题描述

执行 Prettier 格式化后,代码出现语法错误或运行时异常,常见于特定语法结构被错误转换,或自动分号插入(ASI)机制导致的意外行为。

问题原因

  • 无分号配置下,特定语法前缀未添加保护分号
  • 正则表达式或模板字符串被错误解析
  • 多行表达式的换行位置不当
  • Parser 选择错误导致语法误解析
  • 插件版本不兼容导致解析异常

解决方案

typescript
// ❌ 错误示例:无分号模式下的常见陷阱
const a = 1
[1, 2, 3].forEach(n => console.log(n))  // 错误:被解析为 a[1, 2, 3]

const b = 2
(function() { console.log('IIFE') })()  // 错误:被解析为 2(function...)

const c = 3
`template string`  // 错误:被解析为 3`template...`

// ✅ 正确示例:添加保护分号
const a = 1
;[1, 2, 3].forEach(n => console.log(n))  // 正确

const b = 2
;(function() { console.log('IIFE') })()  // 正确

const c = 3
;`template string`  // 正确

// 或者使用 void 运算符避免问题
const d = 4
void [1, 2, 3].forEach(n => console.log(n))

正则表达式保护

typescript
// ❌ 可能被错误格式化的正则
const regex = /\s+/g
/test/.test('hello')  // 可能被误解析

// ✅ 使用变量保存正则
const testRegex = /test/
const hasTest = testRegex.test('hello')

// ✅ 或使用括号保护
;(/test/).test('hello')

Parser 配置修复

javascript
// .prettierrc.js
export default {
  // 确保 Vue 文件使用正确的 parser
  overrides: [
    {
      files: '*.vue',
      options: {
        parser: 'vue'
      }
    },
    {
      files: '*.ts',
      options: {
        parser: 'typescript'
      }
    },
    {
      files: '*.tsx',
      options: {
        parser: 'typescript'
      }
    },
    {
      files: '*.json',
      options: {
        parser: 'json'
      }
    },
    {
      files: '*.md',
      options: {
        parser: 'markdown',
        proseWrap: 'preserve'
      }
    }
  ]
}

ASI 问题自动检测工具

typescript
// 创建自定义 ESLint 规则配合 Prettier
// eslint.config.ts
export default defineConfigWithVueTs(
  {
    rules: {
      // 检测需要保护分号的情况
      'no-unexpected-multiline': 'error'
    }
  }
)

// 或使用 TypeScript 编译器检查
// tsconfig.json
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strict": true
    // 编译时会发现大部分 ASI 问题
  }
}

2. Prettier 与 ESLint 规则冲突导致无限循环

问题描述

保存文件时,Prettier 和 ESLint 轮流修改代码,导致文件不断变化,或者两个工具报告相互矛盾的错误,无法通过任一工具的检查。

问题原因

  • ESLint 中存在与 Prettier 冲突的格式化规则
  • 未正确配置 eslint-config-prettierskipFormatting
  • 运行顺序错误(应该 Prettier 先于 ESLint)
  • 多个格式化工具同时启用
  • IDE 配置了多个格式化器

解决方案

typescript
// eslint.config.ts - 正确的 ESLint 配置
import eslint from '@eslint/js'
import pluginVue from 'eslint-plugin-vue'
import vueTsEslintConfig from '@vue/eslint-config-typescript'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'

export default defineConfigWithVueTs(
  {
    name: 'app/files-to-lint',
    files: ['**/*.{ts,mts,tsx,vue}']
  },
  {
    name: 'app/files-to-ignore',
    ignores: ['**/dist/**', '**/node_modules/**']
  },
  eslint.configs.recommended,
  ...pluginVue.configs['flat/essential'],
  ...vueTsEslintConfig(),
  // 关键:skipFormatting 必须放在最后,覆盖之前的格式化规则
  skipFormatting
)

手动禁用冲突规则

typescript
// 如果不使用 skipFormatting,手动禁用冲突规则
export default defineConfigWithVueTs(
  // ... 其他配置
  {
    rules: {
      // 禁用与 Prettier 冲突的 ESLint 规则
      'indent': 'off',
      'semi': 'off',
      'quotes': 'off',
      'comma-dangle': 'off',
      'max-len': 'off',
      'arrow-parens': 'off',
      'object-curly-spacing': 'off',
      'array-bracket-spacing': 'off',
      'space-before-function-paren': 'off',
      'keyword-spacing': 'off',
      'space-infix-ops': 'off',
      'no-multi-spaces': 'off',
      'no-trailing-spaces': 'off',
      'eol-last': 'off',
      'no-multiple-empty-lines': 'off',

      // Vue 相关格式化规则也需要禁用
      'vue/html-indent': 'off',
      'vue/max-attributes-per-line': 'off',
      'vue/first-attribute-linebreak': 'off',
      'vue/html-closing-bracket-newline': 'off',
      'vue/html-self-closing': 'off',
      'vue/singleline-html-element-content-newline': 'off',
      'vue/multiline-html-element-content-newline': 'off'
    }
  }
)

VS Code 配置修复

json
// .vscode/settings.json
{
  // 只使用 Prettier 作为格式化工具
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,

  // 禁用 ESLint 的格式化功能
  "eslint.format.enable": false,

  // 确保 ESLint 只做代码检查
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },

  // 不同文件类型的格式化器设置
  "[vue]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },

  // 禁用其他可能冲突的格式化扩展
  "vetur.format.enable": false,
  "typescript.format.enable": false,
  "javascript.format.enable": false
}

lint-staged 正确配置

json
// package.json
{
  "lint-staged": {
    "*.{js,ts,vue}": [
      "prettier --write",
      "eslint --fix"
    ],
    "*.{css,scss,json,md}": [
      "prettier --write"
    ]
  }
}
javascript
// lint-staged.config.js - 更精细的控制
export default {
  '*.{js,ts,vue}': (filenames) => [
    // 先格式化
    `prettier --write ${filenames.join(' ')}`,
    // 再 lint(不包含格式化规则)
    `eslint --fix ${filenames.join(' ')}`
  ],
  '*.{css,scss}': ['prettier --write'],
  '*.json': ['prettier --write'],
  '*.md': ['prettier --write --prose-wrap preserve']
}

3. 格式化不生效或配置不被识别

问题描述

执行 Prettier 命令后文件没有变化,或者配置文件中的设置没有生效,代码仍然按照默认配置格式化。

问题原因

  • 配置文件名称或格式错误
  • 配置文件位置不在项目根目录
  • 配置语法错误导致加载失败
  • 文件被 .prettierignore 忽略
  • 缓存问题导致配置未更新
  • 编辑器使用了自己的配置而非项目配置

解决方案

bash
# 检查 Prettier 是否识别到配置文件
npx prettier --find-config-path src/main.ts
# 应该输出配置文件路径,如:.prettierrc.js

# 查看当前使用的配置
npx prettier --config .prettierrc.js --file-info src/main.ts

# 强制使用指定配置文件
npx prettier --config .prettierrc.js --write src/**/*.ts

# 清除缓存后重新格式化
npx prettier --write --cache false .

配置文件格式验证

javascript
// .prettierrc.js - ESM 格式(推荐)
export default {
  printWidth: 150,
  tabWidth: 2,
  useTabs: false,
  semi: false,
  singleQuote: true
  // 注意:不要有尾随逗号如果 JSON 不支持
}

// .prettierrc.cjs - CommonJS 格式
module.exports = {
  printWidth: 150,
  tabWidth: 2
}

// .prettierrc.json - JSON 格式
{
  "printWidth": 150,
  "tabWidth": 2,
  "useTabs": false,
  "semi": false,
  "singleQuote": true
}

// .prettierrc.yaml - YAML 格式
printWidth: 150
tabWidth: 2
useTabs: false
semi: false
singleQuote: true

配置加载调试

javascript
// 创建调试脚本 scripts/prettier-debug.js
import prettier from 'prettier'
import { resolveConfig, resolveConfigFile } from 'prettier'

async function debug() {
  const testFile = 'src/main.ts'

  // 查找配置文件
  const configFile = await resolveConfigFile(testFile)
  console.log('配置文件路径:', configFile)

  // 读取配置
  const config = await resolveConfig(testFile)
  console.log('解析后的配置:', JSON.stringify(config, null, 2))

  // 获取文件信息
  const fileInfo = await prettier.getFileInfo(testFile, {
    ignorePath: '.prettierignore'
  })
  console.log('文件信息:', fileInfo)

  if (fileInfo.ignored) {
    console.log('警告: 文件被 .prettierignore 忽略')
  }
}

debug().catch(console.error)

VS Code 配置验证

json
// .vscode/settings.json
{
  // 强制使用项目配置,不使用用户全局配置
  "prettier.configPath": ".prettierrc.js",

  // 或者设置为 true 自动查找
  "prettier.useEditorConfig": true,
  "prettier.requireConfig": true,

  // 调试时显示 Prettier 输出
  "prettier.enableDebugLogs": true
}

检查 .prettierignore 规则

bash
# .prettierignore
# 检查是否意外忽略了目标文件

# 正确的忽略规则
/dist/*
/node_modules/**
*.min.js

# ❌ 错误示例:过于宽泛的规则
# *.ts        # 这会忽略所有 TypeScript 文件!
# src/**      # 这会忽略整个 src 目录!

# ✅ 使用否定规则排除
*.log
!important.log

# 查看文件是否被忽略
# npx prettier --check src/main.ts
# 如果输出 "ignored",检查 .prettierignore

4. Vue SFC 文件格式化不完整或样式丢失

问题描述

格式化 Vue 单文件组件时,<template><script><style> 部分格式化不正确,或者某些部分的格式化被跳过,样式代码丢失或被破坏。

问题原因

  • 缺少必要的 Prettier 插件
  • Vue parser 配置错误
  • 嵌入语言(如 SCSS)的 parser 未正确配置
  • SFC 语法不规范导致解析失败
  • style 标签的 lang 属性识别问题

解决方案

bash
# 确保安装必要的依赖
pnpm add -D prettier @prettier/plugin-xml prettier-plugin-css-order

# 检查安装的插件版本
pnpm list prettier
javascript
// .prettierrc.js - 完整的 Vue 项目配置
export default {
  printWidth: 150,
  tabWidth: 2,
  useTabs: false,
  semi: false,
  singleQuote: true,
  quoteProps: 'preserve',
  jsxSingleQuote: false,
  bracketSameLine: false,
  trailingComma: 'none',
  bracketSpacing: true,
  arrowParens: 'always',
  vueIndentScriptAndStyle: false,
  endOfLine: 'auto',
  htmlWhitespaceSensitivity: 'css',
  embeddedLanguageFormatting: 'auto',

  // 插件配置
  plugins: [
    // 如果需要排序 CSS 属性
    // 'prettier-plugin-css-order'
  ],

  // 不同文件类型的覆盖配置
  overrides: [
    {
      files: '*.vue',
      options: {
        parser: 'vue',
        // Vue 模板中的 HTML 换行敏感度
        htmlWhitespaceSensitivity: 'ignore'
      }
    },
    {
      files: '*.scss',
      options: {
        parser: 'scss',
        singleQuote: true
      }
    },
    {
      files: '*.css',
      options: {
        parser: 'css',
        singleQuote: true
      }
    }
  ]
}

修复 style 标签格式化问题

vue
<!-- ❌ 问题代码:lang 属性未被正确识别 -->
<style lang=scss scoped>
.container {
  // SCSS 代码
}
</style>

<!-- ✅ 正确写法:lang 属性使用引号 -->
<style lang="scss" scoped>
.container {
  padding: 20px;

  .title {
    font-size: 18px;
  }
}
</style>

<!-- ❌ 问题代码:特殊 CSS 语法被破坏 -->
<style scoped>
/* 深度选择器可能被错误格式化 */
:deep(.el-input) {
  width: 100%;
}
</style>

<!-- ✅ 使用 prettier-ignore 保护特殊语法 -->
<style scoped>
/* prettier-ignore */
:deep(.el-input) {
  width: 100%;
}

.normal-style {
  color: red;
}
</style>

template 格式化控制

vue
<template>
  <!-- ✅ 正常格式化 -->
  <div class="container">
    <span>普通内容</span>
  </div>

  <!-- prettier-ignore -->
  <!-- ✅ 跳过特定元素的格式化 -->
  <pre>
    保持原样的
        缩进内容
  </pre>

  <!-- prettier-ignore-attribute -->
  <!-- ✅ 只跳过属性格式化 -->
  <div
    class="a   b    c"
    style="margin: 0"
  >
    内容
  </div>
</template>

script setup 格式化

vue
<script setup lang="ts">
// ✅ 确保类型导入正确格式化
import type { PropType } from 'vue'
import type { SysUserVo } from '@/api/system/user/userTypes'

// ✅ 定义 props 和 emits
interface Props {
  modelValue: string
  disabled?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  disabled: false
})

const emit = defineEmits<{
  (e: 'update:modelValue', value: string): void
}>()

// ✅ 复杂对象保持格式
// prettier-ignore
const options = [
  { label: '选项一',   value: 1 },
  { label: '选项二',   value: 2 },
  { label: '选项三',   value: 3 }
]
</script>

5. 多人协作时格式化结果不一致

问题描述

团队成员使用相同的配置文件,但格式化后的代码仍然不一致,导致 Git 出现不必要的 diff,或者 CI 检查失败。

问题原因

  • Prettier 版本不一致
  • 操作系统换行符差异
  • IDE 配置覆盖了项目配置
  • 部分成员未安装必要的插件
  • 编辑器使用了用户级别的 Prettier 配置
  • Git 的换行符自动转换配置不同

解决方案

json
// package.json - 锁定 Prettier 版本
{
  "devDependencies": {
    "prettier": "3.4.2"  // 使用精确版本,不使用 ^
  },
  "engines": {
    "node": ">=18.0.0",
    "pnpm": ">=8.0.0"
  }
}

Git 换行符配置

bash
# .gitattributes - 统一换行符处理
* text=auto eol=lf

# 特定文件类型强制 LF
*.ts text eol=lf
*.tsx text eol=lf
*.js text eol=lf
*.jsx text eol=lf
*.vue text eol=lf
*.json text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.md text eol=lf
*.yaml text eol=lf
*.yml text eol=lf

# Windows 批处理文件保持 CRLF
*.bat text eol=crlf
*.cmd text eol=crlf

# 二进制文件
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.woff binary
*.woff2 binary
bash
# 团队成员执行一次性修复
git config core.autocrlf false
git add --renormalize .
git commit -m "chore: 统一换行符为 LF"

EditorConfig 配置

ini
# .editorconfig - 编辑器基础配置
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.{yml,yaml}]
indent_size = 2

[Makefile]
indent_style = tab

VS Code 团队配置

json
// .vscode/settings.json - 项目级配置
{
  // 强制使用项目的 Prettier 配置
  "prettier.configPath": ".prettierrc.js",
  "prettier.requireConfig": true,

  // 禁用用户级别的配置覆盖
  "prettier.ignorePath": ".prettierignore",

  // 统一换行符
  "files.eol": "\n",

  // 统一编码
  "files.encoding": "utf8",

  // 统一缩进
  "editor.tabSize": 2,
  "editor.insertSpaces": true,
  "editor.detectIndentation": false,

  // 保存时格式化
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode"
}
json
// .vscode/extensions.json - 推荐扩展
{
  "recommendations": [
    "esbenp.prettier-vscode",
    "dbaeumer.vscode-eslint",
    "EditorConfig.EditorConfig"
  ],
  "unwantedRecommendations": [
    "octref.vetur"  // 与 Volar 冲突
  ]
}

CI/CD 格式检查

yaml
# .github/workflows/lint.yml
name: Lint

on: [push, pull_request]

jobs:
  format-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 18
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install

      - name: Check formatting
        run: pnpm prettier --check .

      - name: ESLint check
        run: pnpm lint

格式化差异检测脚本

javascript
// scripts/format-check.js
import { execSync } from 'child_process'
import { existsSync } from 'fs'

function checkFormatting() {
  try {
    // 检查是否有未格式化的文件
    execSync('npx prettier --check .', { stdio: 'pipe' })
    console.log('✅ 所有文件格式正确')
    process.exit(0)
  } catch (error) {
    console.error('❌ 发现格式问题的文件:')

    // 获取需要格式化的文件列表
    const output = execSync('npx prettier --list-different .', { encoding: 'utf-8' })
    console.log(output)

    console.log('\n运行 "pnpm prettier" 修复格式问题')
    process.exit(1)
  }
}

checkFormatting()

6. 格式化性能慢或内存溢出

问题描述

执行 Prettier 格式化时速度很慢,或者在处理大型项目时出现内存溢出错误,导致格式化失败。

问题原因

  • 项目文件数量过多
  • 未配置 .prettierignore 忽略不需要的文件
  • 单个文件过大
  • 使用了性能较差的插件
  • 全量格式化而非增量格式化
  • Node.js 内存限制过小

解决方案

bash
# .prettierignore - 优化忽略配置
# 构建输出
/dist/*
/build/*
/.output/*

# 依赖
/node_modules/**

# 缓存
/.cache/*
/.parcel-cache/*
/.vite/*

# 大文件
*.min.js
*.min.css
*.bundle.js
*.chunk.js

# 第三方库
/public/lib/**
/src/vendor/**

# 生成的文件
*.generated.ts
*.generated.js
/src/api/**/*.d.ts

# 图片等二进制文件
*.svg
*.png
*.jpg
*.gif
*.ico
*.woff
*.woff2

# 日志和临时文件
*.log
*.tmp
.DS_Store

# 测试覆盖率
/coverage/**

# 文档构建
/docs/.vitepress/dist/**
/docs/.vitepress/cache/**

增量格式化配置

json
// package.json
{
  "scripts": {
    // 只格式化 Git 暂存区的文件
    "format:staged": "npx lint-staged",

    // 只格式化改动的文件
    "format:changed": "npx prettier --write $(git diff --name-only --diff-filter=d)",

    // 使用缓存加速
    "format:cache": "npx prettier --write --cache ."
  },
  "lint-staged": {
    "*.{js,ts,vue}": ["prettier --write"],
    "*.{css,scss}": ["prettier --write"],
    "*.json": ["prettier --write"]
  }
}

Node.js 内存配置

bash
# 增加 Node.js 内存限制
export NODE_OPTIONS="--max-old-space-size=4096"
npx prettier --write .

# 或在 package.json 中配置
{
  "scripts": {
    "prettier": "node --max-old-space-size=4096 ./node_modules/.bin/prettier --write ."
  }
}

并行处理大型项目

javascript
// scripts/parallel-format.js
import { execSync } from 'child_process'
import { glob } from 'glob'
import os from 'os'

const BATCH_SIZE = 50  // 每批处理的文件数
const cpuCount = os.cpus().length

async function formatInBatches() {
  // 获取所有需要格式化的文件
  const files = await glob('src/**/*.{ts,vue,js}', {
    ignore: ['**/node_modules/**', '**/dist/**']
  })

  console.log(`找到 ${files.length} 个文件需要格式化`)

  // 分批处理
  for (let i = 0; i < files.length; i += BATCH_SIZE) {
    const batch = files.slice(i, i + BATCH_SIZE)
    const fileList = batch.join(' ')

    console.log(`处理第 ${Math.floor(i / BATCH_SIZE) + 1} 批...`)

    try {
      execSync(`npx prettier --write ${fileList}`, {
        stdio: 'inherit',
        maxBuffer: 1024 * 1024 * 10  // 10MB buffer
      })
    } catch (error) {
      console.error(`批次 ${Math.floor(i / BATCH_SIZE) + 1} 处理失败`)
    }
  }

  console.log('格式化完成')
}

formatInBatches().catch(console.error)

监控格式化性能

javascript
// scripts/format-benchmark.js
import { performance } from 'perf_hooks'
import { execSync } from 'child_process'
import { glob } from 'glob'

async function benchmark() {
  const patterns = [
    'src/**/*.ts',
    'src/**/*.vue',
    'src/**/*.js'
  ]

  for (const pattern of patterns) {
    const files = await glob(pattern, {
      ignore: ['**/node_modules/**']
    })

    const start = performance.now()

    execSync(`npx prettier --check "${pattern}"`, {
      stdio: 'pipe'
    })

    const end = performance.now()
    const timePerFile = (end - start) / files.length

    console.log(`${pattern}: ${files.length} 文件, ${(end - start).toFixed(2)}ms 总计, ${timePerFile.toFixed(2)}ms/文件`)
  }
}

benchmark().catch(console.error)

7. 特定文件或代码块需要跳过格式化

问题描述

某些文件或代码块有特殊的格式要求,不希望被 Prettier 自动格式化,需要保持原有格式。

问题原因

  • 手动对齐的数据结构需要保持格式
  • 第三方复制的代码不应修改
  • ASCII art 或特殊排版内容
  • 生成的代码需要保持原样
  • 测试用例中的特定格式

解决方案

typescript
// 单行忽略 - prettier-ignore
// prettier-ignore
const matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]

// prettier-ignore
const permissions = {
  READ:    0b0001,
  WRITE:   0b0010,
  EXECUTE: 0b0100,
  ALL:     0b0111
}

// 多行忽略 - prettier-ignore-start/end
// prettier-ignore-start
const ASCII_ART = `
  _____ _   _ _____
 |  _  | | | |_   _|
 | |_| | | | | | |
 |  _  | |_| | | |
 |_| |_|\\___/  |_|
`
// prettier-ignore-end

// 对齐的导入语句
// prettier-ignore
import {
  UserService,
  RoleService,
  DeptService,
  MenuService
} from '@/api/system'

Vue 模板中的忽略

vue
<template>
  <!-- prettier-ignore -->
  <div class="grid">
    <div>1</div><div>2</div><div>3</div>
    <div>4</div><div>5</div><div>6</div>
    <div>7</div><div>8</div><div>9</div>
  </div>

  <!-- prettier-ignore-attribute (单属性) -->
  <div
    class="a    b     c"
    :style="customStyle"
  >
    内容
  </div>

  <!-- 正常格式化的内容 -->
  <div class="normal">
    <span>这部分会被格式化</span>
  </div>
</template>

CSS/SCSS 中的忽略

scss
/* prettier-ignore */
.grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-rows:    1fr 1fr 1fr;
}

// prettier-ignore-start
$theme-colors: (
  'primary':   #409eff,
  'success':   #67c23a,
  'warning':   #e6a23c,
  'danger':    #f56c6c,
  'info':      #909399
);
// prettier-ignore-end

Markdown 中的忽略

markdown
<!-- prettier-ignore -->
| 列1   | 列2   | 列3   |
|-------|-------|-------|
| 值1   | 值2   | 值3   |
| 值1   | 值2   | 值3   |

<!-- prettier-ignore-start -->

特殊格式的代码块 保持原有缩进 不被格式化

<!-- prettier-ignore-end -->

文件级别忽略

javascript
// 文件开头添加,整个文件不格式化
// prettier-ignore-file

// 或在 .prettierignore 中添加
// src/utils/legacy-code.js
// src/vendor/**

使用 overrides 针对特定文件

javascript
// .prettierrc.js
export default {
  // 默认配置
  printWidth: 150,
  semi: false,

  overrides: [
    {
      // 特定目录使用不同配置
      files: 'src/generated/**',
      options: {
        printWidth: 80,
        semi: true
      }
    },
    {
      // 测试文件更宽松
      files: '**/*.test.ts',
      options: {
        printWidth: 200
      }
    },
    {
      // 配置文件保持紧凑
      files: ['*.config.js', '*.config.ts'],
      options: {
        printWidth: 100
      }
    }
  ]
}

8. 换行符问题导致 Git diff 异常

问题描述

Git 显示文件有大量改动,但实际只是换行符变化(LF vs CRLF),或者每次格式化后文件换行符都会改变,导致不必要的代码变更。

问题原因

  • Windows 和 Unix 系统换行符不同
  • Git 自动换行符转换配置不一致
  • Prettier 的 endOfLine 配置问题
  • 编辑器保存时自动转换换行符
  • 从其他项目复制代码带入了不同的换行符

解决方案

javascript
// .prettierrc.js - 推荐配置
export default {
  // 方案1: 自动模式(推荐)
  endOfLine: 'auto',  // 保持文件原有换行符

  // 方案2: 强制 LF(跨平台项目)
  // endOfLine: 'lf'
}

Git 配置统一换行符

bash
# 全局配置(推荐所有团队成员执行)
git config --global core.autocrlf false
git config --global core.eol lf

# 仅当前项目配置
git config core.autocrlf false
git config core.eol lf

# 查看当前配置
git config --list | grep -E "(autocrlf|eol)"
bash
# .gitattributes - 项目级换行符规则
* text=auto eol=lf

# 确保特定类型文件使用 LF
*.ts text eol=lf
*.tsx text eol=lf
*.js text eol=lf
*.jsx text eol=lf
*.vue text eol=lf
*.json text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.less text eol=lf
*.html text eol=lf
*.md text eol=lf
*.yaml text eol=lf
*.yml text eol=lf

# Windows 脚本保持 CRLF
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf

# 二进制文件不处理
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary

修复已存在的换行符问题

bash
# 1. 确保所有文件使用 LF
# 安装 dos2unix 工具
# Windows: scoop install dos2unix
# Mac: brew install dos2unix
# Linux: apt install dos2unix

# 批量转换
find . -type f -name "*.ts" -exec dos2unix {} \;
find . -type f -name "*.vue" -exec dos2unix {} \;
find . -type f -name "*.js" -exec dos2unix {} \;

# 或使用 Node.js 脚本
node -e "
const fs = require('fs');
const path = require('path');
const glob = require('glob');

glob.sync('src/**/*.{ts,vue,js}').forEach(file => {
  const content = fs.readFileSync(file, 'utf-8');
  const fixed = content.replace(/\r\n/g, '\n');
  if (content !== fixed) {
    fs.writeFileSync(file, fixed);
    console.log('Fixed:', file);
  }
});
"
bash
# 2. Git 重新规范化所有文件
git add --renormalize .
git status  # 查看改动的文件
git commit -m "chore: 统一换行符为 LF"

VS Code 配置

json
// .vscode/settings.json
{
  // 新文件使用 LF
  "files.eol": "\n",

  // 显示换行符
  "editor.renderControlCharacters": true,

  // 保存时不自动转换
  "files.insertFinalNewline": true,
  "files.trimTrailingWhitespace": true
}

换行符检测脚本

javascript
// scripts/check-line-endings.js
import { readFileSync } from 'fs'
import { glob } from 'glob'

async function checkLineEndings() {
  const files = await glob('src/**/*.{ts,vue,js}', {
    ignore: ['**/node_modules/**']
  })

  const issues = []

  for (const file of files) {
    const content = readFileSync(file, 'utf-8')

    if (content.includes('\r\n')) {
      const crlfCount = (content.match(/\r\n/g) || []).length
      const lfCount = (content.match(/(?<!\r)\n/g) || []).length

      issues.push({
        file,
        crlf: crlfCount,
        lf: lfCount,
        type: crlfCount > lfCount ? 'CRLF' : 'Mixed'
      })
    }
  }

  if (issues.length > 0) {
    console.log('❌ 发现换行符问题:')
    console.table(issues)
    console.log('\n运行以下命令修复:')
    console.log('git add --renormalize .')
    process.exit(1)
  } else {
    console.log('✅ 所有文件换行符正确 (LF)')
  }
}

checkLineEndings().catch(console.error)

CI 检查换行符

yaml
# .github/workflows/lint.yml
- name: Check line endings
  run: |
    # 检查是否有 CRLF 换行符
    if git ls-files | xargs file | grep CRLF; then
      echo "❌ 发现 CRLF 换行符,请统一使用 LF"
      exit 1
    else
      echo "✅ 换行符检查通过"
    fi