简介:本文记录作者如何用一天时间开发一个Prettier插件,解决特定代码格式化需求,适合对代码格式化工具开发感兴趣的开发者。
Prettier 作为目前最流行的代码格式化工具之一,凭借其“零配置”理念和开箱即用的特性,已成为前端开发者的标配工具。然而,在实际项目中,我们常常会遇到一些特殊需求:比如团队内部约定使用特定的注释格式、需要保留某些手动调整的代码结构,或者需要支持某些小众语法。这些需求往往无法通过现有的 Prettier 规则完全满足。
最近,我在一个项目中遇到了这样的场景:团队要求所有 SQL 语句必须使用特定的模板格式,而现有的 Prettier 插件要么不支持 SQL,要么无法满足我们的格式化要求。面对这个问题,我决定自己动手开发一个 Prettier 插件。令人惊喜的是,从开始构思到最终完成,我只花了一天时间。接下来,我将详细分享这个过程。
在开始开发之前,首先需要理解 Prettier 的工作原理和插件机制。Prettier 的核心思想是通过解析代码生成抽象语法树(AST),然后根据配置规则重新生成格式化后的代码。插件的作用就是扩展 Prettier 的解析和打印能力,使其能够处理更多类型的代码。
Prettier 插件主要需要实现两个部分:
Prettier 本身已经内置了对 JavaScript、TypeScript、HTML、CSS 等语言的支持,但可以通过插件机制添加对其他语言的支持,或者修改现有语言的处理方式。
为了高效开发,我首先搭建了开发环境:
npm init -y
npm install prettier @types/prettier typescript --save-dev
{"compilerOptions": {"target": "ES2020","module": "CommonJS","outDir": "./dist","esModuleInterop": true,"forceConsistentCasingInFileNames": true,"strict": true},"include": ["src/**/*"]}
Prettier 插件需要实现一个解析器函数,接收源代码和解析选项,返回 AST。对于我的 SQL 插件,我选择使用现有的 SQL 解析器(如 sql-parser)来生成 AST。
import { parse as sqlParse } from "sql-parser";const parsers = {sql: {parse(text: string, parsers: any, options: any) {try {return sqlParse(text);} catch (error) {console.error("SQL parsing error:", error);return null;}},astFormat: "sql",},};export default parsers;
打印机负责将 AST 转换回格式化后的代码。这是插件的核心部分,需要处理各种格式化细节。
const printers = {sql: {print(path: any, options: any, print: any) {const node = path.getValue();// 处理 SELECT 语句if (node.type === "select") {const columns = node.columns.map(c => print(c).parts.join("")).join(", ");const from = print(node.from).parts.join("");const where = node.where ? ` WHERE ${print(node.where).parts.join("")}` : "";return `SELECT ${columns}${from}${where}`;}// 其他 SQL 语句类型的处理...return "";}}};export default printers;
插件需要一个入口文件,将解析器和打印机组合起来,并导出必要的接口。
import parsers from "./parsers";import printers from "./printers";export default {parsers,printers,languages: [{name: "SQL",parsers: ["sql"],extensions: [".sql"],vscodeLanguageIds: ["sql"],},],};
开发过程中,测试和调试至关重要。我采用了以下方法:
单元测试:使用 Jest 编写测试用例,验证解析器和打印机的行为
import plugin from "../src";import prettier from "prettier";test("formats simple SELECT statement", async () => {const input = "SELECT id,name FROM users WHERE id=1";const formatted = await prettier.format(input, {parser: "sql",plugins: [plugin],});expect(formatted).toBe("SELECT id, name FROM users WHERE id = 1");});
手动测试:创建一个测试文件,使用 Prettier CLI 进行格式化
npx prettier --write test.sql --plugin ./dist/index.js
调试技巧:在解析器和打印机中添加 console.log,观察 AST 结构和格式化过程
在基本功能实现后,我进行了以下优化:
完成开发后,我将插件发布到 npm:
.npmignore 文件,排除不必要的文件package.json:
{"name": "prettier-plugin-sql-custom","version": "1.0.0","main": "dist/index.js","scripts": {"build": "tsc","prepare": "npm run build"}}
npm publish其他开发者可以通过以下方式使用:
npm install prettier-plugin-sql-custom --save-dev
然后在 .prettierrc 中配置:
{"plugins": ["prettier-plugin-sql-custom"],"sqlPluginOptions": {"indent": 2,"maxLineLength": 80}}
回顾这一天的开发过程,我有以下几点体会:
如果你也有开发 Prettier 插件的需求,以下建议可能对你有帮助:
这次一天开发 Prettier 插件的经历让我看到,通过合理利用现有工具和框架,开发者可以在短时间内实现有价值的功能。未来,我计划:
用一天时间开发一个 Prettier 插件,不仅解决了实际项目中的问题,也让我对代码格式化工具的内部机制有了更深入的理解。这个过程证明,只要掌握了正确的方法和工具,开发者可以在短时间内创造出有价值的成果。希望我的经验能为其他开发者提供启发和参考,鼓励更多人尝试开发自己的工具和插件。