Я начал использовать скрипты npm в своих проектах примерно полгода назад. До этого я использовал Gulp, а еще раньше Grunt. Они отлично работали и помогали мне быстрее справляться со своей работой, эффективно автоматизируя многие вещи, которые до этого приходилось делать вручную. Однако я начал замечать, что трачу на настройку этих инструментов больше времени, чем на сам код.

Grunt, Gulp, Broccoli, Brunch и подобные инструменты заставляют вас подстраивать ваши задачи под их парадигмы и конфигурации. У каждого инструмента свой собственный синтаксис, свои глюки и заморочки, которые вам приходиться изучать. Это добавляет сложности коду и процессу сборки, заставляет вас фокусироваться на отладке, а не написании кода.

Эти инструменты сборки зависят от плагинов, которые являются обертками для инструментов командной строки. Это добавляет еще один уровень абстракции, что увеличивает вероятность проблем.

Три проблемы, с которыми я многократно сталкивался

  • Если в вашей системе сборки нет плагина для нужного вам инструмента командной строки — можете считать, что вам не повезло. Или написать такой плагин самостоятельно.
  • Плагин, который вы пытаетесь использовать, является оберткой для старой версии нужного вам инструмента. Функционал и документация у инструмента и соответствующего ему плагина из системы сборки не всегда совпадают.
  • Ошибки не всегда обрабатываются так, как надо. Плагин при ошибке может не передать ничего системе сборке, приводя вас в фрустрацию без понимания путей решения проблемы.
Небольшое уточнение

Позвольте мне сказать это: если вас устраивает привычная вам система сборки и она решает все ваши задачи, продолжайте использовать ее! То, что скрипты npm становятся все популярнее, не означает, что вам срочно нужно переходить на них. Фокусируйтесь на написании кода, а не на освоении инструментов. Если у вас появляется чувство, что вы боретесь со своими инструментами, тогда вам стоит подумать об использовании скриптов npm.

Если вы решили, что хотите начать использовать или хотя бы разобраться со скриптами npm, продолжайте читать. Вы найдете множество примеров задач в этой статье. Также я создал npm-build-boilerplate со всеми задачами, который вы можете использовать в качестве образца.

Написание скриптов npm

Большую часть времени мы будем тратить на файл package.json. Именно в нем живут все зависимости и скрипты. Вот несколько урезанная версия из моего шаблонного проекта:

{
  "name": "npm-build-boilerplate",
  "version": "1.0.0",
  "scripts": {
    ...
  },
  "devDependencies": {
    ...
  }
}

Мы будем расширять наш package.json по мере надобности. Наши скрипты будут добавляться в объект scripts, а все необходимые инструменты будут инсталлироваться и помещаться в объект devDependencies.

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

Структура каталогов нашего проекта

Компиляция SCSS в CSS

Я активно использую SCSS, так что без него мне не обойтись. Чтобы скомпилировать SCSS в CSS я использую node-sass. Для начала надо установить node-sass, это делается в командной строке:

npm install --save-dev node-sass

Команда установит node-sass в ваш текущий каталог, а также добавит в объект devDependencies в package.json. Это особенно полезно, когда кто-либо другой запускает ваш проект — у него уже есть все для работы проекта. После инсталляции мы можем компилировать SCSS с помощью команды:

node-sass --output-style compressed -o dist/css src/scss

Разберем, что делает эта команда. Флаг --output-style отвечает за вид скомпилированных стилей, у нас он в значении в значении compressed — стили сжимаются; скомпилированные файлы выводятся в каталог dist/css, это флаг -o; в каталоге src/scss идет поиск на предмет наличия файлов SCSS, которые мы будем компилировать.

Теперь, когда мы разобрались, как это работает в командной строке, вернемся к нашему скрипту npm. Добавьте эту команду в объект scripts вашего файла package.json, примерно так:

"scripts": {
  "scss": "node-sass --output-style compressed -o dist/css src/scss"
}

Вернитесь в командную строку и выполните:

npm run scss

Вы увидите точно такой же результат, как и при непосредственном выполнении node-sass.

