С появлением таких сборщиков пакетов, как webpack, общая тенденция заключается в объединении всего JS-кода в один bundle.js файл. По мере расширения возможностей приложения размер bundle.js файла быстро увеличивается с простых КБ до МБ. Это отрицательно сказывается на производительности веб-страницы, поскольку теперь пользователю приходится ждать загрузки гигантского bundle.js файла, прежде чем он сможет взаимодействовать со страницей. Большая часть этого кода может даже не понадобиться этому пользователю, поскольку он может не перейти к этой части вашего приложения.

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

В проекте, над которым я работаю, мы используем React, библиотеку universal-router для маршрутизации на стороне клиента и webpack (версия 2.x). При такой настройке мы реализовали разделение кода и ленивую загрузку следующим образом:

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

Первое, что нужно сделать, это указать webpack, что конкретный путь в коде вашего приложения не должен быть включен в основной bundle.js файл. Это делается с помощью динамического импорта, как показано ниже:

/* assuming pageId indicates the page the user is on */
switch (pageId) {
    case "login": 
        PageComponent = await import(/* webpackChunkName: "login" */ "./pages/user/Login"); 
        break;
    case "home": 
        PageComponent = await import(/* webpackChunkName: "home" */ "./pages/home/Home"); 
        break;
    case "profile": 
        PageComponent = await import(/* webpackChunkName: "profile" */ "./pages/profile/MyProfile"); 
        break;
    case "assets": 
        PageComponent = await import(/* webpackChunkName: "assets" */ "./pages/assets/Assets"); 
        break;
    case "admin": 
        PageComponent = await import(/* webpackChunkName: "admin" */ "./pages/admin/Admin"); 
        break;
    default: 
        PageComponent = Unknown; 
        break;
}
/* render PageComponent */

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

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

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

Вот код, сгенерированный веб-пакетом, который выполняет этот бит для упомянутого выше динамического импорта:

// This file contains only the entry chunk.
// The chunk loading function for additional chunks
__webpack_require__.e = function requireEnsure(chunkId) {
    var installedChunkData = installedChunks[chunkId];
    if (installedChunkData === 0) {
        return new Promise(function(resolve) {
            resolve();
        });
    }
    // a Promise means "currently loading".
    if (installedChunkData) {
        return installedChunkData[2];
    }
    // setup Promise in chunk cache
    var promise = new Promise(function(resolve, reject) {
        installedChunkData = installedChunks[chunkId] = [resolve, reject];
    });
    installedChunkData[2] = promise;
    // start chunk loading
    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.charset = 'utf-8';
    script.async = true;
    script.timeout = 120000;
    if (__webpack_require__.nc) {
        script.setAttribute("nonce", __webpack_require__.nc);
    }
    script.src = __webpack_require__.p + "" + ({"7": "assets", "10": "admin", "13": "login", "14": "profile", "15": "home"}[chunkId] || chunkId) + ".chunk.js";
    var timeout = setTimeout(onScriptComplete, 120000);
    script.onerror = script.onload = onScriptComplete;
    function onScriptComplete() {
        // avoid mem leaks in IE.
        script.onerror = script.onload = null;
        clearTimeout(timeout);
        var chunk = installedChunks[chunkId];
        if (chunk !== 0) {
            if (chunk) {
                chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
            }
            installedChunks[chunkId] = undefined;
        }
    };
    
    head.appendChild(script);
    return promise;
};

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

2. Включите плагин webpack babel-plugin-syntax-dynamic-import

Синтаксис динамического импорта недавно введен в язык и, следовательно, еще не является стандартом. Итак, чтобы он работал с webpack, вам нужно сначала установить пакет babel-plugin-syntax-dynamic-import через:

npm install --save-dev babel-plugin-syntax-dynamic-import

Затем добавьте плагин syntax-dynamic-import в ваш webpack.config.js файл:

const path = require('path');
const packageJson = require("./package.json");
module.exports = {
    devtool: 'source-map',
    entry: {
        bundle: ['babel-polyfill','./src/main'],
    },
    output: {
        filename: '[name].js',
        chunkFilename: `[name].chunk.${packageJson.version}.js`,
        path: path.join(__dirname, 'public/dist'),
        publicPath: '/dist/'
    },
    resolve:{
        extensions:['.js']
    },
    externals:{
        'react':'React',
        'react-dom':'ReactDOM'
    },
    module:{
        loaders:[
            {
                test:/\.js?$/,
                loader:'babel-loader',
                query:{
                    presets: ['es2015', 'react', 'stage-0'],
                    plugins: ['syntax-dynamic-import']
                },
                exclude:/node_modules/
            }
        ]
    }
}

Как видно из приведенной выше конфигурации, вы также можете дополнительно настроить имя файла фрагмента, указав его в ключе output в файле webpack.config.js. В этом случае я добавляю версию пакета приложения для очистки кеша.

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

Удачного кодирования :-)

Ссылки:
https://medium.com/front-end-weekly/lazy-loading-with-react-and-webpack-2-8e9e586cf442