Привет! Я Сергей Мелюков, инженер в области разработки веб-ПО.
За время работы у меня накопилось какое-то количество опыта в разных областях: сборка, тесты, архитектура и пр.
Этим опытом хочется делиться, хочется двигаться дальше и узнавать что-то новое. Для этого буду писать здесь посты о том, что мне интерес но. Возможно это будет интересно и вам
Мне интересны всякие нетривиальные штуки типа: работа с ast, кастомные сборщики и тест-раннеры, копание в исходниках больших проектов.
В ближайшее время будет какое-то количество постов про webpack 5, буду рассказывать о том как он работает, какие фичи ожидать, как их использовать и какой вклад лично я вношу в развитие webpack.
Сейчас я сконцентрирован на улучшении Development Experience и довольно часто ко мне за помощью обращаются люди из разных компаний и я решил оказывать такую помощь под эгидой https://wdx-lab.com/
WDX Lab - это мой личный небольшой стартап, через который я хочу помогать другим (не всегда бесплатно, конечно 😅) и развиваться сам.
За время работы у меня накопилось какое-то количество опыта в разных областях: сборка, тесты, архитектура и пр.
Этим опытом хочется делиться, хочется двигаться дальше и узнавать что-то новое. Для этого буду писать здесь посты о том, что мне интерес но. Возможно это будет интересно и вам
Мне интересны всякие нетривиальные штуки типа: работа с ast, кастомные сборщики и тест-раннеры, копание в исходниках больших проектов.
В ближайшее время будет какое-то количество постов про webpack 5, буду рассказывать о том как он работает, какие фичи ожидать, как их использовать и какой вклад лично я вношу в развитие webpack.
Сейчас я сконцентрирован на улучшении Development Experience и довольно часто ко мне за помощью обращаются люди из разных компаний и я решил оказывать такую помощь под эгидой https://wdx-lab.com/
WDX Lab - это мой личный небольшой стартап, через который я хочу помогать другим (не всегда бесплатно, конечно 😅) и развиваться сам.
Сергей Мелюков pinned «Привет! Я Сергей Мелюков, инженер в области разработки веб-ПО. За время работы у меня накопилось какое-то количество опыта в разных областях: сборка, тесты, архитектура и пр. Этим опытом хочется делиться, хочется двигаться дальше и узнавать что-то новое.…»
Та же статья, но на английском - https://dev.to/smelukov/webpack-5-asset-modules-2o3h
Хочется расширить круг читателей 😊
Хочется расширить круг читателей 😊
DEV Community
Webpack 5 - Asset Modules
Hello. This post starts a series of articles about the new features of coming webpack 5. Why do I wan...
📝 В webpack 5 можно использовать кастомный парсер для json-модулей. Например, если вы используете yaml-файлы и вас не устраивает тот yaml-парсер который используется в yaml-loader, то вместо использования yaml-loader можно просто указать желаемый парсер:
Рад сообщить, что я приложил руку к реализации как Asset Modules, так и этой фичи ☺️
Совсем скоро будет еще одна статья про новую фичу в webpack 5, которая позволит улучшить code-splitting
#webpack #webpack5
{
test: /\.yaml$/,
type: "json",
parser: {
parse(content) {
return myCustomYamlParser.parse(content);
}
}
}
Да, это не та фича, которую вы будете использовать каждый день, но webpack - это инструмент, и теперь вы знаете о нем больше ☝🏻Рад сообщить, что я приложил руку к реализации как Asset Modules, так и этой фичи ☺️
Совсем скоро будет еще одна статья про новую фичу в webpack 5, которая позволит улучшить code-splitting
#webpack #webpack5
📝 Недавно был замержен еще один мой пулл реквест, но на сей раз в webpack-cli. Смысл этого PR в том, чтобы при запуске вебпака можно было передавать дополнительные параметры для node-процесса. Например, чтобы увеличить лимит доступной процессу памяти, можно запустить webpack так:
Раньше это делалось примерно так:
📚Для справки: webpack-cli - это пакет, в который выносится вся работа с аргументами при запуске вебпака из командной строки. По сути он помогает доопределить ваш конфиг из аргументов командной строки. Вы всегда устанавливаете этот пакет (без него вебпак из консоли не запустить), но напрямую вы его не запускаете и не используете. Предположим, что вы запустили сборку из консоли командой “webpack”
С версией webpack-cli@3 работать это будет так:
- webpack проверит наличие установленного webpack-cli
- если нет, то предложит его установить
- если есть, то подключит его (через require)
- подключенный webpack-cli обработает аргументы из консоли, создает инстанс вебпака (в том же процессе) и запустит его.
В версии webpack-cli@4 все то же самое, только webpack-cli запускает webpack в отдельном процессе, что дает изоляцию и возможность указывать любые аргументы для node-процесса.
На самом деле в webpack-cli@4 довольно много и других изменений. Буду рассказывать по мере надобности, интересности и релизов.
#webpack #cli
webpack --node-args="--max-old-space-size=4096"
Передать можно любое количество аргументов.Раньше это делалось примерно так:
node --max-old-space-size=4096 ./node_modules/.bin/webpack
Нововведение уже доступно в webpack-cli@4📚Для справки: webpack-cli - это пакет, в который выносится вся работа с аргументами при запуске вебпака из командной строки. По сути он помогает доопределить ваш конфиг из аргументов командной строки. Вы всегда устанавливаете этот пакет (без него вебпак из консоли не запустить), но напрямую вы его не запускаете и не используете. Предположим, что вы запустили сборку из консоли командой “webpack”
С версией webpack-cli@3 работать это будет так:
- webpack проверит наличие установленного webpack-cli
- если нет, то предложит его установить
- если есть, то подключит его (через require)
- подключенный webpack-cli обработает аргументы из консоли, создает инстанс вебпака (в том же процессе) и запустит его.
В версии webpack-cli@4 все то же самое, только webpack-cli запускает webpack в отдельном процессе, что дает изоляцию и возможность указывать любые аргументы для node-процесса.
На самом деле в webpack-cli@4 довольно много и других изменений. Буду рассказывать по мере надобности, интересности и релизов.
#webpack #cli
📝 Собирая большой проект вебпаком можно заметить, что прогресс сборки довольно долгое время может висеть примерно на 66-68%. Это связано с тем, что по умолчанию прогресс рассчитывается как отношение количества уже собранных модулей на общее количество модулей. Вроде бы логично, но проблема в том, что вебпак заранее не знает сколько всего модулей в проекте, потому как в любой момент времени любой лоадер может докинуть еще пару сотен модулей. Общее количеством модулей растет по мере сборки, растет и количество собранных модулей, отсюда и дерганье процента прогресса.
Выйти из положения можно явно указав в настройках
То есть сначала нужно один раз собрать проект, как-то подсмотреть общее количество модулей и затем указать эту цифру в настройках плагина и обновлять ее вручную с какой-то периодичностью.
Можно еще написать скрипт, который перед стартом сборки считает количество файлов в проекте и указывает эту цифру в конфиге
В общем, такие себе решения…
Года полтора назад, переводя на вебпак действительно большой проект, я подумал о том, что для больших проектов лучше считать процент не по количеcтву модулей, а по количество точек входа, коих в больших проектах всегда много, скорее всего никому не придет в голову загонять огромный проект под одну точку входа, за редким исключением. Сделал PR в вебпак. Его смысл был в том, чтобы считать прогресс как отношение количества собранных точек входа к общему количеству точек входа. Преимущество такого подхода в том, что общее количество точек входа всегда известно заранее и во время сборки не изменяется.
Мой PR тогда приняли только наполовину, т.к. мне не удалось договориться с Тобиасом (автор вебпака) по поводу того, как реализовать пересчет процентов на основании точек входа и мы решили оставить только текстовое инфо. То есть теперь в консоли хотя бы можно было видеть что-то вроде
Буквально недавно замержили еще один мой PR в webpack 5, в котором можно выбирать как именно нужно высчитывать процент готовности. По умолчанию, процент все так же считается по количеству модулей, но через параметр
Но и это еще не всё, пока я писал этот пост, мне в голову пришла еще одна, казалось бы очевидная, идея.
Я подумал о том, что когда вебпак заканчивает сборку, можно сохранять в кеше количество собранных модулей и восстанавливать их на старте следующей сборки.
Да, первая сборка будет разогревать кеш, а последующие уже будут использовать инфо из кеша и обновлять его автоматически.
Вряд ли между сборками кодовая база изменится так, что количество модулей резко изменится, ну или вряд ли это будет происходить слишком часто.
P.S.: Добавил этот пост в канал сразу же после того, как мой PR был замержен в мастер 😅
Можно будет попробовать в beta.14
#webpack #webpack5
Выйти из положения можно явно указав в настройках
ProgressPlugin
примерное количество модулей в вашем проекте:new ProgressPlugin({ modulesCount: 10000 });
То есть сначала нужно один раз собрать проект, как-то подсмотреть общее количество модулей и затем указать эту цифру в настройках плагина и обновлять ее вручную с какой-то периодичностью.
Можно еще написать скрипт, который перед стартом сборки считает количество файлов в проекте и указывает эту цифру в конфиге
ProgressPlugin
. Это из расчета, что одному файлу соответствует один модуль, а это не всегда так. Плюс ко всему, не понятно как считать node_modules
, т.к. не все модули могут использоваться в вашем проекте.В общем, такие себе решения…
Года полтора назад, переводя на вебпак действительно большой проект, я подумал о том, что для больших проектов лучше считать процент не по количеcтву модулей, а по количество точек входа, коих в больших проектах всегда много, скорее всего никому не придет в голову загонять огромный проект под одну точку входа, за редким исключением. Сделал PR в вебпак. Его смысл был в том, чтобы считать прогресс как отношение количества собранных точек входа к общему количеству точек входа. Преимущество такого подхода в том, что общее количество точек входа всегда известно заранее и во время сборки не изменяется.
Мой PR тогда приняли только наполовину, т.к. мне не удалось договориться с Тобиасом (автор вебпака) по поводу того, как реализовать пересчет процентов на основании точек входа и мы решили оставить только текстовое инфо. То есть теперь в консоли хотя бы можно было видеть что-то вроде
252/420
- то есть сколько точек входа собрано и сколько их всего.Буквально недавно замержили еще один мой PR в webpack 5, в котором можно выбирать как именно нужно высчитывать процент готовности. По умолчанию, процент все так же считается по количеству модулей, но через параметр
percentBy
это поведение можно изменить, например, чтобы начать считать процент готовности по точкам входа, то достаточно сделать так:new ProgressPlugin({ percentBy: 'entries' });
Но и это еще не всё, пока я писал этот пост, мне в голову пришла еще одна, казалось бы очевидная, идея.
Я подумал о том, что когда вебпак заканчивает сборку, можно сохранять в кеше количество собранных модулей и восстанавливать их на старте следующей сборки.
Да, первая сборка будет разогревать кеш, а последующие уже будут использовать инфо из кеша и обновлять его автоматически.
Вряд ли между сборками кодовая база изменится так, что количество модулей резко изменится, ну или вряд ли это будет происходить слишком часто.
P.S.: Добавил этот пост в канал сразу же после того, как мой PR был замержен в мастер 😅
Можно будет попробовать в beta.14
#webpack #webpack5
🗓 10 апреля на HolyJS расскажу как переводил огромный фронтенд Авито на вебпак, с какими трудностями столкнулся и как их решил https://holyjs-piter.ru/2020/spb/talks/5j5rzpe4zskxtbdc0zfo4y/
📝 webpack 5 больше не будет самостоятельно встраивать в бандл полифилы для стандартных node-модулей (path, events, crypto, etc...). Теперь необходимо явно указывать какой модуль и каким полифилом вы хотите заменить:
Установить необходимый полифил:
Явно указать его в конфиге:
#webpack #webpack5
Установить необходимый полифил:
$ npm i path-browserify -D
Явно указать его в конфиге:
module.exports = {
resolve: {
alias: {
path: 'path-browserify'
}
}
}
#webpack #webpack5
📝 Изначально, это должна была быть небольшая заметка о том, как работает долгосрочное кеширование в webpack 5, но когда я ее писал, понял, что начинаю рассказывать о том, как устроена сборка и наводить порядок в терминологии, в которой многие путаются. "А почему бы и нет" - подумал я. Поэтому в этот раз поговорим про основные вещи, а в следующий раз уже про long term caching.
Упрощенно, сборка - это функция, на вход которой поступают одни файлы, а на выходе получаются другие. Но между файлами на входе и на выходе есть еще модули, точки входа, чанки и группы чанков. На самом деле, промежуточных стадий намного больше, но поговорим хотя бы об этих. Обо всем по-порядку...
Каждый файл, который используется в вашем проекте - это модуль (Module).
./a.js:
./b.js:
Используя друг-друга, модули образуют граф модулей (ModuleGraph)
Модули не существуют сами по себе, а объединяются в чанки (Chunk).
У каждого чанка есть соответствующий ему файл - ассет (Asset). Это есть выходные файлы - результат сборки.
Чанки, как и модули, образуют граф (ChunkGraph) т.к. связаны между собой через модули и тоже не существуют само по себе, а объединяются в группы (ChunkGroup).
Каждый раз, когда вы задаете точку входа в конфиге вебпака, вы создаете группу чанков с одним чанком. В этом чанке будут те модули, которые вы укажете как точку входа, это и будет отправной точкой:
./webpack.config.js:
Будет создана одна группа чанков с именем
Еще пример:
./webpack.config.js:
Будет создано две группы чанков с именами
В каждой из них будет по одному чанку с одним модулем -
У каждого модуля и чанка есть свой уникальный идентификатор - это важно.
Если подытожить, то сборку можно представить себе так:
Это та база, которую необходимо знать и не путаться.
Чанков на группу может быть и больше одного, но об этом как-нибудь в другой раз, например, когда буду подробнее писать про оптимизацию сборки в webpack.
#webpack #webpack5
Упрощенно, сборка - это функция, на вход которой поступают одни файлы, а на выходе получаются другие. Но между файлами на входе и на выходе есть еще модули, точки входа, чанки и группы чанков. На самом деле, промежуточных стадий намного больше, но поговорим хотя бы об этих. Обо всем по-порядку...
Каждый файл, который используется в вашем проекте - это модуль (Module).
./a.js:
import foo from './b.js';
./b.js:
export default 123;
Используя друг-друга, модули образуют граф модулей (ModuleGraph)
Модули не существуют сами по себе, а объединяются в чанки (Chunk).
У каждого чанка есть соответствующий ему файл - ассет (Asset). Это есть выходные файлы - результат сборки.
Чанки, как и модули, образуют граф (ChunkGraph) т.к. связаны между собой через модули и тоже не существуют само по себе, а объединяются в группы (ChunkGroup).
Каждый раз, когда вы задаете точку входа в конфиге вебпака, вы создаете группу чанков с одним чанком. В этом чанке будут те модули, которые вы укажете как точку входа, это и будет отправной точкой:
./webpack.config.js:
module.exports = {
entry: './a.js'
};
Будет создана одна группа чанков с именем
main
(имя точки входа по умолчанию), которая будет состоять из одного чанка, в который будет входить один модуль ./a.js
. По мере того, как парсер будет обрабатывать импорты внутри ./a.js
в чанк будут добавляться новые модули.Еще пример:
./webpack.config.js:
module.exports = {
entry: {
home: './home.js',
about: './about.js'
}
};
Будет создано две группы чанков с именами
home
и about
соответственно.В каждой из них будет по одному чанку с одним модулем -
./home.js
и ./about.js
У каждого модуля и чанка есть свой уникальный идентификатор - это важно.
Если подытожить, то сборку можно представить себе так:
files
-> modules
-> chunks
-> assets
-> files
Это та база, которую необходимо знать и не путаться.
Чанков на группу может быть и больше одного, но об этом как-нибудь в другой раз, например, когда буду подробнее писать про оптимизацию сборки в webpack.
#webpack #webpack5
📝 Чем хорош свой телеграм-канал, так это свободой формата. Подумал о том, что можно (и даже нужно) накинуть еще "базы" по тому, как устроена сборка, из чего она состоит.
В прошлом посте мы выяснили, что точки входа превращаются в чанки, а те в ассеты (выходные файлы).
Так же надо отметить, что чанки бывают двух видов -
initial чанк - это основной чанк точки входа, тот самый чанк, в который "упаковываются" те модули (и их подмодули), которые вы указываете, когда описываете точку входа.
non-initial чанк - это все остальные чанки (спасибо кэп!).
non-initlal чанк может появиться, например, в следствии использования динамического импорта:
webpack.config.js
./src/index.js
Здесь будет создан initial чанк с именем
-
-
-
и все их подмодули, кроме
Т.к.
-
-
У non-initial чанков по умолчанию нет собственного имени, поэтому вместо имени будет использован их уникальный идентификатор. В случае с динамическим импортом мы можем явно указать имя для чанка при помощи "магического" комментария:
На выходе получим:
-
-
Так же non-initial чанки могут появляться в результате использования
На имена выходных файлов влияют два поля в конфиге:
В этих полях можно использовать плейсхолдеры, чаще всего эти:
-
-
-
#webpack
В прошлом посте мы выяснили, что точки входа превращаются в чанки, а те в ассеты (выходные файлы).
Так же надо отметить, что чанки бывают двух видов -
initial
и non-initial
initial чанк - это основной чанк точки входа, тот самый чанк, в который "упаковываются" те модули (и их подмодули), которые вы указываете, когда описываете точку входа.
non-initial чанк - это все остальные чанки (спасибо кэп!).
non-initlal чанк может появиться, например, в следствии использования динамического импорта:
webpack.config.js
module.exports = {
entry: './src/index.jsx'
}
./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
const App = await import('./app.jsx');
ReactDOM.render(<App />, root);
Здесь будет создан initial чанк с именем
main
, в него будут входить:-
./src/index.jsx
-
react
-
react-dom
и все их подмодули, кроме
./app.jsx
Т.к.
./app.jsx
был импортирован динамически, для него будет создан отдельный non-initial чанк, куда и будет помещен сам ./app.jsx
с его подмодулями. То есть на выходе мы получим что-то вроде:-
/dist/main.js
- initial чанк -
/dist/394.js
- non-initial чанкУ non-initial чанков по умолчанию нет собственного имени, поэтому вместо имени будет использован их уникальный идентификатор. В случае с динамическим импортом мы можем явно указать имя для чанка при помощи "магического" комментария:
const app = await import(
/* webpackChunkName: "app" */
'./app'
);
На выходе получим:
-
/dist/main.js
- initial чанк -
/dist/app.js
- non-initial чанкТак же non-initial чанки могут появляться в результате использования
SplitChunkPlugin
, но об этом как-нибудь в другой раз.На имена выходных файлов влияют два поля в конфиге:
output.filename
- имена файлов для initial чанковoutput.chunkFilename
- имена файлов для non-initial чанковВ этих полях можно использовать плейсхолдеры, чаще всего эти:
-
[id]
- идентификатор чанка. Например [id].js
-> 485.js
-
[name]
- имя чанка. Например [name].js
-> app.js
-
[contenthash]
- md4-хеш от контента выходного файла. Например [contenthash].js
-> 4ea6ff1de66c537eb9b2.js
#webpack
📝 О том, как устроено Long Term Caching в webpack 5.
Оно основано на хешировани контента ассета:
webpack.config.js
Будет создан чанк
То есть имя файла напрямую зависит от его сожержимого. Как только оно изменится, изменится и имя файла. Что является содержимим файла-ассета? По сути это карта модулей, которые входят в чанк и некоторое количество кода-загрузчика.
Проблема в том, что модули могут быть обработаны в любом порядке, поэтому порядок их сборки не гарантируется. Это совсем не то, что нам нужно, т.к. нам важна консистентность содержимого неизменившихся ассетов между сборками и порядок модулей в них. В качестве идентификаторов модулей можно использовать путь к модулю, например:
Но это сильно раздувает размер ассета, поэтому вместо полного пути к модулю используется хеш-функция от него, которая возвращает целое число.
Для одного и того же модуля всегда возвращается одно и то же число. А для того, чтобы порядок модулей в ассете был всегда одинаковым, модули сортируются по путям до них.
С идентификаторами чанком то же самое, но т.к. у чанка нет своего пути (т.к. это не модуль), то хеш-функция применяется к объединению путей всех модулей внутри этого чанка.
Таким образом в webpack 5 достигается консистентность сожержимого неизменившихся ассетов между сборками.
Про чанки и прочие основные понятия смотрите предыдущие два поста ☝🏻
Чуть позже расскажу о том, как реализован code-splitting и о системе постоянного кеширования.
Пишите в чатик @wdxlabchat о чем еще хотели бы узнать.
#webpack #webpack5
Оно основано на хешировани контента ассета:
webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: '[contenthash].js'
}
};
Будет создан чанк
main
, а контент его ассета будет пропущен через md4
, этим хешем и будет назван выходной файл, что-то вроде dist/6c4c0000f0570789e1e3.js
.То есть имя файла напрямую зависит от его сожержимого. Как только оно изменится, изменится и имя файла. Что является содержимим файла-ассета? По сути это карта модулей, которые входят в чанк и некоторое количество кода-загрузчика.
(() => {
// bootstrap code
const modules = {
['module 1 id']() {
// transformed module 1 code
},
['module 2 id']() {
// transformed module 2 code
},
// ... other modules
}
// bootstrap code
})()
Проблема в том, что модули могут быть обработаны в любом порядке, поэтому порядок их сборки не гарантируется. Это совсем не то, что нам нужно, т.к. нам важна консистентность содержимого неизменившихся ассетов между сборками и порядок модулей в них. В качестве идентификаторов модулей можно использовать путь к модулю, например:
(() => {
// bootstrap code
const modules = {
'/abs/path/to/project/src/index.js'() {
// transformed index-module code
},
'/abs/path/to/project/src/app.js'() {
// transformed app-module code
},
// ... other modules
}
// bootstrap code
})()
Но это сильно раздувает размер ассета, поэтому вместо полного пути к модулю используется хеш-функция от него, которая возвращает целое число.
f('/abs/path/to/project/src/index.js') => 389
f('/abs/path/to/project/src/app.js') => 187
(() => {
// bootstrap code
const modules = {
389() {
// transformed index-module code
},
187() {
// transformed app-module code
},
// ... other modules
}
// bootstrap code
})()
Для одного и того же модуля всегда возвращается одно и то же число. А для того, чтобы порядок модулей в ассете был всегда одинаковым, модули сортируются по путям до них.
С идентификаторами чанком то же самое, но т.к. у чанка нет своего пути (т.к. это не модуль), то хеш-функция применяется к объединению путей всех модулей внутри этого чанка.
Таким образом в webpack 5 достигается консистентность сожержимого неизменившихся ассетов между сборками.
Про чанки и прочие основные понятия смотрите предыдущие два поста ☝🏻
Чуть позже расскажу о том, как реализован code-splitting и о системе постоянного кеширования.
Пишите в чатик @wdxlabchat о чем еще хотели бы узнать.
#webpack #webpack5
✍🏻 Мои мысли по поводу основных понятий сборки теперь изложены в официальной документации - https://webpack.js.org/concepts/under-the-hood/
Буду постепенно пополнять
#webpack #docs
Буду постепенно пополнять
#webpack #docs
webpack
Under The Hood | webpack
webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.
📝 webpack двигается в сторону micro frontends.
Micro frontends - это идея микросервисов переложенная на фронтенд, то есть части страницы делятся на самостоятельные изолированные приложения. Эти части могут быть реализованы разными командами и на разном технологическом стеке.
https://micro-frontends.org
Представим себе крупный сервис с множеством функциональных частей: поиск, фильтры, каталог, реклама, навигация и т.д.
Очень часто эти части могут встречаться на одной странице, но при этом разрабатываться разными командами и с использованием разных подходов к разработке (ведь каждая команда лучше знает "как правильно" ;)). В этом случае встает некоторое количество вопросов, например:
Как разделить большую кодовую базу на микро-приложения?
Здесь важна изоляция, чтобы части страницы можно было разрабатывать независимо. То есть необходимо создать приложение-конструктор, части которого можно менять.
Как части приложения могут "общаться" между собой?
Например, в условиях SPA (single page application) фильтры могут влиять на каталог.
Как обеспечить реиспользование зависимостей?
Например, на странице загружен блок, который использует
Около месяца назад было предложено архитектурное решение для webpack https://github.com/webpack/webpack/issues/10352 и буквально несколько дней назад были сделаны первые демо.
Описание решения можно найти тут https://medium.com/@ScriptedAlchemy/bcdd30e02669, а потыкать можно вот эту демку https://github.com/mizx/mfe-webpack-demo
Если коротко, то идея заключается в том, что совершенно разные собранные бандлы могут реиспользовать различные части друг-друга - одни бандлы предоставляют (expose) свои части (например UI-компоненты или целое приложение), а другие бандлы эти части импортируют и используют.
Фича все еще в разработке
#webpack #webpack5 #microfrontends
Micro frontends - это идея микросервисов переложенная на фронтенд, то есть части страницы делятся на самостоятельные изолированные приложения. Эти части могут быть реализованы разными командами и на разном технологическом стеке.
https://micro-frontends.org
Представим себе крупный сервис с множеством функциональных частей: поиск, фильтры, каталог, реклама, навигация и т.д.
Очень часто эти части могут встречаться на одной странице, но при этом разрабатываться разными командами и с использованием разных подходов к разработке (ведь каждая команда лучше знает "как правильно" ;)). В этом случае встает некоторое количество вопросов, например:
Как разделить большую кодовую базу на микро-приложения?
Здесь важна изоляция, чтобы части страницы можно было разрабатывать независимо. То есть необходимо создать приложение-конструктор, части которого можно менять.
Как части приложения могут "общаться" между собой?
Например, в условиях SPA (single page application) фильтры могут влиять на каталог.
Как обеспечить реиспользование зависимостей?
Например, на странице загружен блок, который использует
react
и redux
. Чуть позже, на страницу, динамически, подъезжает второй блок, который тоже использует react
. Проблема в том, что этот блок разрабатывается другой командой, собирается и релизится отдельно от первого. Здесь необходимо обеспечить совместимость двух блоков и реиспользование уже загруженных на страницу зависимостей - второй блок не должен заново загружать на страницу react
, а должен реиспользовать уже загруженный.Около месяца назад было предложено архитектурное решение для webpack https://github.com/webpack/webpack/issues/10352 и буквально несколько дней назад были сделаны первые демо.
Описание решения можно найти тут https://medium.com/@ScriptedAlchemy/bcdd30e02669, а потыкать можно вот эту демку https://github.com/mizx/mfe-webpack-demo
Если коротко, то идея заключается в том, что совершенно разные собранные бандлы могут реиспользовать различные части друг-друга - одни бандлы предоставляют (expose) свои части (например UI-компоненты или целое приложение), а другие бандлы эти части импортируют и используют.
Фича все еще в разработке
#webpack #webpack5 #microfrontends
Micro Frontends
Micro Frontends - extending the microservice idea to frontend development
Techniques, strategies and recipes for building a modern web app with multiple teams using different JavaScript frameworks.
✏️ От одного из авторов вышеописанного решения вышла статья с примерами реального использования https://dev.to/marais/webpack-5-and-module-federation-4j1i
Для использования решения предусмотрен плагин
Для использования решения предусмотрен плагин
ModuleFederationPlugin
, в настройках которого вы описываете свою часть "конструктора": какие зависимости (react, redux, etc...) будут шариться с другими частями. Если зависимость уже есть на странице, то она не будет заново загружена. Так же вы описываете какими частями своего приложения вы хотите делиться с другими.DEV Community
Webpack 5 and Module Federation - A Microfrontend Revolution
Picture this: you've got yourself a pretty whiz-bang component, not just any component, but that clas...
📝 Мои мысли по поводу поиска неиспользуемого кода (dead code) и его устранения (DCE - dead code elimination).
Для начала, давайте определимся в каких ситуациях код может никогда не исполняться:
Что-то пошло не так
Второй
Да, бывает и такое, но чаще всего это недосмотр разработчика.
В таких ситуациях обычно помогает любой минификатор:
Недостижимые ветки
Представим, что есть переменная
После рефакторинга мы избавились от значения
Неиспользуемые экспорты
Представим, что есть модуль, которых что-то экспортирует, но это никому не нужно и никто это не импортирует (полностью или частично):
app.js
lib.js
Такая ситуация может возникнуть, когда вы используете только часть какой-то библиотеки.
С такими кейсами справляется tree shaking - бандлер (например
Этот метод работает по нижней границе - то есть вырезает код, который точно не используется.
Почему по нижней? Потому что не всегда можно точно определить используется экспорт или нет. Например, в webpack 4 - очень слабая поддержка tree shaking для
Неиспользуемый CSS
Для CSS история похожа на
Есть оптимизаторы, которые вырезают неиспользуемый CSS, например
Неиспользуемые файлы
В кодовой базе могут быть файлы, которые нигде не подключаются. Если вы используете сборщик, то можно использовать плагин, который получает список всех файлов в директории с проектом и удаляет из этого списка те файлы, которые были использованы при сборке. В итоге получается список файлов, которые не были использованы при сборке и их, можно считать неиспользуемыми. Главное здесь соблюдать осторожность при удалении файлов, т.к. какие-то файлы могут не проходить через сборщик и подключаться на страницу просто по ссылке. Но такие файлы лучше держать в отдельной директории.
Пример плагина для вебпака: https://www.npmjs.com/package/webpack-deadcode-plugin
Для начала, давайте определимся в каких ситуациях код может никогда не исполняться:
Что-то пошло не так
function foo() {
console.log(1); // сработает
return true;
console.log(2); // никогда не сработает
}
Второй
console.log
- это недостижимый кодДа, бывает и такое, но чаще всего это недосмотр разработчика.
В таких ситуациях обычно помогает любой минификатор:
uglifyjs
, terser
, closure compiler
Недостижимые ветки
Представим, что есть переменная
foo
, которая может принимать одно из трех значений - bar
, baz
или quux
и эти значения мы хотим как-то обрабатывать:switch (foo) {
case 'bar': ...
case 'baz': ...
case 'quux': ...
}
После рефакторинга мы избавились от значения
quux
и foo
теперь никогда не сможет его принимать, следовательно, последняя ветка будет недостижимой. Есть вероятность, что мы забудем убрать его обработку из свитча. В таком случае минификаторы бесполезны. В этом случае поможет типизация:enum Foo {
bar, baz
}
switch (foo) {
case Foo.bar: ...
case Foo.baz: ...
case Foo.quux: ... // получим ошибку
}
Неиспользуемые экспорты
Представим, что есть модуль, которых что-то экспортирует, но это никому не нужно и никто это не импортирует (полностью или частично):
app.js
import { bar } from './lib.js';
lib.js
export function foo() { ... } // неиспользуемый экспорт
export function bar() { ... }
Такая ситуация может возникнуть, когда вы используете только часть какой-то библиотеки.
С такими кейсами справляется tree shaking - бандлер (например
webpack`) отслеживает все экспорты, которые нигде не используются (не импортируются) и вырезает их. Далее, js-компрессор (например `uglifyjs
или `tercer`) вырезает весь код, который был завязан на эти неиспользуемые экспорты (например функции который были экспортированы).Этот метод работает по нижней границе - то есть вырезает код, который точно не используется.
Почему по нижней? Потому что не всегда можно точно определить используется экспорт или нет. Например, в webpack 4 - очень слабая поддержка tree shaking для
commonjs
, но она намного лучше реализована в webpack 5 (учтено больше кейсов, но все еще в разработке). Плюс, если в модуле используется eval
или new Function
, то модуль сразу помечается как исключенный из оптимизации, т.к. в этом случае вообще ничего нельзя гаратировать (кто его знает что там за код внутри eval).Неиспользуемый CSS
Для CSS история похожа на
commonjs
тем, что там тоже много вариантов того, как можно использовать класс.Есть оптимизаторы, которые вырезают неиспользуемый CSS, например
CSSO
. Но CSSO основывается не на анализе подключения CSS, а на внешнем списке используемых классов. То есть на вход CSSO поступает список классов, которые точно используются а на выходе получается CSS, который содержит декларации только для этих классов. Проблема тут в том, что надо как-то получить список классов, которые точно используются. Но использование классов в коде совершенно не означает, что сам этот код где-то используется.Неиспользуемые файлы
В кодовой базе могут быть файлы, которые нигде не подключаются. Если вы используете сборщик, то можно использовать плагин, который получает список всех файлов в директории с проектом и удаляет из этого списка те файлы, которые были использованы при сборке. В итоге получается список файлов, которые не были использованы при сборке и их, можно считать неиспользуемыми. Главное здесь соблюдать осторожность при удалении файлов, т.к. какие-то файлы могут не проходить через сборщик и подключаться на страницу просто по ссылке. Но такие файлы лучше держать в отдельной директории.
Пример плагина для вебпака: https://www.npmjs.com/package/webpack-deadcode-plugin
npm
npm: webpack-deadcode-plugin
Webpack plugin to detect unused files and unused exports in used files. Latest version: 0.1.17, last published: 3 years ago. Start using webpack-deadcode-plugin in your project by running `npm i webpack-deadcode-plugin`. There are 4 other projects in the…
Неиспользуемые зависимости
Какие-то зависимости из
Этот инструмент проанализирует импорты внешних зависимостей и сравнит их со списком зависимостей из
Недостижимые use-cases
Это отдельный класс проблем по поиску мертвого кода. Например, при нажатии на кнопку нужно выполнить какой-то код, но кнопка по какой-то причине не нажимается (съехала из вьюпорта, в принципе скрыта или в обработчике события ошибка, которая не дает выполниться части кода).
Здесь не поможет ни минификатор, ни типизация. Для таких кейсов нужно писать e2e тесты, эмулировать в них действия пользователя и делать проверки. Логика здесь следующая: выполнили тесты, составили отчет по code coverage и весь код, который помечен в отчете красным - недостижим. Но это очень скользкий способ т.к. нужно иметь ввиду, что код может быть недостижим просто потому, что мы не написали под него тест-кейс. Соответственно этот способ требует от разработчика/QA максимальной концентрации и пребывания в контексте бизнес-логики. Тут же встает вопрос о том, где эти тест-кейсы хранить и как их синхронизировать.
Более честным вариантом здесь будет отслеживать действия реальных пользователей и собирать по ним code coverage. Если в течение какого-то времени код так и не "покрасился" в зеленый, значит пользователи не попадают на этот кейс и код для них не достижим.
Вопрос который сразу здесь возникает - как снимать отчет по code coverage.
Если речь о e2e-тестах, то там мы используем реальный браузер (например, при помощи `puppeteer`) и можем снять отчет по code coverage через DevTools Coverage API. Он, к слову, снимает отчет еще и по использованию CSS. Проблема в том, что тестировать надо в разных браузерах и на разных разрешениях (например, чтобы получить coverage по CSS Media Query), а еще надо учитывать динамику изменения размера экрана и подобных кейсов (для кейсов типа `window.onresize`). А как только мы говорим про "тестировать в разных браузерах", то вся стройная концепция с DevTools Coverage API сыпется, т.к. не во всех браузерах это есть и не для всех браузеров есть "безголовый" режим.
Когда мы говорим о снятии отчета с браузера реальных пользователей, то здесь вообще нет способов получить доступ к DevTools Coverage API.
Что тут можно придумать? Например, можно инструментировать реальный продуктовый код. Это значит, что каждый вызов функции, строка или условие будут обернуты в специальные функции-обертки или будут "дописаны" специальными конструкциями (счетчиками). Как только интерпретатор будет доходить до этих конструкций, он будет помечать их как используемые. Для инструментирования кода есть специальные штуки, например https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-lib-instrument
Таким образом, можно собирать code coverage на реальных пользователях.
Здесь есть три проблемы:
- инструментированный код становится объемным и выполняется дольше, появляется оверхед
- в случае большой кодовой базы, отчет может быть довольно большим
- мы инструментируем минифицированный код
- не решает проблему сбора coverage по CSS
В качестве решения первой и второй проблем можно подумать над тем, чтобы раскатить инструментированный код лишь на какой-то процент пользователей (1-2%) и да, они будут страдать от объема получаемого кода и скорости его исполнения.
Для решения третьей проблемы, получаемые отчеты нужно будет маппить на source-map, чтобы понимать о каких конструкциях в исходнике идет речь о отчете.
Подводя итог: как видите, серебряной пули для поиска и устранения мертвого кода - нет. Задача нетривиальная и решается в несколько заходов, с разных углов и разными инструментами.
А как вы решаете эту задачу? Какие инструменты используете? Пишите в комментариях @wdxlabchat
#webdx #dce
Какие-то зависимости из
package.json
могут вовсе не использоваться, но при этом они будут установлены на npm install
. Например, после рефакторинга забыли удалить из package.json
зависимость, которая больше не нужна. В таких случаях помогут штуки вроде https://www.npmjs.com/package/depcheckЭтот инструмент проанализирует импорты внешних зависимостей и сравнит их со списком зависимостей из
package.json
Недостижимые use-cases
Это отдельный класс проблем по поиску мертвого кода. Например, при нажатии на кнопку нужно выполнить какой-то код, но кнопка по какой-то причине не нажимается (съехала из вьюпорта, в принципе скрыта или в обработчике события ошибка, которая не дает выполниться части кода).
Здесь не поможет ни минификатор, ни типизация. Для таких кейсов нужно писать e2e тесты, эмулировать в них действия пользователя и делать проверки. Логика здесь следующая: выполнили тесты, составили отчет по code coverage и весь код, который помечен в отчете красным - недостижим. Но это очень скользкий способ т.к. нужно иметь ввиду, что код может быть недостижим просто потому, что мы не написали под него тест-кейс. Соответственно этот способ требует от разработчика/QA максимальной концентрации и пребывания в контексте бизнес-логики. Тут же встает вопрос о том, где эти тест-кейсы хранить и как их синхронизировать.
Более честным вариантом здесь будет отслеживать действия реальных пользователей и собирать по ним code coverage. Если в течение какого-то времени код так и не "покрасился" в зеленый, значит пользователи не попадают на этот кейс и код для них не достижим.
Вопрос который сразу здесь возникает - как снимать отчет по code coverage.
Если речь о e2e-тестах, то там мы используем реальный браузер (например, при помощи `puppeteer`) и можем снять отчет по code coverage через DevTools Coverage API. Он, к слову, снимает отчет еще и по использованию CSS. Проблема в том, что тестировать надо в разных браузерах и на разных разрешениях (например, чтобы получить coverage по CSS Media Query), а еще надо учитывать динамику изменения размера экрана и подобных кейсов (для кейсов типа `window.onresize`). А как только мы говорим про "тестировать в разных браузерах", то вся стройная концепция с DevTools Coverage API сыпется, т.к. не во всех браузерах это есть и не для всех браузеров есть "безголовый" режим.
Когда мы говорим о снятии отчета с браузера реальных пользователей, то здесь вообще нет способов получить доступ к DevTools Coverage API.
Что тут можно придумать? Например, можно инструментировать реальный продуктовый код. Это значит, что каждый вызов функции, строка или условие будут обернуты в специальные функции-обертки или будут "дописаны" специальными конструкциями (счетчиками). Как только интерпретатор будет доходить до этих конструкций, он будет помечать их как используемые. Для инструментирования кода есть специальные штуки, например https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-lib-instrument
Таким образом, можно собирать code coverage на реальных пользователях.
Здесь есть три проблемы:
- инструментированный код становится объемным и выполняется дольше, появляется оверхед
- в случае большой кодовой базы, отчет может быть довольно большим
- мы инструментируем минифицированный код
- не решает проблему сбора coverage по CSS
В качестве решения первой и второй проблем можно подумать над тем, чтобы раскатить инструментированный код лишь на какой-то процент пользователей (1-2%) и да, они будут страдать от объема получаемого кода и скорости его исполнения.
Для решения третьей проблемы, получаемые отчеты нужно будет маппить на source-map, чтобы понимать о каких конструкциях в исходнике идет речь о отчете.
Подводя итог: как видите, серебряной пули для поиска и устранения мертвого кода - нет. Задача нетривиальная и решается в несколько заходов, с разных углов и разными инструментами.
А как вы решаете эту задачу? Какие инструменты используете? Пишите в комментариях @wdxlabchat
#webdx #dce
📝 Фича Module Federation, о которой я писал выше, была выпущена в webpack 5-beta.16 https://github.com/webpack/webpack/releases/tag/v5.0.0-beta.16 🎉
GitHub
Release v5.0.0-beta.16 · webpack/webpack
Features
added experimental "Module Federation" - a way to load modules from separate builds into one application
read more about the concepts here: https://github.com/webpack/changelog...
added experimental "Module Federation" - a way to load modules from separate builds into one application
read more about the concepts here: https://github.com/webpack/changelog...