Оптимизация времени загрузки для больших реакт приложений

Оптимизация времени загрузки очень важна, потому что более высокое время загрузки связано с низким коэффициентом конверсии. Большим узким местом во время загрузки обычно являются наши размеры файлов и загрузка внешних ресурсов. В этой статье, моей первой статье, я расскажу вам о процессе, который я выполнил, чтобы оптимизировать свои React модули и сделать их готовыми к производству, уменьшая размер связки и время загрузки. Сайт https://suitup-ui.org был использован в качестве учебного примера, а результаты показаны с использованием pingdom и PageSpeed Insights от Google.

Загрузите только то, что вам нужно

Большая проблема с именованным импортом в ES6 заключается в том, что вы импортируете всю библиотеку, а затем разрушаете доступ к нужному модулю. Для небольших библиотек это нормально, вы не заметите никаких изменений, но для больших зависимостей, таких как lodash или ramda, я призываю вас просто импортировать то, что вам нужно.
это плохо:
//this is bad
            import _ from "lodash";
            import R from "ramda";

            //pretty, but as bad as before
            import { find } from "lodash";
            import { compose } from "ramda";

            ...
так лучше:
import find from "lodash/find";
            import compose from 'ramda/src/compose';
            import curry from 'ramda/src/curry';
            import map from 'ramda/src/map';

            //you can do this if you want:
            const _ = {
            find: find
            }

            const R = {
            compose: compose,
            curry: curry,
            map: map
            }

            ...
Для lodash, вы также можете использовать этот babel плагин и этот webpack плагин

Минифицируй свой код

Вы можете использовать любое средство для этого, но я предпочитаю uglify. Он может уменьшить ваши размеры файла на много. Например, 6 Мб модуль может быть уменьшен до 2 МБ легко. Вот пример конфигурации плагина Uglify в webpack 2, это ведь конфигурация, которую я использую:
plugins: [
            new webpack.optimize.UglifyJsPlugin({
            output: {
            comments: false
            },
            mangle: true,
            sourcemap: false,
            debug: false,
            minimize: true,
            compress: {
            warnings: false,
            screw_ie8: true,
            conditionals: true,
            unused: true,
            comparisons: true,
            sequences: true,
            dead_code: true,
            evaluate: true,
            if_return: true,
            join_vars: true
            }
            })]

Модули Babel свободные

Это необязательно, и это не всегда рекомендуется, но с использованием предварительной настройки babel es2015 с установленным свободным режимом в true, может уменьшить ваш пакет в несколько килобайт.Свободный режим будет генерировать более простой код es5, но это означает, что он, возможно, не будет строго следовать за es6 спецификацией. Если вы переключитесь с babel на родной es6, тогда у вас могут возникнуть проблемы. Чтобы использовать свободный режим в предустановке es2015, вы можете сделать это из конфигурации webpack:
            presets: [
            ["es2015", { loose: true }],
            "react"
            ]
            

Уничтожение дерева и удаление мертвого кода

Если вы можете использовать инструмент для удаления мертвого или неиспользуемого кода, используйте его. В большинстве случаев вы не заметите большой разница (по крайней мере, вы импортируете все библиотеки и не используете их, как в первом совете), но все имеет значение для окончательного размера пакета. В webpack 2 мы можем активировать дрожание дерева, отключив преобразование модуля в предустановке es2015 и позволяя webpack позаботиться об этом. В режиме производства веб-пакет будет искать неиспользуемые импорты и удалять их.
            presets: [
            ["es2015", { loose: true, modules: false }],
            "react"
             ]
            

Разделение кода

Вы должны подумать о том, чтобы расщепить ваш файл в модули, если ваш файл больше, чем 250 кб. Делать так,  вы можете использовать динамический импорт. Динамический импорт - это «функция как» импорт. Promise возвращается при загрузке модуля. Если вы используете webpack, он распознает этот синтаксис и отделит модуль в другом блоке.

Что можно загрузить частями?

Хорошими кандидатами для разделения на модули являются статические json-файлы, такие как переводы, стили и assets обычно. Кроме того, вы можете разделить свои route на модули, если ваш сайт слишком большой. Пример динамического импорта (взятый из документов webpack):
const determineDate = () => {
            import('moment').then((moment) => {
            console.log(moment().format());
            }).catch((err) => {
            console.log('Failed to load moment', err);
            });
            }

            determineDate();

Доставьте ваши файлы, сжатые с помощью Gzip

Это очень важно и уменьшит размер вашего файла до 20% -25% от первоначального размера. Браузеры поддерживают сжатие gzip, поэтому вам не нужно беспокоиться о совместимости. Вы можете использовать 2 подхода для доставки сжатых файлов. Первый - сжать «в полете», статический,assets. Второй - предварительно сжать файлы. Мы посмотрим, как это сделать на webpack и выразить, но вы может выполнить то же самое с nginx, apache и т. д.

Предварительное сжатие с помощью webpack

Я рекомендую этот подход, но его можно настроить немного сложнее. 1- Создать сжатый файл с плагином сжатия webpack
plugins: [
            new CompressionPlugin({
            asset: "[path].gz[query]",
            algorithm: "gzip",
            test: /\.js$|\.css$|\.html$/,
            threshold: 10240,
            minRatio: 0.8
            })
            ]
