Али Рагимов

Али Рагимов

Темы
Неделя
Mar 29, 2021 → Apr 4, 2021

Архив недели @barinbritva

Понедельник


Всем привет! Меня зовут Али Рагимов aka @barinbritva. В веб-разработке около 9 лет. Начинал со стека PHP + jQuery в родном г. Тольятти. Позже переехал в Санкт-Петербург. Сейчас работаю фронтом на React’е. В свободное время пишу на Node.js и пробую интересные для себя вещи

Последние полгода занимаюсь различной публичной деятельностью. Основная повестка – TypeScript. За это время я углубился в конфигурирование и сборку TS проектов. Этим опытом и планирую делиться большую часть недели. Под конец хочу рассказать пару историй. Надеюсь, будет интересно!

Если вы ещё не работали с TypeScript, то вот немного крафтового материала от меня, чтобы войти в контекст: Введение в TS - youtube.com/watch?v=MaONc0… TS как фундамент архитектуры - youtube.com/watch?v=tT58iF…

TS начинается с файла tsconfig.json, где хранится конфигурация компилятора и проекта. Но ему часто не уделяют должного внимания. Чаще всего он кочует из проекта в проект с минимальными изменениями. Просто потому что "работает". И я делал так же

Но в одном проекте пришлось заняться сборкой более плотно. Возникала потребность разобраться в многочисленных опциях. Вкладка документации по TS была открыта 24/7... И так, первый лайфхак: существует команда tsc --init, которая сгенерирует конфиг с самыми основными опциями

При этом конфиг будет содержать большое множество закомментированных опций с их описанием. Читаем описание опции, если находите её полезной - раскомментируем, если нет- удаляем. Посмотреть, как это выглядит можно здесь: gist.github.com/barinbritva/cf… Конфиг создан, можно настраивать

Много путаницы может возникать при указании файлов для компиляции. Речь идёт о директивах files, include, exclude. Опция files, позволяет указать конкретные файлы, что можно использовать если у вас всего несколько файлов в проекте. Но на деле это скорее служебная директива

Обычно используется include с указанием массива масок путей, например ["src"]. Поэтому опцию files использовать нет необходимости. В действительности она используется под капотом. Анализируя маски путей из include, TS составляет полный список файлов и заполняет их за нас

Это можно заметить выполнив малоизвестную команду tsc --showConfig. Несмотря на то, что мы указали в конфиге лишь include: ["src"], мы можем наблюдать появление опции files с указанием каждого файла, лежащим внутри src. По сути, команда позволяет нам дебажить конфиг

К сожалению, команда не демонстрирует другие особенности готовки итогового конфига. А их, на самом деле, очень много. Есть issue на GitHub, чтобы сделать функцию более полной - github.com/microsoft/Type… Надеюсь, однажды у нас будет полноценный инструмент дебага

Что же будет если указать и files и include? TS просто сделает merge. С включением файлов разобрались. А что на счёт exclude? Данная опция является фильтром опции include. Это может быть удобно, например, чтобы быстро включить всю папку src, а потом исключить из неё файлы тестов

Соответственно, exclude не может исключить файлы, указанные в files. Так же exclude не сможет исключить файл, если он импортируется в другом файле. Посмотреть итоговый список всех файлов, которые будут включены в билд, включая файлы из node_modules, можно командой tsc –listFiles

С input'ом файлов разобрались. Немного про output. У нас есть 2 опции outDir и outFile. Первой, думаю, пользуются все — это папка хранения артефактов билда. Вторая же опция умеет конкатенировать все выходные файлы в один. Вау! Бандлинг из коробки! Почему я не знал об этом?

Именно такие мысли были у меня, когда я увидел эту опцию. Но не тут-то было. Почему? Потому что outFile не работает с модульной системой CommonJS или ES6. А именно они и нужны в 90% случаев Если же вам не нужна модульная система или подойдёт AMD, то можно смело использовать опцию

