Angular миграция с tslint на eslint. Перевод монорепозитория Nx на eslint.

Aleksandr Serenko
F.A.F.N.U.R
Published in
7 min readJun 8, 2020
Angular tslint eslint

В данной статье поговорим о том как перевести Angular проект с tslint на eslint. Также рассмотрим нюансы для монорепозитория Nx и добавим соответствующие настройки для приложений и библиотек.

Обновление: 26 марта 2021: Angular-eslint выпустили стабильную версию и теперь можно использовать eslint на полную мощь. Подробнее в статье — Banx. Настройка базовых правил в eslint в Nx в Angular.

Переход с tslint на eslint состоит из следующих шагов:

  • Генерация файла конфигурации eslint с помощью tslint-to-eslint-config;
  • Установка зависимостей;
  • Устранение некорректных правил;
  • + Nx переопределение tslint’ов для приложений и библиотек.

Генерация файла конфигурации eslint

Для генерации необходимо запустить команду:

npx tslint-to-eslint-config

Если используется prettier, то добавить соответствующий аргумент--prettier.

Полное описание всех параметров можно посмотреть на github’e typescript-eslint/tslint-to-eslint-config.

В результате получим файл .eslint.js со следующим содержимым:

Из важных свойств eslint конфига:

  • rules — набор eslint правил
  • parser — парсер, который будет валидировать содержимое файлов
  • plugins — набор подключаемых плагинов, которые не входят в стандартный набор eslint
  • extends — набор предустановленных рекомендаций, которые будут применяться.

Подробнее про все параметры eslint можно посмотреть в официальной документации.

Так как статья является частью цикла статей про Angular, для данного проекта tslint-to-eslint-config сгенерирует следующий файл:

https://gist.github.com/Fafnur/b4c9f342feb8b49113973214582ae046

Формально данный файл eslint работать не будет, так как нет требуемых зависимостей.

Установка зависимостей

После запуска команды, есть рекомендуемые зависимости (можно увидеть на скриншоте выше). Однако, Angular пока не полностью поддерживает eslint из коробки, поэтому лучше сначала установить зависимости, рекомендуемые angular.

Для этого установим пакет — https://github.com/angular-eslint/angular-eslint.

ng add @angular-eslint/schematics

Теперь установим оставшиеся зависимости, которые были рекомендованы tslint-to-eslint-config:

yarn add @typescript-eslint/eslint-plugin-tslint eslint-plugin-import eslint-plugin-jsdoc eslint-plugin-prefer-arrow --dev

Базовая настройка eslint

Для того, чтобы eslint заработал, нужно внести некоторые правки.

Сначала добавить все плагины, которые у вас есть в .eslintrc.js.

Все необходимые плагины есть в package.json:

...
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-jsdoc": "^27.0.4",
"eslint-plugin-prefer-arrow": "^1.2.1",
"eslint-plugin-prettier": "^3.1.3",
...

В данном случае мы имеем 4 плагина. Добавим их в конфигурацию:

module.exports = {
...
plugins: [
'@angular-eslint/template',
'@typescript-eslint',
'import',
'jsdoc',
'prefer-arrow',
'prettier',
],
...
}

Есть 2 плагина, которые мы не включили в список:

  • typescript-eslint/tslint — плагин, который позволяет запускать правила tslint в eslint. Субъективно, это плохая практика и использования подобного решения крайне не рекомендуется
  • eslint-plugin-angular — плагин для AngularJS приложений. А так как Angular и AngularJS несовместимы, потребность в данном плагине отсутствует.

После того, как необходимые плагины были подключены, добавим рекомендуемые наборы правил в extends:

module.exports = {
...
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@angular-eslint/recommended',
'prettier',
'prettier/@typescript-eslint',
],
...
}

В данном случае:

  • первые три настройки это стандартные правила;
  • plugin:@angular-eslint — рекомендации сообщества angular;
  • prettier, prettier/@typescript-eslint — рекомендации prettier.

Так как основной tsconfig.json в монорепозитории Nx в не описывает правила, к которым он должен быть применён, то нужно добавить параметр createDefaultProgram: true в parserOptions:

module.exports = {
...
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
createDefaultProgram: true,
},
...
}

Из названия понятно, что данный параметр создает пустой файл и eslint перестает требовать сконфигурировать пулл файлов, которые необходимо указать в tsconfig.

