Обзор

Если вы уже довольно давно занимаетесь веб-разработкой, особенно в качестве Front-End разработчика, и используете Webpack для объединения кода вашего приложения (React) в один загружаемый пакет для обслуживания вашего приложения с полной поддержкой браузера.

Но в случаях, когда вы работаете с большими приложениями, которые включают в себя сотни компонентов и маршрутов, и вы используете конфигурацию Webpack по умолчанию для объединения и сборки основного пакета плюс его актив, вы столкнетесь с проблемами, когда дело доходит до размера пакета, он будет огромным (например, 4 МБ - 6 МБ), а также для веб-приложения, которое доставляется с вашего сервера тысячам людей одновременно, ну, это вообще не очень хорошая идея.

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

Если вы новичок в Webpack и хотите узнать, как настроить собственную среду сборки, вы можете посмотреть это Учебное пособие.

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

Вот демонстрационный проект, над которым мы будем работать.

Анализируйте свой проект (пакет Webpack)

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

Существует множество плагинов и модулей Webpack, которые позволяют вам анализировать, но мы собираемся использовать webpack-bundle-analyzer специально, поскольку он обеспечивает простой пользовательский интерфейс и его легко понять, чтобы глубже понять ваш пакет.

npm install --save-dev webpack-bundle-analyzer

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

Зайдите в конфигурационный файл webpack и добавьте плагин анализатора.

const path = require("path");
//Webpack Analyzer
const WebpackBundleAnalyzer = require("webpack-bundle-analyzer")
  .BundleAnalyzerPlugin;
//Is it in development mod
let devMode = process.env.devMode || true;

module.exports = {
  entry: path.resolve("./app.js"),
  mode: devMode ? "development" : "production",
  output: {
    filename: "app.js",
    path: path.resolve("./dist"),
    chunkFilename: "[name].js" ///< Used to specify custom chunk name 
  },
  resolve: {
    extensions: [".js", ".json"]
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/, ///< using babel-loader for converting ES6 to browser supported javascript
        loader: "babel-loader",
        exclude: [/node_modules/]
      }
    ]
  },
 //Add Analyzer Plugin alongside your other plugins...
 plugins: [
   new WebpackBundleAnalyzer()
]
};

После добавления плагина просто запустите сборку Webpack.

npm run build

You will notice a new browser window gets opened with the analyzer localhost server running and your bundle visualized. However, you can access the Webpack Analyzer server after running the build command on http://127.0.0.1:8888/

Just opening the server page you will notice containers with a text inside them where each container represents a javascript (Module, folder, or package) you also notice your node_module packages on there and the bigger the size of the container gets the more larger it is.

From there you need to analyze each module and see what dependencies it has it works like a tree so each dependency comes after it’s callee module and so on and so forth.

On the left side, you find your imported module (third party node_modules packages) while on the right side there is your app source code and scripts.

Try to figure out what modules are taking large space on your bundle and try to link them with your app to see if they actually do something or you’re blindly importing something that you don’t need or even don’t use anyway, afterward try to fix it on your source code.

In our Demo app, we are using the find function from Lodash set of function and we are including all the functions and letting Webpack adding them to our bundle without even noticing, therefore, we can just import the find function instead offset of functions.

//So, instead of 
import * as _ from "lodash";
//We only need the find function
import { find } from "lodash";

Если вы попытаетесь исправить свои зависимости и импорт и снова запустите сборку, вы должны заметить разницу в размере пакета между настоящим и предыдущим.

Разделить фрагменты кода (поставщик)

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

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

В нашем демонстрационном проекте мы используем зависимость функции поиска от модуля Lodash (набор функций) и скажем, что эта функция понадобится только тогда, когда пользователь пытается получить список доступных интерфейсных фреймворков, но это не обязательно. для загрузки при запуске и инициализации веб-приложения. Простое решение - разделить код вашего приложения на два пакета (основной пакет и пакет или блок поставщика). Таким образом, основные функции вашего приложения будут отделены от другой сторонней функции. Кроме того, при загрузке лучше загружать приложение меньшим пакетом. комплект другого производителя после инициализации.

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

