Статьи

Использование GNU Make в качестве инструмента для сборки интерфейса разработки

Популярность таких препроцессоров CSS, как Sass, и исполнителей задач, таких как Grunt, сделала процесс сборки принятой частью фронт-энда. Там нет недостатка вариантов или мнений в пространстве для выполнения задач / сборки инструментов, наиболее популярными из которых являются Grunt и Gulp .

Я пишу приличное количество JavaScript на работе и в личных проектах, но я избегаю использования Grunt или Gulp, если в этом нет необходимости. Я предпочитаю GNU Make по ряду причин:

  • Это открывает силу оболочки Unix; STDIN и STDOUT чрезвычайно универсальны.
  • Он уже доступен во всех средах, которые я использую.
  • Он либо имеет все необходимые инструменты, либо позволяет получить к ним доступ без ненужных модулей-оберток, связывающих меня с любой версией, доступной для моего выбранного инструмента сборки.

Я не одинок в предположении, что вам, вероятно, не нужны все эти необычные инструменты для сборки. Однако я вижу недостаток в практических введениях в Make как инструмент для разработки фронт-энда разработки. Хотя я бы посоветовал вам перейти к RTFM , я не собираюсь указывать вам 186-страничное руководство по программному обеспечению, написанное в 70-х годах, и говорить: «Слушайте!».

Итак, давайте посмотрим на Make и увидим, как он может эффективно работать над созданием наших CSS и JavaScript-ресурсов.

Инструменты сборки против бегунов задач

Когда я полностью заболел программированием с помощью декларативного JSON и заново реализовывал компиляцию ресурсов в Makefile, я принес с собой понятие именования задач — таких имен, как «setup», «css» и «assets». Мои цели в Makefile выглядели примерно так:

setup: bower install mkdir -p htdocs/assets/css mkdir -p htdocs/assets/js 

Я привел менталитет бегуна задач, чтобы Сделать. Правильное использование — вместо этого иметь цели в паре с предпосылками, позволяя Make создавать новые файлы только после изменения источников.

Основы правил Makefile

Чтобы использовать Make в полной мере, вам необходимо правильно сформировать желаемые результаты в качестве целей сборки, их предварительных условий (зависимостей) и рецепта, чтобы превратить эти зависимости в предполагаемый вывод. то есть:

 htdocs/robots.txt: support-files/robots.txt cp support-files/robots.txt htdocs/robots.txt 

Умышленно упрощенный пример, но он содержит основные конструкции правила Makefile. На первой строке у нас есть цель ( htdocs/robots.txt ). После двоеточия у нас есть предпосылки цели. Когда make анализирует этот Makefile, он будет читать это правило и интерпретировать его как «если источник был изменен с момента создания цели, заново сгенерируйте цель». Чтобы восстановить цель, она выполняет строки с отступом табуляции под парой target: source , называемой рецептом (рецепты Makefile ДОЛЖНЫ быть с отступом табуляции, а не с пробелом).

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

  • $@ — целевое имя файла
  • $ – the filename of the first prerequisite
  • $? — разделенный пробелами список всех предпосылок

Используя автоматические переменные, наш предыдущий пример становится:

 htdocs/robots.txt: support-files/robots.txt cp $< $@ 

Скажем, мы добавляем humans.txt в support-files и хотим, чтобы этот htdocs опубликован в htdocs . Мы не хотим копировать и вставлять предыдущее правило, но мы можем улучшить его, используя основы в target и предварительном условии:

 htdocs/%.txt: support-files/%.txt cp $< $@ 

Теперь мы куда-то добираемся. Выше будет скопировать любые текстовые файлы, но только при изменении исходного файла. Если вы запускаете его два раза подряд, то при втором запуске он завершится без копирования каких-либо файлов.

Компиляция Sass

Давайте возьмем то, что мы рассмотрели, и сделаем это полезным. Предположим, у нас есть следующая структура каталогов:

 |_ assets | |__ css | |__ js | |_ htdocs | |__ css | |__ js 

Наши исходные файлы находятся в assets , а наши скомпилированные результаты обслуживаются нашим http-сервером из каталога htdocs . Далее следует правило Make для генерации нашего CSS из источника Sass с использованием SassC:

 htdocs/css/%.css: assets/css/%.scss sassc -t compressed -o $@ $? 

Рецепт с единственной командой, с одной предпосылкой. Он инструктирует Make: «Если исходный файл Sass новее, чем целевой файл сборки, выполните эту команду sassc качестве выходных данных ( $@ ) (опция -o ) и обязательное условие в качестве исходного».

Если вы запустите это, вы увидите команду, напечатанную на терминале, а также любой вывод из двоичного sassc . Это быстро зашумит, поэтому мы можем подавить это эхо-команды, добавив команду с @ . Давайте сделаем шаг вперед и сделаем этот рецепт более интересным:

 htdocs/css/%.css: assets/css/%.scss @echo Compiling $@ @mkdir -p $(@D) @sassc -t compressed -o $@ $? @node_modules/.bin/autoprefixer $@ 

Выше выполняются эти команды:

  • Отобразите имя файла назначения, чтобы мы могли видеть, что компилируется
  • Создайте каталог для целевого файла, если он еще не существует (включая подкаталоги!). Это вводит новую Автоматическую переменную — $(@D) — каталог целевого файла.
  • Скомпилируйте CSS из исходного кода Sass
  • Используйте автоматический префикс для добавления любых необходимых префиксов вендора в наш CSS, чтобы нам не нужно было добавлять их в наш Sass.

