🔥

Тред (Павел Лосев)


В продолжение вчерашнего треда, хочу рассказать о том как я пишу свой веб-фреймворк - tinyhttp. Опишу зачем я решил его делать, с чего начал его писать, как настроил тесты, CI и покрытие. Покажу как начать им пользоваться.

Ссылка на фреймворк, если кто не читал прошлый тред: github.com/talentlessguy/…

Начну с проблемы. Вчера в треде про легаси я написал, что несмотря на то, что Express стар, он всё ещё самый популярный фреймворк для Node.js. До сих пор Express использует довольно много устаревших модулей, чтобы поддерживать Node 0.10

Из проблем, не связанных с легаси, можно выделить отсутствие типов в самой либе (нужно ставить @types/express), отсутсвие поддержки ассинхронных обработчиков, и нету ESM экспортов.

Ещё одна субъективная проблема - код самого Express очень огромен и стар, в нём тяжело копаться. По крайней мере для меня, смотреть как работает next() в Express внутри было довольно тяжело. Возможно я просто плохо читаю чужой код ¯(ツ)/¯

Так вот, я захотел написать свой фреймворк, который был бы такой же как Express, но с поддержкой TypeScript, async / await и ESM. Т.к. у меня до этого не было опыта написания BE фреймворков, я начал изучать внутренности Express.

Читать было довольно тяжело, т.к. кода было очень много. Я решил поискать какие-то клоны Express с более простым кодом, и наткнулся на Polka. Polka это маленький веб-фреймворк, у которого промежуточные обработчики как в Express, и к тому же он ещё и сильно быстрее Express.


Для инструментов разработки решил взять pnpm, Rollup и changesets. pnpm - это пакетный менеджер, который переиспользует зависимости, очень хорош для монорепозиториев. Хотя я его использую сейчас везде, чтобы не тратить диск на мусорный node_modules.

Rollup для сборки модулей в ESM и CommonJS, changesets для автоматического апргейда версий, и зависимых модулей. Позже я заменил Rollup на tsup, чтобы быстрее собиралось. Для changesets я написал npm скрипт чтобы всё собиралось, апгрейдились версии и генерился Changelog.

Ссылки: tsup - github.com/egoist/tsup changesets - github.com/atlassian/chan…

Для CI я выбрал Github Actions, для тестов Jest и покрытия - Codecov. Мб это я такой криворукий, но он у меня не раз глючил, зависал на 5 часов билд, хотя все тесты были прогнаны, и при реране тестов всё становилось нормально.

При всём этом, пока что планирую юзать их, чтобы оставаться внутри экосистемы GitHub. На днях ещё попробовал настроить кэширование, и смог закэшировать node_modules, чтобы pnpm быстрее ставил пакеты. Не было прямо огромной разницы, вроде как там на несколько секунд стало меньше.

Ссылка на конфиг GH Actions: github.com/talentlessguy/…

Базовую рабочую версию tinyhttp я набросал довольно быстро. Только у этой версии не было поддержки next(). Сначала я попытался переписать алгоритм применения обработчиков, сделав его через рекурсию (app.handle вызывал самого себя) - получилось не очень.

Не из-за того что там рекурсия, а потому что в обработчик применялся дважды, и next() всё ещё неправильно работал.

Немного подустав, я подумал что лучше посмотреть как это делают другие фреймворки (реализуют next() и применяют обработчики) и начал читать код Polka. Там алгоритм проще чем в Express, и легче его понимать.

Решил взять его за основу, который тоже использует рекурсию, но внутри app.handle, а не сам app.handle.

Реализация в Polka: github.com/lukeed/polka/b… Реализация в tinyhttp: github.com/talentlessguy/…

Параллельно с написанием нормального применения обработчиков, я начал фигачить расширения для req и res , котрые есть в Express. Некоторые простенькие я написал сам, некоторые более сложные (типа res.send) я подсмотрел в коде Express, чтобы не наделать всяких ошибок.

Частично фреймворк был готов, не ломался при запуске, теперь надо было подумать где размещать доку. И я начал делать сайт для tinyhttp. Ещё где-то ранее зафигачил логотип. На главной странице я набросал то, как начать пользоваться tinyhttp, и перечислил фичи.

Ещё я начал делать документацию со всеми описания методов и свойств App, Request и Response. Также начал писать Learn Guide, но до сих пор не закончил. Фигачил сайт конечно же на tinyhttp. Вместо шаблонизаторов временно юзаются Transform Streams для замены внутри HTML.

Для раздачи статики сначала был использован самописный @tinyhttp/static, но оказалось что у него есть несколько багов (которые я в будущем исправлю), и поэтому перешёл на serve-handler. Недавно заменил его на sirv, у него и типы есть, и он поменьше будет.

Сайт всё ещё в процессе создания, потому что нужно поправить некоторые моменты в доке, и дописать гайд. Сейчас можно использовать как референс, хоть он и неполный пока что.

Теперь перейдём к тому, как начать им пользоваться. Если вы хотите использовать ESM синтаксис, то в корне проекта нужно добавить поле "type": "module" и импортировать через import { App from '@tinyhttp/app' }. Если нужен CommonJS, то const { App } = require('@tinyhttp/app').

В остальном, работает идентично Express, за исключеним того, что настройки указываются в конструкторе App: tinyhttp.v1rtl.site/docs#settings

Большинство обработчиков Express будут работать без ошибок в tinyhttp, но для того чтобы не использовать обработчики с легаси зависимостями, в репе tinyhttp имеется набор обработчиков. Среди них логгер, JWT , CORS, и т.д. Полный список тут: tinyhttp.v1rtl.site/mw

Ещё рекомендую прочесть вот эту маленькую статью, если нужно пошаговое введение (а ещё там показано как правильно ошибки обрабатывать внутри async / await): dev.to/talentlessguy/…

Из планов на будущее, нужно написать ещё больше тестов (хотя уже 72% покрытия на момент написания статьи), дописать доку, наделать промежуточных обработчиков и ещё наклепать примеров.

Примеры кстати есть, вот с MongoDB: github.com/talentlessguy/…, вот с GraphQL: github.com/talentlessguy/…. Есть список какие примеры будут добавлены в будущем: github.com/talentlessguy/…

tinyhttp открыт для контрибьютинга, можно предлагать свои идеи и правки, или работать над существующими issues. Самый простой способ внести вклад - это написать пример из списка. Не требует особых знаний, кроме как знаний технологии с которой делается пример, и Express/tinyhttp.