Любой скрипт npm из этого поста можно выполнить с помощью подобной команды.

Просто замените scss на название задачи, которую вы хотите выполнить.

Как видите, у многих инструментов командной строки, которые мы будем использовать, есть многочисленные опции, которые мы можем настраивать под себя. Вот, например, список опций для node-sass. Вот образец конфигурации с передачей множества опций:

"scripts": {
  "scss": "node-sass --output-style nested --indent-type tab --indent-width 4 -o dist/css src/scss"
}

Автопрефиксер

После компиляции Scss в CSS мы можем автоматически добавить вендорные префиксы, используя Autoprefixer & PostCSS. Мы сразу установим несколько модулей, разделив их пробелами:

npm install --save-dev postcss-cli autoprefixer

Мы устанавливаем два модуля, потому как сам по себе PostCSS ничего не делает. Он нуждается в других плагинах типа Autoprefixer, чтобы манипулировать переданным CSS.

После установки и сохранения в devDependencies всех необходимых инструментов, добавьте задачу в объект scripts.

"scripts": {
  ...
  "autoprefixer": "postcss -u autoprefixer -r dist/css/*"
}

Эта задача говорит: “Эй, postcss, используй (use, флаг -u) autoprefixer с заменой всех старых файлов в каталоге dist/css на новые, с вендорными префиксами”. Все! Нужно поменять набор поддерживаемых браузеров для автопрефиксера? Измените его конфигурацию:

"autoprefixer": "postcss -u autoprefixer --autoprefixer.browsers '>5%, ie 9' -r dist/css/*"

Опять-таки, это далеко не все доступные опции, которые вы можете использовать в своей сборке, вот списки опций для postcss-cli и для autoprefixer.

Линтинг JavaScript

Выдерживание стандартного формата и стиля написания кода важно, так как позволяет свести количество ошибок к минимуму и увеличить эффективность разработчика. Линтинг позволяет сделать это автоматически, поэтому добавим линтинг JavaScript из пакета eslint.

Опять, начнем с установки пакета, в этот раз краткой:

npm i -D eslint

По действию эта команда идентична традиционной:

npm install --save-dev eslint

После установки, мы настроим некоторые базовые правила для проверки нашего кода с помощью eslint. Выполните следующую команду для запуска мастера настройки:

eslint --init

Я предлагаю выбрать вариант “Answer questions about your style” и ответить на все вопросы. В результате в корне вашего проекта будет сгенерирован файл с настройками eslint для линтинга вашего кода.

Теперь добавим задачу в объект scripts нашего файла package.json:

"scripts": {
  ...
  "lint": "eslint src/js"
}

Наша задача по линтингу занимает всего 13 символов! Она просматривает все файлы JavaScript в каталоге src/js и проверяет их на соответствие сгенерированным ранее правилам. У линтера очень много доступных опций.

Минификация JavaScript

Теперь объединим и минифицируем наши файлы JavaScript, это мы сделаем с помощью uglify-js. Начнем, как всегда, с установки:

npm i -D uglify-js

Затем настроим задачу по минификации в package.json:

"scripts": {
  ...
  "uglify": "mkdir -p dist/js && uglifyjs src/js/*.js -m -o dist/js/app.js"
}

Одной из сильных сторон скриптов npm является то, что они по своей сути являются псевдонимами для задач в командной строке, которые вы хотите запускать неоднократно. Это значит, что вы можете использовать стандартный код командной строки напрямую в своем скрипте! Наша задача использует две возможности стандартной командной строки, это mkdir и &&.

Первая часть задачи (mkdir -p dist/js) говорит: “создай каталог (mkdir), но только если он еще не существует (флаг -p)”. После успешного выполнения этой команды, запускается вторая часть задачи, непосредственно минификация (uglifyjs). Оператор && соединяет эти две команды в последовательность, позволяя запуск второй только после успешного выполнения первой.

Минификатор (uglifyjs) проходит через все файлы JavaScript (*.js) в каталоге src/js/ и применяет к ним команду “mangle” (флаг м), выводя результат в файл dist/js/app.js. Изучив документацию минификатора, вы найдете множество конфигурационных опций.

