Как стать автором
Обновить

Комментарии 23

А ещё у Злых Марсиан есть специальная утилита для контроля за размером бандла: size-limit.


Про неё даже целую статью написали: Size Limit: Make the Web lighter.

А еще есть встроенный конфиг performance который работает прямо фактически из коробки (и вроде бы с недавних пор — по-умолчанию):
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (250 kB).
This can impact web performance.
Assets:
  firebase@d02e70a5ce3f.js (370 kB)
  app@665998113356.js (874 kB)
  demo@68048cf0bc91.js (982 kB)
  vendor@6d9cbf3751f5.js (1.12 MB)

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (300 kB). This can impact web performance.
Entrypoints:
  app (2 MB)
      vendor@6d9cbf3751f5.js
      app@665998113356.js
  demo (2.11 MB)
      vendor@6d9cbf3751f5.js
      demo@68048cf0bc91.js
Шикарно, спасибо большое. С удовольствием посмотрел доклад. Я тоже выталкиваю библиотеки в отдельный vendor файл, но никогда не обращал внимания, что хеш действительно меняется. Меня интересует один вопрос: как решить проблему одной библиотеки разных версий? Допустим, если в бандл попадет два lodash? Только сменой библиотеки, у которой зависимость?

Можно задать для неё resolve.alias секцию в webpack.config.js, и тогда она будет принудительно грузиться оттуда, откуда укажете. Я столкнулся с более странной но схожей проблемой. У меня приложение разбито на несколько npm-пакетов. Каждый из них имеет свой список зависимостей в dependencies в package.json. И некоторые используются как дочерние в разных родительских пакетах. Всё бы ничего, но все дублированные в родителе и ребёнке пакеты грузятся дважды. Несмотря на полностью идентичную версию. Webpack просто видит, что ближайший node_modules/%lib% не пустой и берёт его. Игнорируя то, что такой же уже есть на вышестоящих уровнях и даже уже загружен. В общем годы идут а webpackbabel) всё также имеет ряд грабель с npm link и симлинками в целом. Спасся всё тем же resolve.alias-ом.

Если я правильно понял проблему, то вам сюда: externals.

Про externals я в курсе. Но не понимаю причём тут оно? Вроде как это для возможности подключения каких-либо библиотек, которые пришли в обход webpack-а. Скажем доступная извне jQuery. Такая возможность сказать webpack-у откуда её взять. К тому же каждую такую библиотеку нужно туда руками прописать. Как это связано с моей проблемой не понимаю :)


Опишу её детальнее. У меня есть 4 репозитория. Пусть будет так: A, B, C, D. D используется в A, B, C. Это такой common-пакет, всякие вспомогательные штуки и всё что нужно для обмена. A и B это два отдельных проекта, друг друга не используют, связаны лишь логически. И A и B опираются на пакет C, который содержит всё нужно для A и B, а также может самостоятельно тестироваться. В общем не особо хитрый клубок. Главное тут то, что все 4 пакета могут тестироваться и каким-либо образом использоваться самостоятельно. IoC вполне применим и даже используется. Однако большая часть зависимостей в пакетах общая. Но не вся. В директориях node_modules каждого из пакетов эти зависимости установлены. По сути они дублируют друг друга. И webpack не понимает этого грузит их по многу раз. Скажем он когда собирает модуль A который зависит от C не проверяет есть ли A/node_modules/react, а просто увидев его в C/node_modules/react его подключает. И получается что подключены и A/node_modules/react и C/node_modules/react. И похоже никаких штатных возможностей не грузить одни и те же зависимости одних и тех же версий дважды у webpack-а нет. Это создаёт проблемы. Все найденные и придуманные мною варианты мне не нравятся, но что ж поделать.

Вроде как это для возможности подключения каких-либо библиотек, которые пришли в обход webpack-а

ну да, в C подключаете D как внешнюю зависимость, а в A и B externals не используете, D попадёт в бандл пакета A|B один раз хоть используется и в C и в A|B. У меня куда более спутанные клубки и ничего два раза не грузится.

Ничего не понял, если честно. Скажем пусть react используется во всех 4-х репозиториях. Каким образом вы избегаете его троекратной загрузки? A/node_modules/react, B/node_modules/react и C/node_modules/react. Пакет react "физически" присутствует в этих директориях, т.к. они могут быть использованы независимо друг от друга (к примеру для теста или в рамках IoC). Куда и что вы прописываете, чтобы использовался только, скажем A/node_modules/react?


