前端工程化 - Prettier 的使用

1,216 阅读11分钟

u=2544329805,3021168342&fm=253&fmt=auto&app=138&f=JPEG.webp 今天我们来使用 Prettier 进行代码格式化,严格来说,需要搭配 ESLint, StyleLint 等一起使用,但是因为知识点比较多,所以还是单开一篇先说一下。 可能有一些从其它语言转过来的童鞋会有一些误解,Node.js 中的包,既可以包含代码,也可以包含可执行的程序,而 Java 中的包更多的就是程序库的概念。

比如 Prettier 包就是这样一个程序,我们安装它以后,就可以通过在命令行调用它的命令,对项目中的源文件进行格式化,格式化的规则放在配置文件里面,在格式化之前,Prettier 命令行工具会读取它。

这里要注意的是,我们在 vscode 等这样的编辑器中,会安装 Prettier 的插件,这个插件实际上也是通过 vscode 去调用命令行来执行的,所以他们在项目中读取的配置文件是同一个,插件只是为了让 vscode 更好的去调用这个命令。

1. 背景

在软件开发中,代码的可读性和一致性是非常重要的。随着项目的规模扩大和团队成员的增加,不同开发者可能会有不同的编码风格,这可能导致代码库中出现风格不一致的情况。不一致的代码风格可能会使得代码难以阅读和维护,也增加了新团队成员的上手难度。此外,一些潜在的代码错误和质量问题也可能因为风格不一致而被忽视。

为了提高代码质量、增强团队协作效率,以及减少代码审查时因风格问题产生的争议,开发者们开始寻求自动化的工具来帮助他们统一代码风格和识别潜在的代码问题。

今天我们主要介绍如何使用 Prettier 来解决代码风格问题。

1.1 Prettier

Prettier 是一款功能强大的代码格式化程序,支持前端多种编程语言文件的格式化,如支持 JavaScript,TypeScript,Vue,HTML,JSON,YAML 等语言。

Prettier 官网: prettier.io/

Prettier 会忽略文件的原始样式,通过解析源文件获得源文件的 AST,最后使用 AST 配上自己的规则重新生成新的源文件,这些规则会自动考虑最大行长度,并在需要的时候包装代码。

AST (Abstract Syntax Tree) astexplorer.net/

AST 是抽象语法树,它用树型结构来表示语言的语法结构,它是语法分析的结果。

📃* Esprima* esprima.org/

Esprima 是一个高性能、符合标准的 ECMAScript 解析器,以 ECMAScript (也被称为 JavaScript ) 编写。

📃* ESTree* github.com/estree/estr…

ESTree是 Esprima 的AST 规范。

📃ESQuery github.com/estools/esq…

ESQuery 是一个库,使用 CSS 样式选择器系统来查询 ESTree 的 AST 输出以获取语法模式。

Prettier优点

  • 自动格式化:Prettier 是一个完全自动化的代码格式化工具,它可以根据预设的规则自动调整代码格式,如空格、缩进、引号等。
  • 一致性:确保整个代码库中的风格一致性,减少因风格问题导致的代码审查时间。
  • 易于集成:Prettier 可以轻松集成到各种编辑器和构建工具中,如 Visual Studio Code、WebStorm、Gulp、Webpack 等。
  • 节省时间:开发者无需手动调整代码格式,可以将更多时间专注于解决实际的业务逻辑问题。
  • 减少争议:团队成员之间因代码风格不同而产生的争议会大大减少,因为 Prettier 会应用统一的规则。

Prettier 也有自己格式化的一套哲学,尤其是在空行、多行、对象等的处理上面,关于这块的内容,可以详细看看官网的关于章节。

总之一句话,Prettier 的格式化更像是一个人类,他懂你想要什么,不像别的格式化工具,只按照规则办事。

prettier.io/docs/en/rat…

2. Prettier 使用

这里分两种使用方式。

  1. 首先 Prettier 可以在项目中使用。
  2. 其次 Prettier 可以在编辑器中使用,这里我们以 vscode 为例。

2.1 项目中手动使用

首先,最好在每个项目中本地安装 prettier, 确保每个项目使用了正确的版本。

这里,我们先新建一个项目,然后写一个 index.js 文件。

# 新建项目
pnpm init# 安装 prettier 
pnpm add --save-dev --save-exact prettier

index.js 的文件内容,这里用于测试,我们简单点,快速让 kimichat 来写。