Мы будем использовать тот же подход, чтобы минимизировать наш JavaScript. Вы также можете перенести ваш CoffeeScript или ES6 JavaScript, если вы их используете.

 htdocs/js/%.js: assets/js/%.js @echo Processing $@ @mkdir -p $(@D) @node_modules/.bin/jsmin --level 2 --output $@ $? 

Я использую Node в процессах сборки JS и CSS, потому что именно там находятся лучшие инструменты для фронт-энда разработки. Но это не значит, что мне нужно обернуть эти модули Node в обертки Grunt или Gulp и запустить их в другой программе Node с собственным синтаксисом.

Несколько предпосылок

Каждый проект Sass, над которым я работал, имел переменные, определенные в файле, который нужно импортировать в другие файлы Sass. Поэтому мы должны учитывать глобальные зависимости в нашем примере компиляции Sass.

 htdocs/css/%.css: assets/css/%.scss assets/css/_settings.scss @echo Compiling $@ @mkdir -p $(@D) @sassc -t compressed -o $@ $< @node_modules/.bin/autoprefixer $@ 

Следует отметить, что мы добавили _settings.scss в список предварительных условий, и мы изменили вызов sassc чтобы использовать в качестве входных данных имя первого необходимого файла ( $ ) вместо использования полного раздела предварительных требований. , Теперь, если файл Sass с соответствующим основанием ( % подстановочный знак) или файл _settings.scss изменился, целевой файл CSS будет перекомпилирован.

Поскольку наш ствол в первом предварительном условии может соответствовать подкаталогам, мы должны убедиться, что подкаталог существует в нашем htdocs/css . Мы делаем это, используя mkdir и автоматическую переменную $(@D) , которая содержит раздел каталога пути к цели. Вам не нужно использовать $(@D) , вы получите тот же результат, вызывая оболочку с помощью $(shell basedir $@) ; автоматическая переменная это просто удобство.

переменные

На мой вкус слишком много повторений. Если мы изменим структуру каталогов нашего проекта, нам придется изменить каждый экземпляр этих имен каталогов. Давайте очистим его, заменив различные каталоги переменными:

 SASS_SRC := assets/css JS_SRC := assets/js CSS_DIR := htdocs/css JS_DIR := htdocs/js BIN := node_modules/bin $(CSS_DIR)/%.css: $(SASS_SRC)/%.scss $(SASS_SRC)/_settings.scss @echo Compiling $@ @mkdir -p $(@D) @sassc -t compressed -o $@ $< @$(BIN)/autoprefixer $@ $(JS_DIR)/%.js: $(JS_SRC)/%.js @echo Processing $@ @mkdir -p $(@D) @$(BIN)/jsmin --level 2 --output $@ $? 

Здесь две новые концепции — это установка переменных с помощью оператора := и использование переменных в правилах Make, заключив их в скобки и добавив префикс $ . Вам не нужно выравнивать назначения переменных, как у меня, это просто личное предпочтение.

Bash на кончиках ваших пальцев

Я осторожен, создавая впечатление, что ваши правила Make должны вызывать другие программы, которые вы установили, для выполнения реальной работы. Чтобы продемонстрировать, давайте использовать основные утилиты GNU, чтобы сохранить нашим пользователям и нашим серверам несколько HTTP-запросов при загрузке библиотек JavaScript, используемых нашей веб-страницей.

 JS_LIB_FILE := $(JS_DIR)/libs.js JS_LIBS := bower_components/jquery/dist/jquery.min.js \ bower_components/lodash/dist/lodash.min.js \ bower_components/fooqux/dist/fooqux.min.js $(JS_LIB_FILE): $(JS_LIBS) cat $? > $@ 

В результате, если какая-либо из этих исходных библиотек изменится (например, новые версии станут доступны из Bower), файл libs.js также будет обновлен. Ницца!

В качестве последнего примера, здесь есть кое-что более сложное, все еще использующее стандартные утилиты — создание манифеста всех имен файлов и контрольных сумм этих файлов. Мы используем разновидность этого в Swiftly и других продуктах 99designs, чтобы предоставить нашему серверу данные, необходимые для записи ссылок на ресурсы для очистки кеша для каждого файла (аналогично Sprockets для Ruby).

 DIST := htdocs/assets # if any compiled assets changes, regenerate the manifest $(DIST)/.manifest: $(shell find $(DIST) -type f -name '*.css' -or -name '*.js') find $(DIST) -type f -exec cksum {} \; | sed -e "s#$(DIST)/##" | cut -f1,3 -d" " > $@ 

Поначалу рецепт немного утомляет, но помните, что вам нужно всего лишь запустить man find в командной строке, чтобы узнать все о find , man sed для set или man cut for cut. Каждый из трех разделов можно выполнить самостоятельно, чтобы узнать о них больше, так как все они используют STDIN и STDOUT.

Подводя итоги

Я могу оценить, что некоторые разработчики, уже знакомые с JavaScript, предпочли бы инструмент сборки JavaScript, настаивая на том, что им не нужно изучать «еще одну вещь». Как мы надеемся, я продемонстрировал здесь, что для наших целей Make — это нечто большее, чем синтаксис target / prerequisite / recipe с некоторыми удобными переменными, заполненными для нас, и прямым доступом к сценариям оболочки для выполнения любой необходимой нам обработки.

Каждый веб-разработчик должен с легкостью выполнять основные файловые задачи в bash, поэтому, если вы сдерживаетесь, возможно, это хороший шанс немного подтолкнуть себя в правильном направлении.