Архив недели @IAmTrySound
Понедельник
Привет! На этой неделе с вами будет TrySound.
Я занимаюсь svgo и избавляюсь от node_modules.
Мой гитхаб github.com/TrySound
Занимаюсь фротендом 7 лет. Работаю в realadvisor.ch вместе с @chicoxyzzy @icelabaratory @rpominov и другими крутыми разработчиками.
В прошлом так же был мейнтейнером rollup и postcss.
Мой настоящий твиттер @IAmTrySound
Тред (Богдан Чадкин)
Девушки, с праздником! Пусть баги легко воспроизводятся, а код легко дебажится.
Итак, есть такой популярный оптимизатор svg - SVGO. В последние годы у мейнтейнеров не было достаточно времени чтобы возиться с ним. И это понятно, инструмент большой и сложный, легко выгореть.
github.com/svg/svgo
Конечно, первым делом я смерджил свои пул реквесты, чтобы избавиться от всякой гадости в node_modules.
Затем решил очень старую проблему, которую помню еще со времен работой над cssnano. SVGO асинхронный. Это мешает запускать cssnano в синхронном режиме, например, внутри babel или node хуках.
Ну и раз уж мы делаем breaking changes, надо поправить фасад. SVGO класс превратился в optimize функцию. Упростил жуткие конфиги плагинов, которые подгоняли под yaml. Да и вообще выпилил yaml - уж больно жирный.
Изначально пакет писался для ноды. Инструменты вроде svgomg запускают svgo в браузере. Специально для них добавил esm бандл
jakearchibald.github.io/svgomg/
Баги я решил не трогать до релиза. Важно было решить крупные проблемы и немного познакомиться с кодом. Поэтому 2.0 был готов уже через 4 дня.
В вебстандартах звучала мысль, что новые мейнтейнеры могут перевернуть все с ног на голову и в какой-то степени испортить проект. SVGO не движется слепо одним безумцем. Мы с @deepsweet обсуждаем все крупные изменения.
Добавляйтесь в наш дискорд сервер, если хотите участвовать в обсуждениях.
discord.gg/z8jX8NYxrE
С новым мейнтейнером хорошо бы и логотип освежить. В этом нам помог André Castillo из Мексики.
Вот она, сила коммьюнити.
Итак, время баг фиксов. В первую очередь быстро закрыл собственные косячки, затем приступил к старым багам. Несколько ребят помогли проверить и закрыть кучу ишьюсов.
В ишьюсах кинули, что svgcleaner запускает visual regressions тесты и выставляет svgo не в лучшем свете.
github.com/RazrFalcon/svg…
Вызов принят!
Спасибо playwright и pixelmatch, теперь мы запускаем 435 из 526 кейсов w3c svg test suite. Скоро добавим много другиих кейсов.
Пришло время взрослого кодинга
Регрессии поймали пару проблем с path data. Раньше svgo парсил с помощью регулярок. Закопался на сутки в спеках и запилил новенький и легонький парсер и стрингифайер Давненько я парсеров не писал. Приятно вернуться к истокам (postcss-value-parser).
Следующий челендж - это стили. Было открытием что у аттрибутов самый низкий приоритет. Поэтому конвертировать стили в аттрибуты оставляя стили в <style> - небезопасное занятие. Пришлось отключить плагин.
И все равно много багов в плагинах которые опираются только аттрибуты и инлайн стили.
Пришлось пилить свой аналог getComputedStyle.
Как результат мы имеем универсальное решение с поддержкой наследования, <style>, style= и аттриибутов. Уже три плагина пофикшены с его помощью.
И наконец я начал вести jsunderhood, чтобы рассказать обо всем. Впереди еще много работы.
Самое важное это много новых кейсов для визуальной регрессии.
Развитие идеи getComputedStyle для более надежной работы со стилями.
Замена нынешнего ast формата на "стандарный" xast от автора remark, micromark, mdx. Это позволит интегрировать существующие инструменты и создать универсальную экосистему.
И собственно создать экосистему с множеством инструментов для работы с jsx и другими фреймворкамии.
@jsunderhood Добрый день! Было бы интересно послушать в какой постоянной работе нуждается svgo. Со стороны кажется, что задача конечная. Поэтому было увлекательно послушать про внутренюю кухню
Работа конечная, но обширная. К тому же со временем svg может все таки продолжит развиваться, а новые идеи оптимизаций приходить.
twitter.com/barinbritva/st…
Тред (Богдан Чадкин)
Вторник
Сегодня расскажу как я мигрировал больше приложение с express на нативный нодовый сервер.
Идея пришла когда я взглянул сколько express тащит ненужного.
packagephobia.com/result?p=expre…
npm.anvaka.com/#/view/2d/expr…
Его API вообще кошмар, особенно с точки зрения типов.
Express патчит req и res своими пропертями и даже перезаписывает нативные. Такая конструкция очень напоминает дженгу.
Поэтому в первую очень я стал заменять специфичные для express методы на нативные.
Например, вот JSON ответ
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(data))
Не такая большая проблема указать один хедер вручную сериализовать данные.
У редиректа аналогичная структура
res.writeHead(302, { Location: url })
res.end()
Для большинства случаев двух методов writeHead и end достаточно.
Самая неприятная проблема express это отсутствие поддержки async/await. Middleware как бы могут быть асинхронными, но любое исключение приведет к зависшему роуту, а начиная с node 15 вырубит приложение.
Решают эту проблему оборачивая каждый роут в try/catch, что, конечно, не очень практично и легко забыть.
const middleware = async (req, res, next) => {
try {
await rejectedPromise
} catch (error) {
next(error)
}
}
Вернемся к началу, как создать нодовый сервер. Для примера буду использовать http. Конфигурация https не сильно отличается.
const http = require('http')
const server = http.createServer((req, res) => {
appRoute(req, res)
})
server.listen(port, host)
appRoute сам обрабатывает ошибки и может быть асинхронным. Весь код оборачивается в общий try/catch как централизованный обработчик ошибок.
const app = async (req, res) => {
try {
// app code
} catch (error) {
res.writeHead(500)
res.end(error.message)
}
}
Если использовать пакет вроде github.com/jshttp/http-er… или кастомные ошибки, можно подменять код с фолбеком на 500
res.writeHead(error.code ?? 500)
Если код ни один роут не сработал, выкидываем 404.
res.headersSent - полпулярный способ проверки, отправился ли респонз.
try {
// app code
if (res.headersSent === false) {
throw new NotFoundError();
}
} catch(error) {...}
Теперь приступим к роутам.
Отделим желток от белка
const [pathname, search] = req.url.split('?')
и взобьем венчиком
if (pathname === '/users' && req.method === 'GET') {
await usersListRoute(req, res)
}
Вуаля, во многих случаях в роутере нет необходимости.
Однако бывают требования с параметрами в роутах, к примеру, /user/:id. Воспользуемся старым добрым регэкспом
const userByIdMatch = pathname.match(/^/user/([^/]+)$/)
if (userByIdMatch) {
await userByIdRoute(req, res, { id: userByIdMatch[1] })
}
Если приложение большое и параметров много, можно воспользоваться микро роутером, с которым можно подстраивать под себя как душе угодно.
github.com/lukeed/trouter
Вот пример в sofa-api
github.com/Urigo/SOFA/blo…
Альтернатива req.query
В ноде уже давно появилcz совместимыq с вебом класс URLSearchParams. Он проще с точки зрения типов чем querystring, qs, qss и тп. Нет необходимости проверять является ли параметр массивом или строкой.
const [pathname, search] = req.url.split('?')
const searchParams = new URLSearchParams(search)
searchParams.get('id')
searchParams.getAll('item')
Альтернатива req.body
Каждый роут знает в каком формате и какие данные ему необходимы. К примеру данные переданы постом как json. Чтобы получить строку из стрима, воспользуемся микро пакетом get-stream, а затем парсим результат.
const data = JSON.parseawait getStream(req))
И наконец, что если необходимо переиспользовать готовый express middleware.
await new Promise((resolve, reject) => {
middleware(req, res, (error) => {
if (error) reject(error)
else resolve()
})
})
очепятка
const data = JSON.parse(await getStream(req))
И в заключение немного мнения. Не стоит использовать абстракции, если доступны простые решения. Разбираться в черной коробке бывает сложнее чем прочитать немного бойлерплейта.
Тред (Богдан Чадкин)
Четверг
Поговорим немного о node_modules и как сделать их меньше.
Зачем тратить на это время? Мой личный мотив это опен сорс. Я контрибьючу во множество проектов и каждые две-три недели приходиться вычищать десятки гигабайт node_modules.
Важно рассматривать несколько факторов, например, размер пакета. Пакет может дублироваться между раными репозиториями. Разные версии пакета могут дублироваться внутри одного проекта, например, так
project
- lodash@4
- webpack-bundle-analyzer
-- lodash@3
- mjml
-- lodash@3
В пакетах может быть опубликовано много лишнего, например тесты, фикстуры, конфиги редакторов.
Мой любимый пример, кто-то закомитил файлик tags. Всего-то 42MB.
unpkg.com/browse/react-f…
Решение тут простое, добавить поле "files" в package.json со списком который нужно опубликовать.
Поддерживать такой список несложно. Readme, license, changelog публикуются всегда. Достаточно только указать папку lib и/или dist.
github.com/ryanseddon/rea…
Так же пакеты часто публикуют сурсмапы, которые могут превышать размер кода в несколько раз.
Нельзя сказать что сурсмапы бесполезны и решением может быть отказ от лишних бандлов, например, в апреле когда все lts версии ноду буду поддерживать esm.
unpkg.com/browse/swagger…
.npmignore напротив довольно неэффективное решение. Список ненужного вести достаточно тяжело и такой список начинает копироваться из проекта в проект, а затем появляется новый редактор или новый инструмент или новая папочка с фикстурами, и пакет после публикации раздувается.
@jsunderhood Проблема практически сводится на нет, если использовать pnpm
Согласен, yarn pnp и pnpm эффективно решают проблему дублирования внутри одного репозитория и даже между репозиториями. Однако у нас есть yarn с node_modules и npm с большой аудиторией.
twitter.com/theDjaler/stat…
Следующий фактор - это количество прямых и транзитивных зависимостей, зачастую необоснованное.
К примеру, тот же lodash. Очень много проектов для ноды без необходимости поддерживать ie до сих пор используют lodash вместо нативных forEach,some,reduce,filter или чудесного for/of.
Другой пример нобосновынных зависимостей это micromatch - популярный glob matcher. Версия три поддерживала старую ноду, поэтому тащила кучу полифилов целый фреймворк для плагинов.
К счастью версия 4 решила эту проблему, однако многие зависимости застряли на старой версии.
Большое количество зависимостей/модулей так же негативно влияет на время запуска node приложений.
Даже если лениво загружать модули, время доставки фичи все равно увеличивается, в печальных случаях на несколько секунд. Спасибо алгоритму резолва модулей.
Как я анализирую node_modules.
Самое простое (bash only), это вывести список веток и отсортировать их размер
du -ckd1 node_modules | sort -n
Второй вариант это написать утилиту которая которая берет размер из статы каждого файла в node_modules и показывает размер каждого пакета без учета вложенных node_modules. Не пугайтесь, прошерстить всю черную дыру можно за несколько секунд.
Очень много дублированных пакетов одной версии появляется со временем после множества манипуляций с зависимостями. Для yarn есть отличная утилита yarn-deduplicate. В своем проекте я запустил ее только для пакетов babel. В результате ~0.5GB испарились.
Еще один способ анализа это чтение локфайла. У yarn.lock достаточно приятный синтакс, с package-lock.json чуть по сложнее. Но в целом появляется некая картина какие зависимости стоит обновить, а какие убрать. К примеру до сих пор можно найти inherits, xtend и прочие.
В последнее время появляется очень хорошая тенденция избавляться от большой цепочки зависимостей. Так webpack выкинул полифилы, next.js бандлит множество зависимостей с помощью ncc. jest избавиться от jsdom из коробки и старого sane вотчера.
Есть и проблемные места. Тот же jsdom не собирается отказываться от "request" который некоторое время назад задепрекейтили. Другая крайность это маниакальная жажда поддержки старых (небезопасных!) версий.
github.com/yannickcr/esli…
github.com/yannickcr/esli…
Причем вы можете увидеть что полифилы - это не безвредные однострочники в данном случае
packagephobia.com/result?p=array…
Такие проекты как find-up и множество производных, можно заменить на миниатюрный escalade или просто скопировать решение в свой проект.
github.com/sindresorhus/f…
github.com/lukeed/escalad…
Бампить мажорные версии полезно вместе с поддержкой только последних lts ноды. Тогда множество пакетов такие как util.promisify можно заменить на доступные в ноде или нативно (Object.assign).
packagephobia.com/result?p=util.…
Пятница
Резюмируя
- добавляйте "files" в package.json пакетов
- убирайте поддержку старой ноды и используйте нативные фичи
- заменяйте громоздкие пакеты на миниатюрные альтернативы
- по желанию можно бандлить зависимости (смотрите rollup и next.js)
Тред (Богдан Чадкин)
Воскресенье
На этом все. С вами был @IAmTrySound
Спасибо за просмотр!
Итак, есть такой популярный оптимизатор svg - SVGO. В последние годы у мейнтейнеров не было достаточно времени чтобы возиться с ним. И это понятно, инструмент большой и сложный, легко выгореть. github.com/svg/svgo
Здесь я рассказал как начал мейнтейнить SVGO
twitter.com/jsunderhood/st…
Сегодня расскажу как я мигрировал больше приложение с express на нативный нодовый сервер. Идея пришла когда я взглянул сколько express тащит ненужного. packagephobia.com/result?p=expre… npm.anvaka.com/#/view/2d/expr… Его API вообще кошмар, особенно с точки зрения типов.
Про замену express на нативный сервер
twitter.com/jsunderhood/st…
Поговорим немного о node_modules и как сделать их меньше. Зачем тратить на это время? Мой личный мотив это опен сорс. Я контрибьючу во множество проектов и каждые две-три недели приходиться вычищать десятки гигабайт node_modules.
И о том как я анализирую node_modules
twitter.com/jsunderhood/st…
Сорян, что было всего три темы.
Тред (Богдан Чадкин)