Prettier 支持处理 Vue 文件,但大体上是将其作为含有部分特殊语法的 HTML 文件处理,这样会和 Vue Style Guide 有部分冲突,并且无法处理 Custom Blocks。为了解决这些问题,可以使用 eslint-plugin-prettier-vue,让 Prettier 更好地处理 Vue SFC。
这篇文章对 eslint-plugin-prettier-vue 的实现思路进行简单的介绍和总结。
# 概述
Prettier 1.15 开始支持处理 Vue 文件,但大体上是把 Vue 文件当作普通的 HTML 文件处理。这样存在两个问题:
- Vue Style Guide 对 SFC 中
<template>
的样式有较为详细的建议,并且通过 eslint-plugin-vue 可以直接引入。但使用 Prettier 会与eslint-plugin-vue
中的规则有一定的冲突,需要通过eslint-config-prettier/vue
关闭相关规则。 - Vue Custom Blocks 可能并不是 HTML 中支持的语言,直接使用 Prettier 是无法处理这些自定义块的样式的。
为了解决这些问题,可以使用 eslint-plugin-prettier-vue,让 Prettier 更好地处理 Vue SFC。
# 实现思路
# 将 Prettier 集成到 ESLint 工作流中
主要参考 Prettier 官方提供的 eslint-plugin-prettier
,将 Prettier 转化为 ESLint 中的一条 rule:
- 在
meta
中设置支持的 Prettier 等相关选项 - 在
create
方法中实现对 Prettier 的集成
这一部分主要是关于 ESLint Plugin 的写法,以及对 eslint-plugin-prettier
的一些重构调整,不再多加赘述。
# 将不同 block 分开处理
我们知道,在通过 webpack
处理 Vue SFC 时,vue-loader 会将 <template>
, <script>
, <style>
, <custom-block>
分开处理,底层实际上是使用了 @vue/component-compiler-utils 和 vue-template-compiler 的能力。
通过 @vue/component-compiler-utils
的 parse()
方法,传入文件内容和文件名,就可以得到包含各个 SFC Blocks 详细描述的对象。将这些 SFC Blocks 当作不同格式的文件传给 Prettier ,即可实现各个 block 分开处理的能力,也能支持 Custom Blocks 的处理了。
parse()
方法所需要的内容均可以在 create()
方法的第一个参数 context
中获取到,熟悉 ESLint 相关 API 即可实现。
# 一些需要注意的点
# 如何处理代码的位置
parse()
方法返回的 SFC Blocks 中,有 block 在原始文件的start
和end
parse()
方法返回的 SFC Blocks 中,content
会默认填充空行和消除缩进,即compilerParseOptions: { pad: 'line', deindent: true }
,这是为了方便 vue-loader 定位原始代码位置,但在 Prettier 中是应该去掉的,否则会影响 Prettier 对格式的处理和代码定位。因此需要设置为compilerParseOptions: { pad: false, deindent: false }
。这两个配置项的使用方法在对应文档里基本都没有,需要到源码中才能找到
有了这些信息,配合 prettier-linter-helpers
提供的 offset
,就可以给 ESLint 反馈正确的代码位置了。
# @vue/component-compiler-utils 的缓存问题
@vue/component-compiler-utils
根据文件内容 filename
和文件内容 source
对 vue 文件进行了缓存,如果缓存命中则会直接返回之前的处理结果 (对应源码)。
由于缓存没有对 compilerParseOptions
进行区分,在 webpack + vue-loader + eslint-loader 的情况下会有问题:
- vue-loader 调用
@vue/component-compiler-utils
时,使用了compilerParseOptions: { pad: 'line' }
- eslint-loader (即
eslint-plugin-prettier-vue
) 调用@vue/component-compiler-utils
时,使用了compilerParseOptions: { pad: false, deindent: false }
- 此时由于 eslint 中的调用同样命中了缓存,
compilerParseOptions: { pad: false, deindent: false }
就会失效,prettier 中就会得到填充空行和消除缩进的代码导致样式处理出错。
为了应对这种情况,可以:
- 给传入
@vue/component-compiler-utils
的filename
加上前缀 / 后缀用以区分 - 向
@vue/component-compiler-utils
提 PR,将compilerParseOptions
也加入缓存 key
由于 @vue/component-compiler-utils
目前并不在积极维护(都在努力搞 vue 3.0 吧),并且该项目基本只是为 vue-loader 服务的,所以目前简单用第一种方式解决。
# 存在的不足
# 整个文件级别的样式处理问题
由于我们将整个 .vue
文件根据 block 拆分为了几个不同格式的小文件交给 prettier 处理,那么 prettier 就无法感知 block 之外内容的格式问题:
- block 之间有多个空行 / 没有空行
- 文件末尾有多个空行 / 没有空行
这些样式问题就无法被 prettier 检测到了。