需求:写一个日期处理的 javascript 函数。
​
该函数根据输入的时间和当前时间,输出以下的内容:
​
1. 如果输入时间是1小时内,返回字符串 "XXX 分钟之前"
2. 如果输入时间在今天之内,返回字符串 " XXX 小时之前"
3. 如果输入时间是昨天和前天,根据凌晨、上午、中午、下午、傍晚、晚上返回字符串“昨天凌晨”,”昨天上午”,“前天凌晨” 等
4. 如果输入时间是大前天以前,返回具体日期字符串 "2024年10月18日 14:00" 
​
请把条件规则和格式化字符串写到一个规则词典里面,方便后续对代码进行修改,比如扩充到月,年等等的比较。
​
另外,请编写对应的测试用例,用于测试。
​
​
function formatRelativeTime(inputTime) {
    const rules = {
        '1 hour': '%d 分钟之前',
        'today': '%d 小时之前',
        'yesterday': '昨天%s',
        'day before yesterday': '前天%s',
        'older': '%s'
    };
​
    const now = new Date();
    const input = new Date(inputTime);
    const diff = now - input;
    const diffDays = Math.floor(diff / (1000 * 60 * 60 * 24));
​
    const minutesAgo = Math.floor((diff / (1000 * 60)) % 60);
    const hoursAgo = Math.floor((diff / (1000 * 60 * 60)) % 24);
​
    let timeOfDay;
    if (diffDays === 0) {
        if (hoursAgo < 1) {
            return rules['1 hour'].replace('%d', minutesAgo);
        } else {
            return rules['today'].replace('%d', hoursAgo);
        }
    } else if (diffDays === 1) {
        timeOfDay = getTimeOfDay(input);
        return rules['yesterday'].replace('%s', timeOfDay);
    } else if (diffDays === 2) {
        timeOfDay = getTimeOfDay(input);
        return rules['day before yesterday'].replace('%s', timeOfDay);
    } else {
        return rules['older'].replace('%s', input.toLocaleDateString() + ' ' + input.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}));
    }
}
​
function getTimeOfDay(date) {
    const hour = date.getHours();
    if (hour < 6) return '凌晨';
    if (hour < 12) return '上午';
    if (hour < 18) return '下午';
    return '晚上';
}
​
// 测试用例
console.log(formatRelativeTime('2024-10-18T23:45:00')); // 应该返回 "55 分钟之前"
console.log(formatRelativeTime('2024-10-18T15:00:00')); // 应该返回 "8 小时之前"
console.log(formatRelativeTime('2024-10-17T23:30:00')); // 应该返回 "昨天晚上"
console.log(formatRelativeTime('2024-10-16T03:00:00')); // 应该返回 "前天凌晨"
console.log(formatRelativeTime('2024-10-15T14:00:00')); // 应该返回 "2024年10月15日 14:00"

然后,我们需要创建两个配置文件 .prettierrc 和 .prettierignore。

.prettierrc 用来告诉编辑器或者其它工具,该如何使用 Prettier。

.prettierignore 用来告诉 Prettier 命令行或者编辑器,哪些文件不需要格式化。

.prettierrc 文件样例

我们可以用 
node --eval "fs.writeFileSync('.prettierrc','{}\n')" 
直接创建一个新文件。
​
{
​
}

.prettierignore 文件样例

我们可以用下面命令
node --eval "fs.writeFileSync('.prettierignore','# Ignore artifacts:\nbuild\ncoverage\nnode_modules\n')"
直接创建一个新文件
​
# Ignore artifacts:
build
coverage
node_modules
.prettierignore
.prettierrc
pnpm-lock.yaml

这个例子说明 build 和 coverage 里面的所有文件不需要格式化。

最后,我们回到项目目录下进行格式化,我们预先修改一下 index.js

pnpm exec prettier . --write
​
# 执行完,我们可以看到
.prettierrc 52ms (unchanged)
index.js 38ms
package.json 2ms (unchanged)
pnpm-lock.yaml 11ms (unchanged)
​
这说明 index.js 已经被修改了
同时,我们也看到了,被忽略的文件和文件夹不会在这里展示出来,这表明程序并没有对它们进行操作。

详情请参考:

prettier.io/docs/en/ins…

2.2 格式化配置

Prettier 遵循可选的哲学,只需要很少的配置就能运行。

在 Prettier 中,推荐使用配置文件来对 Prettier 进行配置,这里有很多种配置文件都可以采用:

一个以 JSON 或 YAML 格式编写的 .prettierrc 文件。
在您的 package.json 或 package.yaml 文件中写一个 "prettier" 键。
一个 .prettierrc.json、.prettierrc.yml、.prettierrc.yaml 或 .prettierrc.json5 文件。
一个使用 export defaultmodule.exports 导出对象的 .prettierrc.js 或 prettier.config.js 文件(取决于您 package.json 中的类型值)。
一个使用 export default 导出对象的 .prettierrc.mjs 或 prettier.config.mjs 文件。
一个使用 module.exports 导出对象的 .prettierrc.cjs 或 prettier.config.cjs 文件。
一个 .prettierrc.toml 文件。