2- Строя сжатый файл, вы можете использовать промежуточное программное обеспечение, подобное этому:
//this middleware serves all js files as gzip
        app.use(function(req, res, next) {
        var originalPath = req.path;
        if(!originalPath.endsWith(".js")) {
        next();
        return;
        }
        try {
        var stats = fs.statSync(path.join("public", `${req.path}.gz`));
        res.append('Content-Encoding', 'gzip');
        res.setHeader('Vary', 'Accept-Encoding');
        res.setHeader('Cache-Control', 'public, max-age=512000');
        req.url = `${req.url}.gz`;

        var type = mime.lookup(path.join("public", originalPath));
        if (typeof type != 'undefined') {
        var charset = mime.charsets.lookup(type);
        res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
        }
        } catch(e) {
        }
        next();
        })

Сжатие на лету

Этот подход проще, но также серверу потребуется большая мощность процессора для сжатия файлов.
var compression = require('compression');
var app = express();
app.use(compression());

Кэш ваших статических файлов

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

Сильно кешируемые заголовки

Для установки максимального времени кеша доступны опции Expires и Cache Control. Второй - старше, введенный в HTTP / 1.1, и Google рекомендует его по истечению срока действия, но вы можете выбрать любой.

Заголовки недельного кэша

Доступны опции Last-Modified и Etag. Last-modified более удобен, тогда как Etag является обычным хэшем. Вот пример, чтобы настроить это на Express, сильный (maxAge) и слабый (Last-Modified) заголовок кеширования:
var oneWeek = 86400000*7;
app.use(express.static(path.join(__dirname, "public"), { maxAge: oneWeek, lastModified: true }));
max-age and last-modified headers with Static express middleware

Версии ваших файлов

Кэш длительный, но что произойдет, если я хочу, чтобы мои пользователи всегда получали последнюю версию моего приложения? Тогда ты можешь запустить версию вашего файла и запросите его с параметром версии, поэтому каждый раз, когда URL изменяется, браузер запросит новую версию, которая не кэшируется. Например, если ваше приложение находится в версии 2.0, URL-адрес должен выглядеть примерно так:
/bundle/app.js?v=2.0.2

Избегайте загрузки внешних ресурсов

Загрузка внешних ресурсов приведет к ошибкам на инструментах проверки скорости страницы. Старайтесь всегда иметь все локальный или CDN. Избегайте таких вещей, как загрузка шрифтов из Google шрифтов api. Иногда это вызывает блокировку.

Асинхронная нагрузка

Асинхронная загрузка вашего файла приложений хороша, если вы хотите сократить время загрузки, поскольку браузеры ждут ваши скрипты, которые нужно загрузить перед рендерингом html, но с асинхронизмом html будет интерпретироваться, пока ваш скрипт загружается. Если вы поместите все свое приложение в один файл, тогда нет никакой опасности загрузить ваши сценарии, подобные этому, в HTML:
<script async src=”/bundle/main.js”></script>
Сначала проверьте свое приложение, иногда бывают странности при загрузке сценариев асинхронно.

Render файлов на сервере

Поисковые системы достаточно умны, чтобы распознавать контент, загруженный с помощью javascript, но они будут распознавать только если он загружается достаточно быстро. Проблема в том, что контент обычно загружается медленно, потому что вам нужно ждатьнекоторые вызовы ajax или что-то еще, поэтому, когда вы используете инструменты поиска Google, чтобы увидеть, как Google видит ваш сайт, вы  просто просмотрите пустую страницу. К счастью, React работает и на сервере, поэтому вы можете сделать рендеринг на стороне сервера. Я не собираюсь делать полный учебник об этом в этой статье, но вы можете исследовать это самостоятельно. Некоторые советы, прежде чем вы начнете делать это, должны избегать использования фейсбука, поскольку сложнее сделать серверный рендеринг с ним, сокращение - лучший выбор. Кроме того, вы должны проверить некоторые фреймворки, такие как next.js, что делать его проще реализовать. Другие варианты приложений, в которых вы не можете реализовать это, - это использовать какой-то инструмент, например PreRender.

Другие детали, которые вы можете улучшить

Некоторые менее важные улучшения заключаются в использовании CDN, таких как cloudflare, предотвращение перенаправления (например, http до https или WWW на не-www) и уменьшить загрузку размера изображения с помощью ленивой загрузки (я предоставляю компонент Image в Suitup UI сделает это).

Время практиковать: инструменты тестирования онлайн для ранжирования скорости вашего сайта

Есть несколько очень хороших инструментов онлайн для анализа и ранжирования скорости вашего сайта. Наиболее известным является PageSpeed Взгляд из Google. Еще одно очень хорошее - это тот, который был у Pindom. Вы можете использовать оба варианта. Не беспокойтесь, если вы не получите идеальный 100, это не всегда возможно достичь. Например, если вы используйте google analytics, вы никогда не получите 100% в свойствах PageSpeed, потому что максимальный возраст скрипта аналитики всего 2 часа на момент написания этой статьи. Даже сайт PageSpeed Insights не достиг 100% по этой причине:

Страница WebSpeed Insights получает 98% на мобильных и desktop компьютерах

После того, как я внес все изменения на свой сайт (suitup-ui), я получил очень хорошие результаты. Только server render и перенаправления все еще отсутствуют, но он уже в пути, а затем я получаю 98% в Google pageSpeed. Текущие результаты:
Google PageSpeed Insights
Mobile: 91% Desktop: 97%
Pingdom:
Класс производительности: A 96              быстрее: 98% сайтов

Google PageSpeed Insights результаты для ПК

pingdom результаты

Заключение

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