npm scripts хороши не потому, что умеют все. Они хороши тем, что закрывают заметную часть сборки без лишнего уровня абстракции. Для небольшого и среднего фронтенд-проекта этого часто достаточно: ты видишь реальные CLI-команды, не прячешь их за отдельный DSL и не тащишь тяжелый task runner только ради пары шагов сборки.
Старые статьи про npm scripts часто быстро устаревают, потому что завязаны на пакеты вроде node-sass или на полуфрагменты package.json, которые невозможно запустить как есть. Ниже разберем современный минимальный набор: сборка CSS, linting, последовательный и параллельный запуск, watch-режим и встроенные pre/post hooks.
Примеры ниже можно взять как основу для простого статического сайта, маркетингового проекта или небольшого SPA без отдельной сборочной платформы.
- Почему npm scripts часто хватает
- Базовый набор зависимостей
- Сборка CSS через Sass и PostCSS
- Linting JavaScript и стилей
- Последовательный и параллельный запуск задач
- Watch-режим и локальный сервер
- Pre и post hooks
- Когда npm scripts уже мало
- Итог
Почему npm scripts часто хватает
У scripts есть три сильные стороны:
- они запускаются прямо из
package.json, без дополнительной прослойки; - локальные бинарные файлы из
node_modules/.binдоступны автоматически; - любой разработчик команды видит реальную команду, а не скрытую логику внутри task runner.
Если задача сводится к “запусти CLI с аргументами”, npm scripts обычно достаточно.
Базовый набор зависимостей
Для примеров из статьи используем такой набор:
npm install -D sass postcss postcss-cli postcss-preset-env stylelint eslint npm-run-all onchange browser-syncОбрати внимание: node-sass больше не нужен. Его давно заменил пакет sass, основанный на Dart Sass.
Минимальная настройка PostCSS может выглядеть так:
module.exports = {
plugins: [require("postcss-preset-env")()],
};Сборка CSS через Sass и PostCSS
Начнем с самого частого сценария: собрать Sass в CSS, а затем прогнать результат через PostCSS.
{
"scripts": {
"build:css": "sass src/scss:dist/css --style=compressed --no-source-map && postcss dist/css/*.css --replace"
}
}Что делает этот скрипт:
sass src/scss:dist/cssкомпилирует все Sass-файлы изsrc/scssвdist/css;--style=compressedсразу сжимает результат;--no-source-mapотключает sourcemap, если он не нужен;postcss dist/css/*.css --replaceберет получившиеся CSS-файлы и перезаписывает их результатом обработки.
Если хочешь сначала получить читаемый CSS, а уже потом минифицировать его отдельным шагом, разделяй эти задачи на две разные команды. Так дебажить проще.
Linting JavaScript и стилей
Скрипты удобно использовать и как тонкую оболочку над линтерами:
{
"scripts": {
"lint:js": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
"lint:css": "stylelint \"src/scss/**/*.scss\""
}
}Важный момент: npm scripts не заменяют конфигурацию инструмента. Они только объявляют, как именно этот инструмент запускать в проекте. Поэтому сами правила ESLint и Stylelint лучше держать в отдельных конфигурационных файлах, а не пытаться впихнуть всю логику в одну команду.
Последовательный и параллельный запуск задач
Голым && можно запускать команды последовательно, но как только в проекте становится больше двух-трех задач, запись быстро теряет читаемость.
Для этого удобен npm-run-all.
{
"scripts": {
"lint:js": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
"lint:css": "stylelint \"src/scss/**/*.scss\"",
"build:css": "sass src/scss:dist/css --style=compressed --no-source-map && postcss dist/css/*.css --replace",
"build": "run-s lint:js lint:css build:css"
}
}run-s запускает задачи последовательно. Это полезно, когда следующий шаг имеет смысл только после успешного завершения предыдущего.
Если задачи независимы и должны жить одновременно, используй run-p:
{
"scripts": {
"serve": "browser-sync start --server dist --files \"dist/**/*\"",
"watch:css": "onchange \"src/scss/**/*.scss\" -- npm run build:css",
"dev": "run-p serve watch:css"
}
}Watch-режим и локальный сервер
Есть два типовых варианта watch-режима:
- использовать встроенный
--watchу самого инструмента; - отслеживать файлы отдельно и на изменения запускать нужный скрипт.
Второй вариант универсальнее, поэтому для примера используем onchange:
{
"scripts": {
"build:css": "sass src/scss:dist/css --style=compressed --no-source-map && postcss dist/css/*.css --replace",
"watch:css": "onchange \"src/scss/**/*.scss\" -- npm run build:css",
"serve": "browser-sync start --server dist --files \"dist/**/*\"",
"dev": "run-p watch:css serve"
}
}Такой набор делает три вещи:
- собирает CSS по запросу;
- следит за изменениями в исходниках;
- поднимает локальный сервер и обновляет браузер при изменениях в
dist.
Для простого фронтенд-проекта этого часто достаточно без Webpack, Gulp или отдельного dev server.
Pre и post hooks
npm автоматически понимает скрипты с префиксами pre и post.
Если у тебя есть основной скрипт build, npm выполнит prebuild перед ним и postbuild после него.
{
"scripts": {
"prebuild": "npm run lint:js && npm run lint:css",
"build": "npm run build:css",
"postbuild": "npm run serve"
}
}Это полезно, когда до основной сборки нужно выполнить предсказуемую подготовку. Но злоупотреблять этими хуками не стоит: если цепочка запуска не очевидна из названия, отладка становится неприятнее.
Когда npm scripts уже мало
npm scripts перестают быть удобными не тогда, когда задач много, а когда сами задачи становятся слишком условными и программируемыми.
Признаки, что пора выносить логику выше:
- в скриптах много shell-магии и OS-зависимых конструкций;
- одна команда растягивается на полэкрана;
- появляются сложные ветвления и вычисления;
- нужно переиспользовать одну и ту же процедурную логику в нескольких местах.
В этот момент лучше написать маленький Node-скрипт и запускать уже его из npm run, а не продолжать усложнять shell-команды.
Итог
npm scripts отлично работают как легкий orchestration-слой над готовыми CLI. Они особенно хороши там, где проекту нужны понятные шаги сборки, linting и локальная разработка, но не нужен отдельный инструмент ради самого инструмента.
Если нужен рабочий минимум, начни с четырех вещей: build:css, lint:*, dev и одного-двух watch-скриптов. Этого достаточно, чтобы поддерживать небольшой фронтенд-проект в порядке без лишней инфраструктуры.