Наверняка, вам приходилось и не раз писать подобное? import * as React from 'react' React содержит CommonJS синтаксис экспорта, а не ES6. Однако TS умеет преобразовывать такие конструкции, что позволяет нам не писать миксы из require и import в коде. Большое спасибо ему за это!

Но включив флаг allowSyntheticDefaultImports в tsconfig, мы можем сделать ещё один шаг к унификации импортов и писать вот так import React from 'react' Флаг не меняет исходный код, а просто закрывает глаза на то, что в тайпингах не объявлен дефолтный ES6 экспорт

Разумеется, это работает и с другими библиотеками. Но бывают случаи, когда после таких трюков проект собирается, но не работает. Например, если подключить вышеуказанным способом библиотеку moment. Какая-то внутренняя кухня готовки модулей. Я, честно, не знаю всех тонкостей

Исправить ситуацию позволяет флаг esModuleInterop. Он добавляет некоторый бойлерплейт в экспорт модулей, который делает так, что всё экспортируется правильно. А мы получаем возможность во всех случаях писать единообразные импорты

Пожалуй, для меня это личное. После того как в 2015 я писал первый проект на TS без понимания про тайпинги и модульные системы и мои импорты выглядели как-то так gist.github.com/barinbritva/cf…
notion image

А последнее время мне кажется отличной идея полностью отказаться от дефолтных экспортов. Подсмотрел у @nestframework. Тогда остаётся только один формат импортов import {something} from 'lib' Выглядит красиво, можно линтером упорядочивать по алфавиту - красота и порядок😍

Но не суть. Если вас заинтересовали подробности тюнинга tsconfig, то предлагаю вашему вниманию статью, в которой я расписал все основные опции конфига: habr.com/ru/post/542234/

🔥Тред (Али Рагимов)

Вторник


Как верно было замечено, TS из коробки не сильно отличается от JS. Это сделано для обратной совместимости - далеко не все проекты начинаются с нуля на TS Поэтому если в вашем проекте конфиг не тюнили, вы получаете лишь малую часть того, что даёт TS. Поговорим о «флагах строгости»

В официальной документации эти флаги раскиданы по секциям Strict Checks, Linter Checks и ещё немного в Advanced. С первыми двумя, на первый взгляд, всё понятно. А Advanced вызывает вопросы. Давайте разберёмся

Вот первая группа флагов – Strict Checks. Это самая понятная секция. По умолчанию все флаги выключены. Но в примере я демонстрирую максимально строгий режим gist.github.com/barinbritva/cf…
notion image

Путаница возникает только с alwaysStrict и strict. alwaysStrict на самом деле не относится к проверкам, а лишь добавляет “use strict” в каждый выходной файл. strict же включает абсолютно все флаги в этой секции. Поэтому можно оставить только его

У такого подхода есть несколько недостатков: нет наглядного отображения, какие именно флаги задействованы после обновления TS в этот список могут войти новые флаги если возникают сложности, то приходится выключать сразу все проверки

Поэтому я предпочитаю наоборот не указывать strict, а включать каждый флаг по отдельности. Теперь расскажу какие флаги я рекомендую включать в первую очередь и почему

TS начинается с noImplicitAny: true Два слова про any. Это тип, который назначается всем переменным, если тип не был задан явно и не может быть выведен компилятором. С точки зрения TS, все переменные в JS это any, поскольку там нет системы типов
notion image

С переменной типа any можно делать что угодно, как мы привыкли это делать в JS. Но мы не для этого пишем на TS noImplicitAny всё ещё разрешает использовать any, но это нужно делать явно. Тем самым автор говорит: я понимаю, что делаю и беру риски на себя
notion image

Это стоит использовать лишь в крайнем случае, когда никак не получается определить или подружить типы TS код это когда noImplicitAny: true и руками мы тоже any нигде не пишем. Или почти нигде - будем реалистами 😉

В JS два нижних типа null и undefined В TS по умолчанию переменная любого типа может содержать значение null или undefined. Проверка переменных перед их использованием лежит на плечах программиста
notion image

Установив strictNullChecks: true мы предохраняем себя от подобных ситуаций С эти флагом мы должны явно разрешать переменным содержать null или undefined: let a: number | null | undefined
notion image

