3 способа уменьшить бандл в webpack


Содержание материала:

3 ways to reduce webpack bundle size

Go through these three steps to get a quick overview of what to do to reduce webpack bundle size. I have listed them with increasing difficulty so that you can start at the top and hopefully get a quick win and then you can work your way down.

Easy: Run webpack in production mode

Make sure you run webpack in production mode. To do that, you run webpack with the -p flag, like this:

This little flag does these two things to optimize your bundle size:

  1. Enable minification using UglifyJs. It removes unnecessary white spaces, new lines, unreachable code, etc.
  2. Set the NODE_ENV to “production”. This tells some packages, like React, to not include debug code.

Medium: use an analyzer tool

There are many good tools to analyze your bundle. The best ones I have used are these two:

They work a bit differently but the output is the same: A nice visual representation of all the modules in your bundle. The output from source-map-explorer looks like this (image from the GitHub repo):

This visualization makes it possible for you to get a really nice overview of what is in your bundle. You can also compare sizes of components and dependencies.

How to analyze the output:

  • Are some dependencies larger than you thought? Could you replace them with a minimalistic or more specialized alternative or rewrite it yourself?
  • Are you using moment.js and have all timezones in the bundle, but are only using one or two? Here is how to fix it.
  • Are you using lodash? Then cons >

Hard: use code splitting

Code splitting means to split your bundle into smaller bundles.

Now you might ask yourself “Why would anyone want to do that? Isn’t that bad for mobile?”. It’s true that you want to reduce the number of new HTTP connections to increase performance on mobile. But the advantage to using code splitting is you can use more of browser cache and have more specialized bundles.

Splitting libraries to separate bundle

Your dependencies usually do not change as often as your production code. With code splitting, you can split your dependencies into a separate bundle. This bundle can be cached by your user’s browser for longer periods than your production code bundle.

A good guide for implementing this is available here. My post about what to do with the bundle files might also be useful for you.

CSS code splitting

Are you using CSS in JavaScript? Then you could extract the CSS to a separate JavaScript bundle or a CSS file. The advantage is the same as extracting the vendor file to separate bundle: the browser can cache it for longer periods.

Check out this guide if you are interested learning more.

Fetching bundles asynchronously

With webpack, you can split your bundle into many smaller bundles, and then the browser can automatically fetch the bundles needed asynchronously. This means the browser always only downloads the JavaScript code needed, and nothing more. It’s pretty cool, but also the most advanced technology in this post.

This guide is really good to learn how to get started.

Next step: get more in-depth knowledge

In this article, I have gone through the most basic things you can do for reducing the webpack bundle size.

Do you want to go even further in your optimizations and get a more in-depth knowledge? I have created a PDF guide that you can access by signing up below.

Как уменьшить размер расслоения WebPack сборки для применения среагировать?

Я работаю на React приложения и использует Webpack для создания bundle.js файла. Даже если мое приложение очень просто bundle.js размер файла составляет около 11 МБ.

Когда я анализирую файл связки с https://webpack.github.io/analyse/ , более 1000 пакетов включены в bundle.js. Некоторые из этих пакетов определены в devDependencies секции файла package.json.

Поэтому я ищу ответы на следующие вопросы. Есть ли способ, чтобы уменьшить размер файла WebPack компиляции? Есть ли WebPack включать devDependencies модули в конечном bundle.js?

У меня есть WebPack 6.0.1. В дополнении к обычному webpack.config.js настройке я могу предложить следующую модель оптимизации (webpack.config.js рабочего режима). Для получения дополнительной информации см оптимизации , настройки режима и комментарии , связанные с плагинами в нижней части:

  1. webpack.optimize.ModuleConcatenationPlugin () — сцепить объем всех модулей в одно закрытия и позволяет коду, чтобы иметь более быстрое время выполнения в браузере
  2. webpack.HashedModuleIdsPlugin () — причина хешей быть основана на относительном пути модуля, создавая строку из четырех символов, как идентификатор модуля
  3. webpack.optimize.OccurrenceOrderPlugin () — варьировать распределение идентификаторов, чтобы получить наименьшую длину идентификатора для часто используемых идентификаторов
  4. webpack.NoEmitOnErrorsPlugin () — пропустить фазу излучающий всякий раз, когда есть ошибки во время компиляции. Это гарантирует, что никаких активов не выбрасывается, которые включает в себя ошибку

изменить devtool: ‘дешево-модуль-Eval-источник-карту’, чтобы devtool: ‘дешево-исток-карту’ в WebPack конфигурации. обусловлен WebPack -p оленья кожа принимает дешевую-модуль-Eval-источник-карту

Как минимизировать размер пакета webpack?

Я пишу веб-приложение с помощью react и webpack как мой модуль bundler. Мой jsx код действительно легкий до сих пор, размер всей папки составляет 25 КБ.

мой bundle.js создан из webpack — в 2,2 Мб хоть. После запуска оптимизации с помощью -p флаг, он уменьшает пакет до 700kb, который по-прежнему очень большой.

Я заглянул в react.min.js файл и его размер составляет 130 КБ.

возможно ли, что webpack производит такой большой файлы или я делаю что-то не так?

EDIT

5 ответов:

по Вашим комментариям вы используете material-ui и react-bootstrap . Эти зависимости в комплекте с webpack вместе с вашим react и react-dom пакеты. В любое время вы require или import пакет он входит в комплект как часть вашего файла пакета.

и вот оно мое предположение. Вы, вероятно, импортируете react-bootstrap и material-ui компоненты с помощью библиотека путь:

это приятно и удобно, но это не только пучки Button и FlatButton (и их зависимости), но весь библиотеки.

один из способов исправить это, чтобы попытаться только import или require что нужно, скажем,компонент путь. Используя тот же пример:

это будет только пакет Button , FlatButton и их соответствующие зависимости. Но не всю библиотеку. Так что я постараюсь избавиться от всех ваших библиотека импорт и использовать компонент путь вместо.

если вы не используете много компонентов, то это должно значительно уменьшить размер вашего файла в комплекте.

а далее объяснения:

когда вы используете библиотека как вы импортируете все эти react-bootstrap и все эти материалы-ui компоненты, независимо от того, какие из них вы используете.