Плюс сама идея рвать свой проект на якобы несвязанные части за счёт externals мне очень не симпатизирует.

Ааа, я кажется вас понял. Вы видимо думаете, что у меня мои же пакеты дублируются. Нет, они не дублируются, т.к. symlink-и. Это как раз webpack6 умеет. Дублируются ИХ внутренние зависимости.


P.S. вы уверены что вам нужен externals? мне кажется вы себе сильно усложнили жизнь почём зря. Достаточно просто npm link. Стандартный механизм. А externals это костыль на случай когда ваш бандл встраивается уже в какие-то готовые условия, которые вам не подвластны.

У вас в результате получается несколько бандлов которые грузятся на одну страницу?

У меня 2 бандла. Первый для A, второй для B. Они не грузятся на одной странице, они предназначены для совсем разных задач, A это, условно, админ-панель, а B это уже для потребителей. Немалая часть кодовой базы общая. Ну по сути это не важно, это нюансы проекта. Тут ключевое в том, что есть такой подход — не монолитный проект-репозиторий, а множество мелких, каждый из которых npm-пакет. Я решил попробовать это в деле. Честно говоря намучался настрадался уже.


Одна из многих проблем это как раз ^. Webpack резолвит зависимости слишком уж просто, снизу вверх, оттого и дубляж. Он не смотрит на version-ы. Не смотрит на названия пакетов. Не проверяет загружена ли такая либа уже или нет. Увидел — подключил. Не увидел — пошёл на уровень выше. Похоже что единственное, что там сделано, это то, что библиотеку по одному и тому же пути (файловому) webpack дважды не подключает.


Что делать? Ну либо патчить его resolve, либо готовить alias-ы. Я выбрал второй путь как менее костыльный.


Получилось как-то так:
exports.genSubAliases = (pkg, dir) =>
{
    const nodeDir = `${dir}/node_modules`;
    const { dependencies } = require(`${nodeDir}/${pkg}/package.json`);
    const set = new Set(Object.keys(dependencies));
    const own = require(`${dir}/package.json`).dependencies;

    return Object
        .keys(own)
        .filter(m => set.has(m))
        .reduce((hash, m) =>
        {
            hash[m] = `${nodeDir}/${m}`;
            return hash;
        }, {});
};

Если у вас общая зависимость ваших зависимостей одной и той же версии, то какой-нибудь yarn, делая плоский список зависимостей, вынесет общую зависимость в рутовый node_modules.
И тогда у вас будет 1 версия общей зависимости в одном месте и вебпак не должен это дублировать.

И тогда у вас будет 1 версия общей зависимости в одном месте и вебпак не должен это дублировать

Вот только эта зависимость будет недоступна, если я буду работать из вложенных модулей. Например тестировать их. Ведь вложенный модуль ничего знать не знает о рутовом.

Давайте еще раз, я видимо чего-то не понял.
Вот как я это вижу.
У вас есть пакет Main и есть его зависимости dep-a и dep-b.
И у пакета Main и у его зависимостей dep-a, dep-b есть third-party зависимость not-react.


Если этот not-react и в Main и в dep-a и в dep-b одинаковой версии, то при установке зависимостей Main с помощью yarnа not-react будет только в рутовом node_modules.
И при сборке webpack будет тянуть один и тот же not-react в Main, dep-a, dep-b => нет дублирования.
При этом ваши пакеты dep-a, dep-b разрабатывайте и тестируйте как хотите в их собственных репозиториях\папках.

При этом ваши пакеты dep-a, dep-b разрабатывайте и тестируйте как хотите в их собственных репозиториях\папках.

Что будет в dep-a/node_modules/not-react? Будет symlink на Main/node_modules/not-react? Или вообще ничего не будет?


