Сергей Мелюков – Telegram
Сергей Мелюков
667 subscribers
12 photos
3 videos
4 files
70 links
Про веб-разработку, JS, Webpack, open source, etc...
Ведет @smelukov
Download Telegram
А еще, я ищу JS-разработчиков, прямо в мою команду инфраструктуры фронта 🚀
Если для вас это актуально, то присылайте резюме в личку ☺️
https://yandex.ru/jobs/vacancies/dev/front_develop_market/
https://yandex.ru/jobs/vacancies/dev/nodejs_market/
Привет! Небольшая заметка о том, как я затаскивал поддержку больших статов в генерируемые Статоскопом HTML-отчеты.
Как мы с вами уже знаем, у JS-движков есть ограничение на максимальный размер строки (например 512мб для V8)
Это значит, что при попытке получить строку большего размера, мы получим ошибку RangeError: Invalid string length
Очевидно, что для статов размером больше 512mb тот же JSON.parse работать не будет, но как раз JSON.parse нам и нужен.
Обойти ограничение можно при помощи поточного JSON-парсера. Такому парсеру нужен источник строки, который будет отдавать ее не целиком, а кусочками (например по 64кb). По мере того, как парсер получает кусочки, он пытается их распарсить - превратить в объект. Очевидно, что этот объект растет по мере парсинга. Смысл в том, что мы не обрабатываем большие строки, а следовательно, у нас не возникает исключения RangeError, а сам объект путь себе растет пока хватает оперативной памяти.
Ну хорошо, делаю первый подход - взял поточный парсер от @rdvornov, формирую HTML и инъекчу в него код вроде такого:

const chunks = [
сюда вставляю большой JSON порубленный на строки по 64kb
];
const data = await jsonExt.parseChunked(() => chunks);
Statoscope(data);


Запускаю - не работает, браузер просто зависает 🤔
Методом проб и ошибок выяснил, что бразуер просто не переваривает такой большой тег скрипт (несколько сот мегабайт). Никакой особо полезной информации я по этому ограничению не нашел, но стало ясно, что теперь я воткнулся в проблему лимитов самого браузера. Стал думать что здесь можно сделать. Пришла идея попилить на куски не только JSON, но и теги noscript:


<noscript>
chunks.push(КУСОК_JSON)
</noscript>
<noscript>
chunks.push(СЛЕДУЮЩИЙ_КУСОК_JSON)
</noscript>

..... здесь еще много подобных тегов noscript

<noscript>
const data = await jsonExt.parseChunked(() => chunks);
Statoscope(data);
</noscript>


Запустил, заработало! Теперь я смог обойти и ограничение на JSON.parse и ограничение браузера на размер тега noscript.
Учитывая то, что загружать в отчет можно сразу несколько файлов со статами, например, для сравнения сборок, предусмотрел пуш чанка по идентификатору стата:


<noscript>
api.pushChunk("stat1.json", КУСОК_JSON)
</noscript>

<noscript>
api.pushChunk("stat2.json", КУСОК_JSON)
</noscript>

<noscript>
api.pushChunk("stat1.json", КУСОК_JSON)
</noscript>


Таким образом даже не важен порядок, в котором статы сбрасываются в отчет.

Прошло какое-то время и @rdvornov задал интересный вопрос: "Слушай, а почему бы не использовать теги noscript не как скрипт, а как текст, тогда можно было бы сэкономить на парсинге?"
И действительно, если сказать браузеру, что содержимое noscript - это не скрипт, а текст, то браузер не будет тратить время на парсинг сожержимого. А, на минуточку, 64kb x Nk тегов noscript - это ощутимо.

В итоге получилось что-то вроде:


<noscript type="text/plain" data-id="stat1.json">КУСОК_JSON</noscript>
<noscript type="text/plain" data-id="stat1.json">КУСОК_JSON</noscript>

......

<noscript>
for (const element of document.querySelectorAll('noscript')) {
api.pushChunk(element.dataset.id, element.textContent);
}

const data = await jsonExt.parseChunked(() => api.getChunks());
Statoscope(data);
</noscript>


Там конечно чуть сложнее, полную версию изменений можно посмотреть тут

Итог этой простой манипуляции такой: время загрузки отчета со статами в 650mb сократилась с 21 секунды до 14 (профит в 33%!!!)
Мораль: почти всегда можно что-то придумать, чтобы стало лучше (даже когда кажется, что нельзя) 😉
Выпустил Statoscope 3.5
Туда вошло изменение, ускоряющее получения списка npm-пакетов и их инстансов https://github.com/smelukov/statoscope/pull/43
Это значительно уменьшило время генерирования отчета при помощи плагина.
Кстати, это первый сторонний ПР в Statoscope, который что-то улучшает 🚀