Установив strictFunctionTypes: true мы включаем более строгую проверку сигнатур функций
notion image

Вот теперь это больше похоже на TypeScript! Так сказать джентельменский набор TS разработчика Оставшиеся флаги, конечно тоже лучше включить. Если вдруг что-то пойдёт не так, можно выключить именно проблемный флаг до выяснения обстоятельств Про них буквально по паре слов:

strictPropertyInitialization - следит, чтобы свойства класса были проинициализированы. Дополнение к strictNullChecks, который должен быть тоже включен noImplicitThis - при использовании this проверяет, что контекст выполнения известен
notion image
notion image

strictBindCallApply - более строго проверят сигнатуры при использовании соответствующих методов: bind, call, apply
notion image

Подведём итоги секции Strict Checks: здесь действительно все флаги очень полезные. С ними TS раскрывает свой потенциал все флаги можно включить разом через strict: true как минимум следуют включить noImplicitAny, strictNullChecks и strictFunctionTypes

Среда


Следующая секция - Linter Checks. Название подразумевает, что это не про компиляцию, а качество кода. И эти проверки можно переложить на соответствующий инструмент - eslint. И это так, но лишь отчасти. Кажется, первые 2 флага находятся не на своих местах gist.github.com/barinbritva/cf…
notion image

В анонсах флагов noPropertyAccessFromIndexSignature и noUncheckedIndexedAccess было написано, что эти флаги не поместили под управление опцией strict, просто потому, что они на любителя Стало быть Linter Checks не место для этих флагов. Всё таки это про проверку типов

noPropertyAccessFromIndexSignature запрещает обращаться к свойствам объекта через точку, если свойства не объявлены в объекте явно, а например, через [key: string]: string devblogs.microsoft.com/typescript/ann…
notion image

Если мы объявляем переменную как let a: string[], то объекты, полученные по любым индексам a[0], a[1000] будут иметь тип string. На деле же это может быть undefined noUncheckedIndexedAccess будет заставлять проверять переменные перед использованием devblogs.microsoft.com/typescript/ann…
notion image

Оставшиеся же флаги можно легко заменить аналогичными правилами eslint noFallthroughCasesInSwitch - eslint.org/docs/rules/no-… noImplicitReturns -eslint.org/docs/rules/con… noUnusedLocals - eslint.org/docs/rules/no-… noUnusedParameters - eslint.org/docs/rules/no-…

Секция Advanced отличается от предыдущих тем, что содержит помимо флагов строгости множество других (их мы пропустим). И тем, что для строгости эти флаги должны быть установлены в ❗false❗ Мы рассмотрим только те, что касаются нашей темы gist.github.com/barinbritva/cf…
notion image

allowUnreachableCode разрешает недосягаемый код - код, написанный после операторов return, throw, break, continue allowUnusedLabels разрешает не используемые лэйблы. Так понимаю, конструкция, на которой наши далёкие предки писали циклы. Мы их вряд ли когда встретим

Мы запрещаем это поведение установкой false или настройкой eslint allowUnreachableCode - eslint.org/docs/rules/no-… allowUnusedLabels - eslint.org/docs/rules/no-… И переходим к завершающей фазе

С последними 4 флагами ничего не делаем - с ними всё хорошо. Скажу лишь, что noImplicitUseStrict это немного иначе работающий alwaysStrict - только он отменяет добавление "use strict" в некоторых случаях... Можно просто не думать о нём

А noStrictGenericChecks, suppressExcessPropertyErrors и suppressImplicitAnyIndexErrors запрещают практическое явное нарушение типизации. Временно устанавливать их в true можно лишь в крайних случаях - например портирование большого проекта с JS. В других случаях не стоит

Итак, вот наш итоговый, реорганизованный, самый строгий tsconfig! gist.github.com/barinbritva/cf…
notion image

Чтобы всё это добро не cмешивалось с другими опциям конфига мы можем прибегнуть к директиве extends 1 Создаём tsconfg-checks.json 2 Создаём tsconfig.json 3 В tsconfig.json пишем: gist.github.com/barinbritva/cf… Теперь полный порядок! 😉
notion image