01/2020 EDIT — С тех пор я узнал немного больше о различных плагинах Webpack и хотел обновить это. Оказывается, что UglifyJS имеет небольшое количество опций конфигурации, которые, похоже, не очень распространены, но могут оказать значительное влияние на размер вашего пакета. Это моя текущая конфигурация с некоторыми аннотациями (документы на сайте отличные):

однажды я столкнулся с неясной проблемой с uglify — ication экранированных символов Юникода, так что помните, что если вы используете эти преобразования, то такие крайние случаи возможны.

вы можете прочитать больше о конкретных вариантах webpack поддерживает в webpack docs С некоторыми последующими ссылками для дальнейшего чтения.

(примечание: Я думаю, что ваш пакет.JSON-это смесь. по крайней мере, некоторые из этих dev-зависимостей являются зависимостями в каждом пакете.json я видел (например,react-starter-kit)

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

1) уменьшает / уродует ваш код

2) заменяет дубликат кода для минимизации размера файла


3) говорит webpack опустить некоторые вещи, которые он использует для сборки среды узла

наконец, если вы используете исходную карту (что, вероятно, следует), вы захотите добавить соответствующую строку. Часовой написал хороший пост в блоге об этом.

в моей сборке, я использую devtool: ‘source-map’ для производства

обновлено 05/18 : обновление UglifyJsPlugin настройки для лучшего минимизации

Я использую ниже конфигурацию для минимизации в производственном коде.

вы смотрели на то, как вы скрипты отправляются по проводу. У меня было несколько очень простых компонентов react, которые были около 300 КБ каждый, и это было после оптимизации webpack. После того, как они были gzipped они спустились до 38kb. Все еще значительный — но это то, что мы получаем за использование функций tomorrows сегодня. Если вы используете node / express для обслуживания статических ресурсов, включая ваш javascript-посмотрите на сжатие (https://github.com/expressjs/compression). Я бы тоже предложите посмотреть руководство по передовой практике узла для производства https://expressjs.com/en/advanced/best-practice-performance.html Если вы не обслуживаете файлы через узел, то apache (или другой веб-сервер) будет иметь параметры для сжатия текстовых файлов.

Я считаю полезным упомянуть утилиту source-map-explorer, которая помогает узнать, что именно находится в вашем пакете.js файл. Это может помочь вам определить, есть ли какие-либо ненужные вещи в bundle js. вы можете установить source-map-explorer из npm и использовать его как

кроме того , как упоминалось @kimmiju, проверьте, использует ли ваш сервер некоторое сжатие.

вы также можете попробовать асинхронно загрузить маршруты (ленивая загрузка в webpack), так что весь ваш файл bundlejs не отправляется за один раз, вместо этого он отправляется кусками, когда пользователь переходит к этим маршрутам.

7 способов ускорить сборку webpack’ом

Если Вы работаете над крупным проектом, в котором много файлов относящихся к фронтенду (причем это могут быть еще и различные пакеты из node_modules), то в какой-то момент скорость сборки фронтенда начинает снижаться. И если в инструментах вроде gulp’a есть достаточно простые средства для кэширования сборок (см. плагины gulp-cached и gulp-changed), то в Webpack похожих инструментов для кэширования нет*

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

1. Включите кэширование для babel-loader

Если включить опцию cacheDirectory, то для кэширования результатов babel-loader’а будет использоваться отдельный каталог. При последующих сборках лоадер будет пытаться считать кэш предыдущих сборок, чтобы избежать потенциально «дорогой» операции повторной компиляции. Кэш будет взят из node_modules/.cache/babel-loader или, в случае если лоадер не найдет такую директорию, он обратится к стандартной директории для хранения временных файлов в ОС. Способ довольно простой, но и выигрыш он дает крайне скромный, всего 3-5 секунд.

2. Откатите css-loader до версии 0.14.5

Баг в css-loader, допущенный в версии 0.15, в несколько раз замедляет обработку css-файлов. По ссылке выше можно увидеть, что откат до более ранней версии (например 0.14.5) позволяет ускорить обработку css примерно в 5 раз! На реальном проекте сэкономленное время будет варьироваться, в зависимости от того, насколько много у вас CSS-файлов. В проекте, над которым я сейчас работаю, выигрыш составил около 15 секунд.

3. Используйте плагин webpack-uglify-parallel

Сборку можно ускорить примерно на 5-10 секунд, если минифицировать js-файлы в нескольких потоках. Добиться этого поможет плагин webpack-uglify-parallel.

4. Используйте DLL-сборки

По аналогии с динамически подключаемыми библиотеками (DLL) из мира настольных приложений, у webpack есть отличные встроенные инструменты для создания динамически подключаемых библиотек. Называются эти инструменты DllPlugin и DllReferencePlugin.

Принцип использования этих плагинов примерно такой:

  1. Создается дополнительный webpack-конфиг для сборки библиотек (вендоров)
  2. В основном webpack-конфиге дается ссылка на манифест, созданный первым конфигом.
  3. Запускается дополнительный конфиг, собирающий все указанные библиотеки в отдельный файл.
  4. Основной webpack-конфиг исключает из сборки библиотеки, собранные на этапе 3.
  5. Profit.

Итак, перейдем к конкретному примеру конфига с DLL-сборкой.

Первым делом, создадим дополнительный конфиг, который будет отвечать за сборку наших библиотек (вендоров):

Как можно заметить, в единственной точке входа (entry point) vendor, я перечислил все библиотеки, которые хочу исключить из основного файла сборки. Разумеется, таких точек входа может быть несколько. Например, если какие-то библиотеки используются только в админке, имеет смысл вынести их в отдельную точку входа. Здесь все зависит от проекта.

Итак, теперь, после создания дополнительного конфига, необходимо «привязать» его к основному конфигу. С этим делом поможет DllReferencePlugin. Добавим его к списку плагинов основного конфига webpack:

Теперь, когда произведена настройка конфигов, можно собрать вендоров (запустив vendor.webpack.config.js), после чего запустить основной конфиг webpack’а (webpack.config.js) и увидеть ускорение скорости сборки почти в 2 раза!

5. Кэшируйте все файлы сборки с помощью HardSourceWebpackPlugin

Итак, в финале рассмотрим самые действенные, но в то же время самые ненадежные способы ускорения сборки. И начну я с HardSourceWebpackPlugin. Пример настройки можно найти прямо по ссылке.

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

> I’ve seen times for a 5s project drop down to 500ms and a 8s project drop down to 1shttps://t.co/KyDVVoBIpS

К сожалению, мне не удалось применить этот плагин к реальному конфигу (однако, как можно заметить выше, автору удалось сократить время сборки в 8-10 раз на разных проектах!)

6. Исправьте баг движка V8 с помощью плагина V8LazyParseWebpackPlugin

Этот экспериментальный плагин устраняет уязвимость движков V8/Chakra, в результате чего сокращается время начальной сборки webpack’а.

7. Удалите лишние зависимости

Помимо манипуляций с webpack-конфигом, есть еще один способ немного ускорить сборку. Флаг —production позволяет исключить из сборки пакеты, перечисленные в devDependencies, что тоже дает некоторый выигрыш в скорости.

Заключение

Надеюсь, вышеперечисленные способы помогут вам ускорить процесс сборки webpack. Особенно хочу отметить способы 1-4, как наиболее надежные. Вполне возможно, со временем появятся какие-то еще способы ускорения сборки, но даже первых четырех способов хватит для ускорения сборки примерно в два раза.

* — в режиме разработки (с использованием webpack-dev-server) webpack умеет осуществлять частичную пересборку (т.е. пересобирает только то, что изменилось).

Как уменьшить размер пучка WebPack в?

November 2020

26.4k раз

Я пишу веб — приложение , используя react и в webpack качестве моего модуля Bundler. Мой jsx код действительно свет до сих пор, размера всей папки 25 кб.

Мой bundle.js создан из webpack 2,2 Мб , хотя. После запуска оптимизации , с -p флагом, это уменьшает расслоение к 700Кбу, который по — прежнему очень большой.

Я посмотрел в react.min.js файл и его размер 130Kb.

Возможно ли, что WebPack производит такие большие файлы или я делаю что-то не так?

РЕДАКТИРОВАТЬ

5 ответы

ОБНОВЛЕНИЕ 05/18: обновление UglifyJsPlugin настройки для лучшей минификации

Я использую ниже конфигурации для минификация в производстве код.

01/2020 EDIT — С тех пор я узнал немного больше о различных WebPack плагинов, и хотел обновить это. Оказывается, что UglifyJS имеет littany вариантов конфигурации , которые не кажутся очень мейнстрим, но может иметь драматическое влияние на ваш размер пучка. Это мой текущий конфиг ж / некоторые аннотации (документы на сайте велики):

Однажды я столкнулся с неясной проблемой с uglify -ication сбежавшего Юникода символов, так что будьте внимательны , если вы используете эти преобразования, край случай и тому подобное возможно.

Вы можете прочитать больше о конкретных вариантах webpack поддерживают в WebPack документах ж / некоторые последующие по ссылкам для дальнейшего чтения.

(Sidenote: Я думаю , что ваш package.json смешан вверх . по крайней мере , некоторые из этих DEV-зависимостей являются зависимости в каждом package.json я видел (например, реагирует стартер-комплект )

Если вы готовите для производства, есть несколько шагов, которые необходимо предпринять, чтобы получить размер файла вниз. Вот надрез моего webpack.config.js:

1) minifies / uglifies код

2) заменяет повторяющийся код, чтобы минимизировать размер файла


