外观
02.Prettier
5176字约17分钟
2024-05-31
Prettier 是一个代码风格的修正工具。
基本介绍
代码风格是所有程序员都要遇到的问题,不管是团队协作还是个人练习。有的喜欢有分号,代码更安全;有的喜欢没分号,能少打一个字符;有的喜欢单引号,能少按一下 Shift;有的喜欢反引号,扩展更高;camelCase、 PascalCase、 snake_case 总是在团队里无法统一,就算统一了,有些队员心里也不服,因为代码风格太主观了,根本无法让谁信服谁,每个程序最喜欢看的代码还是自己的代码。
那么有没有一种非常标准且又好看的代码风格来停止这场代码风格的圣战呢?
Prettier 这时就出来了。Prettier 是一个流行的代码格式化工具,它可以自动调整代码的样式,使其更具可读性和一致性。它支持多种编程语言,包括 JavaScript、TypeScript、HTML、CSS、SCSS、GraphQL、JSON、Markdown 等。
Prettier 的核心特点包括:
- 一致的代码风格:Prettier 可以帮助团队成员统一代码风格,减少代码审查时关于代码格式的讨论。
- 无需配置:Prettier 的默认配置就足以满足大多数项目的需求。使用 Prettier 时,通常不需要花费时间调整和维护配置文件。
- 集成多种编辑器:Prettier 支持许多流行的代码编辑器,如 Visual Studio Code、Sublime Text、Atom 等都有相应的插件,可以在编写代码时自动运行 Prettier。
- 可配置性:虽然 Prettier 默认配置通常已经足够,但它仍支持自定义配置。你可以在项目根目录下创建一个 .prettierrc 文件,根据项目需求调整格式化选项。
- 自动格式化:Prettier 可以自动格式化文件,无需手动调整代码格式。
- 支持多种语言:Prettier 支持多种编程语言和文件格式,提供广泛的适用性。
Prettier 官网:https://prettier.io/
Prettier 的诞生让代码风格得到了统一:我格式化后的代码是最好看的,谁同意,谁反对?
“我反对!凭什么你说最好看就是最好看?”
就凭你不会写论文!
其实在很早之前已经有人开始研究哪种方式来格式化长文本是最好的(Prettier Printer),比如 Philip Wadler 在 《A prettier printer》这里给出了一些自动格式化换行的理论依据。
A good pretty printer must strike a balance between ease of use, flexibility of format, and optimality of output.
Prettier 的作者 James 在这篇论文基础上再完善了一些代码风格规则,最终成为了 Prettier 格式化代码的最终方案。比如像下面的链式调用,Prettier 输出的就比原来论文描述的要好看一些:
// 原版 "A prettier printer" 的实现
hello()
.then(() => {
something();
})
.catch(console.error);
// Prettier 的实现
hello()
.then(() => {
something();
})
.catch(console.error);
工作原理
那么,Prettier 是如何能够做到代码的格式化的呢?
首先,Prettier 会把代码转换成 AST (Abstract Syntax Tree),这里用到的是一个叫 Recast 的库,而 Recast 实际上也用了 Esprima 来解析 ES6。
Esprima can be used to perform lexical analysis (tokenization) or syntactic analysis (parsing) of a JavaScript program.
A simple example on Node.js REPL:
> var esprima = require('esprima'); > var program = 'const answer = 42'; > esprima.tokenize(program); > [ { type: 'Keyword', value: 'const' }, { type: 'Identifier', value: 'answer' }, { type: 'Punctuator', value: '=' }, { type: 'Numeric', value: '42' } ] > esprima.parseScript(program); > { type: 'Program', body: [ { type: 'VariableDeclaration', declarations: [Object], kind: 'const' } ], sourceType: 'script' }
所以无论之前的代码怎么乱,怎么屎,Prettier 都抹掉之前的所有样式,抽成最本质的语法树。
然后再用 Prettier 的代码风格规则来输出格式化后的代码。
Prettier 能够支持的格式化语言是多种多样的,并非仅仅只为 JS 服务:
理论上来讲,只要把一门语言的代码抽象为语法树,然后再有对应的格式化规则,那么无论什么语言都是可以的。
Prettier 官方以及社区就提供了一些插件,通过使用插件,你可以让 Prettier 格式化更多种类的代码,添加对新语言和文件类型的支持。Prettier 插件通常是单独的 npm 包,你需要安装它们作为项目的依赖。插件的名称通常遵循 prettier-plugin-* 的命名规范。安装插件后,*Prettier* 会自动发现并使用它们。
以下是一些常见的 Prettier 插件示例:
- prettier-plugin-svelte:这个插件为 Svelte 组件提供了格式化支持。安装这个插件后,你可以使用 Prettier 格式化 Svelte 文件。
- prettier-plugin-toml:这个插件为 TOML 配置文件提供了格式化支持。安装这个插件后,你可以使用 Prettier 格式化 TOML 文件。
- prettier-plugin-java:这个插件为 Java 语言提供了格式化支持。安装这个插件后,你可以使用 Prettier 格式化 Java 文件。
- prettier-plugin-php:这个插件为 PHP 语言提供了格式化支持。安装这个插件后,你可以使用 Prettier 格式化 PHP 文件。
另外,Prettier 的官方文档里一直在强调自己是一个 Opinionated 的工具,这里想展开跟大家聊聊 Opinionated。
其实不仅 Prettier,我们日常使用的一些库和框架都会标明自己是 opinionated 还是 unopinionated:
按照框架/库的 opinionated 还是 unopinionated 思路来使用它们非常重要。
Opinionated 的思路是 你的一切我全包了,使用者就别自己发明设计模式和轮子,用我的就行,有锅我背。
Unopinionated 的思路则是 我就给你一堆零件,每个有优有劣,自己组装来玩了,相当于每人都是装机猿。
Prettier 属于 Opinionated 哲学,这意味着它提供的代码风格已经是最优的,不希望使用者做太多自定义的内容,而应该相信 Prettier 已经服务到位了。
快速上手
新建一个项目目录 prettier-demo,使用 pnpm init 进行一个项目初始化,安装 prettier
pnpm add --save-dev --save-exact prettier
--save-exact 表示在安装 prettier 的时候在 package.json 里面记录具体的版本号。
接下来可以书写一些需要格式化的代码:
const str = 'Helo World';
const arr = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30,
];
const obj = {
name: 'John Doe',
age: 30,
address: {
city: 'New York',
state: 'NY',
},
};
然后在 package.json 里面添加命令行脚本
"scripts": {
// ...
"format": "prettier --write ."
},
之后就可以使用 pnpm format 这条命令来进行格式化代码操作。
如果想要自定义规则,可以在项目根目录下面创建一个 .prettierrc,之后使用对象的形式写入自定义规则即可:
{
"singleQuote": true,
"semi": false,
"printWidth": 80,
"trailingComma": "es5"
}
- singleQuote:单引号
- semi:语句末尾是否添加分号
- printWidth:一行的最长长度
- trailingComma:对象或者数组字面量中的最后一个元素是否添加逗号
关于更多的规则,在下一节课会进行介绍。
我们也可以通过安装 vscode 插件的方式来使用 prettier。使用脚本命令和使用 vscode 插件两者之间的差别如下:
实时性:使用 Prettier 的 vscode 插件,你可以在编写代码时立即看到格式化效果,而不需要等待执行脚本命令。这有助于在编写代码时就保持良好的代码风格。
范围:通过运行脚本命令 "prettier --write .",你可以一次性格式化整个项目中的所有文件。而 Prettier 插件只会在你需要时(如保存文件)格式化当前打开的文件。
格式化规则
格式化规则
作为一个格式化代码的工具,最最重要的就是格式化依据的规则是什么?以及如何配置这些规则。
在官网的 https://prettier.io/docs/en/options.html 页面就可以看到所有的格式化规则。
这些规则虽然很重要,但是本身来讲都很简单,只要会看一个,其他的也就都会了。
例如下面是 prettier 里面其中的一条规则,主要需要学会看规则说明以及下面的表:
那么多规则不可能挨着挨着去过一遍,一般来讲,了解一些常用的规则,之后另外一些规则用到了自然也就会了。和当初学习编程语言关键字非常类似,用到一个自然就会记住一个。
行宽:Prettier 默认将行宽限制在 80 个字符。如果一行代码超过这个长度,Prettier 会自动进行换行。你可以通过 printWidth 配置项自定义行宽。
缩进:Prettier 默认使用 2 个空格进行缩进。你可以通过 tabWidth 和 useTabs 配置项自定义缩进宽度和是否使用制表符(tab)。
分号:Prettier 默认在每个语句结尾添加分号。你可以通过 semi 配置项选择是否强制添加分号。
引号:Prettier 默认使用双引号。你可以通过 singleQuote 配置项自定义引号类型。
尾随逗号:Prettier 默认在多行结构(如对象字面量和数组字面量)的最后一个元素后添加尾随逗号。你可以通过 trailingComma 配置项自定义尾随逗号的使用。
括号空格:Prettier 默认在括号内添加空格。例如,{ foo: bar }。你可以通过 bracketSpacing 配置项自定义括号内的空格。
箭头函数参数括号:Prettier 默认在箭头函数只有一个参数时省略参数括号。你可以通过 arrowParens 配置项自定义箭头函数参数括号的使用。
HTML 属性换行:Prettier 会在 HTML 标签的属性超过 printWidth 时进行换行。你可以通过 htmlWhitespaceSensitivity 配置项自定义换行策略。
JSX 标签闭合:Prettier 默认将没有子元素的 JSX 标签闭合。例如,<br />。你可以通过 jsxBracketSameLine 配置项自定义 JSX 标签闭合的风格。
注意这些规则是 prettier 默认规则,也是行业内的最佳实践标准,因此我们平时在书写代码的时候,就应该主动的按照这些规则去规范书写我们的代码。
配置文件
配置文件的作用是做规则自定义。在 prettier 里面实际上提供了多种配置方式,并且多种配置方式之间会有一个优先级。
接下来我们来看一下究竟有哪些配置方式(优先级从高到低)
- 命令行选项:在 package.json 中配置 script 脚本命令的时候,是可以添加配置参数的
"format": "prettier --no-semi --print-width 50 --write ."
这种命令行的配置方式优先级是最高的,但是这种方式仅限于自定义一两条规则,如果需要自定义规则比较多,那么还是推荐使用单独的配置文件。
- 文件中配置:这种方式就是单独创建一个配置文件,这里支持的配置文件的格式实际上是比较多。例如 .prettierrc、.prettierrc.json、.prettierrc.yaml、.prettierrc.yml、.prettierrc.js 或 prettier.config.js。Prettier 会自动识别并应用这些文件中的配置。在这些文件里面,一般就是一个对象
{
"singleQuote": true,
"semi": false,
"printWidth": 80,
"trailingComma": "es5"
}
正常情况下,一个项目中写一个配置文件就可以了,但是如果不小心有多个配置文件,那么确实也涉及到一个优先级问题。优先级顺序如下(由高到低):
- .prettierrc.js 或 prettier.config.js
- .prettierrc.yaml 或 .prettierrc.yml
- .prettierrc.json
- .prettierrc
- 在 pacakge.json 中进行配置:可以在 package.json 文件中添加一个配置选项 prettier,然后进行配置
{
"name": "my-project",
"version": "1.0.0",
"prettier": {
"singleQuote": true,
"tabWidth": 4
}
}
- 编辑器配置:许多代码编辑器可以安装和 prettier 相关的插件,之后可以在编辑器的 settings > extensions 里面进行配置
- prettier 默认配置:默认配置一般优先级是最低,但是却是最常用的,因为这套配置已经是行业最佳实践了。
有点类似于 .gitignore,prettier 也支持 .prettierignore,用来排出一些文件或者目录,也就是说,出现在 .prettierignore 里面的文件或者目录不会被 prettier 进行处理。
例如:
# 忽略所有的 .min.js 文件
*.min.js
# 忽略整个 build 目录
/build/
# 忽略 node_modules 目录
node_modules/
# 忽略特定文件
my-special-file.js
关于 .prettierignore 里面具体的路径写法,可以参阅 .gitignore 的写法:https://git-scm.com/docs/gitignore
命令行工具
在最初的时候,我们在 pacakge.json 里面做了一个命令行的配置:
"scripts": {
//...
"format": "prettier --write ."
},
关于 prettier 究竟支持哪些 CLI 命令,我们可以在官网中看到:https://prettier.io/docs/en/cli.html
关于 CLI 命令有一个最基本的格式:
prettier [options] [file/dir/glob ...]
- options: 格式化的选项
- file/dir/glob:要格式化的文件或者目录
接下来我们来看一下在第一节课写的命令:
prettier --write .
--write 就是配置选项,本来 prettier 格式化完成后是在控制台输出的, --write 代表写入到原本的文件,--write 还有一个别名就是 -w
. 是第二个参数,代表的是要格式化的路径,正常情况下你可以写一个文件或者写一个目录,例如
prettier --write file.js # 只格式化 file.js 这个文件 prettier -w "src/**/*.js" # 格式化 src 目录下面的所有的 js 文件(包含 src 下面的子目录)
上面的 . 表示当前目录以及子目录下所有支持的文件,全部格式化之后写回原来的文件。
接下来介绍一些常见的 options
--check:该配置参数用于检查文件是否已经按照 prettier 规则进行了格式化,如果匹配的路径下面的所支持的文件已经全部被格式化,那么会输出 All matched files use Prettier code style!,否则会显示哪些文件还没有被 prettier 格式化
--find-config-path
and --config:指定配置文件的路径,正常情况下,prettier 会自动去找项目下面的配置文件。但是如果你的配置文件不在项目中,而是在其他的位置,那么这个也是可以指定的
prettier --config ~/configs/prettier.json --write .
- --no-config: 不读取任何配置文件,直接使用 prettier 里面默认的配置。
- --ignore-path:指定忽略文件的路径,正常情况下也是在当前项目中去寻找,但是如果你的忽略文件不在项目中,而是在其他位置,也是可以指定的
prettier --ignore-path ~/configs/ignore/.prettierignore --write .
- 规则的配置:上一节课介绍了的 prettier 所有的规则都可以在 CLI 命令里面进行配置的。不过这种只适用于单独的一两个规则,如果你的规则比较多还是应该单独拿一个配置文件来配置规则。
APIs
在前面我们对代码进行格式化的时候,我们采用的是命令行工具的形式,但是这些命令行工具所提供的命令实际上也是调用的 prettier 背后对应的各种 API。
在官网能够查看到这些 API:https://prettier.io/docs/en/api.html
prettier.format(source, options)
这个 API 是整个 prettier 里面最最核心的 API,该 API 负责的就是格式化操作。
下面是一个使用该 API 进行代码格式化的例子:
// 该文件使用 API 的形式来对代码进行格式化
const prettier = require('prettier');
const fs = require('fs');
const path = require('path');
// prettier.format(source, options)
// console.log(sourcePath);
// /Users/jie/Desktop/prettier-demo/src/index.js
// const optionsPath = path.resolve(".prettierrc");
// 书写 prettier 规则配置
const options = {
singleQuote: false,
printWidth: 50,
semi: false,
trailingComma: 'es5',
parser: 'babel',
};
// 读取 src 目录
fs.readdir('src', (err, files) => {
if (err) throw err;
for (let i = 0; i < files.length; i++) {
// 拼接路径
const sourcePath = path.resolve('src', files[i]);
// 读取源码文件
const jsSource = fs.readFileSync(sourcePath, 'utf8');
// 使用 prettier.format 来进行格式化
// 通过 API 的方式来格式化,一定要指定 parser
prettier.format(jsSource, options).then((res) => {
// 将格式化好的结果重新写入到原来的文件里面
fs.writeFileSync(sourcePath, res, 'utf-8');
});
}
console.log('格式化完毕...');
});
注意在使用 API 进行格式化的时候,格式化规则里面需要添加 parser,指定对应的解析器,关于能够添加哪些 parser,可以参阅:https://prettier.io/docs/en/options.html#parser
之后就是读取目录下面的文件,调用 format API 进行格式化操作,格式化完成之后将格式化的结果重新写入到原来的文件里面。
prettier.check(source, [ options ])
该 API 主要负责核对对应的文件是否已经被 prettier 格式化,如果已经被格式化,则返回 true,否则返回 false
下面是一个使用示例:
// 判断 src 下面是否所有的文件都已经格式化
const prettier = require('prettier');
const fs = require('fs');
const path = require('path');
// 书写 prettier 规则配置
const options = {
singleQuote: false,
printWidth: 50,
semi: false,
trailingComma: 'es5',
parser: 'babel',
};
fs.readdir('src', async (err, files) => {
if (err) throw err;
let isAllFormated = true;
for (let i = 0; i < files.length; i++) {
// 拼接路径
const sourcePath = path.resolve('src', files[i]);
// 读取源码文件
const jsSource = fs.readFileSync(sourcePath, 'utf8');
const res = await prettier.check(jsSource, options);
if (!res) {
// 说明这个文件没有被格式化
console.log(`${files[i]} 文件还没有格式化`);
isAllFormated = false;
}
}
if (isAllFormated) {
console.log('所有文件都已经格式化...');
}
});
实现简易 CLI
一个工具的 CLI 背后其实就是调用的对应的 API,所以我们这里来实现一个简易的 CLI 工具。
首先新建一个名为 formattool 项目,使用 pnpm init 进行初始化。然后在项目中新建一个 index.js,代码如下:
#!/usr/bin/env node
// 上面的第一行代码,通常称之为 shebang(sharp bang)
// 这个是在类 unix 操作系统里面所支持的一种特性,用于告诉系统如何执行之后的脚本
// 因此在 #! 后面一般会跟上一个解释器的绝对路径
// 获取命令行参数
const args = process.argv.slice(2);
console.log(args);
主要就是要注意第一行代码,该代码是类 Unix(Linux、MacOS)操作系统所拥有的一种特性,告诉操作系统如何执行之后的脚本,后面会跟上解释器的绝对路径。
接下来我要进行一个全局的链接。
终端定位到在 formattool 项目根目录下,使用 npm link 进行一个全局的链接,接下来回到要链接的项目(prettier-demo),使用 npm link formattool 链接刚才放到了全局下面的包。
接下来回到 formattool 下面的 index.js 文件中,补充如下的代码:
#!/usr/bin/env node
// 上面的第一行代码,通常称之为 shebang(sharp bang)
// 这个是在类 unix 操作系统里面所支持的一种特性,用于高速系统如何执行之后的脚本
// 因此在 #! 后面一般会跟上一个解释器的绝对路径
const prettier = require('prettier');
const fs = require('fs');
const path = require('path');
// 获取命令行参数
const args = process.argv.slice(2);
// 要做格式化的操作
// pnpm formattool --write src/index.js
// 读取源码
const sourcePath = path.resolve('src', 'index.js');
const jsSource = fs.readFileSync(sourcePath, 'utf8');
// 读取配置文件
const options = JSON.parse(fs.readFileSync(path.resolve('.prettierrc')));
if (args.length === 0) {
console.error('请提供一个参数!');
process.exit(1);
}
const input = args[0];
if (input === '--write' || input === '-w') {
// 使用 prettier 的 api 对代码进行格式化操作
prettier.format(jsSource, options).then((res) => {
// 将格式化后的 js 代码重新写回到原来的文件
fs.writeFileSync(sourcePath, res, 'utf-8');
});
console.log('格式化操作已经完成...');
}
在上面的代码中,我们调用了 prettier 的 format 方法来对 src/index.js 文件进行一个格式化,并且将格式化后的代码写回到原来的文件。
之后在 prettier-demo 项目中,我们需要在 package.json 中添加一条命令:
"scripts": {
// ...
"formattool": "formattool"
},
之后我们就可以在控制台通过 pnpm formatool --write 对 src/index.js 文件进行格式化操作。
注意,我们上面所完成的代码只是一个最最基本的演示,目的是为了让大家明白 CLI 背后的原理其实就是获取用户在命令行所输入的参数,然后调用对应的 API。实际的 CLI 背后还会有更多的判断。