🔥Тред (Али Рагимов)

Четверг


Различия any и unknown Оба этих типа означают, что тип переменной не определён. Но вот поведение этих типов диаметрально противоположное: any - означает "что угодно". Поэтому и делать с переменной такого типа можно всё, что угодно
notion image

Но мы не для этого в предыдущем треде делали TS таким строгим. Видимо, команда TS думала так же и создала тип unknown unknown - означает "не известно, что это". Поэтому делать с переменной этого типа наоборот ничего нельзя
notion image

Перед использованием unknown переменной, нужно выявить её тип. Для этого придётся писать проверки Другими словами, unknown это другой инструмент для написания строгого кода
notion image

Для наглядности приведу пример, где any и unknown в роли возвращаемых значений
notion image
notion image

Думаю, что на вопрос когда и в каких случаях нужно использовать unknown справедливо будет ответить: Как только вы решаете написать в коде any, напишите вместо него unknown и попробуйте сделать так, чтобы всё работало

🔥Тред (Али Рагимов)

Пятница


Сегодня думал сделать тред про сборку. В плоскости bable-loader и ts-loader С одной стороны, для некоторых становится открытием, что babel-loader просто удаляет весь TS из кода и ничего не проверяет. С другой - материала по теме много Было бы интересно почитать про это?
🤔 91.4% Да, хотел бы узнать!
🤔 8.6% Нет, тема понятная

Раз спрос есть, расскажу основные моменты и покидаю полезного материала 😉 Если мы пишем код на TS для Node - backend и cli, то ничего дополнительно делать не нужно. Запускаем tsc, он подцепляет наш tsconfig.json и всё - идём в прод Но frontend это почти всегда webpack + babel

Эту связку можно считать стандартом индустрии. Логично использовать её и в проекте с TS. Для этого есть babel/preset-typescript (плагин plugin-transform-typescript) Однако, он гласит: this plugin does not add the ability to type-check... babeljs.io/docs/en/babel-…

Другими словами, плагин умеет лишь удалять синтаксис TS из кода и превращать его обратно в JS 😱 Проблему можно решить 3 способами: 1 Использовать ts-loader + babel-loader 2 использовать babel-loader, проверку типов запускать отдельно 3 использовать только ts-loader

Вариант ts-loader + babel-loader имеет место быть Однако, тут много проблем. Больше зависимостей, сложнее конфигурировать, больше багов. Самое главное - проект будет собираться заметно дольше Думаю, такой вариант не стоит рассматривать. А вот с 2 другими не всё так однозначно

Вариант babel-loader, проверка типов отдельно - пожалуй самый распространённый Интегрируем preset-typescript и сборка работает привычным образом. TS ошибки нам будет подсказывать IDE, так как она видит tsconfig.json Плюс мы параллельно запускаем проверку типов

Сделать это можно дёшево и сердито, запустив рядом tsc --watch --noEmit watch запускает tsc на изменение файлов, а noEmit делает так, что выходные файлы никуда не сохраняются. TS будет лишь проверять и выводить ошибки Хорошей идей будет запускать tsc --noEmit на pre-commit

Хорошо детали подобной настройки описаны в этой статье iamturns.com/typescript-bab… Там же есть некоторые спойлеры о том, что babel-loader не может из того, что ts-loader умеет. Об этом я тоже скажу

Но если не хотите запускать проверку типов в отдельной вкладке, то можно интегрировать её в основной процесс сборки, подключив github.com/TypeStrong/for… Этот плагин запускает проверку типов в параллель со сборкой

Думаю, что этот подход под номером №2 можно назвать эталонным способом готовки TS в 2021. Как минимум он самый распространённый Но серебряных пуль, как известно, не бывает. Чуть позже я хочу рассказать, чем может быть привлекателен вариант №3 - только ts-loader

Пока хотел бы попросить вас накидать вариантов, что вам нравится в babel и какие его возможности для вас must have? Для меня самой мощной его фичей является babel-polyfill 💪

