В апреле я съездил на HolyJS. Еще до поездки в расписании конференции моё внимание привлек доклад Виктора Хомякова «Удаление мертвого кода в проекте: практическое руководство». Послушав его, я понял, что могу использовать полученные знания в своем текущем проекте, при этом не затрачивая много усилий. В этой статье я расскажу, что у меня получилось.
Что такое мертвый код
Мертвый код — это участки кода или зависимостей, которые:
Никогда не выполняются
Не используются нигде в проекте
Включают ненужные пакеты и дубли транзитивных зависимостей (например, пакеты в dependencies)
Невозможно выполнить
import someLib from 'someLib';
if (Math.random() > 1) {
someLib.doSomething();
}
Какой план
Мы решили заняться оптимизацией производительности нашего приложения в три этапа:
Анализ производительности — оценка текущего веса и скорости приложения
Удаление мертвого кода
Автоматизация — внедрение автоматизированных процессов для оптимизации
Анализ производительности
Наше приложение собирается при помощи webpack. Для анализа бандлов использовал webpack-bundle-analyzer. Результаты получились следующие:

Время сборки: 10–16 минут.
Проблема: серверный бандл содержит шрифтовые ассеты.
Удаление мертвого кода
Бандл проанализировали, печальные выводы сделали. Переходим к практике!
Дедуплиĸация npm
Пакетный менеджер у нас npm. Поэтому вот что я делал:
Диагностировал через команды
npm list --include=prod
npm find-dupes
Устранил дубли
npm dedupe
added 28 packages , removed 57 packages , ¨NBSP; and changed 117 packages in 20s
Сказал команде поправить конфиг npm
npm config set prefer-dedupe true

Настройки ESLint и tsconfig
В .eslintrs добавлены правила:
"no-unused-vars": "error",
"no-unused-private-class-members": "error",
"no-unreachable": "error",
"no-unused-expressions": "error"
А в tsconfig.json добавлено:
"noUnusedLocals": true /* Enable error reporting when a local variables aren't read. */,
"noUnusedParameters": true /* Raise an error when a function parameter isn't read */,
Настройка knip и удаление мертвечины
Для этого я выбрал инструмент knip. Несмотря на сложность настройки, он покрывает большинство моих потребностей в плане статистического анализа проекта, да и разработчики ESLint его рекомендуют.
Помимо knip рассматривал ts-prune и TSR, но первый больше не развивается, а у второго мне не хватило более тонкой настройки конфигурации.
Результаты проверки knip

Выглядит НЕ круто!
Фиксим
Сначала разобрался с зависимостями, затем пришлось править конфиг knip, добавлять новые точки входа, игноры. Итоговый knip.json вышел таким:
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": ["src/client/index.tsx", "src/server/server.js", "bin/dev.js", "jest.config.js", "jest.setup.js"],
"project": ["src/**/*.{ts,tsx,js,jsx,mjs}"],
"ignore": ["**/*.global.css", "**/*.td.ts", "**/index.ts"],
"webpack": {
"config": ["webpack.config.js", "cfg/webpack.client.config.js", "cfg/webpack.server.config.js"]
},
"babel": {
"config": ["babel.config.js"]
},
"ignoreDependencies": [
"enzyme",
"enzyme-to-json",
"@types/enzyme",
"@types/react-router-dom",
"prettier",
"prettier-eslint"
]
}
Потом разрешил удалять неиспользуемые файлы. Реализуется через scripts в package.json:
"knip": "knip",
"knip:md": "knip --reporter markdown > knip-report.md",
"knip:fix": "knip --fix-type exports,types,files --allow-remove-files"
knip:md
формирует отчет в .md формате.knip:fix
позволяет knip фиксить обнаруженные проблемы. Флаг --allow-remove-files
дает удалять неиспользуемые файлы.
В результате проделанных действий все проблемы, что выявил knip, были решены.
Что дальше:
Запихнуть проверку knip в pre commit hooks.
В knip.json добавлены '**/index.ts' в игнор. Нужно правильно обрабатывать такие файлы.
Итоги
Папка | До оптимизации | После оптимизации | Разница (МБ) | Разница (%) |
---|---|---|---|---|
node_modules | 825M | 669M | -156M | ▼ 18.90% |
dist | 12.8M | 8.0M | -4.8M | ▼ 37.50% |
client | 7.1M | 5.9M | -1.2M | ▼ 16.90% |
server | 5.7M | 2.1M | -3.6M | ▼ 63.16% |
Раньше приложение на поде поднималось 10-16 минут, теперь за 3–7 минут.