vscode + vetur + eslint + prettier 实现团队代码风格统一

vscode + vetur + eslint + prettier 实现团队代码风格统一

如果你也是 front-end coder,也采用 ES6、vue 单文件、css 预编译,希望实现团队代码格式/风格统一,可参考本文。

值得参考并执行的规范

落地方案

  • IDE: vs code
    • ESLint // ide lint 报错/warning 要靠它
    • Prettier - Code formatter // 格式化 html/css/less/scss etc…
    • Vetur // 这里只用来语法提示,代码格式化交给 eslint(npm)
  • npm:
    • eslint
    • eslint-config-airbnb-base
    • eslint-config-prettier
    • eslint-plugin-import
    • eslint-plugin-vue
    • eslint-plugin-prettier
    • prettier
  • 关键配置文件(in git repo)
    • .vscode/setting.json // ESLint(ide 插件),vetur(ide 插件)配置在这里
    • .editorconfig // 字符集配置,缩进等配置
    • .eslintrc.js // eslint(npm)配置,ide eroor/warning 信息在这里配置
    • .eslintignore // eslint 排除文件,如一些库文件
    • .prettierrc.js // prettier 配置,如逗号分号规则
    • .prettierignore //prettier 排除文件

简单说下思路

  • eslint 优先级最高,装插件eslint-config-airbnb-baseeslint-config-prettiereslint-plugin-vue,可以覆盖 prettier 的设置
  • eslint 主要负责 vue/js 的格式化
  • prettier 主要负责 html/css/less/scss… 的格式化
  • Vetur 也有格式化功能,直接 disable 掉,我们用更高级的
  • prettier 不支持 stylus,但是 Vetur 的依赖包括了stylusSupremacy,可以解决
  • 所有的配置都可以放在项目 repo 里,且优先级会高于 coder 本地的设置,保证团队所有成员提交的代码一致
  • 如果装了其他 format 插件,请在本项目中 disable 掉,我们不需要

附:配置文件

EditorConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
root = true # 表明是最顶层的配置文件,发现设为true时,才会停止查找.editorconfig文件。

[*]
charset = utf-8 # 编码格式,支持latin1、utf-8、utf-8-bom、utf-16be和utf-16le,不建议使用uft-8-bom。
indent_style = space # tab为hard-tabs,space为soft-tabs。
indent_size = 2 # 设置整数表示规定每级缩进的列数和soft-tabs的宽度(译注:空格数)。如果设定为tab,则会使用tab_width的值(如果已指定)。
end_of_line = lf # 定义换行符,支持lf、cr和crlf。
insert_final_newline = # 设为true表明使文件以一个空白行结尾,false反之。
trim_trailing_whitespace = true # 设为true表示会除去换行行首的任意空白字符,false反之。

[*.md]
insert_final_newline = false # 设为true表明使文件以一个空白行结尾,false反之。
trim_trailing_whitespace = false # 设为true表示会除去换行行首的任意空白字符,false反之。

.eslintrc.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
*
* 规则说明见 https://cn.eslint.org/docs/rules/
* eslint-plugin-import 规则见 https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/default.md
* eslint-plugin-vue 规则见 https://github.com/vuejs/eslint-plugin-vue
*
* "off" 或 0 - 关闭规则
* "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
* "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
*
*/