Давайте обновим задачу uglify для создания сжатой версии dist/js/app.js. Добавим еще одну команду uglifyjs, передав ей флаг “compress” (-c):

"scripts": {
  ...
  "uglify": "mkdir -p dist/js && uglifyjs src/js/*.js -m -o dist/js/app.js && uglifyjs src/js/*.js -m -c -o dist/js/app.min.js"
}

Сжатие изображений

После минификации JavaScript самое время перейти к сжатию изображений. Согласно данным httparchive.org, средний вес страницы из топ-1000 составляет 1.9мб, при этом изображения занимают 1.1мб. Поэтому уменьшение размера изображений на странице это один из лучших способов ускорить ее загрузку.

Установим imagemin-cli:

npm i -D imagemin-cli

Imagemin хорош тем, что сжимает большинство типов изображений, включая GIF, JPG, PNG и SVG. Вы можете передать ему каталог с изображениями, остальное он сделает сам:

"scripts": {
  ...
  "imagemin": "imagemin src/images dist/images -p",
}

Эта задача заставляет imagemin найти и сжать все изображения в каталоге src/images, поместив сжатые изображения в каталог dist/images. Флаг -p (progressive) означает прогрессивное сжатие изображений, когда это возможно. В документации описаны все доступные опции.

SVG спрайты

Шумиха вокруг SVG усилилась за последние несколько лет и для этого есть хорошая причина. Изображения SVG четкие на всех устройствах, редактируются с помощью CSS и хорошо работают со скринридерами. Однако программное обеспечение для создания SVG обычно оставляет много постороннего и ненужного кода. К счастью, svgo помогает избавиться от мусора (мы установим svgo чуть позже, вместе с другим пакетом).

Вы можете автоматизировать процесс комбинирования и создания спрайтов из SVG для получения одного файла SVG (подробнее эта техника описана в статье на css-tricks.com). Для автоматизации процесса мы установим svg-sprite-generator.

npm i -D svgo svg-sprite-generator

Последовательность действий уже должна быть знакома вам: после установки мы добавим задачу в объект scripts в файле package.json:

"scripts": {
  ...
  "icons": "svgo -f src/images/icons && mkdir -p dist/images && svg-sprite-generate -d src/images/icons -o dist/images/icons.svg"
}

Обратите внимание, что задача icons делает три вещи, благодаря наличию двух операторов &&. Во-первых, мы используем svgo передается каталог (флаг -f, folder) с SVG, которые svgo сжимает. Во-вторых, с помощью команды mkdir -p мы создаем каталог dist/images, если он до сих пор не создан. И в-третьих, мы используем svg-sprite-generator, передавая ему каталог с исходниками (флаг -f, folder) и указав каталог-назначение (флаг -o, output) для готового спрайта.

Локальный сервер и автоматическое применение изменений с BrowserSync

Один из последних фрагментов нашего пазла это BrowserSync. Он может делать несколько вещей: запускать локальный сервер, автоматически обновлять файлы в любом подключенном браузере и синхронизировать клики и прокрутку между браузерами. Установите его и добавьте задачу:

npm i -D browser-sync
"scripts": {
  ...
  "serve": "browser-sync start --server --files 'dist/css/*.css, dist/js/*.js'"
}

Наша задача BrowserSync запускает сервер (флаг --server), используя в качестве корневого для сервера текущий каталог. Флаг --files задает путь к отслеживаемым файлам CSS или JS в каталоге dist, когда что-то в этом каталоге меняется, изменения автоматически вставляются на страницу.

Вы можете открыть несколько браузеров (даже на нескольких устройствах) и они будут получать обновления файлов в режиме реального времени.

Группирование задач

Добавив все перечисленные задачи, мы можем делать следующее:

  • Компилировать SCSS в CSS и автоматически добавлять вендорные префиксы
  • Проверять и минифицировать JavaScript
  • Сжимать изображения
  • Конвертировать каталог с изображениями SVG в один спрайт SVG
  • Запускать локальный сервер и автоматически вставлять изменения в любой браузер, подсоединенный к серверу.

