В процессе работы над проектами разработчики придерживаются определенного кодстайла. Как правило, за это отвечает ESLint. ESLint — это линтер для языка программирования JavaScript. Он статически анализирует код на наличие проблем, многие из которых можно исправить автоматически.
Как показывает практика, команды в проектах часто пренебрегают кастомной настройкой ESLint, оставляя дефолтную. В этом случае большая часть кодстайла остается на совести разработчика. Кодстайл, как правило, в таких проектах нигде не описан или существует в формате устной договоренности. При таком подходе большую часть правил приходится держать в уме, не говоря уже о том, что многие из них основаны на субъективных предпочтениях. Нередки случаи, когда разные части приложения отформатированы под разные правила. Например, если разработчики пишут код в разных операционных системах, то переносы строк у них отличаются. Правил так много, а настройки столь обширны, что использование разных редакторов кода в командной разработке может усложнить взаимодействие.
В этой статье рассмотрим пример настройки ESLint для разработки приложений на Vue. В итоге мы получим настройки ESLint, которые будут проверять наш код на соответствие большинству правил официального стайлгайда Vue. Материал полезен начинающим разработчикам, которые хотят улучшить свой стиль кода, и более опытным на старте нового проекта в незнакомой или большой распределенной команде. Эти настройки помогут придерживаться кодстайла и отслеживать некоторые ошибки (синтаксические, логические, ошибки, связанные с динамической типизацией) еще на этапе написания кода, повысят его читаемость и упростят код-ревью. В конечном итоге это приведет к сокращению сроков разработки.
Все форматирование кода будет осуществляться с помощью ESLint, поэтому если у вас установлен prettier или Vetur, желательно их отключить. ESLint необходимо установить в extensions вашей IDE, чтобы пользоваться его функционалом.
В конце статьи приведем пример файла .eslintrc.js с настройками.
Начало работы
Создадим новое приложение. В настройках при установке выберем ESLint + Airbnb config.
Файл .eslintrc.js должен выглядеть так:
В качестве базовых настроек используем конфигурацию @vue/airbnb, plugin:vue/essential заменим на plugin:vue/strongly-recommended (или plugin:vue/vue3-strongly-recommended, plugin:vue/vue3-essential, plugin:vue/vue3-recommended для vue 3) и дополним кастомными настройками. Если вы еще не знакомы с отличиями настроек essential от strongly-recommended, то рекомендуем изучить их здесь.
Далее в объект rules будем добавлять правила, которые хотим использовать.
Правила для секции <template>
#vue/attributes-order Проверка порядка атрибутов:
<template>
<!-- ✓ GOOD -->
<div
is="header"
v-for="item in items"
v-if="!visible"
v-once
id="uniqueID"
ref="header"
v-model="headerData"
my-prop="prop"
@click="functionCall"
v-text="textContent">
</div>
<!-- ✗ BAD -->
<div
ref="header"
my-prop="prop"
v-for="item in items"
v-once
@click="functionCall"
id="uniqueID"
v-model="headerData"
v-if="!visible"
is="header"
v-text="textContent">
</div>
</template>
Пример настройки:
"vue/attributes-order": ["error", {
"order": [
"DEFINITION",
"LIST_RENDERING",
"CONDITIONALS",
"RENDER_MODIFIERS",
"GLOBAL",
["UNIQUE", "SLOT"],
"TWO_WAY_BINDING",
"OTHER_DIRECTIVES",
"OTHER_ATTR",
"EVENTS",
"CONTENT"
],
"alphabetical": false
}],
Это правило упорядочивания атрибутов компонентов. Порядок по умолчанию указан в руководстве по стилю Vue.js. и определен:
DEFINITION e.g. 'is', 'v-is'
LIST_RENDERING e.g. 'v-for item in items'
CONDITIONALS e.g. 'v-if', 'v-else-if', 'v-else', 'v-show', 'v-cloak'
RENDER_MODIFIERS e.g. 'v-once', 'v-pre'
GLOBAL e.g. 'id'
UNIQUE e.g. 'ref', 'key'
SLOT e.g. 'v-slot', 'slot'
TWO_WAY_BINDING e.g. 'v-model'
OTHER_DIRECTIVES e.g. 'v-custom-directive'
OTHER_ATTR e.g. 'custom-prop="foo"', 'v-bind:prop="foo"', ':prop="foo"'
EVENTS e.g. '@click="functionCall"', 'v-on="event"'
CONTENT e.g. 'v-text', 'v-html'
#vue/max-attributes-per-line Проверка на максимальное количество атрибутов в строке:
<template>
<!-- ✓ GOOD -->
<MyComponent lorem="1"/>
<MyComponent
lorem="1"
ipsum="2"
/>
<!-- ✗ BAD -->
<MyComponent lorem="1" ipsum="2"/>
<MyComponent
lorem="1" ipsum="2"
/>
<MyComponent
lorem="1" ipsum="2"
dolor="3"
/>
</template>
Пример настройки:
"vue/max-attributes-per-line": ["error", {
"singleline": {
"max": 1
},
"multiline": {
"max": 1
}
}]
Значения singleline.max (number) и multiline.max (number) установим в значение 1, чтобы каждый атрибут начинался с новой строчки.
#vue/html-self-closing Проверка на самозакрывающийся тег или компонент:
<template>
<!-- ✓ GOOD -->
<div/>
<img>
<MyComponent/>
<svg><path d=""/></svg>
<!-- ✗ BAD -->
<div></div>
<img/>
<MyComponent></MyComponent>
<svg><path d=""></path></svg>
</template>
"vue/html-self-closing": ["error", {
"html": {
"void": "never",
"normal": "always",
"component": "always"
},
"svg": "always",
"math": "always"
}]
html.void ("never" по умолчанию) — стиль хорошо известных пустых элементов HTML.
html.normal ("always" по умолчанию) — стиль известных элементов HTML за исключением пустых элементов.
html.component ("always" по умолчанию) — стиль пользовательских компонентов Vue.js.
svg ("always" по умолчанию) — стиль известных элементов SVG.
math ("always" по умолчанию) — стиль известных элементов MathML.
Каждый параметр может быть установлен в одно из следующих значений:
"always" — требовать самозакрытия элементов, у которых нет своего содержимого.
"never" — запретить самозакрытие.
"any" — не применять самозакрывающийся стиль.
#vue/html-indent Проверка последовательного отступа в шаблоне <template>:
<template>
<!-- ✓ GOOD -->
<div class="foo">
Hello.
</div>
<div class="foo">
Hello.
</div>
<div class="foo"
:foo="bar"
>
World.
</div>
<div
id="a"
class="b"
:other-attr="{
aaa: 1,
bbb: 2
}"
@other-attr2="
foo();
bar();
"
>
{{
displayMessage
}}
</div>
<!-- ✗ BAD -->
<div class="foo">
Hello.
</div>
</template>
Пример настройки:
'vue/html-indent': [
'error',
4,
{
attribute: 1,
baseIndent: 1,
closeBracket: 0,
alignAttributesVertically: true,
ignores: []
}
],
type (number | "tab") — тип отступа. Значение по умолчанию 2. Если это число, то это количество пробелов для одного отступа. Если это "tab", он использует одну вкладку для одного отступа.
attribute (integer) — множитель отступа для атрибутов. Значение по умолчанию 1.
baseIndent (integer) — множитель отступа для операторов верхнего уровня. Значение по умолчанию 1.
-
closeBracket (integer | object) — множитель отступа для правых скобок. Значение по умолчанию 0.
Вы можете применить все нижеперечисленное, установив числовое значение.closeBracket.startTag (integer) — множитель отступа для правых скобок открывающих тегов (<div>). Значение по умолчанию 0.
closeBracket.endTag (integer) — множитель отступа для правых скобок закрывающих тегов (</div>). Значение по умолчанию 0.
closeBracket.selfClosingTag (integer) — множитель отступа для правых скобок открывающих тегов (<div/>). Значение по умолчанию 0.
alignAttributesVertically (boolean) — условие того, должны ли атрибуты выравниваться по вертикали с первым атрибутом в многострочном случае или нет. По умолчанию true.
ignores (string[]) — селектор для игнорирования узлов. Со спецификацией AST можно ознакомиться здесь.
#vue/component-name-in-template-casing Проверка регистра для стиля именования компонентов в шаблоне:
<template>
<!-- ✓ GOOD -->
<cool-component />
<!-- ✗ BAD -->
<CoolComponent />
<coolComponent />
<Cool-component />
<!-- ignore -->
<unregistered-component />
<UnregisteredComponent />
</template>
<script>
export default {
components: {
CoolComponent
}
}
</script>
Пример настройки:
"vue/component-name-in-template-casing": ["error", "kebab-case", {
"registeredComponentsOnly": true,
}],
"PascalCase" (по умолчанию) — требует написание имен тегов в регистре паскаля. Например, <CoolComponent>. Это соответствует практике JSX.
"kebab-case" — требует написание имен тегов в регистре кебаба: например, <cool-component>. Это согласуется с практикой HTML, которая изначально нечувствительна к регистру.
registeredComponentsOnly — если true, проверяются только зарегистрированные компоненты (в PascalCase). если false, проверьте все. По умолчанию true.
ignores (string[]) — имена элементов, которые следует игнорировать. Устанавливает разрешающее имя элемента. Например, пользовательские элементы или компоненты Vue со специальным именем. Вы можете установить регулярное выражение, написав его как "/^name/".
#vue/no-irregular-whitespace Проверка нерегулярных пробелов:
<template>
<!-- ✓ GOOD -->
<div class="foo bar" />
<!-- ✗ BAD -->
<div class="foo
bar" />
<!-- ^ LINE TABULATION (U+000B) -->
</template>
<script>
/* ✓ GOOD */
var foo = bar;
/* ✗ BAD */
var foo =
bar;
// ^ LINE TABULATION (U+000B)
</script>
Пример настройки:
"vue/no-irregular-whitespace": ["error", {
"skipStrings": true,
"skipComments": false,
"skipRegExps": false,
"skipTemplates": false,
"skipHTMLAttributeValues": false,
"skipHTMLTextContents": false
}],
skipStrings: true — разрешает любые пробельные символы в строковых литералах. По умолчанию true.
skipComments: true — разрешает любые пробельные символы в комментариях. По умолчанию false.
skipRegExps: true — разрешает любые пробельные символы в литералах регулярных выражений. По умолчанию false.
skipTemplates: true — разрешает любые пробельные символы в литералах шаблона. По умолчанию false.
skipHTMLAttributeValues: true — разрешает любые пробельные символы в значениях атрибутов HTML. По умолчанию false.
skipHTMLTextContents: true — разрешает любые пробельные символы в текстовом содержимом HTML. По умолчанию false.
Правила для секции <script>
#vue/component-definition-name-casing Проверка на определенный регистр для имени компонента:
<script>
export default {
/* ✓ GOOD */
name: 'MyComponent'
/* ✗ BAD */
name: 'my-component'
}
</script>
Пример настройки:
"vue/component-definition-name-casing": ["error", "PascalCase"],
"PascalCase" (по умолчанию) — требует преобразования имен компонентов к регистру паскаля.
"kebab-case" — требует преобразования имен компонентов к регистру kebab.
#vue/match-component-file-name Проверка имени компонента — оно должно соответствовать имени файла, в котором он находится:
// file name: src/MyComponent.vue
export default {
/* ✓ GOOD */
name: 'MyComponent',
render() {
return <h1>Hello world</h1>
}
}
// file name: src/MyComponent.vue
export default {
/* ✓ GOOD */
name: 'my-component',
render() { return <div /> }
}
// file name: src/MyComponent.vue
export default {
/* ✗ BAD */
name: 'MComponent', // пропущена буква y
render() {
return <h1>Hello world</h1>
}
}
Пример настройки:
"vue/match-component-file-name": ["error", {
"extensions": ["vue"],
"shouldMatchCase": false
}],
"extensions": [] — массив расширений файлов для проверки. По умолчанию установлено значение ["jsx"].
"shouldMatchCase": false — логическое значение, указывающее, должно ли имя компонента также соответствовать регистру имени файла. По умолчанию установлено значение false.
#vue/no-dupe-keys Запретить дублирование имен полей:
<script>
/* ✗ BAD */
export default {
props: {
foo: String
},
computed: {
foo: { // дубликат свойства
get () {}
}
},
data: {
foo: null // дубликат свойства
},
methods: {
foo () {} // дубликат свойства
}
}
</script>
Пример настройки:
"vue/no-dupe-keys": ["error", {
"groups": []
}]
"groups" (string[]) — массив дополнительных групп для поиска дубликатов. По умолчанию пусто.
#vue/order-in-components Порядок свойств в компонентах:
<script>
/* ✗ BAD */
export default {
name: 'app',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
props: {// неправильный порядок свойств props перед data
propA: Number
},
methods: { // неправильный порядок свойств после computed
add() {}
},
computed: { // неправильный порядок свойств перед methods
foo () {}
}
}
</script>
'vue/order-in-components': ['error', {
order: [
'el',
'name',
'key',
'parent',
'functional',
['delimiters', 'comments'],
['components', 'directives', 'filters'],
'extends',
'mixins',
['provide', 'inject'],
'ROUTER_GUARDS',
'layout',
'middleware',
'validate',
'scrollToTop',
'transition',
'loading',
'inheritAttrs',
'model',
['props', 'propsData'],
'emits',
'setup',
'asyncData',
'data',
'fetch',
'head',
'computed',
'watch',
'watchQuery',
'LIFECYCLE_HOOKS',
'methods',
['template', 'render'],
'renderError'
]
}],
#comma-dangle Проверка запятых:
arrays для литералов массива и шаблонов деструктуризации массива.
например, let [a,] = [1,];objects для объектных литералов и объектных шаблонов деструктуризации.
например, let {a,} = {a: 1};imports предназначен для деклараций импорта модулей ES.
например, import {a,} from "foo";exports для экспортных деклараций модулей ES.
например, export {a,};functions предназначен для объявлений функций и вызовов функций.
например, (function(a,){ })(b,);functions следует включать только при анализе ECMAScript 2017 или более поздней версии.
/* ✗ BAD */
var foo = {
bar: "baz",
qux: "quux",
};
var arr = [1,2,];
foo({
bar: "baz",
qux: "quux",
});
/* ✓ GOOD */
var foo = {
bar: "baz",
qux: "quux"
};
var arr = [1,2];
foo({
bar: "baz",
qux: "quux"
});
Пример настройки:
"comma-dangle": ["error", {
"arrays": "never",
"objects": "never",
"imports": "never",
"exports": "never",
"functions": "never"
}]
"never" (по умолчанию) запрещает запятые в конце.
"always" требует наличие запятых в конце.
"always-multiline" требует замыкающих запятых, когда последний элемент или свойство находятся в другой строке, а закрывающий “]” или “}” на следующей и запрещает замыкающие запятые, когда последний элемент или свойство находится в той же строке, что и закрывающий “]” или “}”.
"only-multiline" разрешает (но не требует) замыкающие запятые, когда последний элемент или свойство находятся в иной строке, чем закрывающий “]” или “}”, и запрещает замыкающие запятые, когда последний элемент или свойство находятся на той же строке, что и закрывающий “]” или “}”.
Вы также можете использовать параметр объекта, чтобы настроить это правило для каждого типа синтаксиса. Для каждого из следующих параметров можно установить значения "never", "always", "always-multiline", "only-multiline" или "ignore". Значение по умолчанию для каждого параметра равно "never", если не указано иное.
Общие настройки ESLint
'linebreak-style': ["error", "unix"], //стиль разрыва строки linebreak-style: ["error", "unix || windows"]
'no-console': 'error', // без console.log
'no-debugger': 'error',// без debugger
'arrow-parens': ['error', 'as-needed'], // скобки в стрелочной функции
'no-plusplus': 'off', //запрещает унарные операторы ++и --
'constructor-super': 'off', // конструкторы производных классов должны вызывать super(). Конструкторы не производных классов не должны вызывать super().
"no-mixed-operators": [ //Заключение сложных выражений в круглые скобки проясняет замысел разработчика
"error",
{
"groups": [
["+", "-", "*", "/", "%", "**"],
["&", "|", "^", "~", "<<", ">>", ">>>"],
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["&&", "||"],
["in", "instanceof"]
],
"allowSamePrecedence": true
}
],
'import/extensions': 'off', // обеспечить согласованное использование расширения файла в пути импорта
'import/prefer-default-export': 'off', // ESLint предпочитает экспорт по умолчанию импорт/предпочитает экспорт по умолчанию
'no-unused-expressions': 'error', //нет неиспользуемых выражений
'no-param-reassign': 'off', //без переназначения параметров
'prefer-destructuring': ["error", { // требуется деструктуризация массивов и/или объектов.
"array": true,
"object": true
}, {
"enforceForRenamedProperties": false
}
],
'no-bitwise': ['error', { allow: ['~'] }], // запрещает побитовые операторы.
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], // запрещает неиспользуемые переменные.
'max-len': ['error', { code: 120 }], // обеспечивает максимальную длину строки.
'object-curly-newline': ['error', {
ObjectExpression: { multiline: true, consistent: true },
ObjectPattern: { multiline: true, consistent: true }
}], // применяет согласованные разрывы строк после открытия и перед закрытием фигурных скобок.
'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }] // требует или запрещает пустую строку между членами класса.
module.exports = {
root: true,
env: {
node: true,//Указание среды Глобальные переменные Node.js и область видимости Node.js. browser- глобальные переменные браузера.
},
globals: {
var1: "writable",
var2: "readonly",
Promise: "off"
}, // настройка глобальных переменных
extends: [
'plugin:vue/strongly-recommended', //Использование общей конфигурации
'@vue/airbnb',
],
parserOptions: {
parser: 'babel-eslint',
},
rules: {} //Настройка правил
}
Окончательно файл .eslintrc.js выглядит так:
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/strongly-recommended',
'@vue/airbnb',
],
parserOptions: {
parser: 'babel-eslint',
},
rules: {
"vue/html-self-closing": ["error", {
"html": {
"void": "never",
"normal": "always",
"component": "always"
},
"svg": "always",
"math": "always"
}],
'vue/html-indent': [
'error',
4,
{
attribute: 1,
baseIndent: 1,
closeBracket: 0,
alignAttributesVertically: true,
ignores: []
}
],
"vue/max-attributes-per-line": ["error", {
"singleline": {
"max": 1
},
"multiline": {
"max": 1
}
}],
'vue/order-in-components': ['error', {
order: [
'el',
'name',
'key',
'parent',
'functional',
['delimiters', 'comments'],
['components', 'directives', 'filters'],
'extends',
'mixins',
['provide', 'inject'],
'ROUTER_GUARDS',
'layout',
'middleware',
'validate',
'scrollToTop',
'transition',
'loading',
'inheritAttrs',
'model',
['props', 'propsData'],
'emits',
'setup',
'asyncData',
'data',
'fetch',
'head',
'computed',
'watch',
'watchQuery',
'LIFECYCLE_HOOKS',
'methods',
['template', 'render'],
'renderError'
]
}],
"vue/no-irregular-whitespace": ["error", {
"skipStrings": true,
"skipComments": false,
"skipRegExps": false,
"skipTemplates": false,
"skipHTMLAttributeValues": false,
"skipHTMLTextContents": false
}],
"vue/component-definition-name-casing": ["error", "PascalCase"],
"vue/match-component-file-name": ["error", {
"extensions": ["vue"],
"shouldMatchCase": false
}],
"vue/no-dupe-keys": ["error", {
"groups": []
}],
"vue/component-name-in-template-casing": ["error", "kebab-case", {
"registeredComponentsOnly": true,
}],
'comma-dangle': ['error', {
arrays: 'never',
objects: 'never',
imports: 'never',
exports: 'never',
functions: 'never'
}],
'linebreak-style': ["error", "windows"],
'no-console': 'error',
'no-debugger': 'error',
'arrow-parens': ['error', 'as-needed'],
'no-plusplus': 'off',
'constructor-super': 'off',
"no-mixed-operators": [
"error",
{
"groups": [
["+", "-", "*", "/", "%", "**"],
["&", "|", "^", "~", "<<", ">>", ">>>"],
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["&&", "||"],
["in", "instanceof"]
],
"allowSamePrecedence": true
}
],
'import/extensions': 'off',
'import/prefer-default-export': 'off',
'no-unused-expressions': 'error',
'no-param-reassign': 'off',
'prefer-destructuring': ["error", {
"array": true,
"object": true
}, {
"enforceForRenamedProperties": false
}
],
'no-bitwise': ['error', { allow: ['~'] }],
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'max-len': ['error', { code: 120 }],
'object-curly-newline': ['error', {
ObjectExpression: { multiline: true, consistent: true },
ObjectPattern: { multiline: true, consistent: true }
}],
'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }]
},
};
Рассмотрим на примере одного компонента, что у нас получилось. Для этого возьмем следующий компонент:
<template>
<div>
<HelloWorld v-model="headerData" is="header" v-once id="uniqueID" @click="functionCall"
v-text="textContent" ref="header" my-prop="prop" v-for="item in items"
/>
</div>
</template>
<script>
import HelloWorld from './HelloWorld.vue';
export default {
name: 'MyHeadergа',
components: {
HelloWorld
},
data() {
return {
array: [],
firstName: "Alex",
lastName: "Ivanov"
};
},
props: {
foo: String
},
methods: {
foo() {}
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
},
reversedArray() {
return this.array.reverse(); // <- side effect - orginal array is being mutated
}
},
};
</script>
После выполнения команды eslint --fix получаем следующее:
Автоматически отформатировалось название компонента template в стиле kebab-case, атрибуты в компонентах выстроены по порядку и каждый с новой строки. Название myProps заменено на my-props в соответствии с кодстайлом vue. Появилась пустая строка 19 между секцией template и script. На 21 строке подсвечивается название компонента которое не совпадает с названием файла. Двойные кавычки при определении строковых переменных заменены на одинарные. Свойства в секции script выстроены по порядку name, components, props, data, computed, method. На строке 40 подсвечивается ошибка, так как мы создаем сайд-эффекты в вычисляемом свойстве.
Заключение
Итак, мы разобрали большое количество правил настройки ESLint, которые можно без труда скорректировать по собственному желанию. Результат в большей степени соответствует рекомендациям по стилю написания кода Vue style guide, а если настроить Auto Fix On Save, то код будет автоматически формироваться согласно этим правилам. Все это в целом приведет к написанию более качественного кода, упрощению и ускорению разработки и код ревью.
Полезные ссылки:
Доступные правила для настройки eslint-plugin-vue можно посмотреть тут.
Спасибо за внимание! Полезные материалы для разработчиков мы также публикуем в наших соцсетях – ВК и Telegram.
Комментарии (5)
Madeas
30.06.2022 18:20Насколько мне известно, v-for не рекомендуется использовать вместе с v-if. Линтет и это подчёркивает. Поправьте меня или пример с порядком в template.
SimbirSoft_frontend Автор
01.07.2022 11:30Да, вы правы, v-for имеет более высокий приоритет, чем v-if, поэтому их не рекомендуется использовать вместе и ESLint будет выдавать ошибку. Но в данном случае мы говорим только о порядке атрибутов в секции template. Этот порядок определен, в статье об этом написано в самом первом пункте правила для секции <template>, там же приводится ссылка на документацию, где этот порядок задан. В примере приводятся все возможные варианты атрибутов и их порядок. Этот пример следует рассматривать как наглядное представление только этого правила, а не всех правил в совокупности.
Jolli
А можно как-то поставить настройку, которая будет проверять, чтобы в импорте были одинарные кавычки?
@importComponent from './Component', а не @import Component from "./Component"
Ares_ekb
Если речь об импорте в JS/TS коде, то можно так:
SimbirSoft_frontend Автор
Добрый день! Правило использования одинарных или двойных кавычек в ESLint:
quotes: [ERROR, 'single' ]
требует использования одинарных кавычек везде, где это возможно. Чтобы правило проверяло кавычки только в импортах, такого нет в ESLint, но можно использовать сторонний пакет npmjs, github.