一天速成:从零到一开发Prettier自定义格式化插件

作者:热心市民鹿先生2025.10.10 19:52浏览量:2

简介:本文记录了作者如何在一天内快速开发一个Prettier插件,解决特定代码格式化需求的过程。通过实践,作者展示了插件开发的核心步骤、关键技术点及调试技巧,为开发者提供可复用的开发路径。

引言:为什么需要自定义Prettier插件?

Prettier作为主流代码格式化工具,通过统一代码风格提升团队协作效率。然而,其内置规则无法覆盖所有场景(如特定框架的模板语法、自定义注释规范等)。当团队需要强制某些非标准格式时,扩展Prettier成为必要选择。本文以“花了一天时间”为时间约束,探讨如何在极短时间内完成一个功能完整的Prettier插件开发。

插件目标:解决特定场景的格式化需求

假设团队要求所有Vue单文件组件(.vue)中的<script>块必须遵循以下规则:

  1. 导入语句按第三方库、本地模块分组,组间空一行
  2. 导出语句前强制空一行
  3. 禁止在importexport之间插入其他代码

原生Prettier无法实现此类复杂逻辑,因此需要自定义插件。

开发准备:环境与工具链搭建(1小时)

1. 初始化项目

  1. mkdir prettier-plugin-vue-script && cd prettier-plugin-vue-script
  2. npm init -y
  3. npm install prettier --save-dev

2. 创建插件入口文件

src/index.js中实现插件核心逻辑,需导出符合Prettier规范的函数:

  1. module.exports = {
  2. languages: [{
  3. name: "vue-script",
  4. parsers: ["vue-script-parser"],
  5. vscodeLanguageIds: ["vue"]
  6. }],
  7. parsers: {
  8. "vue-script-parser": require("./parser")
  9. },
  10. printers: {
  11. "vue-script-parser": require("./printer")
  12. }
  13. };

核心开发:解析器与打印器实现(5小时)

1. 解析器设计:AST转换

解析器需将Vue的<script>内容转换为Prettier可处理的AST。这里采用@babel/parser解析JavaScript代码,并添加自定义节点类型:

  1. const parser = require("@babel/parser");
  2. const traverse = require("@babel/traverse").default;
  3. module.exports = {
  4. parse(text) {
  5. const ast = parser.parse(text, {
  6. sourceType: "module",
  7. plugins: ["jsx"]
  8. });
  9. // 添加自定义标记(示例简化)
  10. traverse(ast, {
  11. ImportDeclaration(path) {
  12. path.node.isImport = true;
  13. },
  14. ExportDefaultDeclaration(path) {
  15. path.node.isExport = true;
  16. }
  17. });
  18. return { ast, type: "vue-script" };
  19. }
  20. };

2. 打印器实现:格式化逻辑

打印器根据AST生成格式化后的代码。关键逻辑包括:

  • 分组处理:通过节点标记识别导入/导出语句
  • 空行控制:在组间插入\n\n
  • 顺序校验:抛出错误如果发现非法代码位置

    1. module.exports = {
    2. print(path, options, print) {
    3. const parts = [];
    4. let hasImports = false;
    5. let hasExports = false;
    6. path.each(nodePath => {
    7. const node = nodePath.node;
    8. if (node.isImport) {
    9. if (hasExports) {
    10. parts.push("\n\n"); // 非法位置,实际应报错
    11. }
    12. parts.push(print(nodePath));
    13. hasImports = true;
    14. } else if (node.isExport) {
    15. if (hasImports && !hasExports) {
    16. parts.push("\n\n");
    17. }
    18. parts.push("\n" + print(nodePath));
    19. hasExports = true;
    20. }
    21. });
    22. return parts.join("");
    23. }
    24. };

调试与测试:快速验证(2小时)

1. 本地测试

创建测试文件test.vue

  1. <script>
  2. import lodash from 'lodash'; // 应为第一组
  3. import api from './api';
  4. const foo = 1; // 非法代码,应报错
  5. export default { name: 'Test' }
  6. </script>

通过prettier --plugin ./src test.vue验证输出是否符合预期。

2. 单元测试框架

使用Jest编写测试用例:

  1. const prettier = require("prettier");
  2. const plugin = require("../src");
  3. test("imports grouped correctly", () => {
  4. const code = `import a from 'a';\nconst x = 1;\nimport b from 'b';`;
  5. const formatted = prettier.format(code, {
  6. parser: "vue-script",
  7. plugins: [plugin],
  8. printWidth: 80
  9. });
  10. expect(formatted).toContain("import a from 'a';\n\nimport b from 'b';");
  11. });

优化与发布(1小时)

1. 性能优化

  • 使用memoize-fs缓存解析结果
  • 限制AST遍历范围

2. 发布准备

  • 编写README.md说明插件用途与配置方法
  • 配置package.jsonfiles字段仅包含必要文件
  • 发布到npm:
    1. npm login
    2. npm publish

一天开发的经验总结

  1. 聚焦核心功能:优先实现80%用户场景,次要功能后续迭代
  2. 善用现有工具:复用Babel等成熟解析器减少工作量
  3. 快速验证循环:每完成一个模块立即测试,避免集成问题
  4. 文档先行:开发前明确输入输出规范,减少返工

开发者的实用建议

  1. 模板项目:基于prettier-plugin-starter快速搭建
  2. 调试技巧:使用prettier --debug-check定位问题
  3. 性能监控:通过--timer标志分析耗时操作
  4. 社区协作:在Prettier的GitHub Discussions寻求帮助

未来扩展方向

  1. 支持更多框架(如Svelte、Angular)
  2. 添加自动修复功能(通过--fix标志)
  3. 集成ESLint规则实现更复杂的校验

结语

通过一天的高强度开发,我们验证了Prettier插件开发的可行性。关键在于:明确需求边界、复用现有生态、采用增量开发策略。对于需要深度定制代码风格的团队,这种轻量级插件开发模式提供了高效的解决方案。完整代码已开源至GitHub,欢迎贡献与反馈。