В процессе работы над проектами разработчики придерживаются определенного кодстайла. Как правило, за это отвечает 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.
ссылка на оригинал статьи https://habr.com/ru/company/simbirsoft/blog/674036/
Добавить комментарий