我们这里先采用第一种,使用 .prettierrc 文件来配置。

详细配置请看文档: prettier.io/docs/en/opt…

{
  "trailingComma": "es5",
  "tabWidth": 4,
  "semi": false,
  "singleQuote": true
}

配置文件我们可以借助 AI,让 AI 进行详细的解释,我们把上面的文本提交给 AI,告诉他请帮我详细解释一下每个字段的含义。

这段代码是一个 JSON 对象,用于配置 Prettier,下面是每个配置项的详细解释:
​
"trailingComma": "es5":
这个选项控制着在对象或数组的最后一个元素后面是否添加逗号(也称为尾随逗号)。
"es5" 表示在 ES5 环境下,尾随逗号是有效的,因此 Prettier 会在每行的末尾添加逗号。
这在某些情况下可以提高可读性,尤其是当添加或删除元素时,不需要重新格式化整个列表。
注意:在 ES5 环境下,尾随逗号在对象字面量中是合法的,但在数组中不是。但在 ES2015ES6)及以后的版本中,数组中的尾随逗号也是合法的。
​
"tabWidth": 4:
这个选项定义了每个缩进级别应该使用多少个空格。
在这里,4 表示每个缩进级别使用 4 个空格。这是编程中常见的缩进宽度,但这个值可以根据个人或团队的编码风格来调整。
​
"semi": false:
这个选项控制着是否在每个语句的末尾添加分号。
false 表示 Prettier 将不会在语句末尾添加分号。
这意味着它会根据 ASI(自动分号插入)规则来决定是否需要分号。
​
"singleQuote": true:
这个选项决定了 Prettier 是否使用单引号而不是双引号来包围字符串。
true 表示 Prettier 将使用单引号来格式化字符串。
这是一个风格选择问题,有些开发者更喜欢单引号,因为它们通常被认为更轻量级,而且可以在某些编辑器中更快地输入。
这些配置项可以根据个人或团队的偏好进行调整,以确保代码的一致性和可读性

添加配置以后,我们再运行格式化的命令,打开 index.js 文件后我们发现已经全部变为单引号了,这表明新的配置起作用了。

function formatRelativeTime(inputTime) {
    const rules = {
        '1 hour': '%d 分钟之前',
        today: '%d 小时之前',
        yesterday: '昨天%s',
        'day before yesterday': '前天%s',
        older: '%s',
    }
    ......
}

2.3 在编辑器vscode中使用

上面的方式,我们是每次修改完都要运行命令才能对文件进行格式化,不是特别方便,vscode提供了比较方便的集成,这样我们在每次修改完代码后,编辑器会自动进行格式化。

安装 Prettier - Code formatter插件

prettier-vscode 组件可以被安装成插件,插件的名称是 Prettier - Code formatter。

我们打开 vscode , 搜索该插件,安装即可。

如果你想控制格式打开和关闭,还可以安装 vscode-status-bar-format-toggle 插件。

配置编辑器

首先,我们需要把 Prettier 配置为 vscode 的默认格式化工具。

打开 vscode ,在 settings.json 中添加以下属性,特别注意,需要勾选

{
  ...
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

image-20241018161117734.png 然后,我们需要打开文件的保存就格式化属性。

用户(User)和工作区(Workspace)有什么区别?

用户设置:适用于该用户在所有项目中的全局默认设置,为个人偏好设置一个全局默认值。

工作区设置:仅适用于当前打开的特定项目或工作区,允许针对特定项目自定义设置,而不影响其他项目。

工作区设置优先级高于用户设置。如果工作区中定义了特定设置,它会覆盖用户的全局设置。

设置用户设置和工作区设置主要有以下原因:

  1. 灵活性:允许在全局和项目级别上进行不同的配置。
  2. 团队协作:工作区设置可以与项目一起版本控制,确保团队成员使用一致的格式化规则。
  3. 项目特殊需求:某些项目可能需要特定的格式化规则,而这些规则可能不适用于其他项目。
  4. 个人偏好与项目需求的平衡:允许开发者在保持个人偏好的同时遵守项目规范。
  5. 隔离性:防止一个项目的特殊设置影响到其他项目。

通过这种方式,VS Code 提供了更大的灵活性和精细的控制,使得配置可以在个人和项目层面上都得到优化。

最后,我们来测试一下,在 index.js 中修改相关的字符串,修改为双引号。

可以看到, 当我们保存的时候,我们双引号已经变为了单引号,格式化成功。

详情请参考:

prettier.io/docs/en/edi…

至于如何和 ESLint 和 StyleLint 联动,那我们下期再详细说。

Prettier 实践基于我们在开发 XPlaza 过程中遇到的各种疑惑和问题编写,欢迎大家说说自己是如何来使用它的。