Вариант 1-ый:


  1. будет symlink — тогда я могу обособленно работать с dep-a, из dep-a игнорируя существование Main. Вроде всё ок. Но в следующий раз когда я захочу добавить другую зависимость, и я не буду заранее знать, будет ли она в Main тоже нужна или нет, я встану перед проблемой. Если я выполню npm install some-dep из dep-a, то когда эта же либа потребуется в Main и я выполню npm install some-dep уже из Main — у меня снова будет две копии. И снова те же проблемы. И чтобы этого избежать, я вынужден весь этот граф зависимостей держать в голове, следить за дубликатами и прочая муть.
  2. Вариант два. Нет symlink-а. Ничего нет. В этом случае код из dep-a выполняемый не из Main, а сам по себе, не запустится вовсе. Даже тесты. Самый плохой вариант.
Вот видимо про симлинки я и упустил.
А как вы используете симлинки?
У нас есть workflow работы с симлинками во время разработки. Мы линкуем также *dep-a*, *dep-b* и там действительно будут дубликаты *not-react*, но это не важно во время разработки.
Во время сборки для выкладки нет никаких симлинков до *dep-a*, *dep-b*, они ставятся из *registry* и дубликатов *not-react* нет.

Хотя в целом проблем с симлинками достаточно.

Я использую симлинки в рамках npm link (вроде как это самый, что ни на есть, стандартный механизм работы с собственными модулями). Но выше я веду речь про то, как, возможно, работает yarn.


но это не важно во время разработки

Это как сказать. Как сказать. Если вы динамически меняете что-нибудь во внешней зависимости, то если их будет две, то у вас будет разное поведение на проде и деве :) Помимо прочего это ещё и замедлит время сборки (одни и те же вещи грузятся дважды, трижды, четырежды...), и столько же watcher-ов для пересборки. Плюс не все библиотеки вообще смогут работать в таком мульти-режиме. Скажем если у них какие-нибудь "глобальные" пулы чего-нибудь, то это всё может затрещать по швам.


Хотя в целом проблем с симлинками достаточно.

Это да. Прямо беда. Сразу по всем фронтам. Была даже мысль забить на симлинки и npm link и использовать /etc/fstab для этих целей. Пока спасаюсь "костылями".


Я думаю, что подход "разбить проект на множество npm-модулей" в моём случае показал себя просто отвратительно. Проблемы прямо по всем фронтам, начиная от npm, babel, webpack, заканчивая mocha.

Если вы динамически меняете что-нибудь во внешней зависимости
Так и думал что дело не чисто и тут есть зависимость со стейтом :)


Да, если в деве дубликаты критичны то вы правы. Все очень плохо :)


Мы сейчас переходим с несколько npm-модулей на множество npm-модулей, но npm link используем только если нужно одновременно разрабатывать и слинкованный пакет и тот пакет, к которому линковка исполняется.

Так и думал что дело не чисто и тут есть зависимость со стейтом :)

У меня нет такой. Надеюсь :) Просто глаза зацепились за "не важно" :)


но npm link используем только если нужно одновременно разрабатывать и слинкованный пакет и тот пакет, к которому линковка исполняется.

А бывает по-другому? Это как? Когда слинкованный пакет уже "готов"? Или когда он "библиотечный", и к проекту относится опосредованно? У меня просто это большие куски проекта. Для примера — часто я делаю push-ы сразу из 4 репозиториев. Одна задача затрагивает сразу все 4. Прямо регулярное явление. Честно говоря очень неудобно, хотя и вроде как эти части логически друг от друга отвязаны и можно даже инверсию управления использовать.


И в итоге я столкнулся с тем, что собирается сразу 4 react, 4 redux, 4 всех остальных общих либ. 4 lodash-а. Бомбит от этого знатно :) Как меня, так и бандл.


А лечится, казалось бы, просто элементарно — смотреть на номер версии в package.json, и если таковая уже загружена, — не грузить второй раз. Зарегистрировано множество issue с 15-го года, а воз и поныне там.

И такая проблема возникает неизбежно если вы пишете проект не в моно-репозитории, а во многих сразу, используя npm link. Я нашёл много issues чуть ли не с 2015г и ничего не изменилось. Советуют явным образом указывать все такие библиотеки руками в alias. Ужасно :)

Для vendor файлов есть отличная штука, которая называется DLL, но про неё мало кто пишет. Ускоряет сборку, потому что не нужно держать в памяти vender код, он редко обновляется и тп.
Только злые марсиане могут в этом во всем разобраться… :)
Зарегистрируйтесь на Хабре , чтобы оставить комментарий