Архив недели @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…
А последнее время мне кажется отличной идея полностью отказаться от дефолтных экспортов. Подсмотрел у @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…
Путаница возникает только с alwaysStrict и strict.
alwaysStrict на самом деле не относится к проверкам, а лишь добавляет “use strict” в каждый выходной файл.
strict же включает абсолютно все флаги в этой секции. Поэтому можно оставить только его
У такого подхода есть несколько недостатков:
нет наглядного отображения, какие именно флаги задействованы
после обновления TS в этот список могут войти новые флаги
если возникают сложности, то приходится выключать сразу все проверки
Поэтому я предпочитаю наоборот не указывать strict, а включать каждый флаг по отдельности.
Теперь расскажу какие флаги я рекомендую включать в первую очередь и почему
TS начинается с noImplicitAny: true
Два слова про any. Это тип, который назначается всем переменным, если тип не был задан явно и не может быть выведен компилятором. С точки зрения TS, все переменные в JS это any, поскольку там нет системы типов
С переменной типа any можно делать что угодно, как мы привыкли это делать в JS. Но мы не для этого пишем на TS
noImplicitAny всё ещё разрешает использовать any, но это нужно делать явно. Тем самым автор говорит: я понимаю, что делаю и беру риски на себя
Это стоит использовать лишь в крайнем случае, когда никак не получается определить или подружить типы
TS код это когда noImplicitAny: true и руками мы тоже any нигде не пишем. Или почти нигде - будем реалистами 😉
В JS два нижних типа null и undefined
В TS по умолчанию переменная любого типа может содержать значение null или undefined. Проверка переменных перед их использованием лежит на плечах программиста
Установив strictNullChecks: true мы предохраняем себя от подобных ситуаций
С эти флагом мы должны явно разрешать переменным содержать null или undefined:
let a: number | null | undefined
Установив strictFunctionTypes: true мы включаем более строгую проверку сигнатур функций
Вот теперь это больше похоже на TypeScript! Так сказать джентельменский набор TS разработчика
Оставшиеся флаги, конечно тоже лучше включить. Если вдруг что-то пойдёт не так, можно выключить именно проблемный флаг до выяснения обстоятельств
Про них буквально по паре слов:
strictPropertyInitialization - следит, чтобы свойства класса были проинициализированы. Дополнение к strictNullChecks, который должен быть тоже включен
noImplicitThis - при использовании this проверяет, что контекст выполнения известен
strictBindCallApply - более строго проверят сигнатуры при использовании соответствующих методов: bind, call, apply
Подведём итоги секции Strict Checks:
здесь действительно все флаги очень полезные. С ними TS раскрывает свой потенциал
все флаги можно включить разом через strict: true
как минимум следуют включить noImplicitAny, strictNullChecks и strictFunctionTypes
Среда
Следующая секция - Linter Checks. Название подразумевает, что это не про компиляцию, а качество кода. И эти проверки можно переложить на соответствующий инструмент - eslint. И это так, но лишь отчасти. Кажется, первые 2 флага находятся не на своих местах
gist.github.com/barinbritva/cf…
В анонсах флагов noPropertyAccessFromIndexSignature и noUncheckedIndexedAccess было написано, что эти флаги не поместили под управление опцией strict, просто потому, что они на любителя
Стало быть Linter Checks не место для этих флагов. Всё таки это про проверку типов
noPropertyAccessFromIndexSignature запрещает обращаться к свойствам объекта через точку, если свойства не объявлены в объекте явно, а например, через [key: string]: string
devblogs.microsoft.com/typescript/ann…
Если мы объявляем переменную как let a: string[], то объекты, полученные по любым индексам a[0], a[1000] будут иметь тип string. На деле же это может быть undefined
noUncheckedIndexedAccess будет заставлять проверять переменные перед использованием
devblogs.microsoft.com/typescript/ann…
Оставшиеся же флаги можно легко заменить аналогичными правилами 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…
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…
Чтобы всё это добро не cмешивалось с другими опциям конфига мы можем прибегнуть к директиве extends
1 Создаём tsconfg-checks.json
2 Создаём tsconfig.json
3 В tsconfig.json пишем:
gist.github.com/barinbritva/cf…
Теперь полный порядок! 😉
Тред (Али Рагимов)
Четверг
Различия any и unknown
Оба этих типа означают, что тип переменной не определён. Но вот поведение этих типов диаметрально противоположное:
any - означает "что угодно". Поэтому и делать с переменной такого типа можно всё, что угодно
Но мы не для этого в предыдущем треде делали TS таким строгим. Видимо, команда TS думала так же и создала тип unknown
unknown - означает "не известно, что это". Поэтому делать с переменной этого типа наоборот ничего нельзя
Перед использованием unknown переменной, нужно выявить её тип. Для этого придётся писать проверки
Другими словами, unknown это другой инструмент для написания строгого кода
Для наглядности приведу пример, где any и unknown в роли возвращаемых значений
Думаю, что на вопрос когда и в каких случаях нужно использовать 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 конструкций и подстановки хардкод-значений
Фича достаточно приятная
Я замечал, что Babel генерирует более лаконичный бойлерплейт, чем TS
Но в TS можно сделать так:
npm install tslib
в tsconfig.json установить importHelpers: true
получить сокращение количества бойлерплейта в разы за счёт того, что он теперь шарится
typescriptlang.org/tsconfig#impor…
В одном из проектов, над которым я работаю, 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…
Тред (Али Рагимов)