简介:本文记录了作者如何在一天内快速开发一个Prettier插件,解决特定代码格式化需求的过程。通过实践,作者展示了插件开发的核心步骤、关键技术点及调试技巧,为开发者提供可复用的开发路径。
Prettier作为主流代码格式化工具,通过统一代码风格提升团队协作效率。然而,其内置规则无法覆盖所有场景(如特定框架的模板语法、自定义注释规范等)。当团队需要强制某些非标准格式时,扩展Prettier成为必要选择。本文以“花了一天时间”为时间约束,探讨如何在极短时间内完成一个功能完整的Prettier插件开发。
假设团队要求所有Vue单文件组件(.vue)中的<script>块必须遵循以下规则:
import和export之间插入其他代码原生Prettier无法实现此类复杂逻辑,因此需要自定义插件。
mkdir prettier-plugin-vue-script && cd prettier-plugin-vue-scriptnpm init -ynpm install prettier --save-dev
在src/index.js中实现插件核心逻辑,需导出符合Prettier规范的函数:
module.exports = {languages: [{name: "vue-script",parsers: ["vue-script-parser"],vscodeLanguageIds: ["vue"]}],parsers: {"vue-script-parser": require("./parser")},printers: {"vue-script-parser": require("./printer")}};
解析器需将Vue的<script>内容转换为Prettier可处理的AST。这里采用@babel/parser解析JavaScript代码,并添加自定义节点类型:
const parser = require("@babel/parser");const traverse = require("@babel/traverse").default;module.exports = {parse(text) {const ast = parser.parse(text, {sourceType: "module",plugins: ["jsx"]});// 添加自定义标记(示例简化)traverse(ast, {ImportDeclaration(path) {path.node.isImport = true;},ExportDefaultDeclaration(path) {path.node.isExport = true;}});return { ast, type: "vue-script" };}};
打印器根据AST生成格式化后的代码。关键逻辑包括:
\n\n顺序校验:抛出错误如果发现非法代码位置
module.exports = {print(path, options, print) {const parts = [];let hasImports = false;let hasExports = false;path.each(nodePath => {const node = nodePath.node;if (node.isImport) {if (hasExports) {parts.push("\n\n"); // 非法位置,实际应报错}parts.push(print(nodePath));hasImports = true;} else if (node.isExport) {if (hasImports && !hasExports) {parts.push("\n\n");}parts.push("\n" + print(nodePath));hasExports = true;}});return parts.join("");}};
创建测试文件test.vue:
<script>import lodash from 'lodash'; // 应为第一组import api from './api';const foo = 1; // 非法代码,应报错export default { name: 'Test' }</script>
通过prettier --plugin ./src test.vue验证输出是否符合预期。
使用Jest编写测试用例:
const prettier = require("prettier");const plugin = require("../src");test("imports grouped correctly", () => {const code = `import a from 'a';\nconst x = 1;\nimport b from 'b';`;const formatted = prettier.format(code, {parser: "vue-script",plugins: [plugin],printWidth: 80});expect(formatted).toContain("import a from 'a';\n\nimport b from 'b';");});
memoize-fs缓存解析结果README.md说明插件用途与配置方法package.json的files字段仅包含必要文件
npm loginnpm publish
prettier-plugin-starter快速搭建prettier --debug-check定位问题--timer标志分析耗时操作--fix标志)通过一天的高强度开发,我们验证了Prettier插件开发的可行性。关键在于:明确需求边界、复用现有生态、采用增量开发策略。对于需要深度定制代码风格的团队,这种轻量级插件开发模式提供了高效的解决方案。完整代码已开源至GitHub,欢迎贡献与反馈。