А еще, видимо что-то пошло ТАК и Statoscope начали использовать на CI, т.к. количество скачиваний резко возросло https://npm-stat.com/charts.html?package=%40statoscope%2Fui-webpack 🤘🏻
Хотя, если вы большой проект, то лучше использовать кеширующий npm-proxy, например Verdaccio
Привет!
Году в 2017 я залип на соревновании return true
Вам дается функция, нужно изучить исходный код и понять что нужно передать в функцию в качестве аргумента так, чтобы функция вернула true.
Проблема в том, что задание нужно не просто решить, а сделать это за минимальное количество символов.
Есть простые задания, а есть и зубодробительные.

Я тут раскопал свои решения из первого сезона соревнования, но с тех пор вышло много других задач.
Интересно было бы видеть тут разбор этих задачек с моими размышлениями и комментариями? В виде ссылок на статьи в телеграфе (чтобы не спойлерить тем, кто хочет решить все сам)
Хотите разбор return true?
Final Results
95%
Да
5%
Нет
https://www.joinclubhouse.com/event/M1zov8YL
Сегодня в 19:00 по МСК мы с коллегами будем болтать про сборку и отвечать на вопросы, подключайтесь ;)
Сергей Мелюков
Хотите разбор return true?
Привет! Судя по результатам опроса, почти все проголосовавшие хотят разбора.
Подготовил для вас первую статью серии
Милости прошу в комменты с вопросами/предложениями по содержимому и формату 😉
За что люблю return true, так это за то, что он побуждает вгрызаться в особенности языка и иногда заставляет читать спеку, даже если не очень-то и планировалось. Например, с удивлением обнаружил, что при постинкременте, старое значение тоже приводится к числу:
f = () => 0
f++ // NaN

Я ожидал увидеть здесь f вместо NaN, т.к. это ПОСТинкремент.
Но спека говорит примерно следующее:
...
oldValue = ToNumeric(lhs)
...
Return oldValue

В результате чего, мы вполне логично получаем NaN при попытке привести функцию к числу.
О некоторых вещах часто не задумываешься и не замечаешь, пока дело не доходит до нестандартных кейсов 🤷🏻‍♂️
Друзья, спасибо за первые 300 ⭐️ Статоскопу на Гитхабе 🎉
А давайте немного поболтаем и порешаем задачки? 🙂
Как вы уже знаете, я люблю всякие челледжи и недавно я наткнулся на такую вот задачку:


You are asked to square every digit of a number and concatenate them.
For example, if we run 9119 through the function, 811181 will come out, because 9^2 is 81 and 1^2 is 1.
Note: The function accepts an integer and returns an integer


Иначе говоря: На вход функции поступает число, каждый разряд которого нужно возвести в квадрат. Полученные квадраты разрядов нужно объединить и вернуть из функции.
На входе и на выходе должны быть именно числа (`typeof x === 'number'`). Примеры:


fn(3212) // 9414
fn(9119) //811181


Как вы решали бы эту задачу? При условии, что вам надо сразу сделать так, чтобы с точки зрения кода все было круто и эффективно.
Будет круто, если пришлете кусочки кода в какой-нибудь code pen. Или просто поделитесь мыслями.

PS: очевидно, не все так просто, иначе я бы не поднял этот вопрос 😉
Чуть позже поделюсь своими мыслями и решением 😉
PPS: Возможно такой формат окажется интересным.
Читатели задают вопросы о том, что делает ~~(number / 10). Давайте разбираться, ведь одна из целей разборов этих задач - узнать что-то новое.

Оператор ~ - это "побитовое НЕ", он инвертирует биты числа на противоположные: 10101 -> 01010.

Итак, зачем это всё? Дело в том, что побитовые операции можно производить только с целыми числами, поэтому при попытка применить побитовые операции к числу с плавающей точкой, дробная часть будет отброшена и это дешевле нежели использовать Math.floor и т.п.

Тут встает вопрос в том, какой побитовый оператор использовать, ведь каждый их них как-то модифицирует число. То есть применив побитовый оператор, мы так или иначе модифицируем число, а значит нам нужно применить такой оператор, чтобы биты числа можно было восстановить в их изначальный вид. Таким оператор как раз является ~:

10101 -> 01010

А теперь, если мы снова применим этот оператор к получившемуся числу, то получим изначальный порядок бит:

01010 -> 10101

Таким образом мы очень дешево избавились от дробной части при помощи битовых операций, не повредив само число.
Завел несложный issue в Статоскоп и пометил его тегом Good for contribution
Такие задачки можно смело брать в работу если хочется поконтрибьютить 😉
Если берете себе задачку, отмечайте себя
В будущем буду еще заводить 😉
Привет!
Вышел Statoscope 4.0 📦
Накопилось 😊

- Ломающее: Удалена сборка с отдельными стилями. Теперь Статоскоп - это изолированный монолит и все его стили встроены в единый бандл.

-
Добавлена обработка дочерних комиляций
Теперь Статоскоп показывает информацию о дочерних компиляциях. Например, если вы собираете проект с веб-воркерами, то веб-воркеры собираются дочерней компиляцией и теперь Статоскоп показывает информацию о таких компиляциях.
По умолчанию такие компиляции скрыты, но при помощи настройки Hide child compilations в UI, вы можете это изменить.

-
Теперь Статоскоп показывает не просто размер чанка, а отдельно, размер чанка и размер ассетов, связанных с этим чанком (это тот самый issue, который я создавал для того, чтобы вы могли поконтрибьютит в Статоскоп, спасибо пользователю с ником immitsu на гитхабе).

- Обновил версию DiscoveryJS (спасибо @rdvornov)

-
Обновил стек сборки самого Статоскопа

-
Обновил Foam Tree

Так же хочется сказать спасибо @iamakulov и @vladkorobov за поддержку Статоскопа 🙏

PS: Впереди много планов на то, какие задачи должен решать Статоскоп и как именно он это будет делать. Оставайтесь на связи
Привет! Ох и летит время... Последний пост был ровно месяц назад 😅
Со мной все хорошо, просто стало больше проектов на работе.
Сегодня я подвез вам релиз Статоскопа 4.1.
Теперь Статоскоп стартует на 25% быстрее (за счет оптимизации логики нормализации данных), а еще в разы улучшил скорость сравнения статов. Статы, которые раньше сравнивались за 25 секунд, теперь сравниваются за сотни миллисекунд.
Как только появится чуть больше свободного времени, Статоскоп ждет большой рефакторинг с целью декомпозировать его на разные независимые части. Это позволит наконец покрыть его тестами и реиспользовать логику анализа статов в разных окружениях (например в CLI на CI). А еще, я буду пробовать внедрять собственный универсальный формат статов (не webpack-specific). Это позволит в разы уменьшить потребление ресурсов при обработке статов и размер самих статов.

P.S.: Спасибо пользователю с ником TchernyavskyD за поддержку Статоскопа на OpenCollective 🙏
Друзья, я выпустил Statoscope 5.0
Главная фича этого релиза в том, что статоскоп наконец может работать как CLI-инструмент 🎉

statoscope serve stats.json
Поднять HTTP-сервер с HTML-отчетом по указанным статам (можно указать несколько и тогда они будут объеденены)

statoscope generate stats.json report.html
Сгенирировать HTML-отчет из статов (можно указать несколько и тогда они будут объеденены)

И на десерт киллер фича - провалидировать статы (или разницу в статах):
statoscope validate rules.js stats.json

Супер полезно на CI.
Примеры можно посмотреть в README

⚠️ Важно: пакет @statoscope/ui-webpack был распилен на 2:
- @statoscope/webpack-plugin
- @statoscope/webpack-ui
Если вы использовали статоскоп как webpack-плагин, то юзайте пакет @statoscope/webpack-plugin
Api не изменилось ☺️

В этом релизе я был сконцентрирован на декомпозиции большого пакета ui-webpack на несколько мелких.
Так же у статоскопа теперь своя github-организация.

Дальше будет переезд на TS и покрытие тестами. А еще дальше буду пробовать собственный формат статов и двигать его в массы, так сказать :)

А еще, хочу уделять больше времени написанию технических постов (давно ничего не писал, исправлюсь)
Всем привет!
Я наконец написал технический пост на тему внутренного устройства Statoscope - https://medium.com/@smelukov/метаинформация-о-сборке-5469a7ab6aa4
Заходите, читайте, комментируйте, задавайте вопросы ☺️
Ох, как интересно, в webpack 5.49 можно импортировать http-модули 🚀
Импортированные модули кладутся в локальный кеш, поддерживается его обновление. Консистентность обеспечиваешь хешиком(как в package-lock)

Пример из директории examples

Доступно под флагом experiments.buildHttp