//Other Config 
...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/, ///< put all used node_modules modules in this chunk
          name: "vendor", ///< name of bundle
          chunks: "all" ///< type of code to put in this bundle
        }
      }
    }
  },
...

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

Кроме того, добавьте конфигурацию chunksName в раздел вывода на webpack.config.js, чтобы настроить имя для имени пакета настраиваемых фрагментов.

output: {
  filename: "app.js",
  path: path.resolve("./dist"),
  chunkFilename: "[name].js" ///< Custom name for Chunks [name] represents the name of the module
},

Теперь попробуйте запустить сборку.

//The Output should be simillar to
Hash: 220096268cc0847a202c
Version: webpack 4.26.0
Time: 1140ms
Built at: 2018-11-23 22:51:33
    Asset      Size  Chunks             Chunk Names
   app.js  8.61 KiB    main  [emitted]  main
vendor.js   547 KiB  vendor  [emitted]  vendor
Entrypoint main = vendor.js app.js
[./app.js] 27 bytes {main} [built]
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 489 bytes {vendor} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {vendor} [built]
[./src/bootstraper.js] 130 bytes {main} [built]
[./src/utils/index.js] 530 bytes {main} [built]
    + 1 hidden module

Вы должны получить в разделе ресурсов два пакета (ваш основной пакет app.js) и (затем новый пакет поставщика).

Убедитесь, что вы связали новый пакет поставщиков, который сгенерировал Webpack, чтобы вы могли использовать функции сторонних модулей.

<script src="./app.js"></script>
<script src="./vendor.js"></script>

Теперь вы можете попытаться получить доступ к своему приложению через простой HTTP-сервер и открыть вкладку Dev Tools Network, чтобы увидеть время, необходимое для загрузки пакета app.js и vendor.js, и то, как он загружается быстрее.

Загрузить код динамически (асинхронно)

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

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

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

Во-первых, не забудьте установить плагин динамического синтаксиса babel только в том случае, если вы используете плагин, но если вы используете другие загрузчики, это зависит от вашего загрузчика, поэтому лучше использовать Babel.

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

And add it to the .babelrc configuration

/* .babelrc */
{
  "presets": [],
  "env": {
    "development": {
      "plugins": ["@babel/syntax-dynamic-import"]
    }
  }
}

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

//Don't use import {find} from "lodash" since we will do it Asynchronously
export function getFrontEndFrameworks() {
  const frameworks = [
    { name: "React", type: "front-end" },
    { name: "Angular", type: "front-end" },
    { name: "Vue", type: "front-end" },
    { name: "Express", type: "back-end" }
  ];
  let frontEnd = [];
  //Now We need the find function let's load it Dynamically 
  import("lodash").then(({ find }) => {
   //import() is a function that returns a Promise gets resolved with the Request Module
   //We only import { find } function from the "lodash" module
   //Then use it normally
   //NOTICE: is you use import() you need to refactor your code to support Async Work Flow
    find(frameworks, (fw, idx) => {
      if (fw.type == "front-end") {
        frontEnd.push(fw.name);
        console.log("Front-End Framework: ", fw.name);
      }
    });
    return frameworks;
  });
}

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

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

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

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

Сжатие связок

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

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

Существует два типа алгоритмов, которые в основном используются для сжатия пакетов Webpack и доставки вашим клиентам:

  • GZIP, это хороший алгоритм сжатия, вы можете использовать сжатие-webpack-plugin для сжатия ваших пакетов (например, vendor.js.gz)
  • Brotil, алгоритм Google, вы можете использовать brotli-webpack-plugin для сжатия ваших пакетов (например, vendor.js.br)

Вы можете попробовать поискать, как использовать это с вашей конкретной структурой или веб-сервером, но вы можете проверить express-static-gzip для обслуживания сжатых пакетов gzip и Brotil с веб-сервера Express.

Что дальше

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

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