Устранение некорректных правил

Сначала можно удалить все правила, связанные с AngularJS:

'angular/component-name': 'error',
'angular/constant-name': 'error',
'angular/controller-as': 'error',
'angular/controller-as-route': 'error',
'angular/controller-as-vm': 'error',
'angular/controller-name': 'error',
'angular/directive-name': 'error',
'angular/directive-restrict': 'error',
'angular/document-service': 'error',
'angular/factory-name': 'error',
'angular/file-name': 'error',
'angular/filter-name': 'error',
'angular/function-type': 'error',
'angular/interval-service': 'error',
'angular/module-getter': 'error',
'angular/module-name': 'error',
'angular/module-setter': 'error',
'angular/no-run-logic': 'error',
'angular/no-service-method': 'error',
'angular/provider-name': 'error',
'angular/service-name': 'error',
'angular/timeout-service': 'error',
'angular/value-name': 'error',
'angular/window-service': 'error',

Angular-eslint добавляет несколько правил для валидации шаблонов. Однако оно должно применяться только к файлам шаблонов, поэтому их можно выключить:

'@angular-eslint/template/banana-in-box': 'off',
'@angular-eslint/template/no-negated-async': 'off',

или настроить с помощью override:

module.exports = {
...
overrides: [
{
files: ['*.component.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
plugins: ['@angular-eslint/template'],
processor: '@angular-eslint/template/extract-inline-html',
},
],
...
}

При выполнении команды миграции был сгенерирован файл — tslint-to-eslint-config.log.

В данном файле будут описаны все проблемные правила, которые ведут себя в eslint не так, как это было в tslint.

В нашем случае это:

6 ESLint rules behave differently from their TSLint counterparts:
* @typescript-eslint/no-unused-expressions:
- The TSLint optional config "allow-new" is the default ESLint behavior and will no longer be ignored.
* eqeqeq:
- Option "smart" allows for comparing two literal values, evaluating the value of typeof and null comparisons.
* no-console:
- Custom console methods, if they exist, will no longer be allowed.
* no-invalid-this:
- Functions in methods will no longer be ignored.
* no-underscore-dangle:
- Leading or trailing underscores (_) on identifiers will now be forbidden.
* prefer-arrow/prefer-arrow-functions:
- ESLint does not support allowing standalone function declarations.
- ESLint does not support allowing named functions defined with the function keyword.

Как можно увидеть, в основном это либо правила, которые уже по умолчанию используются в eslint, либо добавляют ряд особых опций.

Для решения данных проблем, просто пройдите по каждому правилу и ознакомитесь с ними на сайте eslint.

Так как Nx предоставляет собственные правила валидации — nx-enforce-module-boundaries. Для включения их в eslint нужно установить плагин Nx:

yarn add -D @nrwl/eslint-plugin-nx

Добавим плагин в список плагинов:

plugins: [
...,
'@nrwl/nx',
...
]

Заменим правило tslint:

'@typescript-eslint/tslint/config': [
'error',
{
rules: {
'nx-enforce-module-boundaries': true,
prettier: true,
},
},
],

на eslint правило:

'@nrwl/nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }],
},
],

В целом, данных настроек достаточно, чтобы eslint корректно работал.

Устранение других ошибок

Если вы видите сообщения eslint вида:

ESLint: Definition for rule '<your-rule-name>' was not found.(<your-rule-name>)

Это говорит о том, что в eslint импортированы правила, которые не являются стандартными и для работы которых необходимо добавить в раздел plugins соответствующие плагины.

Eslint для тестирования

Для файлов используемых для тестирования можно переопределить конфиг для расширений .spec.ts:

...
overrides: [
{
files: ['*.spec.ts'],
rules: {
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-assignmentall': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
},
},
]
...

Авторские изменения

Одной из проблем eslint, которая бросается в глаза это сортировка import’ов.

Задача в следующем:

  • Сначала выводить внешние зависимости (angular/core, moment, …)
  • Затем выводить внутренние зависимости c Nx workspace (medium-stories/core, medium-stories/storage, ..)
  • В конце выводить локальные зависимости (../user.ts, ./user.service.ts, …)

Настроить стандартный плагин под собственные нужны я не смог, поэтому пришлось использовать сторонний плагин — eslint-plugin-simple-import-sort.