module.exports = {
extends: [
// add more generic rulesets here, such as:
// 'eslint:recommended',
"airbnb-base", // airbnb lint规范
"plugin:vue/essential", // eslint-plugin-vue
"plugin:prettier/recommended" // eslint-config-prettier
],
// settings: {
// 'import/resolver': {
// webpack: {
// config: 'build/webpack.base.conf.js',
// },
// },
// },
// plugins: ['vue'],
rules: {
"prettier/prettier": 1,
"no-undef": 2, // 禁用未声明的变量,除非它们在 /*global */ 注释中被提到. e.g. /* global Stomp SockJS */ in .vue file's <scrpit>
"no-extend-native": 2, // 禁止扩展原生类型
"no-return-assign": 2, // 禁止在 return 语句中使用赋值语句
"import/order": 0, // Enforce a convention in module import order
"import/no-extraneous-dependencies": 0, // 禁止导入未在package.json的依赖项,devDependencies,optionalDependencies或peerDependencies中声明的外部模块。将使用最接近的父package.json。
"import/no-dynamic-require": 1, // CommonJS的require方法用于从不同文件导入模块。与ES6导入语法不同,可以为其提供将在运行时解析的表达式。虽然这有时是必要且有用的,但在大多数情况下并非如此。使用表达式(例如,连接路径和变量)作为参数使得工具更难以进行静态代码分析,或者在代码库中找到使用模块的位置。
"import/extensions": 0, // 某些文件解析算法允许您在导入源路径中省略文件扩展名。例如,节点解析器可以将./foo/bar解析为绝对路径/User/someone/foo/bar.js,因为默认情况下会自动解析.js扩展名。根据解析程序,您可以配置更多扩展程序以自动解决。
"import/no-unresolved": 0, // 确保导入的模块可以解析为本地文件系统上的模块,如标准Node require.resolve行为所定义。
"import/prefer-default-export": 1, // 当模块中只有一个导出时,更喜欢使用默认导出而不是命名导出。
"vue/no-async-in-computed-properties": 1, // 计算属性应该是同步的。其中的异步操作可能无法按预期工作,并可能导致意外行为,这就是您应该避免它们的原因。如果您需要异步计算属性,可能需要考虑使用其他插件[vue-async-computed]
"vue/no-side-effects-in-computed-properties": 1, // 在计算属性中引入副作用被认为是一种非常糟糕的做法。它使代码无法预测且难以理解。
"vue/no-reserved-keys": 1, // 此规则阻止使用保留名称以避免冲突和意外行为。
"vue/require-v-for-key": 2, // 当v-for写在自定义组件上时,它需要同时使用v-bind:key。在其他元素上,v-bind:key也最好写。
"vue/require-valid-default-prop": 1, // 此规则检查每个prop的默认值是否对给定类型有效。当使用函数未返回类型Array或Object的默认值时,它应报告错误。
"no-unused-vars": 1, // 禁止出现未使用过的变量
"generator-star-spacing": 0, // 强制 generator 函数中 * 号周围使用一致的空格
"no-plusplus": 0, // 禁用一元操作符 ++ 和 --
"func-names": 0, // 要求或禁止使用命名的 function 表达式
"no-console": 0, // no-console
radix: 0, // 强制在parseInt()使用基数参数
"no-control-regex": 0, // 禁止在正则表达式中使用控制字符
"no-continue": 0, // 禁用 continue 语句
"no-debugger": process.env.NODE_ENV === "production" ? 2 : 0,
"no-param-reassign": 1, // 禁止对 function 的参数进行重新赋值
"no-underscore-dangle": 1, // 禁止标识符中有悬空下划线"_"
"global-require": 1, // 要求 require() 出现在顶层模块作用域中
"no-var": 1, // 要求使用 let 或 const 而不是 var
"vars-on-top": 1, // 要求所有的 var 声明出现在它们所在的作用域顶部
"prefer-destructuring": 1, // 优先使用数组和对象解构
"no-useless-concat": 1, // 禁止不必要的字符串字面量或模板字面量的连接
"no-shadow": 1, // 禁止变量声明与外层作用域的变量同名
"guard-for-in": 1, // 要求 for-in 循环中有一个 if 语句,旨在防止使用for in循环而不过滤循环中的结果时可能出现的意外行为。
"no-restricted-syntax": 1, // 禁用特定的语法
"global-require": 1, // 要求 require() 出现在顶层模块作用域中
"consistent-return": 1, // 要求 return 语句要么总是指定返回的值,要么不指定
eqeqeq: 1, // 要求使用 === 和 !==
"no-unused-expressions": 1, // 禁止出现未使用过的表达式
camelcase: 1, // 强制使用骆驼拼写法命名约定
"block-scoped-var": 1, // 强制把变量的使用限制在其定义的作用域范围内,旨在减少绑定上下文之外的变量的使用,并从其他语言模拟传统的块范围。这是为了帮助语言新手避免变量提升的难题。
"no-redeclare": 1, // 禁止多次声明同一变量
"prefer-arrow-callback": 1, // 要求回调函数使用箭头函数
"array-callback-return": 1, // 强制数组方法的回调函数中有 return 语句,Array有几种过滤,映射和折叠的方法。如果我们忘记return在这些回调中写入语句,那可能是一个错误。
"default-case": 1, // 要求 switch 语句中有 default 分支
"no-loop-func": 1, // 禁止在循环中出现 function 声明和表达式
"no-fallthrough": 1, // 禁止 case 语句落空
"no-multi-assign": 1, // 禁止连续赋值
"no-lonely-if": 1, // 禁止 if 作为唯一的语句出现在 else 语句中.如果一个if陈述是该else块中唯一的陈述,那么使用一个else if表格通常会更清晰。
"no-irregular-whitespace": 1, // 禁止在字符串和注释之外不规则的空白
"prefer-const": 1, // 要求使用 const 声明那些声明后不再被修改的变量.如果一个变量从不重新分配,使用const声明更好。const 声明告诉读者,“这个变量永远不会被重新分配,”减少认知负荷并提高可维护性。
"no-use-before-define": 1, // 禁止在变量定义之前使用它们
"no-useless-escape": 1, // 禁用不必要的转义字符
"no-array-constructor": 1, // 禁用 Array 构造函数,由于单参数的缺陷以及Array全局可能被重新定义,所以通常不鼓励使用构造函数来构造新Array数组,而是倾向于使用数组文字符号。例外情况是Array构造函数用于通过给构造函数一个数字参数有意创建指定大小的稀疏数组。
"object-shorthand": 1, // 要求或禁止对象字面量中方法和属性使用简写语法
"no-prototype-builtins": 1, // 禁止直接调用 Object.prototypes 的内置属性.当假定对象将具有属性时,这可能导致错误Object.prototype。此规则可防止Object.prototype直接从对象调用方法。
"no-nested-ternary": 1, // 禁用嵌套的三元表达式.嵌套三元表达式会使代码更难理解。
"no-new-wrappers": 1, // 禁止对 String,Number 和 Boolean 使用 new 操作符.没有任何理由将这些基本包装用作构造函数
"prefer-promise-reject-errors": 1, // 要求使用 Error 对象作为 Promise 拒绝的原因
"no-labels": 1 // 禁用标签语句
}
};

