Примечание: это руководство изначально было написано 14 ноября 2017 года, но я планирую регулярно обновлять его, чтобы узнать, что мы делаем, чтобы наши сборки были оптимизированы и с использованием самых последних технологий. Кроме того, эта страница является результатом усилий сообщества, и я полагаю, что, несмотря на всю мою тяжелую работу, я все еще могу оптимизировать массу вещей, поэтому, пожалуйста, оставляйте комментарии, и я обновлю сообщение и приписываю вам ваши предложения!

Введение

Вчера вечером я потратил 7,5 часов на оптимизацию нашей сборки Javascript для минимально возможного размера артефакта. Я подумал, что было бы хорошо написать сообщение в блоге о том, что я узнал о некоторых из последних функций Webpack, UglifyJS, фрагментировании, минимизации CSS, дедупликации артефактов и сопоставлении источников.

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

В качестве ориентира в нашем проекте используется следующий технический стек:

  • Узел 8.7.0
  • Реагировать 16.1.1
  • Ringa JS (последняя сборка, в настоящее время в разработке нашей командой)
  • Webpack 3.6.0
  • Несколько тщательно подобранных библиотек: showdown.js, highlight.js, xlsx, flag-icon-css и moment относятся к числу наиболее крупных артефактов, которые мы используем.

Начало работы

Прежде всего, убедитесь, что вы используете одну из последних версий Webpack. На момент написания этой статьи я полагаю, что за последние несколько недель Webpack поднялся до 3.8.0. Я бы порекомендовал не использовать Webpack 1 или 2 в новом проекте (это особенно важно, если вы используете генератор Yeoman или шаблон приложения в качестве основы для своего SPA), и недавно прошел обновление с Webpack 2 до 3, Я искренне рекомендую этот коммутатор за его преимущества в скорости и конфигурации. Это немного болезненно, но оно того стоит.

Во-вторых, ознакомьтесь с анализатором пакетов Webpack (webpack-bundle-analyzer). Этот инструмент абсолютно бесценен.

В-третьих, узнайте, как использовать вкладку Chrome Network для отслеживания общего размера загрузки страницы, количества загруженных артефактов и особенно того, какие артефакты у вас самые большие:

Наконец, хотя он и не идеален, я рекомендую использовать Инструмент оптимизации страниц Google:

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

Ад конфигурации Webpack (и Babel)

Когда вы начинаете работать с Webpack, этот комикс становится более острым:

Webpack - это монументальная коллекция плагинов, версий, несовместимых обратных изменений и разрозненных руководств по обновлению до новых версий. Это рай с открытым исходным кодом и фантастический инструмент, но часто могут потребоваться дни, чтобы отследить ошибки или этот неуловимый форум ошибок Github, где кто-то наконец раскрывает крошечный волшебный код для решения вашей проблемы.

Как упоминалось выше, убедитесь, что вы используете только самые последние стабильные версии вещей. Причина в том, что нет смысла тратить время на обучение настройке более старой версии. Разработка Webpack и его плагинов быстро продвигается, поэтому, если вы начнете отставать, вам, возможно, придется начинать с нуля, если вы захотите выполнить обновление через пару месяцев.

Пара указателей конфигурации Webpack / Babel:

  • Не обязательно доверять какому-либо из предварительно упакованных плагинов в комплектеwebpack.optimize.*. UglifyJSPlugin, который был связан с 3.6.0, имел огромную ошибку, когда mangleoption искажал аргументы функции, хотя я специально включил переменные, которые нужно игнорировать (Ringa JS использует инъекции, аналогичные Angular JS, но плагина ng-annotate недостаточно для нашего потребности). Принудительное использование последней версии UglifyJS плагина webpack решило проблему, а не плагина, поставляемого с Webpack.
  • Узнайте, что такое peerDependencies и почему от них отказались.
  • Если вы зависите от библиотек ES6 и используете Babel с Webpack, абсолютно необходимо узнать о Webpack externals, , Правилеinclude и exclude загрузчика Webpack и параметрах alias.resolve.
  • Если вы активно разрабатываете библиотеку или фреймворк ES6, зависящие от вашего основного проекта, важно, чтобы вы узнали о npm link, а также заметили, что Babel / Webpack обрабатывает проект, связанный с npm, несколько иначе, чем проект, загруженный прямо из npmjs. org. Вот почему вы получаете разные ошибки при использовании npm link, а не npm i пакета. При загрузке с npmjs.org Babel / Webpack по умолчанию предполагает, что артефакт, на который ссылается ваш package.json main, уже перенесен. Вы должны убедиться, что ваше правило / загрузчик Babel Webpack включает папку вашего связанного пакета в node_modules, иначе он не будет перенесен, и вы получите самые странные ошибки.
  • Когда придет время развертывать в производственной среде, ознакомьтесь с package-lock.json или просто используйте Yarn.
  • Для управления версиями прочтите Semver, убедитесь, что вы знаете важность tag в Github, как создать его с помощью git push —-follow-tags, а также как использовать npm version patch, npm version minor и npm version major.