Установим плагин:

yarn add -D eslint-plugin-simple-import-sort

Добавим в список плагинов eslint.js:

plugins: [
...,
'simple-import-sort',
...
]

Добавим требуемые правила, которые выключат базовые eslint и включат плавила плагина:

"rules": {
"simple-import-sort/sort": "error",
"sort-imports": "off",
"import/first": "error",
"import/newline-after-import": "error",
"import/no-duplicates": "error"
},

Так как необходимо создать отдельную группу в импортах для workspace, добавим собственную группу:

'simple-import-sort/sort': [
'error',
{
groups: [
// Side effect imports.
['^\\u0000'],
// Packages.
['^@?(?!mediu@nrwl/eslint-plugin-nxm\-stories)\\w'],
['^@medium\-stories?\\w'],
['^[^.]'],
// Relative imports.
// Anything that starts with a dot.
['^\\.'],
],
},
],

Это позволит из списка import’ов:

import { NgModule } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { LayoutsModule } from '@medium-stories/layouts';
import { ResponsiveModule } from '@medium-stories/responsive';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@medium-stories/shared';
import { homeContainers, homeRoutes } from './home.common';
import { RouterModule } from '@angular/router';

Получить:

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';

import { LayoutsModule } from '@medium-stories/layouts';
import { ResponsiveModule } from '@medium-stories/responsive';
import { SharedModule } from '@medium-stories/shared';

import { homeContainers, homeRoutes } from './home.common';

Nx интеграция

После того, как был настроен основной .eslintrc.js в монорепозиторий Nx нужно всего лишь добавить соответствующие конфиги для приложений и библиотек.

Для приложений .eslintrc.js примет вид:

module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
sourceType: 'module',
},
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'med@nrwl/eslint-plugin-nxiumStories',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: 'medium-stories',
style: 'kebab-case',
},
],
},
extends: '../../../.eslintrc.js',
};

Как видно, мы просто расширяем базовый .eslintrc.js и добавляем собственные имена для компонентов и директив.

Аналогично делаем для библиотек:

module.exports = {
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'mediumStories',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: 'medium-stories',
style: 'kebab-case',
},
],
},
extends: '../../.eslintrc.js',
};

Резюме

В данной статье разобрали основные нюансы связанные с переходом с tslint на eslint. Весь процесс разбили на 4 шага, где поэтапно рассмотрели каждый шаг. После установки всех зависимостей и генерации файла eslint, разобрали основные нюансы связанные с применяемыми правилами:

  • добавили требуемые плагины в eslint
  • исправили параметры парсера
  • удалили legacy правила для angular
  • исправили правила для проверки шаблонов в angular
  • добавили правила для Nx

В конце добавили небольшое решение, которое сортирует import’ы в требуемом формате.

Спасибо за внимание!

Исходники

Все исходники находятся на github, в репозитории:

Для того, чтобы посмотреть состояние проекта на момент написания статьи, нужно выбрать соответствующий тег — eslint.

git checkout eslint

Код можно посмотреть в приложении medium-stories — https://github.com/Fafnur/medium-stories/blob/master/.eslintrc.js

Ссылки

Подписывайтесь на блог, чтобы не пропустить новые статьи про Angular, веб-разработку и новости из мира фронтенда.

Medium: https://medium.com/fafnur
Добавляйтесь в группу ВК: https://vk.com/fafnur
Добавляйтесь в группу в Fb: https://www.facebook.com/groups/fafnur/
Телеграм канал: https://t.me/f_a_f_n_u_r
Twitter: https://twitter.com/Fafnur1
Instagram: https://www.instagram.com/fafnur
LinkedIn: https://www.linkedin.com/in/fafnur

Предыдущие статьи:

  1. Тестирование Ngrx store в Angular. Методы и подходы для упрощения тестирование stat’ов Ngrx в Nx.
  2. Сборка Typescript приложения с помощью Webpack.
  3. Архитектура enterprise Angular приложений с использованием монорепозитория Nx.
  4. Angular тестирование component с помощью Jest.
  5. CSS решения. Фиксированный блок плюс адаптивный блок.
  6. CSS решения. Создание сетки, колонок и гридов.

--

--

Aleksandr Serenko
F.A.F.N.U.R

Senior Front-end Developer, Angular evangelist, Nx apologist, NodeJS warlock