NPM скрипты для сборки проектов

Почему «npm run build» не равно «сборка»? Реальная разница, которую ты упускаешь
Когда ты набираешь в консоли npm run build, в голове возникает картинка: что-то собралось, сжалось и готово к бою. Но на практике за этой короткой командой скрывается целый мир подводных камней. И главный миф: что скрипт в package.json — это просто ярлык для длинной строки. На деле, если твой скрипт не учитывает окружение (NODE_ENV), пути импортов и порядок выполнения плагинов, «сборка» может превратиться в «сломанный билд», который не откроется в браузере.
Первый секрет, который веб-разработчики узнают на собственном горьком опыте: NPM скрипты выполняют команды в том же процессе, что и текущий терминал. Это значит, что переменные окружения, установленные в сессии, могут влиять на результат. Ты настраиваешь scripts, а через час другой разработчик (или ты сам на другом компьютере) получает ошибку — потому что в системе нет нужной версии Node или установлен глобальный плагин, который конфликтует.
Профессионалы решают это иначе: вместо того чтобы полагаться на «чистоту» окружения, они явно определяют переменные внутри самого скрипта через cross-env или встроенный в команду синтаксис. Например, NODE_ENV=production webpack --mode production — это не просто стиль, это гарантия, что сборка пройдёт одинаково на Windows и macOS. И это не туториал, это выживание.
Вложенные скрипты: как не получить каскад ошибок
Ты видел в проектах такие конструкции: "start": "npm run build && npm run server". Выглядит логично: сначала собрать, потом запустить. Но есть неочевидный нюанс: оператор && в NPM скриптах работает не так, как в Bash. Если команда слева завершится с ошибкой, выполнение прекратится. Однако ошибка может быть «тихой» — например, если webpack выдал warning, но exit code оказался нулевым. Ты думаешь, что билд прошёл успешно, а на самом деле там сломан код.
Хитрость, которую используют опытные команды: разделяют скрипты на контрольные точки с явной проверкой статуса. Вместо одной строчки — три: "prebuild": "npm run lint", "build": "webpack --config webpack.prod.js", "postbuild": "npm run notify:deploy". Префиксы pre- и post- заставляют NPM выполнять скрипты в строго определённом порядке, и если линтинг провалился — build даже не запустится. Это спасает от деплоя «грязного» кода на продакшен.
Ещё один нюанс: никогда не используй && для длинных цепочек. Вместо этого группируй скрипты в отдельный файл bin/scripts.sh и вызывай его из package.json. Так ты получаешь контроль над стримами ошибок, логами и временем выполнения. И это не паранойя — это разница между «проект работает» и «проект работает у всех».
Скрытый параметр — синонимы и псевдонимы: когда npm run утомляет
Каждый день набирать npm run build:dev — нормально, но когда у тебя 15 скриптов для разных окружений, пальцы начинают запоминать не команды, а их длины. Инженеры с опытом используют трюк с одной буквой: "bd": "npm run build:dev". Кажется мелочью, но это сокращает время на ввод команд на 30% — не веришь? Проверь в своём проекте.
Однако здесь засада: если ты используешь автодополнение (Tab) в терминале, короткие псевдонимы могут конфликтовать с другими утилитами. Например, bd может совпасть с командой системы. Решение: используй префикс, который не встречается в системных командах, например двойной подчёркивание: "__bd": "...". Это выглядит нетипично, зато работает без коллизий.
Ещё один лайфхак: добавляй описание в ключи. Прямо в package.json можно писать "bd": "build:dev — собирает dev-версию с картами кода". Когда проект в разработке, ты быстро вспоминаешь, что делает каждый скрипт. В противном случае через месяц ты будешь гадать, зачем нужен "fix:css" — и запускать его на страховку, а он случайно удалит стили.
Кэширование node_modules: главная иллюзия скорости
Ты замечал, что после десятой сборки npm run build выполняется быстрее? Это не магия — это кэширование webpack-ом в папке node_modules/.cache. Но здесь спрятан капкан: если обновить пакет, а кэш не очистить — билд использует старые блоки, и ты получишь «битую» сборку. Классика: поменяли версию Babel, а скрипт всё ещё тащит старую транпиляцию.
Профессионалы всегда включают флаг --cache false в CI-скриптах, а для локальной разработки используют "prebuild": "rimraf node_modules/.cache". Звучит затратно по времени, но на деле — это добавляет 2 секунды к сборке, зато спасает от часов дебага. И главное: не верь туториалам, которые учат добавлять --cache без оговорок. Кэш полезен, но только если ты понимаешь, когда его сбрасывать.
Ещё один секрет: разделяй кэш для разных режимов. Например, "build:dev": "webpack --cache --cache-location node_modules/.cache/dev" и "build:prod": "webpack --cache --cache-location node_modules/.cache/prod". Это гарантирует, что они не перезаписывают друг друга. Иначе однажды запустишь продакшен-сборку, а она подхватит кэш от дев-сборки — и код minify не выполнится.
Линтинг как часть сборки: must have или trap?
Добавить lint-staged в precommit-хук — это база. Но многие допускают ошибку: линтер запускается до сборки, но не учитывает последние изменения. Если ты используешь husky и --no-verify в NPM скриптах, то после коммита может оказаться, что свежий код не прошёл линтинг. Выход: настрой линтинг как part of build цепочки, а не отдельный хук.
Пример: "build": "eslint src/ && stylelint src/ && webpack --mode production". Здесь порядок важен: сначала статический анализ, потом сборка. Но есть нюанс: если линтер найдёт ошибки — билд прервётся, и ты не получишь артефакты. Для продакшена это правильно. Для разработки — нет: ты хочешь видеть черновик, даже если код неидеальный. Поэтому создай два скрипта: "build:dev": "webpack --mode development" и "build:check": "npm run lint && npm run build:dev". Первый — для быстрой разработки, второй — для проверки перед коммитом.
Главное, о чём молчат: ESLint может замедлять сборку на 40% на больших проектах. Оптимальное решение — запускать его только для изменённых файлов (через lint-staged с настройкой glob). В NPM скриптах это выглядит так: "lint:changed": "lint-staged" (и прописать правила в .lintstagedrc.js). Твоя сборка не должна ждать полную индексацию кода — только свежие строки.
Переменные окружения: когда env не работает, а документация врёт
Есть миф, что в package.json можно использовать process.env.NODE_ENV напрямую. Нет, не работает. Скрипты выполняются в shell, и Node.js не видит эти переменные, если ты их явно не передашь. Ты пишешь "build": "NODE_ENV=production webpack" — и это сработает на Linux/macOS, но упадёт на Windows. Решение — пакет cross-env или использование dotenv для разных файлов.
Профессиональный приём: создай .env.development и .env.production и в скрипте укажи "build:dev": "dotenv -e .env.development -- webpack". Это чище, чем --env в конфиге. Плюс — ты можешь версионировать эти файлы отдельно, без риска смержить продакшен-ключи.
Важно: никогда не храни в .env чувствительные данные, если скрипт запускается в CI. Используй секреты репозитория, а в package.json только ссылки на них через --env. Иначе однажды ты запушишь ключи от сервера — и проект станет уязвим.
Вот четыре ключевых правила, которые гарантированно спасут твою сборку от хаоса:
- Всегда явно определяй окружение внутри скрипта. Никогда не полагайся на глобальный
NODE_ENV. Используйcross-envили встроенный синтаксис с проверкой. Это убирает 90% ошибок при переезде проекта на другую машину. - Разделяй кэш для разных режимов сборки. Не пиши
--cacheбез указания--cache-location. Иначе один билд испортит данные для другого. Лучше вообще отключить кэш в CI, чтобы гарантировать чистоту. - Используй префиксы
pre-иpost-вместо цепочек&&. Это даёт контроль над ошибками и порядком выполнения. Если какой-то этап упал — остальные не запустятся. Для сложных сценариев пиши отдельные shell-скрипты. - Не ленись писать короткие псевдонимы, но с уникальным префиксом. Например, двойное подчёркивание
__или символ_. Чтобы случайно не вызвать системную команду. И добавляй описание в значение скрипта — через месяц ты скажешь спасибо.
Добавлено: 23.04.2026
