设计理念
Prettier 是一个强约定的代码格式化工具。本文阐述其核心设计原则。
Prettier 的核心关注点
正确性
Prettier 的首要原则是输出行为完全一致的合法代码。若发现格式化后行为改变,请务必https://github.com/prettier/prettier/issues - 这属于需要修复的严重缺陷!
字符串处理
引号选择策略:
- 优先选择转义字符更少的引号类型
"It's gettin' better!"
(双引号需0次转义)- 而非
'It\'s gettin\' better!'
(单引号需2次转义) - 默认使用双引号(可通过 /docs/options#quotes 选项修改)
JSX特殊规则:
- 属性值默认双引号(遵循HTML惯例)
- 通过 /docs/options#jsx-quotes 启用单引号
- 保持原始转义形式(如
"🙂"
不转为"\uD83D\uDE42"
)
空行处理
空行自动生成存在技术挑战,Prettier采用智能策略:
- 保留原始空行结构
- 合并连续空行为单空行
- 移除块级结构首尾空行
- 文件始终以单个换行符结尾
多行对象
智能折行策略:
// 原始含换行 → 保持多行
const user = {
name: "John Doe",
age: 30,
};
// 原始无换行 → 保持单行
const user = { name: "John Doe", age: 30 };
手动控制技巧:
// 单行转多行:在 { 后添加换行
const user = {
name: "John Doe", age: 30 };
// 格式化后 →
const user = {
name: "John Doe",
age: 30,
};
// 多行转单行:删除 { 后换行
const user = { name: "John Doe",
age: 30
};
// 格式化后 →
const user = { name: "John Doe", age: 30 };
:::note[格式化可逆性说明] 当前策略是临时方案,团队正在研究更优启发式规则,目标是实现完全可逆的格式化(增删属性不改变原有格式结构)。 :::
装饰器处理
类装饰器:强制独立行
@observer // 始终单独成行
class OrderLine {
@observable price: number = 0; // 方法装饰器保持原位
}
方法装饰器:尊重原始布局
class HeroButton {
// 单行装饰器保持单行
@Output() change = new EventEmitter();
// 多行装饰器保持多行
@readonly
@nonenumerable
NODE_TYPE: 2;
}
模板字符串
智能折行规则:
// 含换位的插值保持多行
`User: ${user.firstName} ${ // 原始有换行
user.lastName
}`
// 无换位插值保持单行(即使超宽)
`thisIsAVeryLongString${andThisIsAVeryLongInterpolation}`
提示
在 ${}
内添加换行可强制折行
分号策略
自动安全防护:
// 原始无分号
[-1, 1].forEach(delta => addLine(delta))
// 格式化后添加防护分号
;[-1, 1].forEach(delta => addLine(delta))
防护分号避免后续添加代码时出现https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Lexical_grammar#自动分号插入错误
注意
Prettier 不修复现有分号相关错误:
// 错误代码保持错误行为
console.log('Running')(
async () => await work()
)()
打印宽度
options.md#print-width 是指导值而非严格限制:
特殊场景突破限制:
- 导入语句
import { SingleComponent } from "../components/single-component/main";
- 测试描述
it("验证超长描述文本不被意外折行", () => { /*...*/ })
JSX格式化
特殊处理原则:
// 普通函数
function greet(user) {
return user ? `Welcome, ${user.name}!` : "Greetings!";
}
// JSX组件:括号包裹+条件表达式分行
function Greet({ user }) {
return (
<div>
{user ? (
<p>Welcome, {user.name}!</p>
) : (
<p>Greetings!</p>
)}
</div>
);
}
避免残留分号导致的渲染问题:<p>Greetings!</p>;
注释处理
内容保留:
- 普通注释保持原样
- JSDoc风格注释自动对齐缩进
位置策略:
// 推荐:独立行注释
// eslint-disable-next-line no-eval
const result = safeEval ? eval(input) : fallback;
// 避免:行尾注释
const result = safeEval ? eval(input) : fallback; // eslint-disable-line
魔法注释注意事项:
// 错误:折行后失效
// eslint-disable-next-line no-eval
const result = safeEval && allowNative ? eval(input) : fallback;
// 正确:移至表达式前
const result =
// eslint-disable-next-line no-eval
safeEval && allowNative ? eval(input) : fallback;
最佳实践
优先使用范围注释:/* eslint-disable */
和 /* istanbul ignore next */
非标准语法声明
Prettier 对 ECMAScript 提案和非标准 Markdown 语法的支持属于实验性功能:
- 支持力度:尽力而为(best-effort)
- 兼容性:可能在不通知的情况下变更
- 版本变更:不视为破坏性更新
机器生成文件声明
package.json
等机器生成文件采用特殊格式化策略:
// 使用 JSON.stringify 风格格式化
{"dependencies":{"prettier":"^3.0.0"}}
移除垂直空白等差异属于预期行为,避免与包管理器冲突。
Prettier 的明确边界
Prettier 仅专注代码打印,不进行代码转换:
不做转换的典型案例:
- 引号类型转换(单引号↔双引号↔模板字符串)
- 字符串连接符拆分(
+
操作符) - 可选语法简化(移除
{}
/return
) - 条件表达式转
if-else
语句 - 导入/对象键/类成员排序
- CSS属性重排
这些转换可能影响代码行为,违背#正确性核心原则。
设计总结
Prettier 通过约束性设计实现统一的代码风格:
- 可靠性优先:保证格式化前后代码行为完全一致
- 最小意外原则:
- 字符串引号智能选择(最少转义)
- 空行与对象结构保留原始意图
- 装饰器位置尊重开发者习惯
- 安全防护:
- 自动添加防护分号避免ASI错误
- JSX括号包裹防止语法错误
- 灵活约束:
- 打印宽度作为指导值而非绝对限制
- 特殊语法(测试用例/导入语句)突破限制
- 边界清晰:
- 不改变代码语义行为
- 不进行语法转换
- 不重排代码结构
这种"有主见的格式化"消除了风格争论,使开发者专注于逻辑而非排版。通过3000+项目的实践检验,Prettier已成为现代工作流的基石工具。