Но мы не будем останавливаться на этом.

Объединение задач CSS

Добавим задачу, сочетающую две задачи, связанные с CSS (обработка Sass и запуск автопрефиксера), чтобы нам не приходилось запускать их по отдельности:

"scripts": {
  ...
  "build:css": "npm run scss && npm run autoprefixer"
}

Когда вы выполняете npm run build:css, в командной строке выполняется npm run scss, после успешного выполнения этой команды оператор && запускает выполнение второй команды: npm run autoprefixer.

Объединение задач JavaScript

Аналогично задаче build:css мы можем объединить вместе и задачи JavaScript для облегчения их запуска.

"scripts": {
  ...
  "build:js": "npm run lint && npm run uglify"
}

Теперь мы можем использовать команду npm run build:js для проверки, объединения и минификации JavaScript в один шаг!

Объединение остальных задач

Мы можем объединить все задачи, связанные с изображениями, а также сделать одну универсальную задачу, объединяющую все предыдущие:

"scripts": {
  ...
  "build:images": "npm run imagemin && npm run icons",
  "build:all": "npm run build:css && npm run build:js && npm run build:images",
}

Отслеживание изменений

До этой точки все наши задачи требовали внесения изменений в файл, переключения обратно в командную строку и запуска соответствующей задачи. Одна из самых полезных вещей, которые мы можем сделать, это отслеживание изменений в файлах с соответствующим автоматическим запуском задач. Для того, чтобы сделать это я рекомендую использовать onchange. Устанавливаем как обычно:

npm i -D onchange

Настроим отслеживание с выполнением задач для CSS и JavaScript:

"scripts": {
  ...
  "watch:css": "onchange 'src/scss/*.scss' -- npm run build:css",
  "watch:js": "onchange 'src/js/*.js' -- npm run build:js",
}

Вот разбор основных моментов: onchange принимает каталог, который нужно отслеживать. В нашем случае это файлы SCSS и JS. После двух дефисов (--) указывается команда, которая будет выполняться при любом добавлении, изменении или удалении файлов в соответствующем отслеживаемом каталоге.

Окончательный вид процесса сборки с помощью скриптов npm

Установим еще один пакет parallelshell:

npm i -D parallelshell

Как обычно, добавим новую задачу в объект scripts:

"scripts": {
  ...
  "watch:all": "parallelshell 'npm run serve' 'npm run watch:css' 'npm run watch:js'"
}

parallelshell принимает в качестве аргументов несколько строк с командами, которые выполняет при запуске npm run.

Почему мы используем parallelshell для объединения множественных задач, а не оператор &&, как в предыдущих случаях. Я пытался. Проблема в том, что && связывает команды вместе и ждет успешного окончания каждой из них перед тем, как запускать следующую. Однако, так как у нас в цепочке есть команда watch, завершения не будет — мы застрянем в бесконечном цикле.

Поэтому использование parallelshell позволяет нам одновременно выполнять несколько задач watch. Подобным функционалом обладает также npm-run-all.

Итоговая задача сначала запускает сервер с BrowserSync, используя задачу npm run serve. Затем отслеживание файлов CSS и JavaScript, при наличии изменений запускается сборка, а так как BrowserSync настроен на отслеживание измений в каталоге dist, все результаты изменений автоматически передаются в подсоединенный браузер.

Прочие полезные команды

npm может решать множество задач. Добавим еще одну задачу к нашим сборочным скриптам.

"scripts": {
  ...
  "postinstall": "npm run watch:all"
}

postinstall запускается сразу после выполнения npm install в командной строке. Это особенно хорошо для работы в команде: когда кто-либо клонирует ваш проект и выполняет npm install, задачи в watch:all стартуют немедленно, то есть запускается сервер и открывается окно браузера, в котором отслеживаются все изменения файлов.

Заключение

Да, мы сделали это! Надеюсь, что вы изучили некоторые основы использования npm-скриптов для процесса сборки и работы командной строки в целом.

На случай, если вы что-то упустили, я создал npm-build-boilerplate, в котором есть все упомянутые задачи и который можно использовать в качестве стартовой точки.