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

Время сборки: 10–16 минут.
Проблема: серверный бандл содержит шрифтовые ассеты.
Бандл проанализировали, печальные выводы сделали. Переходим к практике!
Пакетный менеджер у нас 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
В .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. Несмотря на сложность настройки, он покрывает большинство моих потребностей в плане статистического анализа проекта, да и разработчики ESLint его рекомендуют.
Помимо knip рассматривал ts-prune и TSR, но первый больше не развивается, а у второго мне не хватило более тонкой настройки конфигурации.

Выглядит НЕ круто!
Сначала разобрался с зависимостями, затем пришлось править конфиг 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 минут.