Проблема с просмотром SCSS
Есть язык SASS (SCSS), который великолепен. Есть node-sass, это круто. У него очень приятный функционал — --watch
(-w
):
node-sass -w input.scss -o build/
Он начинает отслеживать любые изменения в input.scss
и перекомпилировать их в build/input.css
. Это очень полезно в развитии.
Проблема
Так вот проблема: что, если я хочу сделать какие-то дополнительные действия после каждой перекомпиляции? Например. Я хочу применить Автопрефиксер к output.css
.
Если бы это была обычная (не «просмотровая») подборка, я бы сделал:
node-sass input.scss | postcss -u autoprefixer ...
> output.css
Но каналы POSIX (| …
) не работают с процессами переднего плана, а также с перенаправлением файловых дескрипторов POSIX (> …
). И, к сожалению, node-sass
CLI не предоставляет функций постобработки (начиная с версии 4.1.1).
Существующие решения
Существует несколько решений, основанных на следующем подходе:
- Следите за изменениями в
src/*.scss
с помощью внешнего инструмента наблюдения (например,nodemon
,watch
и т. д.). - Делайте обычную компиляцию изменения (чтобы мы могли сцепить несколько действий).
Это нормально, когда у вас есть только один основной файл SCSS, который необходимо (повторно) скомпилировать.
Хотя и в этом случае подход не идеален — основной файл будет перекомпилирован при любых src/*.scss
изменениях файла, даже если некоторые из них не используются в основном файле.
Но большая проблема возникает, когда у вас есть несколько основных файлов SCSS. Например, в Django есть несколько статических папок (по одной для каждого многократно используемого приложения Django), которые могут содержать файлы SCSS.
Поэтому мы не можем следить за изменениями в каждой статической папке и перекомпилировать все основные файлы. Нам нужен лучший способ.
Анализ
Преимущество node-sass -w input.scss
в том, что:
- он следит за изменениями в
input.scss
- … и во всех включенных файлах
- … и больше нигде
Так что это кажется лучшим способом просмотра. Но он не позволяет постобработку. Итак, как действовать?
Способ 1: интерфейс командной строки
Идея состоит в том, чтобы использовать node-sass --watch
с postcss --watch
:
node-sass -w src/input.scss -o tmp
/postcss -w -u autoperfixer ... tmp/input.css -o build/output.css
Вот полный пример:
mkdir -p build/ node-sass src/input.scss -o tmp/ node-sass -w src/input.scss -o tmp/ & postcss -w -u autoprefixer \ --autoprefixer.browsers="ie >= 9, > 1%" \ tmp/input.css -o build/output.css &
Примечание. Я делаю две дополнительные вещи:
mkdir -p build/
— иначеpostcss -o build/output.css
сломается.node-sass src/input.scss -o tmp/
— согласно данному тикету,node-sass
больше не занимается первоначальной компиляцией. Без негоpostcss tmp/input.css
не получится; а он нам нужен в любом случае.
Вы можете изменить подход и использовать $tmpdir=`mktemp -d`
вместо tmp/
, чтобы избежать конфликтов имен файлов.
Не так уж плохо, но выглядит немного хрупким и громоздким.
Способ 2: JavaScript
node-sass
написан на JS, как и autoprefixer
, так почему бы не использовать JS?
Увы, после прочтения исходного кода я понял, что --watch
является частью node-sass
клиента, а не node-sass
библиотеки. Поэтому мы не можем использовать его повторно, нам нужно скопировать и вставить некоторый код JS из клиента.
Но сначала нам нужно понять, как это работает:
- sass-graph получает все файлы, входящие в просматриваемый
- взгляд следит за изменениями в файлах
node-sass
lib отображает файлы при изменении
Вот упрощенный пример:
var fs = require('fs'); var sass = require('node-sass'); var grapher = require('sass-graph'); var Gaze = require('gaze'); // This probably should come from outside var inputFile = 'src/input.scss'; var outputFile = 'build/output.css'; // Make sure 'build/' dir exists try { fs.mkdirSync('build'); } catch(e) {} // Renderer function render() { var result = sass.renderSync({file: inputFile}); fs.writeFile(outputFile, result.css.toString()); } // Initial render render(); // Get watched files function getWatchedFiles() { var includedFiles = grapher.parseFile(inputFile); return Object.keys(includedFiles.index); } // Start watching var gaze = new Gaze(); gaze.add(getWatchedFiles()); gaze.on('changed', function() { gaze.add(getWatchedFiles()); // update watched files index render(); });
Это только обычные функции просмотра SCSS. Добавим autoprefixer
:
var fs = require('fs'); var sass = require('node-sass'); var grapher = require('sass-graph'); var Gaze = require('gaze'); var postcss = require('postcss'); var autoprefixer = require('autoprefixer'); // This probably should come from outside var inputFile = 'src/input.scss'; var outputFile = 'build/output.css'; // Make sure 'build/' dir exists try { fs.mkdirSync('build'); } catch(e) {} // Renderer: SCSS -> Autoprefizer function render() { sass.render({file: inputFile}, function(err, result) { var processor = postcss([ autoprefixer({browsers: ['ie >= 9', '> 1%']})] ); processor.process(result.css.toString()).then(function(result) { fs.writeFile(outputFile, result.css); }); }); } // Initial render render(); // Get watched files function getWatchedFiles() { var includedFiles = grapher.parseFile(inputFile); return Object.keys(includedFiles.index); } // Start watching var gaze = new Gaze(); gaze.add(getWatchedFiles()); gaze.on('changed', function() { gaze.add(getWatchedFiles()); // update watched files index render(); });
Примечание: в этом примере изменена только render()
функция.
ПЛЮСЫ: мы достигли желаемого:
- мы смотрим исходный файл SCSS так же, как это делает
node-sass
- мы применяем
autoprefixer
при каждой перекомпиляции - делаем первоначальную компиляцию
- и у нас есть только один процесс просмотра
ПРОТИВ: это был всего лишь демонстрационный пример:
- он не обрабатывает ошибки
- он не обрабатывает некоторые пограничные случаи (например, добавление/удаление зависимых файлов)
- он не принимает необходимые аргументы извне
- и требует изменений исходного кода для дополнительной постобработки
Также это выглядит немного сложно (несмотря на то, что уже упрощено).
Способ 3: новый интерфейс командной строки
Я хочу иметь интерфейс командной строки, который просматривает файл SCSS, как это делает node-sass
, и позволяет выполнять любую постобработку при изменении. Что-то типа:
watch-my-sass src/input.scss -- "node-sass | postcss -u autoperfixer
>build
/output.css"
Я не нашел существующего решения, поэтому я собираюсь написать его.
Я мог бы внести свой вклад в node-sass
(и, возможно, это лучший способ), но я не могу дождаться выхода новой версии, мне это нужно сейчас. И я предвижу другие трудности в этом :) Может быть, я (или ты?) сделаю это в будущем.
(пишу, пишу, пишу…) и вот он, новый блестящий интерфейс командной строки:
https://github.com/kottenator/node-sass-watcher
Просто несколько примеров того, как это работает:
node-sass-watcher src/input.scss -o build/output.css -c 'node-sass <input> | postcss -u autoprefixer --autoprefixer.browsers="> 1%"'
Кроме того, он также предоставляет JS API:
var Watcher = require('node-sass-watcher'); var watcher = new Watcher('src/input.scss'); // triggered once watcher.on('init', render); // triggered on the input file changes and all it's // dependecies changes, incl. file addition/deletion watcher.on('update', render); // start watching watcher.run(); function render() { doSomethingWith('src/input.scss'); }
Я надеюсь, что это было полезно. Если вы знаете лучшее решение — поделитесь им в комментариях! ;)