3) говорит WebPack опустить некоторые вещи, которые он использует для окружающей среды узла строит

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

В моих сборках, я использую devtool: ‘source-map’ для производства

Webpack — увеличение в размере конечной сборки bundle.js #10

Comments

Copy link Quote reply

maplemap commented Jun 20, 2020

  1. Смотрел скринкаст Ильи Кантора по webpack, также собрал собственный по кусочкам, используя тот же скринкаст, твои уроки и интернет.
    Какой ты конфиг webpack’а используешь в живых проектах? Интересует использование в конфиге таких вещей:
    • hot module replacement
    • разделение сборки на ‘development’ и ‘production’
    • делаешь ли ты сборку в классическую модель для ‘production’, в виде:
    • как поступаешь с изображениями в проекте? Ложишь их в папку /public или также разделяешь по компонентам?
  2. Был ли опыт работы с React-Bootstrap?
    Если да (но даже если и нет), то может будет у тебя мнение насчет того, почему в проекте с такими зависимостями —

Как построить минифицированный и несжатый пакет с webpack?

вот мой webpack.config.js

в своем dist Папка, я только получаю

Я также хотел бы видеть несжатый bundle.js

12 ответов:

Начиная С Webpack 4, webpack.optimize.UglifyJsPlugin устарел и его использование приводит к ошибке:

webpack.оптимизировать.UglifyJsPlugin был удален, пожалуйста, используйте config.оптимизация.свернуть вместо

как руководство объясняет, плагин может быть заменен на . Пользовательская конфигурация может быть предоставлена плагину, указав UglifyJsPlugin пример:

это делает работу для простой настройки. Более эффективным решением является использование Gulp вместе с Webpack и сделать то же самое за один проход.

вы можете использовать один конфигурационный файл и включить плагин UglifyJS условно, используя переменную окружения:

а затем просто установите эту переменную, когда вы хотите ее минимизировать:

Edit:

как уже упоминалось в комментариях, NODE_ENV обычно используется (по соглашению) для определения того, является ли конкретная среда производственной или средой разработки. Чтобы проверить это, вы также можете установить var PROD = (process.env.NODE_ENV === ‘production’) и далее обычно.

вы можете запустить webpack дважды с разными аргументами:

затем проверьте аргументы командной строки в webpack.config.js :

добавить еще один ответ, флаг -p (сокращенно —optimize-minimize ) включит UglifyJS с аргументами по умолчанию.

вы не получите минимизированный и необработанный пакет из одного запуска или не создадите пакеты с разными именами, поэтому -p флаг может не соответствовать вашему варианту использования.

напротив сокращенно —debug —devtool sourcemap —output-pathinfo

мой webpack.конфиг.js опускает devtool , debug , pathinfo , и плагин minmize в пользу этих двух флаги.