.prettierrc

1
2
3
4
5
6
7
8
9
10
module.exports = {
printWidth: 80, //一行的字符数,如果超过会进行换行,默认为80
tabWidth: 2, //一个tab代表几个空格数,默认为80
useTabs: false, //是否使用tab进行缩进,默认为false,表示用空格进行缩减
singleQuote: true, //字符串是否使用单引号,默认为false,使用双引号
semi: true, //行位是否使用分号,默认为true
trailingComma: "es5", //是否使用尾逗号,有三个可选值"<none|es5|all>"
bracketSpacing: true //对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
// parser: 'babylon', //代码的解析引擎,默认为babylon,与babel相同。
};

.vscode/setting.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
// 使用2个空格作为tab,并将对齐基准线设置为2个字符test
"editor.tabSize": 2,
// 保存文件时自动格式化,主要针对html/css/less/scss等eslint未覆盖的文件格式
// "editor.formatOnSave": true,
// 保存文件时按照eslint配置自动处理vue/js/jsx/ts/json文件格式
// "eslint.autoFixOnSave": true,
// 打开对vue的lint,并自动fix
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "vue",
"autoFix": true
}
],
// 对于.vue文件,关闭prettier,交给eslint fix
"vetur.format.defaultFormatter.css": "none",
"vetur.format.defaultFormatter.html": "none",
"vetur.format.defaultFormatter.js": "none",
"vetur.format.defaultFormatter.less": "none",
"vetur.format.defaultFormatter.postcss": "none",
"vetur.format.defaultFormatter.scss": "none",
"vetur.format.defaultFormatter.stylus": "stylus-supremacy",
"vetur.format.defaultFormatter.ts": "none",

/* stylus 配置 start */
// 是否插入冒号
"stylusSupremacy.insertColons": false,
// 是否插入分号
"stylusSupremacy.insertSemicolons": false,
// 是否插入大括号
"stylusSupremacy.insertBraces": false,
// import之后是否换行
"stylusSupremacy.insertNewLineAroundImports": false,
// 两个选择器中是否换行
"stylusSupremacy.insertNewLineAroundBlocks": false
/* stylus 配置 end */
}

后记

  • 代码格式化相关的文章已经汗牛充栋,关键点不在于各种圣战,而在于团队风格统一
  • vue 格式文件格式化比较特殊,template/script/style 部分由不同的插件来完成
  • 通过统一 IDE/插件/配置文件实现:
    • 自动格式化前端开发常见文件,实现团队代码风格统一
    • 杜绝因格式问题导致的提交冲突
    • 统一配置,团队共享,实现同步
  • 搞清楚各种 linter/formater/IDE 插件/npm 插件的分工/优先级很重要
  • ts 解决方案/git commit lint/eslint webpack loader 应该是更好也是更严格的方案,目前暂未涉及,将会是以后努力的方向

欢迎拍砖,欢迎交流

本文完