Пример

Чтобы увидеть пример всего этого в действии, просмотрите следующие файлы в нашем шаблоне приложения Ringa JS (будет регулярно обновляться):

Шаг 1. Включение информации о сборке в артефакт Javascript

Как часто вы запускали процесс развертывания, загружали страницу и говорили: «Ой, этот код выглядит старым». Было бы неплохо включить информацию о сборке в процесс развертывания, например номер версии в вашем package.json? Нравится:

Лично я хотел включить следующее в свой развернутый JS-артефакт (для сред stage / dev… очевидно, что вы можете не захотеть всего этого в производственной среде):

  • package.json: в первую очередь для информации о версии
  • git log -l 10: чтобы видеть последних коммиттеров и хэш последнего коммита

Теперь, чтобы сделать это, мне нужно было иметь возможность запускать мою сборку Webpack асинхронно, чтобы делать внешние вызовы git. Обычно ваш файл конфигурации Webpack просто возвращает плоский POJO.

Это заняло у меня некоторое время, но я узнал, что если вы вернете Promise в свой файл конфигурации Webpack, Webpack будет ждать, пока вы предоставите объект конфигурации:

Пример загрузки сведений git и package.json см. в buildInfo.js в ringa-app-template.

Шаг 2. Настройка сред

У разных команд будут разные потребности в среде, но, как мы обнаружили, эта установка нам подходит:

Среда разработки (webpack.config.dev.js, npm run start)

  • Для разработки локальной машины
  • API_ROOT обращается к локальному хосту corsproxy instance, чтобы избежать проблем CORS при попадании в нашу рабочую среду, поскольку он находится в другом домене.
  • Использует webpack-dev-server и замену горячих модулей, чтобы браузер обновлялся после каждого изменения.
  • npm link все зависимости, которые активно разрабатываются

Среда рабочей области (webpack.config.stage.js, npm run stage)

  • Для демонстрации новых функций клиентам с использованием сценических данных
  • Полная сборка Webpack с фрагментированными файлами Javascript (например, app.js и vendor.js), минимизированным и извлеченным CSS (app.css)
  • Может содержать, в зависимости от того, что тестируется, включенные функции разработки (например, process.env.NODE_ENV = ‘development’ или исходные карты).
  • Каждый этап развертывания должен быть полностью помечен git и построен такой системой развертывания, как TeamCity.

PreProd Environment (webpack.config.prod.js, npm run prod)

  • Для тестирования новых функций с использованием производственных данных
  • Полностью производственная сборка (урезанный JS, без комментариев, ноль или только внешние исходные карты, разбиты на части, насколько это возможно, полностью проверены, чтобы гарантировать отсутствие повторяющихся пакетов в окончательных артефактах, а также минимизированный и оптимизированный CSS)
  • Каждое развертывание PreProd должно быть полностью помечено git и построено системой развертывания, такой как TeamCity, на сервере preprod.

Prod Environment

  • Отправка точной сборки с тегами из PreProd на рабочий сервер

Примечание. Единственная разница между PreProd и Prod заключается в том, где развертываются клиентские артефакты.

Шаг 3: Оптимизация: знать, что искать

Рекомендуется постоянно отслеживать свои производственные артефакты JS и CSS, чтобы проверять следующее:

  • Убедитесь, что вы упаковываете только ту информацию о локали или языке, которая вам нужна. Например, moment.js и highlight.js по умолчанию содержат языковые стандарты для всей планеты или информацию о языке для каждого языка программирования. Очевидно, что если вы обращаетесь только к англоговорящим, вам не нужна локализация времени на суахили.
  • Убедитесь, что вы не упаковываете несколько (или одинаковых) версий одной и той же библиотеки. Из-за того, как работают зависимости в Webpack, легко случайно упаковать две версии одной и той же библиотеки и раздуть ваш JS размер артефакта существенно. Некоторые из них легко обнаружить, потому что они вызывают странные ошибки (например, случайное включение React.js дважды), но другие более тонкие, потому что они работают так, как ожидалось (например, случайное включение moment.js дважды, три… или дюжину раз).
  • Дважды проверьте свою url-loader и file-loader конфигурацию, чтобы убедиться, что вы случайно не упаковываете все свои изображения или файлы JSON прямо в артефакт. По умолчанию url-loader, например, имеет параметр limit, который является порогом, при котором он определяет, делать ли включенный файл внешним или нет. Если вы включили что-то вроде flag-icon-css и установили высокий лимит, каждый флаг для всей планеты будет упакован в вашу окончательную сборку, даже если вам может понадобиться несколько для поддерживаемых вами локалей.
  • Если вы действительно хотите оптимизировать, вы можете изучить интенсивное использование фрагментов JS, чтобы отложить загрузку некоторых из ваших более крупных артефактов до тех пор, пока вы не дойдете до этого экрана (например, зачем вам загружать большой пакет xlsx.js на вашей домашней странице, если пользователи не могут использовать его, пока не увидят экран отчета?).
  • Убедитесь, что вы не дублируете свои определения CSS / SCSS! Это очень просто сделать, потому что, если каждый из ваших файлов SCSS случайно загружает другой файл, это определение может быть включено в ваш вывод. CSS N раз! Вы можете использовать OptimizeCssAssetsPlugin, чтобы избежать этого. Вы можете легко определить, есть ли у вас эта проблема, потому что вы увидите повторяющиеся определения CSS, например:

Шаг 4: Анализ и тестирование вашей сборки

Настройка сценариев NPM

Теперь, когда у вас есть вся информация в вашем арсенале, чтобы начать оптимизацию сборки Webpack, начинается самое интересное: уменьшение размера ресурсов.

Здесь есть два разных сценария:

  1. Конечный веб-сайт: убедитесь, что включены все ресурсы
  2. Библиотека / платформа: убедитесь, что все ресурсы одноранговых зависимостей исключены.

Я рекомендую создать следующие три скрипта npm:

  • npm run prod: очистите node_modules, переустановите и соберите производственные артефакты в папку dist.
  • npm run prod:analyze: запустите prod, но прикрепите к нему webpack-bundle-analyzer, чтобы вы могли видеть дерево размеров артефактов в конечных фрагментах Javascript.
  • npm run prod:test (SPA only, not libraries): запустите prod, затем cd dist, а затем nws. NWS - это мини-веб-сервер. Это сделано для того, чтобы вы могли запустить производственную сборку локально и убедиться, что искажение вашего Javascript ничего не сломало.

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

Как настроить фазу анализа

Есть несколько способов сделать это, но поскольку мои сборки Webpack асинхронны и используют обещания, в итоге я сделал вот что:

Приведенный выше пример создан на этапе npm analyze библиотеки ringa-fw-react, которую вы можете найти здесь.

Как может выглядеть плохая сборка

Для ringa-fw-react, который является библиотекой, мы должны быть осторожны, чтобы объединять только те ресурсы, которые являются важными и необходимыми. В идеале мы объединяем только те классы, которые могут понадобиться конечному пользователю.

Я намеренно «сломал» сборку prod / dist для ringa-fw-react, а затем запустил npm run analyze, и вот что получилось:

На что обратить внимание:

  • Почему моя библиотека включает react и react-dom?
  • Почему я использую moment.js и почему включены все языковые стандарты?
  • Какого черта эта крошечная библиотека сжата до 438 КБ?

Как выглядит хорошая сборка

Очистив конфигурацию своего веб-пакета, я создал следующее:

Приведенный выше результат - отличный пример того, что вы хотите. Уведомление:

  • Никаких дублированных файлов или библиотек
  • В пакет входят только крошечные зависимости (например, highlight.js только включает язык javascript, если на самом деле их больше 20). Это можно дополнительно оптимизировать, используя ключевое слово external.
  • Окончательный размер уменьшился с 438 КБ до 76 КБ.
  • Артефакт не включает одноранговые зависимости. Например, любой, кто использует эту библиотеку, также будет использовать React, поэтому нет причин включать React в библиотеку.

Как очистить сборку Webpack

  • Используйте свойство external, чтобы заставить Webpack пропустить включение пакета в ваш последний артефакт, если вы знаете, что хотите заставить конечного пользователя включить его (только для библиотек / фреймворков).
  • Используйте плагин webpack-context-replacement, чтобы гарантировать, что библиотеки, такие как Moment, не включают все языковые стандарты. Хороший пример находится здесь.
  • Используйте свойство alias, если необходимо, чтобы ваши зависимости использовали локальную версию библиотеки, а не ту, что находится в их собственной папке node_modules (это поможет избежать дублирования версий).
  • По возможности всегда import ваш ресурс прямо из файла библиотеки, используя синтаксис типа import TheirComponent from 'my_node_modules_dependency/src/TheirComponent', а не обычный синтаксис import {TheirComponent} from 'my_node_modules_dependency'. Когда вы используете последний синтаксис, Webpack по умолчанию включает всю зависимость в вашу сборку, и вам нужно будет использовать встряхивание дерева через плагин UglifyJS, чтобы удалить его. Если вы начнете с прямого включения ресурса, вы сможете немного ускорить сборку Webpack.

Дополнения

Настройка UglifyJS

Мы используем последнюю версию подключаемого модуля UglifyJS Webpack (1.0.1)

Вывод

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

В течение следующих месяцев я постараюсь обновлять его, рассказывая о том, что я узнаю, и по мере изменения ландшафта. Пожалуйста, оставьте любые комментарии или предложения ниже!