Если вы не пользуетесь возможностями экосистемы babel, а просто транспилите код, то есть смысл рассмотреть вариант использования ts-loader без babel Я делаю именно так, когда есть выбор. Мне не хватает лишь полифиллов из коробки

Тут описаны 2 варианта как готовить полифилы: mydatahack.com/adding-polyfil… Это лишняя работа, которой заниматься не хочется. Придётся думать, о том, какие фичи вообще нуждаются в полифиллах и так далее Но посмотрим, что какие плюсы может дать этот вариант взамен

Babel не поддерживает некоторые фичи TS. Таких фич немного: babeljs.io/docs/en/babel-… Я сталкивался только с отсутствием поддержки конструкции const enum. Фича позволяет облегчать бандл путём удаления enum конструкций и подстановки хардкод-значений Фича достаточно приятная
notion image
notion image

Я замечал, что Babel генерирует более лаконичный бойлерплейт, чем TS Но в TS можно сделать так: npm install tslib в tsconfig.json установить importHelpers: true получить сокращение количества бойлерплейта в разы за счёт того, что он теперь шарится typescriptlang.org/tsconfig#impor…
notion image

В одном из проектов, над которым я работаю, 20+ babel пакетов. Практически каждая синтаксическая конструкция это отдельный пакет. Со всеми вытекающими Есть плагины, есть пресеты... Всё это нужно понимать, уметь конфигурировать и связывать между собой

На другой чаше весов всего 2 зависимости - typescript и ts-loader и всё управление декларативно через конфиг Поэтому если вы не используете мощь Babel, то есть смысл рассмотреть такой вариант. Кажется, плюсов здесь хватает

Кстати, не обязательно писать на TS, чтобы собирать проект через tsc или ts-loader. Вы можете собрать свой проект на чистом JS. Для этого добавьте в tsconfig.json флаг allowJs: true и в бой!

Суббота


Вот такие 3 варианта мы сегодня рассмотрели В комментариях предложили замечательный 4-й, который я теперь хочу попробовать 😍 ❌№1 - плюсов не обнаружено, минусов много 🌟№2 - самый распространённый и универсальный ⭐№3 - крутой альтернативный вариант

🔥Тред (Али Рагимов)

Воскресенье


Вот и подошла к концу моя неделя. Надеюсь, вам было интересно, а главное полезно! Для меня это был безусловно ценный опыт! Всем спасибо за внимание и ценные комментарии! А с вами был Али Рагимов. Подписывайтесь @barinbritva. И до новых встреч! 😉 Рекап:

TS начинается с файла tsconfig.json, где хранится конфигурация компилятора и проекта. Но ему часто не уделяют должного внимания. Чаще всего он кочует из проекта в проект с минимальными изменениями. Просто потому что "работает". И я делал так же
Особенности некоторых опций tsconfig.json: twitter.com/jsunderhood/st…

Как верно было замечено, TS из коробки не сильно отличается от JS. Это сделано для обратной совместимости - далеко не все проекты начинаются с нуля на TS Поэтому если в вашем проекте конфиг не тюнили, вы получаете лишь малую часть того, что даёт TS. Поговорим о «флагах строгости»
Настройки строгости в TypeScript: twitter.com/jsunderhood/st…

Различия any и unknown Оба этих типа означают, что тип переменной не определён. Но вот поведение этих типов диаметрально противоположное: any - означает "что угодно". Поэтому и делать с переменной такого типа можно всё, что угодно pic.twitter.com/FtcyeJvJra
Про типы any и unknown: twitter.com/jsunderhood/st…

Сегодня думал сделать тред про сборку. В плоскости bable-loader и ts-loader С одной стороны, для некоторых становится открытием, что babel-loader просто удаляет весь TS из кода и ничего не проверяет. С другой - материала по теме много Было бы интересно почитать про это?
Обзор различных вариантов сборки TypeScript проектов: twitter.com/jsunderhood/st…

🔥Тред (Али Рагимов)

Ссылки