Очевидная неочевидность в сборке с помощью Webpack

в 21:36, , рубрики: bundle, javascript, node.js, nodejs, optimization, refactoring, webpack, рефакторинг

image

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

А началось все...

… с моего удивления. Неминифицированный бандл приложения весил 44 Мб, включал в себя около 4 тыс. модулей, на сборку уходило порядка 8 мин. С еще большим удивлением я смотрел на одновременно подключенные lodash и lodash-es. Пляски с бубном, вырезание "мертвого" кода, удаление модулей, которые практически дублируют друг друга (например, все те же lodash и lodash-es) и как результат — 3 тыс. модулей, 30 Мб, 5 мин. Затем были еще искания на Хабре и нехабре, применение найденной инфы, обрезание лишних локалей moment'а, переход на TypeScript (пока используется только как транспайлер, но это совсем другая история) — 2,5 тыс модулей, 1 мин. 30 сек., 20 Мб.
Вполне достойно.

Но однажды исследуя beta-версию material-ui наткнулся на интересную страничку. Если вы перешли по ссылке, прочитали и все поняли, то дальше вам будет не интересно.

Для тех кто, еще с нами

Допустим, мы имеем небольшой проект, в корневой директории которого находятся:

  • package.json

{
  "name": "webpack-bundle-example",
  "version": "1.0.0",
  "scripts": {
    "start": "webpack"
  },
  "dependencies": {
    "lodash-es": "^4.17.4",
    "webpack": "^3.6.0",
    "webpack-bundle-analyzer": "^2.9.0"
  }
}

  • Конфиг сборщика webpack.config.js

const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
const path = require('path');

module.exports = {
    entry: './index',
    output: {
        filename: 'index.bundle.js',
        path: path.resolve(__dirname, 'build')
    },
    plugins: [
        new BundleAnalyzerPlugin({
            analyzerMode: 'static'
        })
    ]
};

  • Entry point нашего приложения — index.js (совершенно не важно что происходит в скрипте, нас интересует только импорт)

import {mapValues} from 'lodash-es';

console.log(mapValues({a: 0, b: 1}, (...args) => args));

В корне выполняем npm i и после установки зависимостей запускаем сборку npm start.

Плагин BundleAnalyzerPlugin сгенерирует для нас карту, по которой будет понятно из чего же состоит бандл.
И по этой карте мы видим, что в бандле содержатся файлы из директории node_modules общим весом в 608 Kb(!).
Очевидная неочевидность в сборке с помощью Webpack - 2

А мы всего лишь импортировали одну функцию из lodash-es. Жирновато, не правда ли?

Не очевидно очевидное решение

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

You can import directly from lodash-es/ to avoid pulling in unused modules. For instance, instead of:

import {mapValues} from 'lodash-es';

use:

import mapValues from 'lodash-es/mapValues';

А после сборки получаем следующую карту наших модулей и удивляемся — 82 Кб:
Очевидная неочевидность в сборке с помощью Webpack - 3

Поменяв всего одну строку в проекте мы получили чуть ли не 8-ми кратное уменьшение размера бандла.

Вы еще помните про проект из начала статьи? Так вот… 11 Мб, 40 сек, 1,7 тыс. модулей

Автор: Katsuba

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js