может быть, я опаздываю здесь, но у меня та же проблема, поэтому я написал unminified-webpack-plugin для этой цели.

установка

использование

делая, как указано выше, вы получите две библиотеки файлов.минута.js и библиотека.js. Нет необходимости выполнять webpack дважды, он просто работает!^^

по-моему это много проще просто использовать UglifyJS напрямую:

  1. npm install —save-dev uglify-js
  2. используйте webpack как обычно, например, построение .

добавить на package.json :

  • всякий раз, когда вы хотите построить свой пакет, а также уродливый код и исходные карты, запустите
  • вы можете создать две конфигурации для webpack, один из которых минимизирует код, а другой-нет (просто удалите оптимизацию.UglifyJSPlugin line), а затем запустить обе конфигурации одновременно $ webpack && webpack —config webpack.config.min.js

    должно быть что-то вроде:

    действительно, Вы можете иметь несколько сборок, экспортируя различные конфигурации в соответствии с вашими стратегиями env / argv.

    работает для меня, с -p флаг.

    вы можете отформатировать ваш webpack.конфиг.Яш такой:

    а затем построить его unminified run (в то время как в главном каталоге проекта):

    чтобы построить его минимизированный запуск:

    заметки: Убедитесь, что для unminified версии вы измените имя выходного файла library.js и уменьшенная library.min.js таким образом, они не перезаписывают друг друга.

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

    • уменьшенная + не уменьшенная версия (как в вопросе)
    • ES6
    • кросс-платформенный (Windows + Linux).

    Я, наконец, решил ее следующим образом:

    тогда я могу построить (не забудьте npm install до):

    вы можете определить две точки входа в вашей конфигурации webpack, один для вашего обычного js и другой для минимизированного js. Затем вы должны вывести свой пакет с его именем и настроить плагин UglifyJS для включения min.JS файлы. См. пример конфигурации webpack для получения более подробной информации:

    после запуска webpack, вы получите пакет.js и bundle.минута.js в вашей папке dist, нет необходимости в дополнительном плагине.

    Уменьшение размеров бандлов с помощью Webpack Analyzer и React Lazy/Suspense

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


    Автор статьи, перевод которой мы сегодня публикуем, работает в Wix. Он хочет рассказать о том, как смог уменьшить размер одного бандла примерно на 80%, используя Webpack Analyzer и React Lazy/Suspense.

    Насколько рано стоит приступать к оптимизации?

    Если вы только начали работу над своим новым веб-приложением, то вы, возможно, пытаетесь сосредоточить усилия на том, чтобы оно, так сказать, «оторвалось от земли», стараетесь сделать так, чтобы оно заработало. Вы, вероятно, не обращаете особого внимания на производительность или на размер бандла. Я могу это понять. Однако мой опыт подсказывает, что о производительности и о размерах бандлов стоит заботиться с самого начала. Хорошая архитектура приложения и своевременные «размышления о будущем проекта» сэкономят вам, в долгосрочной перспективе, немало времени и помогут не накопить серьёзного технического долга. Очевидно то, что сложно заранее всё «предвидеть», но вам стоит очень постараться сделать всё правильно с первого дня работы над проектом.

    Вот пара отличных инструментов, которые, как мне кажется, нужно использовать с самого начала. Эти инструменты помогут распознать «проблематичные» NPM-пакеты даже ещё до того, как они займут сколько-нибудь важное место в приложении.

    ▍Bundlephobia

    Bundlephobia — это сайт, который позволяет узнавать о том, насколько некий NPM-пакет увеличит размер бандла. Это — отличный инструмент, помогающий программисту принимать правильные решения, касающиеся выбора пакетов сторонних разработчиков, которые могут ему понадобиться. Bundlephobia помогает проектировать архитектуру приложения так, чтобы его размер не оказался бы слишком большим. На следующем рисунке показаны результаты проверки популярной библиотеки для работы со временем, которая называется moment. Можно видеть, что эта библиотека довольно велика — почти 66 Кб в сжатом виде. Для многих пользователей, работающих на скоростном интернете, это — ничто. Однако стоит обратить внимание на то, каким становится время загрузки этого пакета в 2G/3G сетях. Оно, соответственно, составляет 2.2 и 1.32 секунды. И, обратите внимание, речь идёт только об одном этом пакете.

    Результаты анализа пакета moment средствами Bundlephobia

    ▍Import Cost

    Import Cost — это весьма интересное расширение для множества популярных редакторов кода (у него более миллиона загрузок для VS Code). Оно умеет показывать «стоимость» импортированных пакетов. Особенно мне в нём нравится то, что оно помогает выявлять проблемные области приложения прямо во время работы над ним. На следующем рисунке (он взят с GitHub-страницы Import Cost) показан отличный пример воздействия на размеры проекта разного подхода к импорту сущностей. Так, импорт единственного свойства uniqueId из Lodash приводит к импорту в проект всей этой библиотеки (70 Кб). А если напрямую импортировать функцию uniqueId , то к размеру проекта будет добавлено всего 2 Кб. Подробнее об Import Cost можно почитать здесь.

    «Стоимость» импорта в проект всей библиотеки Lodash и только одной конкретной функции из этой библиотеки

    Дело о неоправданно больших бандлах

    Итак, вы создали своё замечательное приложение. Оно отлично работает на вашем скоростном интернете и на вашем мощнейшем компьютере, набитом оперативной памятью. Вы выпустили его в реальный мир. Через некоторое время к вам начали поступать жалобы от пользователей или от ваших же аналитиков. Эти жалобы касались времени загрузки приложения. Нечто подобное недавно случилось со мной, когда мы, в Wix, зарелизили новую возможность, над которой я работал.

    Для того чтобы немного ввести вас в курс дела — давайте поговорим об этой новой возможности. Это — новый прогресс-бар, расположенный в верхней части боковой панели интерфейса настройки сайтов пользователей. Цель этого механизма заключается в том, чтобы привлечь внимание пользователя к различным шагам, которые ему нужно выполнить для того, чтобы у его бизнес-проекта было бы больше шансов на успех (подключение SEO, добавка регионов, в которые осуществляется доставка товаров, добавление первого продукта, и так далее).

    Прогресс-бар обновляется автоматически, подключаясь к серверу с использованием веб-сокетов. Когда пользователь завершает выполнение всех рекомендованных шагов — выводится всплывающее окно с поздравлениями. После того, как это окно закрывают, прогресс-бар скрывается и больше никогда не выводится при работе с тем сайтом, в настройке которого он использовался. Вот как выглядит то, о чём мы только что говорили.

    Прогресс-бар и окно с поздравлениями

    Что же случилось? Почему наши аналитики жаловались мне на то, что время загрузки страницы выросло? Когда я изучил состояние дел, воспользовавшись вкладкой Network инструментов разработчика Chrome, мне сразу же стало ясно то, что мой бандл оказался довольно большим. А именно — его размер составлял 190 Кб.

    Размер бандла, выясненный с помощью инструментов разработчика Chrome

    «Почему для этой мелочи нужен сравнительно большой бандл?», — подумал я тогда. А правда — почему?

    ▍Поиск проблемных мест в бандле

    После того, как я понял, что размер бандла слишком велик, пришло время выяснить причину этого. Здесь мне пригодился Webpack Bundle Analyzer — отличный инструмент для выявления проблемных мест бандлов. Он открывает новую вкладку браузера и показывает сведения о зависимостях.

    Вот что получилось после того, как я проанализировал бандл с помощью этого инструмента.

    Результаты работы Webpack Bundle Analyzer

    С помощью анализатора я смог обнаружить «преступника». Здесь использовался пакет lottie-web, что добавляло к размеру бандла 61.45 Кб. Lottie — это весьма приятная JavaScript-библиотека, которая позволяет, применяя стандартные средства браузера, выводить анимации, созданные в Adobe After Effect. В моём случае было так, что нашему дизайнеру нужна была приятная анимация, выполнявшаяся при появлении окна с поздравлениями. Он эту анимацию создал и дал её мне в виде JSON-файла, который я передал пакету Lottie и получил красивую анимацию. В дополнение к пакету lottie-web у меня был ещё и JSON-файл с описанием анимации. Размер этого файла составлял 26 Кб. В результате библиотека Lottie, JSON-файл, а также некоторые вспомогательные маленькие зависимости «стоили» мне примерно 94 Кб. И это — всего лишь анимация окна с поздравлениями пользователю. Пользователь, когда эти поздравления видел, должен был радоваться. Мне же от всего этого было грустно.

    Технологии React Lazy/Suspense приходят на помощь

    После того, как я обнаружил причину проблемы, пришло время эту проблему решать. Ясно было то, что не нужно было загружать в самом начале работы всё, что требовалось для анимации. На самом деле, существовала немалая вероятность того, что в ходе текущей сессии пользователя ему не придётся показывать окно с поздравлениями. Тогда я ознакомился с недавно появившимися технологиями React Lazy/Suspense и подумал, что сейчас у меня, вероятно, появилась хорошая возможность их испытать.

    Если вы не знакомы с концепцией «ленивых» (lazy) компонентов, то знайте, что их смысл заключается в разделении приложения на небольшие фрагменты кода. Загрузка этих фрагментов выполняется только тогда, когда они нужны. В моём случае это выражалось в том, что мне нужно было выделить из основного функционала прогресс-бара компонент, который был ответственен за показ поздравления. Загружать этот компонент надо было только тогда, когда пользователь завершал выполнение рекомендованной последовательности шагов.

    В React 16.6.0 (и в более новых версиях) есть простой API, который предназначен для рендеринга ленивых компонентов. Речь идёт о React.lazy и React.Suspense. Рассмотрим пример:

    У нас имеется компонент, который выводит элемент

    , а в нём — компонент Suspense , который оборачивает компонент OtherComponent . Если посмотреть на первую строчку этого примера — можно увидеть, что OtherComponent не импортируется в код напрямую. Обычно подобный импорт выглядит как import OtherComponent from ‘./OtherComponent’; .

    Вместо этого команда импорта оформлена в виде функции, которая принимает путь к файлу. Этот механизм работает благодаря тому, что в Webpack есть встроенные средства разделения кода. Когда в коде присутствует подобная конструкция — возвращается промис, который, после загрузки файла, разрешается с содержимым этого файла. Наша команда импорта обёрнута в функцию React.lazy .

    В материалах для рендеринга, возвращаемых MyComponent , OtherComponent обёрнут в компонент React.Suspense , у которого есть свойство fallback . В нашем случае оказывается, что когда рендеринг доходит до OtherComponent , начинается загрузка соответствующего компонента. Тем временем рендерится то, что записано в свойство fallback . В данном примере это — текст Loading… . Вот, собственно говоря, и всё. Эти механизмы просто делают своё дело.

    Правда, есть пара особенностей, которые нужно учитывать при работе с Lazy/Suspense.

    1. Компонент, который импортируется «ленивым» способом, должен содержать экспорт по умолчанию, который будет входной точкой компонента. Именованный экспорт тут использовать нельзя.
    2. Нужно оборачивать компонент, импортируемый с помощью функции React.lazy , в компонент React.Suspense . Компоненту React.Suspense нужно предоставить свойство fallback . В противном случае возникнет ошибка. Однако если вы попросту не хотите ничего рендерить до завершения ленивой загрузки компонента — вы можете просто записать в fallback значение null , не пытаясь каким-то хитрым способом обойти необходимость записи чего-то в этой свойство.

    Помогло ли мне использование React Lazy/Suspense?

    Да, помогло! Разделение кода сработало прямо-таки изумительно. Взглянем на результаты анализа нового бандла средствами Webpack.

    Результаты работы Webpack Bundle Analyzer после разделения кода

    Как видите, размер моего бандла уменьшился примерно на 50% — до 96 Кб. Отлично!

    И что же, теперь проблема решена? Нет, к сожалению. Когда я взглянул на страницу, то оказалось, что у всплывающего окна с поздравлениями сбилось позиционирование.

    Окно с поздравлениями выводится не там, где нужно

    Проблема заключалась в том, что я «попросил» окно открыться, изменив состояние React-компонента. Между тем, я уже отрендерил null (то есть — ничего не отрендерил), используя компонент React.Suspense . После «ленивой» загрузки необходимых данных соответствующие материалы были добавлены в DOM. Однако позиционирование всплывающего окна уже было выполнено. В результате, из-за того, что свойства соответствующего компонента не менялись, этот компонент «не знал» о том, что ему нужно решить вопрос, касающийся позиционирования. Если я менял размер окна браузера, то всплывающее окно возникало в правильном месте из-за того, что соответствующий компонент наблюдал за изменениями свойств и за событиями изменения размеров окна, инициируя, если надо, повторное позиционирование.

    Как же решить эту проблему? Решение заключалось в устранении «посредника».

    Мне нужно было сначала загрузить «ленивый» компонент, а только тогда писать в состояние то, что сообщало бы окну с поздравлениями о том, что ему надо открыться. Я смог сделать это, используя те же механизмы Webpack по разделению кода, но теперь — без реализации импорта с помощью React.lazy :

    Эта функция вызывается после того, как моему компоненту, через механизм веб-сокетов, сообщется о том, что ему нужно показать окно с поздравлениями. Я воспользовался функцией Webpack import (вторая строчка кода). Если помните, то выше я говорил о том, что эта функция возвращает промис, в результате я смог воспользоваться конструкцией async/await .

    После завершения загрузки компонента я записываю его в экземпляр моего компонента (командой this.S >). Это даёт мне возможность позже использовать его при рендеринге. Обратите внимание на то, что теперь я могу использовать именованные экспорты. В моём случае я воспользовался, в вышеупомянутой строчке, именем SidebarHappyMoment . И наконец, причём это не менее важно, чем всё остальное, я «сообщаю» окну о том, что ему нужно открыться, соответствующим образом меняя состояние уже после того, как я знаю о том, что компонент готов к работе.

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

    Обратите внимание на то, что команда return ; возвращает this.SidebarHappyMoment — то, что я ранее записал в экземпляр моего компонента. Теперь это — нормальная синхронная render-функция, такая же, как те, которыми вы уже миллион раз пользовались. И теперь окно с поздравлениями выводится в точности там, где оно и должно выводиться. А всё дело в том, что оно открывается только после того, как его содержимое готово к использованию.

    Продукт определяет архитектуру

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

    Речь идёт о том, что продукт определяет то, что должно быть видимым и интерактивным тогда, когда компонент впервые рендерится. Это помогает разработчику разобраться в том, что именно он может отделить от основного кода и загрузить позже, когда в этом возникнет необходимость. Я поразмыслил над ситуацией и «вспомнил», что после того, как пользователь завершит выполнение рекомендованных шагов по настройке сайта, или в том случае, если он не является администратором сайта, мне вообще не нужно показывать ему прогресс-бар и то, что с ним связано. Теперь я, воспользовавшись этой информацией, смог продолжить разделение бандла. Вот что у меня получилось.

    Продолжение разделения бандла

    После этого размер бандла составлял всего 38 Кб. А мы, напомню, начинали со 190 Кб. Налицо уменьшение размера бандла на 80%. И я, кстати, уже вижу другие возможности по разделению кода. Мне не терпится продолжить оптимизацию бандла.

    Итоги

    Разработчики имеют свойство стремиться к тому, чтобы оставаться в своей «зоне комфорта» и не вникать ни во что кроме устройства самого кода и его функциональности. Однако программист, который пользуется вышеописанными инструментами, креативно мыслит и работает в тесной связи с другими специалистами, может получить возможность улучшения производительности своего приложения, значительно уменьшив размер бандла, содержащего то, что нужно для начала работы с этим приложением.

    Уважаемые читатели! Пользуетесь ли вы разделением кода для повышения скорости загрузки своих веб-приложений?

    Webpack + React. Как уменьшить бандл в 15 раз

    Ясной инструкции по сборке webpack для продакшена я не нашел. Поэтому решил написать эту статью. Надеюсь, пригодится. Существует множество сборщиков скриптов. Я выбрал для себя Webpack по таким критериям:

    • Гибкость настройки
    • Большое количество плагинов и лоадеров
    • Lazy loading
    • Использование es6 и es7 синтаксиса с помощью babel-loader



    Из недостатков я бы выделил отсутствие ясной документации. Для тех, кто никогда не сталкивался с Webpack, я рекомендую скринкаст от Ильи Кантора. В данной статье хочу рассказать о настройках сборки проекта, написанного на React + Redux.
    Я не буду углубляться в основы, а расскажу, какие плагины использую для сборки.
    Читать дальше →

    Уменьшение размеров бандлов с помощью Webpack Analyzer и React Lazy/Suspense

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

    Автор статьи, перевод которой мы сегодня публикуем, работает в Wix. Он хочет рассказать о том, как смог уменьшить размер одного бандла примерно на 80%, используя Webpack Analyzer и React Lazy/Suspense.

    Насколько рано стоит приступать к оптимизации?

    Если вы только начали работу над своим новым веб-приложением, то вы, возможно, пытаетесь сосредоточить усилия на том, чтобы оно, так сказать, «оторвалось от земли», стараетесь сделать так, чтобы оно заработало. Вы, вероятно, не обращаете особого внимания на производительность или на размер бандла. Я могу это понять. Однако мой опыт подсказывает, что о производительности и о размерах бандлов стоит заботиться с самого начала. Хорошая архитектура приложения и своевременные «размышления о будущем проекта» сэкономят вам, в долгосрочной перспективе, немало времени и помогут не накопить серьёзного технического долга. Очевидно то, что сложно заранее всё «предвидеть», но вам стоит очень постараться сделать всё правильно с первого дня работы над проектом.

    Вот пара отличных инструментов, которые, как мне кажется, нужно использовать с самого начала. Эти инструменты помогут распознать «проблематичные» NPM-пакеты даже ещё до того, как они займут сколько-нибудь важное место в приложении.

    ▍Bundlephobia

    Bundlephobia — это сайт, который позволяет узнавать о том, насколько некий NPM-пакет увеличит размер бандла. Это — отличный инструмент, помогающий программисту принимать правильные решения, касающиеся выбора пакетов сторонних разработчиков, которые могут ему понадобиться. Bundlephobia помогает проектировать архитектуру приложения так, чтобы его размер не оказался бы слишком большим. На следующем рисунке показаны результаты проверки популярной библиотеки для работы со временем, которая называется moment. Можно видеть, что эта библиотека довольно велика — почти 66 Кб в сжатом виде. Для многих пользователей, работающих на скоростном интернете, это — ничто. Однако стоит обратить внимание на то, каким становится время загрузки этого пакета в 2G/3G сетях. Оно, соответственно, составляет 2.2 и 1.32 секунды. И, обратите внимание, речь идёт только об одном этом пакете.

    Результаты анализа пакета moment средствами Bundlephobia

    ▍Import Cost

    Import Cost — это весьма интересное расширение для множества популярных редакторов кода (у него более миллиона загрузок для VS Code). Оно умеет показывать «стоимость» импортированных пакетов. Особенно мне в нём нравится то, что оно помогает выявлять проблемные области приложения прямо во время работы над ним. На следующем рисунке (он взят с GitHub-страницы Import Cost) показан отличный пример воздействия на размеры проекта разного подхода к импорту сущностей. Так, импорт единственного свойства uniqueId из Lodash приводит к импорту в проект всей этой библиотеки (70 Кб). А если напрямую импортировать функцию uniqueId , то к размеру проекта будет добавлено всего 2 Кб. Подробнее об Import Cost можно почитать здесь.

    «Стоимость» импорта в проект всей библиотеки Lodash и только одной конкретной функции из этой библиотеки

    Дело о неоправданно больших бандлах

    Итак, вы создали своё замечательное приложение. Оно отлично работает на вашем скоростном интернете и на вашем мощнейшем компьютере, набитом оперативной памятью. Вы выпустили его в реальный мир. Через некоторое время к вам начали поступать жалобы от пользователей или от ваших же аналитиков. Эти жалобы касались времени загрузки приложения. Нечто подобное недавно случилось со мной, когда мы, в Wix, зарелизили новую возможность, над которой я работал.

    Для того чтобы немного ввести вас в курс дела — давайте поговорим об этой новой возможности. Это — новый прогресс-бар, расположенный в верхней части боковой панели интерфейса настройки сайтов пользователей. Цель этого механизма заключается в том, чтобы привлечь внимание пользователя к различным шагам, которые ему нужно выполнить для того, чтобы у его бизнес-проекта было бы больше шансов на успех (подключение SEO, добавка регионов, в которые осуществляется доставка товаров, добавление первого продукта, и так далее).

    Прогресс-бар обновляется автоматически, подключаясь к серверу с использованием веб-сокетов. Когда пользователь завершает выполнение всех рекомендованных шагов — выводится всплывающее окно с поздравлениями. После того, как это окно закрывают, прогресс-бар скрывается и больше никогда не выводится при работе с тем сайтом, в настройке которого он использовался. Вот как выглядит то, о чём мы только что говорили.

    Прогресс-бар и окно с поздравлениями

    Что же случилось? Почему наши аналитики жаловались мне на то, что время загрузки страницы выросло? Когда я изучил состояние дел, воспользовавшись вкладкой Network инструментов разработчика Chrome, мне сразу же стало ясно то, что мой бандл оказался довольно большим. А именно — его размер составлял 190 Кб.

    Размер бандла, выясненный с помощью инструментов разработчика Chrome

    «Почему для этой мелочи нужен сравнительно большой бандл?», — подумал я тогда. А правда — почему?

    ▍Поиск проблемных мест в бандле

    После того, как я понял, что размер бандла слишком велик, пришло время выяснить причину этого. Здесь мне пригодился Webpack Bundle Analyzer — отличный инструмент для выявления проблемных мест бандлов. Он открывает новую вкладку браузера и показывает сведения о зависимостях.

    Вот что получилось после того, как я проанализировал бандл с помощью этого инструмента.

    Результаты работы Webpack Bundle Analyzer

    С помощью анализатора я смог обнаружить «преступника». Здесь использовался пакет lottie-web, что добавляло к размеру бандла 61.45 Кб. Lottie — это весьма приятная JavaScript-библиотека, которая позволяет, применяя стандартные средства браузера, выводить анимации, созданные в Adobe After Effect. В моём случае было так, что нашему дизайнеру нужна была приятная анимация, выполнявшаяся при появлении окна с поздравлениями. Он эту анимацию создал и дал её мне в виде JSON-файла, который я передал пакету Lottie и получил красивую анимацию. В дополнение к пакету lottie-web у меня был ещё и JSON-файл с описанием анимации. Размер этого файла составлял 26 Кб. В результате библиотека Lottie, JSON-файл, а также некоторые вспомогательные маленькие зависимости «стоили» мне примерно 94 Кб. И это — всего лишь анимация окна с поздравлениями пользователю. Пользователь, когда эти поздравления видел, должен был радоваться. Мне же от всего этого было грустно.

    Технологии React Lazy/Suspense приходят на помощь

    После того, как я обнаружил причину проблемы, пришло время эту проблему решать. Ясно было то, что не нужно было загружать в самом начале работы всё, что требовалось для анимации. На самом деле, существовала немалая вероятность того, что в ходе текущей сессии пользователя ему не придётся показывать окно с поздравлениями. Тогда я ознакомился с недавно появившимися технологиями React Lazy/Suspense и подумал, что сейчас у меня, вероятно, появилась хорошая возможность их испытать.

    Если вы не знакомы с концепцией «ленивых» (lazy) компонентов, то знайте, что их смысл заключается в разделении приложения на небольшие фрагменты кода. Загрузка этих фрагментов выполняется только тогда, когда они нужны. В моём случае это выражалось в том, что мне нужно было выделить из основного функционала прогресс-бара компонент, который был ответственен за показ поздравления. Загружать этот компонент надо было только тогда, когда пользователь завершал выполнение рекомендованной последовательности шагов.

    В React 16.6.0 (и в более новых версиях) есть простой API, который предназначен для рендеринга ленивых компонентов. Речь идёт о React.lazy и React.Suspense. Рассмотрим пример:

    У нас имеется компонент, который выводит элемент

    , а в нём — компонент Suspense , который оборачивает компонент OtherComponent . Если посмотреть на первую строчку этого примера — можно увидеть, что OtherComponent не импортируется в код напрямую. Обычно подобный импорт выглядит как import OtherComponent from ‘./OtherComponent’; .

    Вместо этого команда импорта оформлена в виде функции, которая принимает путь к файлу. Этот механизм работает благодаря тому, что в Webpack есть встроенные средства разделения кода. Когда в коде присутствует подобная конструкция — возвращается промис, который, после загрузки файла, разрешается с содержимым этого файла. Наша команда импорта обёрнута в функцию React.lazy .

    В материалах для рендеринга, возвращаемых MyComponent , OtherComponent обёрнут в компонент React.Suspense , у которого есть свойство fallback . В нашем случае оказывается, что когда рендеринг доходит до OtherComponent , начинается загрузка соответствующего компонента. Тем временем рендерится то, что записано в свойство fallback . В данном примере это — текст Loading… . Вот, собственно говоря, и всё. Эти механизмы просто делают своё дело.

    Правда, есть пара особенностей, которые нужно учитывать при работе с Lazy/Suspense.

    1. Компонент, который импортируется «ленивым» способом, должен содержать экспорт по умолчанию, который будет входной точкой компонента. Именованный экспорт тут использовать нельзя.
    2. Нужно оборачивать компонент, импортируемый с помощью функции React.lazy , в компонент React.Suspense . Компоненту React.Suspense нужно предоставить свойство fallback . В противном случае возникнет ошибка. Однако если вы попросту не хотите ничего рендерить до завершения ленивой загрузки компонента — вы можете просто записать в fallback значение null , не пытаясь каким-то хитрым способом обойти необходимость записи чего-то в этой свойство.

    Помогло ли мне использование React Lazy/Suspense?

    Да, помогло! Разделение кода сработало прямо-таки изумительно. Взглянем на результаты анализа нового бандла средствами Webpack.

    Результаты работы Webpack Bundle Analyzer после разделения кода

    Как видите, размер моего бандла уменьшился примерно на 50% — до 96 Кб. Отлично!

    И что же, теперь проблема решена? Нет, к сожалению. Когда я взглянул на страницу, то оказалось, что у всплывающего окна с поздравлениями сбилось позиционирование.

    Окно с поздравлениями выводится не там, где нужно

    Проблема заключалась в том, что я «попросил» окно открыться, изменив состояние React-компонента. Между тем, я уже отрендерил null (то есть — ничего не отрендерил), используя компонент React.Suspense . После «ленивой» загрузки необходимых данных соответствующие материалы были добавлены в DOM. Однако позиционирование всплывающего окна уже было выполнено. В результате, из-за того, что свойства соответствующего компонента не менялись, этот компонент «не знал» о том, что ему нужно решить вопрос, касающийся позиционирования. Если я менял размер окна браузера, то всплывающее окно возникало в правильном месте из-за того, что соответствующий компонент наблюдал за изменениями свойств и за событиями изменения размеров окна, инициируя, если надо, повторное позиционирование.

    Как же решить эту проблему? Решение заключалось в устранении «посредника».

    Мне нужно было сначала загрузить «ленивый» компонент, а только тогда писать в состояние то, что сообщало бы окну с поздравлениями о том, что ему надо открыться. Я смог сделать это, используя те же механизмы Webpack по разделению кода, но теперь — без реализации импорта с помощью React.lazy :

    Эта функция вызывается после того, как моему компоненту, через механизм веб-сокетов, сообщется о том, что ему нужно показать окно с поздравлениями. Я воспользовался функцией Webpack import (вторая строчка кода). Если помните, то выше я говорил о том, что эта функция возвращает промис, в результате я смог воспользоваться конструкцией async/await .

    После завершения загрузки компонента я записываю его в экземпляр моего компонента (командой this.S >). Это даёт мне возможность позже использовать его при рендеринге. Обратите внимание на то, что теперь я могу использовать именованные экспорты. В моём случае я воспользовался, в вышеупомянутой строчке, именем SidebarHappyMoment . И наконец, причём это не менее важно, чем всё остальное, я «сообщаю» окну о том, что ему нужно открыться, соответствующим образом меняя состояние уже после того, как я знаю о том, что компонент готов к работе.

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

    Обратите внимание на то, что команда return ; возвращает this.SidebarHappyMoment — то, что я ранее записал в экземпляр моего компонента. Теперь это — нормальная синхронная render-функция, такая же, как те, которыми вы уже миллион раз пользовались. И теперь окно с поздравлениями выводится в точности там, где оно и должно выводиться. А всё дело в том, что оно открывается только после того, как его содержимое готово к использованию.

    Продукт определяет архитектуру

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

    Речь идёт о том, что продукт определяет то, что должно быть видимым и интерактивным тогда, когда компонент впервые рендерится. Это помогает разработчику разобраться в том, что именно он может отделить от основного кода и загрузить позже, когда в этом возникнет необходимость. Я поразмыслил над ситуацией и «вспомнил», что после того, как пользователь завершит выполнение рекомендованных шагов по настройке сайта, или в том случае, если он не является администратором сайта, мне вообще не нужно показывать ему прогресс-бар и то, что с ним связано. Теперь я, воспользовавшись этой информацией, смог продолжить разделение бандла. Вот что у меня получилось.

    Продолжение разделения бандла

    После этого размер бандла составлял всего 38 Кб. А мы, напомню, начинали со 190 Кб. Налицо уменьшение размера бандла на 80%. И я, кстати, уже вижу другие возможности по разделению кода. Мне не терпится продолжить оптимизацию бандла.

    Итоги

    Разработчики имеют свойство стремиться к тому, чтобы оставаться в своей «зоне комфорта» и не вникать ни во что кроме устройства самого кода и его функциональности. Однако программист, который пользуется вышеописанными инструментами, креативно мыслит и работает в тесной связи с другими специалистами, может получить возможность улучшения производительности своего приложения, значительно уменьшив размер бандла, содержащего то, что нужно для начала работы с этим приложением.

    Уважаемые читатели! Пользуетесь ли вы разделением кода для повышения скорости загрузки своих веб-приложений?

    Топ-пост этого месяца:  Форма авторизации PHP без трагических последствий
    Добавить комментарий