Приятная сборка frontend проекта

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

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

UPD (13 марта 2015): Заменил несколько плагинов на более актуальные + решил проблему с импортом CSS файлов внутрь SCSS


Мы будем использовать сборщик Gulp. Соответственно у вас в системе должен быть установлен Node js. Установку ноды под конкретную платформу мы рассматривать не будем, т.к. это гуглится за пару минут.
И для начала отвечу на вопрос — почему Gulp?

Из более или менее сносных альтернатив мы имеем Grunt и Brunch.

Когда я только начал приобщаться к сборщикам — на рынке уже были и Grunt и Gulp. Первый появился раньше и по этому имеет более большое коммьюнити и разнообразие плагинов. По данным с npm:
Grunt — 11171 пакет
Gulp — 4371 пакет

Но Grunt мне показался черезчур многословным. И после прочтения нескольких статей-сравнений — я предпочел Gulp за его простоту и наглядность.

Brunch — это сравнительно молодой проект, со всеми вытекающими из этого плюсами и минусами. Я с интересом наблюдаю за ним, но в работе пока не использовал.

Приступим:

Создадим папку под наш проект, например «habr». Откроем ее в консоли и выполним команду:

npm init

Можно просто нажать Enter на все вопросы установщика, т.к. сейчас это не принципиально.
В итоге в папке с проектом у нас сгенерируется файл package.json, примерно такого содержания:

{
  "name": "habr",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Немного видоизменим его под наши нужды:

{
  "name": "habr",
  "version": "1.0.0",
  "description": "",
  "author": "",
  "license": "ISC",
  "dependencies": {
      "gulp": "^3.8.11"
   }
}

В блоке dependencies мы указали что нам нужен gulp и тут же будем прописывать все наши плагины.

Плагины:

gulp-autoprefixer — автоматически добавляет вендорные префиксы к CSS свойствам (пару лет назад я бы убил за такую тулзу)
gulp-minify-css — нужен для сжатия CSS кода
browser-sync — с помощью этого плагина мы можем легко развернуть локальный dev сервер с блэкджеком и livereload, а так же с его помощью мы сможем сделать тунель на наш localhost, что бы легко демонстрировать верстку заказчику
gulp-imagemin — для сжатия картинок
imagemin-pngquant — дополнения к предыдущему плагину, для работы с PNG
gulp-uglify — будет сжимать наш JS
gulp-sass — для компиляции нашего SCSS кода
Не холивара ради
Я очень долгое время использовал в своей работе LESS. Мне очень импонировал этот препроцессор за его скорость и простоту изучения. Даже делал доклад по нему на одном Ростовском хакатоне. И в частности в этом докладе я не очень лестно отзывался о SASS.
Но прошло время, я стал старше и мудрее :) и теперь я приобщился к этому препроцессору.
Основой моего недовольства SASS — было то что я не пишу на руби. И когда то для компиляции SASS/SCSS кода — надо было тащить в проект руби, с нужными бандлами — что меня очень огорчало.
Но все изменилось с появлением такой штуки как LibSass. Это С/C++ порт компилятора для SASS. Плагин gulp-sass использует именно его. Теперь мы можем использовать SASS в нативном node окружении — что меня безгранично радует.

gulp-sourcemaps — возьмем для генерации css sourscemaps, которые будут помогать нам при отладке кода
gulp-rigger — это просто киллер фича. Плагин позволяет импортировать один файл в другой простой конструкцией
//= footer.html

и эта строка при компиляции будет заменена на содержимое файла footer.html
gulp-watch — Будет нужен для наблюдения за изменениями файлов. Знаю что в Gulp есть встроенный watch, но у меня возникли с ним некоторые проблемы, в частности он не видел вновь созданные файлы, и приходилось его перезапускать. Этот плагин решил проблему (надеюсь в следующих версиях gulp это поправят).
rimraf — rm -rf для ноды

Установим все плагины и на выходе получим такой package.json:

{
  "name": "habr",
  "version": "1.0.0",
  "description": "",
  "author": "",
  "license": "ISC",
 "dependencies": {
    "browser-sync": "^2.2.3",
    "gulp": "^3.8.11",
    "gulp-autoprefixer": "^2.1.0",
    "gulp-imagemin": "^2.2.1",
    "gulp-minify-css": "^1.0.0",
    "gulp-rigger": "^0.5.8",
    "gulp-sass": "^1.3.3",
    "gulp-sourcemaps": "^1.5.0",
    "gulp-uglify": "^1.1.0",
    "gulp-watch": "^4.1.1",
    "imagemin-pngquant": "^4.0.0",
    "rimraf": "^2.3.1"
  }
}


Bower

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

bower init

Можно так же Enter на все вопросы.
В конце мы получаем примерно такой файл bower.json:

{
  "name": "habr",
  "version": "0.0.0",
  "authors": [
    "Insayt <insait.rostov@ya.ru>"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ]
}

И модифицируем его до нужного нам состояния:

{
  "name": "habr",
  "version": "0.0.0",
  "authors": [
    "Insayt <insait.rostov@ya.ru>"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "normalize.css": "*",
    "jquery": "2.*"
  }
}

В блоке dependencies мы будем указывать зависимости нашего проекта. Сейчас просто для теста это normalize и jQuery (хотя я уже не помню когда начинал проект без этих вещей).
Ну и конечно установим их командой:

bower i

Ну а теперь самое интересное. Создадим структуру нашего проекта и настроим сборщик.

Структура проекта:


Это очень спорный момент. Конечно проекты бывают разные, так же как и предпочтения разработчиков. Стоит только взглянуть на сайт yeoman.io (кстати это очень классный инструмент, который предоставляет большое кол-во заготовленных основ для проекта со всякими плюшками. Однозначно стоит присмотреться к нему). Мы не будем ничего выдумывать и сделаем самую простую структуру.

Для начала нам понадобится 2 папки. Одна (src) в которой мы собственно будем писать код, и вторая (build), в которую сборщик будет выплевывать готовые файлы. Добавим их в проект. Текущая структура у нас выглядит так:



В папке src создадим типичную структуру среднестатистического проекта. Сделаем main файлы в папках js/ и style/ и создадим первую html страничку такого содержания.

index.html
<!DOCTYPE html>
<html>
<head lang="ru">
    <meta charset="UTF-8">
    <title>Я собираю проекты как рок звезда</title>
</head>
<body>
    <section class="header">
        Header
    </section>
    <section class="content">
        Content
    </section>
    <section class="footer">
        Footer
    </section>
</body>
</html>

Структура папки src теперь будет выглядеть так:



Тут все тривиально:
fonts — шрифты
img — картинки
js — скрипты. В корне этой папки будет только файл main.js, который пригодится нам для сборки. Все свои js файлы — надо будет класть в папку partials
style — стили. Тут так же в корне только main.scss, а рабочие файлы в папке partials
template — тут будем хранить повторяющиеся куски html кода
Все html страницы которые мы верстаем — будут лежать в корне src/
Добавим в partials первые js и scss файлы и напоследок — перейдем в корень нашего проекта и создадим там файл gulpfile.js. Вся папка проекта сейчас выглядит так:



Теперь все готово к настройке нашего сборщика, так что let's rock!

Gulpfile.js

Вся магия будет заключена в этом файле. Для начала мы импортируем все наши плагины и сам gulp

gulpfile.js
'use strict';

var gulp = require('gulp'),
    watch = require('gulp-watch'),
    prefixer = require('gulp-autoprefixer'),
    uglify = require('gulp-uglify'),
    sass = require('gulp-sass'),
    sourcemaps = require('gulp-sourcemaps'),
    rigger = require('gulp-rigger'),
    cssmin = require('gulp-minify-css'),
    imagemin = require('gulp-imagemin'),
    pngquant = require('imagemin-pngquant'),
    rimraf = require('rimraf'),
    browserSync = require("browser-sync"),
    reload = browserSync.reload;


Конечно, не обязательно делать это именно так. Существует плагин gulp-load-plugins который позволяет не писать всю эту лапшу из require. Но мне нравится когда я четко вижу что и где подключается, и при желании могу это отключить. По этому пишу по старинке.

Так же создадим js объект в который пропишем все нужные нам пути, чтобы при необходимости легко в одном месте их редактировать:

var path = {
    build: { //Тут мы укажем куда складывать готовые после сборки файлы
        html: 'build/',
        js: 'build/js/',
        css: 'build/css/',
        img: 'build/img/',
        fonts: 'build/fonts/'
    },
    src: { //Пути откуда брать исходники
        html: 'src/*.html', //Синтаксис src/*.html говорит gulp что мы хотим взять все файлы с расширением .html
        js: 'src/js/main.js',//В стилях и скриптах нам понадобятся только main файлы
        style: 'src/style/main.scss',
        img: 'src/img/**/*.*', //Синтаксис img/**/*.* означает - взять все файлы всех расширений из папки и из вложенных каталогов
        fonts: 'src/fonts/**/*.*'
    },
    watch: { //Тут мы укажем, за изменением каких файлов мы хотим наблюдать
        html: 'src/**/*.html',
        js: 'src/js/**/*.js',
        style: 'src/style/**/*.scss',
        img: 'src/img/**/*.*',
        fonts: 'src/fonts/**/*.*'
    },
    clean: './build'
};


Создадим переменную с настройками нашего dev сервера:

var config = {
    server: {
        baseDir: "./build"
    },
    tunnel: true,
    host: 'localhost',
    port: 9000,
    logPrefix: "Frontend_Devil"
};


Собираем html

Напишем таск для сборки html:

gulp.task('html:build', function () {
    gulp.src(path.src.html) //Выберем файлы по нужному пути
        .pipe(rigger()) //Прогоним через rigger
        .pipe(gulp.dest(path.build.html)) //Выплюнем их в папку build
        .pipe(reload({stream: true})); //И перезагрузим наш сервер для обновлений
});

Напомню, что rigger это наш плагин, позволяющий использовать такую конструкцию для импорта файлов:

//= template/footer.html

Давай те же применим его в деле!
В папке src/template/ — создадим файлы header.html и footer.html следующего содержания

header.html
<section class="header">
    Header
</section>


footer.html
<section class="header">
    Footer
</section>


а наш файл index.html изменим вот так:
<!DOCTYPE html>
<html>
<head lang="ru">
    <meta charset="UTF-8">
    <title>Я собираю проекты как рок звезда</title>
</head>
<body>
    //= template/header.html

    <section class="content">
        Content
    </section>
    
    //= template/footer.html
</body>
</html>


Осталось перейти в консоль и запустить наш таск командой:

gulp html:build

После того как она отработает — идем в папку build и видим там наш файл index.html, который превратился в это:

<!DOCTYPE html>
<html>
<head lang="ru">
    <meta charset="UTF-8">
    <title>Я собираю проекты как рок звезда</title>
</head>
<body>
    <section class="header">
        Header
    </section>
    <section class="content">
        Content
    </section>
    <section class="footer">
        Footer
    </section>
</body>
</html>

Это же просто восхитительно!

Помню как много неудобств доставляло бегать по всем сверстанным страничкам и вносить изменения в какую-то повторяющуюся на них часть. Теперь это делается удобно в одном месте.

Собираем javascript

Таск по сборке скриптов будет выглядеть так:

gulp.task('js:build', function () {
    gulp.src(path.src.js) //Найдем наш main файл
        .pipe(rigger()) //Прогоним через rigger
        .pipe(sourcemaps.init()) //Инициализируем sourcemap
        .pipe(uglify()) //Сожмем наш js
        .pipe(sourcemaps.write()) //Пропишем карты
        .pipe(gulp.dest(path.build.js)) //Выплюнем готовый файл в build
        .pipe(reload({stream: true})); //И перезагрузим сервер
});

Помните наш файл main.js?
Вся идея тут состоит в том, чтобы с помощью rigger'a инклюдить в него все нужные нам js файлы в нужном нам порядке. Именно ради контроля над порядком подключения — я и делаю это именно так, вместо того что бы попросить gulp найти все *.js файлы и склеить их.

Часто, при поиске места ошибки я по очереди выключаю какие то файлы из сборки, что бы локализовать место проблемы. В случае если бездумно склеивать все .js — дебаг будет усложнен.

Заполним наш main.js:

/*
 * Third party
 */
//= ../../bower_components/jquery/dist/jquery.js


/*
 * Custom
 */
//= partials/app.js

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

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

Осталось только запустить наш таск из консоли командой:

gulp js:build  

И в папке build/js — мы увидим наш скомпилированный и сжатый файл.

Собираем стили


Напишем задачу для сборки нашего SCSS:

gulp.task('style:build', function () {
    gulp.src(path.src.style) //Выберем наш main.scss
        .pipe(sourcemaps.init()) //То же самое что и с js
        .pipe(sass()) //Скомпилируем
        .pipe(prefixer()) //Добавим вендорные префиксы
        .pipe(cssmin()) //Сожмем
        .pipe(sourcemaps.write())
        .pipe(gulp.dest(path.build.css)) //И в build
        .pipe(reload({stream: true}));
});

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

Со стилями я поступаю так же как и с js, но только вместо rigger'a — использую встроенный в SCSS импорт.
UPD (13 марта 2015): У некоторых людей возникла проблема с импортом css файлов инлайн. Как оказалось, gulp-sass не умеет этого делать, и на выходе дает простой CSS импорт. Но этот вопрос решает наш плагин gulp-minify-css, который заменяет CSS импорт на содержимое файла.
Наш main.scss будет выглядеть так:
/*
* Third Party
*/
@import "../../bower_components/normalize.css/normalize.css";

/*
* Custom
*/
@import "partials/app";

Таким способом получается легко управлять порядком подключения стилей.
Проверим наш таск, запустив
gulp style:build


Собираем картинки


Таск по картинкам будет выглядеть так:

gulp.task('image:build', function () {
    gulp.src(path.src.img) //Выберем наши картинки
        .pipe(imagemin({ //Сожмем их
            progressive: true,
            svgoPlugins: [{removeViewBox: false}],
            use: [pngquant()],
            interlaced: true
        }))
        .pipe(gulp.dest(path.build.img)) //И бросим в build
        .pipe(reload({stream: true}));
});

Я использую дефолтные настройки imagemin, за исключением interlaced. Подробнее об API этого плагина можно прочесть тут.

Теперь, если мы положим какую-нибудь картинку в src/img и запустим команду:

gulp image:build

то увидим в build нашу оптимизированную картинку. Так же gulp любезно напишет в консоли сколько места он сэкономил для пользователей нашего сайта.

Шрифты


Со шрифтами мне обычно не нужно проводить никаких манипуляций, но что бы не рушить парадигму «Работаем в src/ и собираем в build/» — я просто копирую файлы из src/fonts и вставляю в build/fonts. Вот таск:

gulp.task('fonts:build', function() {
    gulp.src(path.src.fonts)
        .pipe(gulp.dest(path.build.fonts))
});


Теперь давайте определим таск с именем «build», который будет запускать все что мы с вами тут накодили:

gulp.task('build', [
    'html:build',
    'js:build',
    'style:build',
    'fonts:build',
    'image:build'
]);


Изменения файлов


Чтобы не лазить все время в консоль давайте попросим gulp каждый раз при изменении какого то файла запускать нужную задачу. Для этого напишет такой таск:

gulp.task('watch', function(){
    watch([path.watch.html], function(event, cb) {
        gulp.start('html:build');
    });
    watch([path.watch.style], function(event, cb) {
        gulp.start('style:build');
    });
    watch([path.watch.js], function(event, cb) {
        gulp.start('js:build');
    });
    watch([path.watch.img], function(event, cb) {
        gulp.start('image:build');
    });
    watch([path.watch.fonts], function(event, cb) {
        gulp.start('fonts:build');
    });
});

С понимаем не должно возникнуть проблем. Мы просто идем по нашим путям определенным в переменной path, и в функции вызывающейся при изменении файла — просим запустить нужный нам таск.

Попробуйте запустить в консоли:

gulp watch

И поменяйте разные файлы.
Ну не круто ли?

Веб сервер


Что бы насладиться чудом livereload — нам необходимо создать себе локальный веб-сервер. Для этого напишем следующий простой таск:

gulp.task('webserver', function () {
    browserSync(config);
});

Тут даже нечего комментировать. Мы просто запустим livereload сервер с настройками, которые мы определили в объекте config. К тому же gulp вежливо откроет наш проект в браузере, а в консоль напишет ссылки на локальный сервер, и на тунель, который мы можем скинуть заказчику для демонстрации.

Очистка


Если вы добавите какую-нибудь картинку, потом запустите задачу image:build и потом картинку удалите — она останется в папке build. Так что было бы удобно — периодически подчищать ее. Создадим для этого простой таск

gulp.task('clean', function (cb) {
    rimraf(path.clean, cb);
});

Теперь при запуске команды
gulp clean

просто будет удаляться папка build.

Финальный аккорд


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

gulp.task('default', ['build', 'webserver', 'watch']);


Окончательно ваш gulpfile.js будет выглядеть примерно вот так.
Теперь выполним в консоли
gulp

И вуаля. Заготовка для вашего проекта готова и ждет вас.

Пара слов в заключение


Эта статья задумывалась как способ еще раз освежить в памяти тонкости сборки frontend проектов, и для легкости передачи этого опыта новым разработчикам. Вам не обязательно использовать на своих проектах именно такой вариант сборки. Есть yeoman.io, на котором вы найдете генераторы почти под любые нужды.
Я написал этот сборщик по трём причинам.
— Мне нравится использовать rigger в своем html коде
— Почти во всех сборках что я встречал — используется временная папка (обычно .tmp/), для записи промежуточных результатов сборки. Мне не нравится такой подход и я хотел избавиться от временных папок.
— И я хотел что бы все это было у меня из коробки.

Мою рабочую версию сборщика вы можете скачать на моем github.

Надеюсь, статья оказалась полезной для вас.

P.S. Обо всех ошибках, недочетах и косяках — пожалуйста пишите в личку.
Метки:
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 118
  • +1
    gulp-rigger — это просто киллер фича. Плагин позволяет импортировать один файл в другой простой конструкцией


    круто, надо поробовать
    • 0
      В jade эту роль играет оператор include.
      • +1
        Разница в том, что rigger может работать в абсолютно любом файле с любым расширением.
        • –1
          Любой шаблонизатор помимо include реализует возможность помимо подключения файлов добавлять в шаблон helpers, экранирование символов по требованию, условную логику и прочие плюшки, а вот rigger я вижу выполняет лишь малость, поэтому не вижу смысла его использовать для простого импорта…
          • +2
            ну так rigger это и не шаблонизатор. Вся его функциональность — простой импорт чего угодно и куда угодно. И он в своем деле хорош :)
        • 0
          я в курсе как это делается в jade :)
        • +1
          А лучше сразу начать использовать gulp-preprocess (grunt-preprocess).
          • 0
            блин, ведь это правда офигенно, например sass не умеет импортировать обычный css, приходилось делать concat и в gulpfile.js прописывать вендорские css руками
            • 0
              как раз на днях с той же целью использовала gulp-rename, чтобы просто менять расширение css на scss )
              • +4
                Умеет. Надо использовать вот такую конструкцию
                @import "CSS:../../bower_components/normalize.css/normalize.css";
                
                • 0
                  бедняжка… а вот less могет!
                  • 0
                    В Less так же нужна особая конструкция
                    @import (css) "foo.css";
                    

                    или
                    @import (inline) "foo.css";
                    

                    Так что разница невелика :)
                  • 0
                    А я подключил `wiredep` и он мне во все нужные места распихивает `html`, `css`, `sass` и `scss`, после чего `usemin` это всё собирает. Гораздо лучше ручного управления: после `bower install` система сборки делает всё сама, мне остаётся выполнить лишь действительно нужные действия – например, создать объект настройки jQuery-библиотеки.
                  • 0
                    Оставлю тут плагин, который для себя делал, может пригодится
                  • +2
                    И в итоге получились rails assets pipeline :)
                    • +7
                      Rails assets pipeline который не привязан к Rails :)
                      • 0
                        Тогда можно говорить, что получилась привязка к Gulp. )
                        • +1
                          sprockets, строго говоря, не привязан к рельсам. Я его с удовольствием использую из middleman'а (генератор статических сайтов).
                        • +1
                          Мам, смотри, Спрокетс для Ноды!
                        • +3
                          var gulp = require('gulp'),
                              watch = require('gulp-watch'),
                              prefixer = require('gulp-autoprefixer'),
                              uglify = require('gulp-uglify'),
                              cssmin = require('gulp-cssmin'),
                              sass = require('gulp-sass'),
                              sourcemaps = require('gulp-sourcemaps'),
                              rigger = require('gulp-rigger'),
                              imagemin = require('gulp-imagemin'),
                              pngquant = require('imagemin-pngquant'),
                              rimraf = require('rimraf'),
                              connect = require('gulp-connect'),
                              opn = require('opn');
                          


                          Ещё рекомендую gulp-load-plugins. Тогда весь код выше заменяется строчкой:

                          var plugins = require('gulp-load-plugins')();
                          


                          Соответственно плагины будут доступны как plugins.autoprefixer, plugins.sass и т.д.
                          • 0
                            В статье есть про это
                            Конечно не обязательно делать это именно так. Существует плагин gulp-load-plugins который позволяет не писать всю эту лапшу из require. Но мне нравится когда я четко вижу что и где подключается, и при желании могу это отключить. По этому пишу по старинке.

                            + новым разработчикам все таки проще с такой записью знакомиться. Конечно кода получается больше, но зато его понятность выше. Но это исключительно мое имхо, поэтому и отметил в статье про альтернативу
                          • –44
                            В этих строчках заключена ошибка:
                            <title>Я собираю проекты как рок звезда</title>
                            
                            И вот какая. В тексте допущены 2 грамматичческие ошибки. Содержание же текста говорит, что автор «подобен профессионалу своего дела». Суть не в том, что рок-звезда допустит подобные ошибки. Он(а) в своей деятельности вообще ничего не пишет, кроме муз.композиций. Но никто не может сказать, что в песнях звезда ошибается. В тексте же HTML автор допускает ошибки, противореча своему высказыванию. (Программисту вообще трудно утверждать, что он пишет без багов.) Поэтому, надо или утверждение изменить, или исправить ошибки.
                            • +9
                              В вашем комментарии заключена ошибка. И вот какая. В тексте допущена 1 грамматическая ошибка. Содержание же текста говорит, что автор «подобен профессионалу в области оценки текстов на грамотность». Суть не в том, что Grammar Nazi не допустит подобные ошибки. Он(а) в своей деятельности вообще ничего не пишет, кроме рецензий на авторские тексты. Но никто не может сказать, что в своих рецензиях Grammar Nazi ошибается. В тексте же комментария автор допускает ошибки, противореча своему высказыванию. (Комментатору вообще трудно утверждать, что он пишет без ошибок). Поэтому, надо или утверждение изменить, или исправить ошибки.

                              Простите.
                              • 0
                                Ошибки редактор поправит)
                              • 0
                                Недавно занимался настройкой окружения под себя и в итоге оформил это как npm-модуль с возможностью развернуть новый проект: fmp.

                                Разница с описанными вами процессами небольшая, только прикрутил возможность при создании нового поректа указать используемый css-препроцессор, мне приходится работать с разными, ну и еще по-мелочи. В ближайших планах — добавить вменяемый HTML-шаблонизатор с возможностью определять переменные на любом уровне и устанавливать их значение в подключающих файлах (смотрю в сторону Handlebars и Jade), минификацию файлов и еще мелочевок.
                              • 0
                                >…я и делаю это именно так, вместо того что бы попросить gulp найти все *.js файлы и склеить их.
                                Заметил, что gulp склеивает их (у меня) в алфавитном порядке. И пользуюсь этим.

                                >…при поиске места ошибки я по очереди выключаю какие то файлы из сборки…
                                Добавил правило — исключать из сборки файлы с префиксами "-" и "_". Соответственно, что бы исключить файл из сборки достаточно его переименовать.
                                • 0
                                  1) Все верно, он склеивает в алфавитном порядке. Но это же не удобно. Хочется сохранить для себя свободу в именовании файлов.
                                  2) Закоментировать строчку в rigger'e будет быстрей, чем переименовывать файл. Зачем лишние действия?
                                  • 0
                                    >Но это же не удобно.
                                    Наверное, тут скорее вопрос привычки/предпочтений.
                                    Можно много чего придумать. Как вариант — делать обязательный префикс с нумерацией, с определённым шагом.
                                    Файл же всё-равно копируется или сохраняется — на этих этапах и можно добавить префикс, после которого уже можно написать нечто осмысленное.

                                    >Закоментировать строчку в rigger'e будет быстрей, чем переименовывать файл.
                                    Мне кажется, что это от IDE больше зависит.
                                    Для изменения порядка сборки вам приходится менять местами куски кода, тогда как в моём случае — опять же достаточно переименования.
                                    Впрочем, я и не утверждаю, что мой способ всегда лучше. Он всего лишь удобнее для меня — не нужно прописывать каждый файл вручную, а порядок подключения виден в любом подобии файлового менеджера, где поддерживается сортировка по имени.
                                • +1
                                  Вопрос — а почему для сборки в альтернативах вы не рассмотрели npm? Есть неплохая статья, сравнивающая gulp, grunt & npm. Я вот остановился на npm и остался просто в восторге от него.
                                  • 0
                                    Я думаю, что использовать специальный пакет — просто логичнее. Например gulp использует системы стримов, и файл пишется на диск только в самом конце, пройдя, зачастую, через несколько пайпов. В случае использования чистого npm придется все же что-то придумывать самому, так просто не получится.
                                    • +1
                                      Логично, да, но npm такой же специальный пакет, только нативный :)
                                      Насчет стримов — смотрите, если вам нужны программные стримы, такие, как в gulp — можно пойти путем gulp, создать файл для сборки build.js, где делать все, что угодно и как угодно, не ограничиваясь плагинами gulp, которые по сути обертки над пакетами npm. В том числе вы можете использовать и стримы, и gulp, и что угодно, без каких-либо ограничений.

                                      Но на самом деле это все достаточно сложно для повседневных задач, и практически всегда можно обойтись тасками в "scripts" с нативными пайпами вместо стримов.

                                      Чем это классно? У вас нет лишнего файла gulpfile.js в проекте; вы используете декларативную нотацию тасков, что весьма читабельно; новый пользователь может узнать все возможные операции для проекта прямо из package.json, в одном месте, что удобно; наконец, вы используете нативный инструмент, который гарантированно будет существовать долго и всегда включает все возможные пакеты, в отличие от систем сборки, где их может недоставать.
                                      • +1
                                        Я с некоторых пор переехал на npm scripts и абсолютно с вами согласен — package.json это отличная точка входа, чтобы узнать о проекте всё, что нужно.
                                        • +1
                                          На самом деле используя gulp — вы можете все также использовать что угодно. Например browserify, или del. Этим он и отличается от grunt.
                                          Пожалуйста расскажите мне, как вы с помощью scripts напишите следующий gulpfile:
                                          var gulp = require('gulp');
                                          var _if = require('gulp-if');
                                          // опущу подключение остальных плагинов
                                          
                                          var production = process.env.NODE_ENV === 'production';
                                          
                                          gulp.task('less', function () {
                                              gulp.src('src/less/**/*.less')
                                                  .pipe(plumber()) // не даем ошибкам всплывать
                                                  .pipe(_if(!production, sourcemaps.init())) // для сорсмепов в дев-режиме
                                                  .pipe(less()) // компилируем less
                                                  .pipe(autoprefixer()) // расставляем префиксы для браузеров
                                                  .pipe(_if(production, csso())) // в продакшене жмем
                                                  .pipe(_if(!production, sourcemaps.write())) // пишем сормепы в дев-режиме
                                                  .pipe(gulp.dest('public/css')) // пишем на диск
                                                  .pipe(_if(production, gzip())) // если продакшен - жмем gzip
                                                  .pipe(_if(production, gulp.dest('public/css'))) // и тоже записываем на диск
                                          });
                                          
                                          gulp.task('default', ['less'], function () {
                                              gulp.watch('src/less/**/*.less', ['less']);
                                          });
                                          

                                          И это только кусок для сборки less.
                                          • 0
                                            Проще простого:

                                            "scripts": {
                                              "build": "node build.js",
                                              "watch": "watch 'npm run build' ./src/**/*"
                                            }
                                            


                                            Хаха! :)

                                            А если серьезно — вы правы, c globules это не так просто сделать, особенно на windows (я пытался, не получилось, на windows много ограничений в shell — нет xargs, нет подстановки комманд, нет globules, нет find *.less -exec cat {} и тд и тп). В то же время на виндах и в gulp с globules не все гладко.

                                            Если же вам известны точки входа less, то все гораздо проще:

                                            "scripts": {
                                              "build-dev": "npm run lessc-dev && npm run build-js && cat public/css/*.css | gzip-size | pretty-bytes",
                                              "lessc-dev": "lessc src/less/main.less --source-map | autoprefixer > public/css/main.css",
                                              "lessc-production": "lessc src/less/main.less | autoprefixer | csso > public/css/main.css ",
                                              "build-js": "browserify src/js/index.js | ccjs - > public/js/index.js",
                                              "watch": "watch 'npm run lessc-dev' less/*.less"
                                            
                                              ...
                                            }
                                            


                                            В принципе, в небольших проектах точки входа всегда известны. Если же проект гигантский, то тут уж ничего не поделаешь — надо писать скрипт сборки. Но это уже дело предпочтения — писать сборку через gulp или через простые стримы с прямыми зависимостями.
                                          • 0
                                            с grunt нельзя? правда?

                                            Скажите, зачем вам лишние проверки на енвайронмент, если они только усложняют логику сборки? Не проще ли сконфигурировать для каждого свой запуск если енвайронменты отличаются?
                                            • –1
                                              Нет, нельзя. В grunt вы пишите конфиг, а он отрабатывает. То есть шаг в лево-право — новый плагин. Тк gulp основан на нативных потоках, вы можете очень легко встроить свой обработчик посреди обработки любого файла.
                                              Нет, не проще. Если я что-то могу сделать без дублирования кода — я стараюсь это сделать именно так.
                                              Ну и грант с его устаревшей системой при каждом изменении файла запишет это изменение на диск. Круто!
                                              • –1
                                                jmreidy/grunt-browserify это вам к примеру. Не дублировать код — это прелестно, но писать почти в каждой строчке if'ы — гораздо хуже.
                                                • 0
                                                  Кстати, насчет дублирования. Есть так называемое правило третьего, где рекомендуется фолдить клоны именно на третьем повторении. Почему не на втором — потому что два похожих куска еще не обязательно клоны, есть риск сфолдить преждевременно, потратив время впустую.

                                                  В вашем случае, для того, чтобы изменить девелоперский пайплайн — надо глазами пройтись по всему обобщенному пайплайну, на каждом условии вычислять, к dev или к production оно относится. А если появятся переменная ci?

                                                  Я к тому, что у вас скорее не про дублирование, а про разделение ответственности.
                                      • +4
                                        Вместо gulp-connect удобнее пользоваться browsersync.
                                        • +2
                                          Оказывается, у Gulp есть чёрный список плагинов, и в нём, например, значатся gulp-connect и gulp-cssmin
                                          • 0
                                            Ого, спасибо за список)
                                            На днях пробегусь по нему — и поправлю статью
                                          • 0
                                            Такой вопрос — стоит ли добавлять сборку (директория build/ в статье) под .gitignore?

                                            Ведь при инициализации в любом случае сборка происходит заново.
                                            • +1
                                              Если вы настроите сборку на сервере — то можете добавить в игнор.
                                            • +8
                                              Хочется вспомнить этот боянистый твит:
                                              image
                                              Но если честно сам пользуюсь и gulp, и grunt (разные проекты), вот только мне bower не зашел совсем — попытался, но это уже для меня перебор. Устанавливать JQuery через npm — перебор.
                                              Может кто-нибудь кто пользуется разъяснит — в чем реальный смысл от него? Не хипстерства ради, а реальное применение, которое что-то облегчает.
                                              • 0
                                                Пристрастился к bower после прочтения Bower: зачем фронтенду нужен менеджер пакетов.
                                                • +1
                                                  Чем лезть на сайт и искать последнюю версию библиотеки, проще написать bower update jquery

                                                  Плюс, имея исходник в bower или npm, можно сделать свою сборку, взяв только нужные модули.

                                                  И более того, даже если не исключать ненужное, можно добиться меньшего размера при сборке.
                                                  Например, webpack рекомендует собирать reactjs из npm.
                                                  • 0
                                                    Пока писал комментарий — ниже ответили более развернуто
                                                  • 0
                                                    Собственно, управление зависимостями. Вы можете в bower.json указать конкретные версии библиотек от которых зависит ваше приложение. Можете даже указать конкретный коммит в GIT-репозитории (иногда бывает нужно и такое, когда, например, в какой-то из веток/пулл-реквестов есть фикс, но автор либы не спешит выпускать новую версию).

                                                    Указали — и всё, теперь одной командой можно выгрузить все зависимости. И скачаются именно те версии, с которыми ваше приложение точно работает.

                                                    В проект приходит новый разработчик — bower install и у него есть всё.

                                                    Собираем версию на продакшен — bower install, получили сборку с нужными зависимостям.

                                                    И да, если нужный вам пакет (bootstrap, например) зависит от другого (jquery в случае с бутстрапом), то Bower автоматически скачает зависимости.

                                                    Да, всё это будет не нужно, если вы будете ходить по сайтам, качать нужные версии и хранить их в репозитории. Но и обновление версий, и разрешение зависимостей — всё надо делать вручную. А это, в общем-то, довольно примитивная и нудная задача с которой лучше всего справляются машины.
                                                    • 0
                                                      А если нету никаких зависимостей? Я пользуюсь как правило только Dojo и без особого труда контролирую её версию (и тащу её с CDN или кладу в git). Пока не очень понимаю необходимость такого нагромождения технологий для frontend. Frontend бывает разный и для разных нужд.
                                                      Очень напоминает бум батареек для Django или RoR, что в итоге ИМХО ни к чему хорошему не привело.
                                                      • 0
                                                        Тогда да, Bower не нужен. Как вы сами отметили, фронтенд бывает разный, для каких-то проектов достаточно одного HTML-файла с парой-тройкой ссылок на CDN, а для каких-то и Bower не помешает.
                                                    • 0
                                                      Если вы испольузуете модульность в JS с помощью browserify, например, то еще как имеет.

                                                      npm install jquery
                                                      

                                                      и потом

                                                      var $ = require('jquery');
                                                      

                                                    • 0
                                                      Не пойму, как собирать зависимости при помощи gulp-bower? Ведь просто вытаскивать по маске все js файлы не вариант, в пакетах обычно уже идут и минифицированные версии и исходники. Структура опять же везде разная.
                                                      • 0
                                                        Я использую такой скрипт:
                                                            gulp.src('app/bower-manifest.html')
                                                                .pipe($.filter(manifestFilter))
                                                                .pipe(wiredep(wiredepConfig))
                                                                .pipe($.usemin({
                                                                    js: [$.sourcemaps.init(), $.uglify(), $.rev(), $.sourcemaps.write('.')]
                                                                }))
                                                        

                                                        где $ — ссылка на gulp-load-plugins, а manifestFilter — проверка для ускорения билда, что не добавились новые зависимости.
                                                        • 0
                                                          Спасибо за ответ, а за
                                                          где $ — ссылка на gulp-load-plugins
                                                          отдельное спасибо.

                                                          В итоге накидал вот такой скрипт.
                                                          'use strict';
                                                          
                                                          var gulp = require('gulp');
                                                          var plugins = require('gulp-load-plugins')({
                                                              pattern: ['gulp-*', 'main-*'] //это необходимо для плагина main-bower-files
                                                          });
                                                          
                                                          var path = {
                                                              build: {
                                                                  js: 'dist/js'
                                                              }
                                                          };
                                                          
                                                          //установка зависимостей
                                                          gulp.task('bower', function() {
                                                              return plugins.bower();
                                                          });
                                                          
                                                          //сборка зависимостей
                                                          gulp.task("bower-files", function(){
                                                              gulp.src(plugins.mainBowerFiles())
                                                                  .pipe(plugins.uglify())
                                                                  .pipe(plugins.concat('all.js'))
                                                                  .pipe(gulp.dest(path.build.js))
                                                          
                                                          });
                                                          


                                                          gulp-bower нужен для установки зависимостей. А для обработки зависимостей нужен (gulp-bower-files) main-bower-files
                                                          • 0
                                                            Пока не понял только, как бы последовательно запустить эти две задачи)
                                                          • 0
                                                            У вас нет сортировки зависимости, например, поставить jquery или angularjs первым.
                                                            Вот накидал свой велосипед на эту тему:
                                                            gist.github.com/dshster/2535b4317e495d070bcb
                                                            • 0
                                                              Я правильно понял, в данном примере $.order(['angular/*.js'… ставит на первое место соответственно angular, затем все остальные в случайном порядке?

                                                              Как-то это все не то :) Вот и получается, что самый просто выход, это как автор, прописывать очередность в main.js
                                                              • 0
                                                                Попробуйте wiredep. Он должен сам правильно разруливать зависимости
                                                                • 0
                                                                  в нем есть одна проблема — wiredep будет генерировать новые теги script и link для всех библиотек. На деве это конечно не страшно, но на прод так пускать нельзя. В итоге придется для прода все равно собирать все в один файл, и скорее всего — это придется делать похожим способом, как в статье. Так что проще один раз прописать это дело и забыть как страшный сон :)
                                                                  • 0
                                                                    Я понимаю это. :) Мой скрипт выше по ветке как раз все либы минифицирует в один файл. :)
                                                                • 0
                                                                  Варианты? Во-первых не нужно прописывать все модули, нужно прописать только те, которые строго должны идти в первую очередь. Я не смог добиться, чтобы main-bower-files выводил в том порядке в котором прописаны в bower.json.

                                                                  Даже если в консоли выполнить команду bower list -p, то порядок вывода модулей будет другой.
                                                                • 0
                                                                  main-bower-files выдает файлы из пакетов, уже отсортированных согласно дереву зависимостей («dependencies» в bower.json пакета).
                                                                  Но еще один просто огромный плюс main-bower-files это то, что он может использовать секцию «overrides» в bower.json, чтобы переопределить описание установленного пакета.
                                                                  Вот пример:

                                                                  "dependencies": {
                                                                    "angular": "1.3.13",
                                                                    "jquery":"1.2.2",
                                                                    "some-another-package": "0.0.1"
                                                                  },
                                                                  "overrides": {
                                                                    "some-another-package": {
                                                                      "dependencies": {
                                                                        "jquery": "*"
                                                                      },
                                                                      "main": {
                                                                        "development": ["some-another-package.js"],
                                                                        "production": ["some-another-package.min.js"],    
                                                                      }
                                                                    }
                                                                  }
                                                                  


                                                                  Предположим, что пакет some-another-package использует jquery, но в зависимостях у него он не прописан.
                                                                  Тогда при обработке main-bower-files его файлы могут выпасть перед файлами jquery и будет у нас красная консоль.
                                                                  Мы принудительно указываем ему зависимость от jquery и также можем указать разные файлы пакета для разных версий сборки (версию сборки можно указать при вызове mainBowerFiles() в скрипте.

                                                                  И исходя из всего вышенаписанного открывается еще одна невиданная доселе возможность: указать в качестве зависимости любой файл из git-репозитория, впоследстии описав его зависимости, после чего он будет обработан mainBowerFiles() и включен в проект в правильном месте. Вот «живой» пример:

                                                                  "dependencies": {
                                                                     ...
                                                                    "flot-splines": "https://raw.githubusercontent.com/AMKohn/flot/master/jquery.flot.spline.js",
                                                                     ...
                                                                  },
                                                                  ...
                                                                  "overrides": {
                                                                    "flot-splines": {
                                                                      "main": [
                                                                        "index.js"
                                                                      ],
                                                                      "dependencies": {
                                                                        "flot": "*"
                                                                      }
                                                                    }
                                                                  } 
                                                                  


                                                                  Само собой, это грязный трюк — завтра реп могут благополучно грохнуть, но иногда он выручает просто безмерно.
                                                          • 0
                                                            Странно, что не используется ничего для AMD модулей.

                                                            Ну и спорный вопрос насчет необходимости Bower. Достаточно долго живем только c npm проблем нет пока, у нас часть кода шарится между клиентом и сервером. При использовании Bower это бы только добавило проблем.
                                                            • –4
                                                              AMD модули уже не вымирании.
                                                              • +1
                                                                с чего это вдруг?
                                                                • 0
                                                                  С чего это вдруг что?
                                                                  С чего это вдруг сейчас или вопрос о фундаментальных причинах вымирания AMD?
                                                                  • 0
                                                                    Давайте перейдем сразу к фундаментальным причинам.
                                                                    • 0
                                                                      Человек стремится делать проще и красивее.
                                                                      // amd
                                                                      define(['./foo', './bar'], (foo, bar) => {
                                                                          console.log({foo, bar});
                                                                      });
                                                                      
                                                                      // common
                                                                      var foo = require('./foo');
                                                                      var bar = require('./bar');
                                                                      
                                                                      console.log({foo, bar});
                                                                      

                                                                      Ну и на замену amd приходит System, это факт. А на замену common, возможно, es-modules.
                                                                      • 0
                                                                        А почему нельзя сделать вот так?

                                                                        define((require) => {
                                                                        var foo = require('./foo');
                                                                        var bar = require('./bar');

                                                                        console.log({foo, bar});
                                                                        });
                                                                        • 0
                                                                          А зачем сложнее если можно проще?
                                                                          • 0
                                                                            Ну мне проще дебажить requirejs, чем commonjs с сорсмапами.
                                                                            • 0
                                                                              Интересная ситуация. Однако я не могу себе вообразить такого.
                                                                      • 0
                                                                        Модули решают проблему модульности. Все 3 стандарта (ES6H, CJS, AMD) по сути себя дублируют.

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

                                                                        И вопрос, к примеру, AMD vs CJS это уже просто выбор сахара. А сахар CJS слаще.
                                                                        А любой транспайлер/сборщик сделает модули удобные для платформы.

                                                                        Тем более сборщикам и остальным инструментам проще работать с CJS.
                                                                        К примеру webpack. Он может «подключать» .less в файл .stylus обычным require.
                                                                        «everything is a module»

                                                                        CJS стал де факто стандартом nodejs. ES6H станет стандартом для нода и веба. И AMD тут места нет.
                                                                        Он просто стал не нужен как стандарт. Как техника экспорта/компиляции/транспайлинга ок. Но не стандарт.
                                                                        • 0
                                                                          Я ошибался. Думал, что AMD это общее описание принципа модульной загрузки скриптов (особенно, если учесть что requirejs поддерживает и commonjs формат). Поэтому оригинальный вопрос стоит читать как «Странно, что не используется ничего для JS-модулей»
                                                              • 0
                                                                Есть разница в подключении зависимостей Bower и NPM, последний для каждого модуля создаёт свою копию зависимостей, Bower — использует общие зависимости. Это сделано для того, чтобы не было ситуации с подключением одновременно разных версий jquery, например.
                                                              • –4
                                                                Половину хлама можно выбросить если использовать webpack для зависимостей.
                                                                • 0
                                                                  А что потом происходит с этой папкой build, как она попадает на продакшен? Ее прямо в git/whatever коммитят, или же на продакшене запускается тот же самый скрипт сборки после обновления исходников?
                                                                  • 0
                                                                    Если настроите сборку на сервере — можно не коммитить. Но не всегда хорошо захламлять сервер тулзами для сборки
                                                                    • 0
                                                                      А репозиторий бинарниками? Тут палка о двух концах.
                                                                      Наверное по хорошему надо deploy-server делать промежуточный, который как раз будет все собирать.
                                                                      • 0
                                                                        Ну это уже от специфики проекта будет зависить и можно много чего напридумывать) По этому и не стал в статье затрагивать данный вопрос. Пусть каждый решает сам, как ему удобнее.
                                                                  • 0
                                                                    В списке не хватает плагина для создания спрайтов.
                                                                    • 0
                                                                      Спрайты устарели, нынче модно в dataURI всё кодировать в отдельный CSS.
                                                                      • 0
                                                                        Мода это конечно круто, но можно узнать, в чем выигрыш кодирования в dataurl? Сборка спрайтов у меня происходит автоматически через spritesmith, на выходе less файл с переменными названий спрайтов. Сам графический файл со спрайтами кешируется.
                                                                        • 0
                                                                          Не заметил этой моды. В основном встречаю спрайты или svg. При использовании плагинов для генерации спрайтов, удобство от использования dataURI совсем сомнительно, к тому же отдельная css с dataURI весит в полтора раза больше, чем спрайт.
                                                                          • 0
                                                                            к тому же отдельная css с dataURI весит в полтора раза больше, чем спрайт

                                                                            Может это потому что в png применяется сжатие по умолчанию, а для css оно применится только при передаче по сети?

                                                                            • 0
                                                                              Что-то я очень сомневаюсь что закодированный в Base64 сжатый PNG еще сожмётся при передаче, иначе был бы нехилый профит.
                                                                              • 0
                                                                                Провел эксперимент, с картинкой png (этой: xkcd.com/1445/)
                                                                                Размер: 11 597 байт,
                                                                                В архиве: 11 613 байт

                                                                                В виде строки base64
                                                                                Размер: 15 464 байт
                                                                                В архиве: 11 694 байт

                                                                                Разница после gzip менее процента.
                                                                                Попробовал поменять архиватор, тенденция сохранилась.

                                                                                PS: на всякий случай прогнал картинку через punypng: No savings
                                                                                • 0
                                                                                  Другими словами смысла в экономии трафика никакого. В оптимизации автоматизации тоже выигрыша нет, осталось выяснить скорость рендеринга тысячи спрайтов в виде одной картинки или в виде отдельных dataurl.
                                                                                  • 0
                                                                                    В плане удобства — каждому свое.

                                                                                    Мне, например, нравится base64, тем что:
                                                                                    * его можно использовать в виде повторяющегося фона
                                                                                    * не нужен отдельный элемент для иконки <i class="icon"></i>
                                                                                    * base64 делается нажатием одной кнопки и без настроек — можно обойтись без сборки вообще, используя онлайн-декодер
                                                                                    * удаление иконки происходит так же просто как удаление класса из css
                                                                                    * добавление одной иконки никак не повлияет на другие. Со спрайтом вполне может произойти так, что неопытный специалист добавит что-то свое, пережмет из png-24 в png-8, и не заметит что стало хуже.

                                                                                    В общем, по мне baseurl проще. А значит, при прочих равных — лучше.
                                                                                    • 0
                                                                                      Использую spritesmish

                                                                                      > не нужен отдельный элемент для иконки

                                                                                      Всё уже давно делают через псевдоклассы

                                                                                      > base64 делается нажатием одной кнопки и без настроек — можно обойтись без сборки вообще, используя онлайн-декодер
                                                                                      > удаление иконки происходит так же просто как удаление класса из css

                                                                                      Я кидаю иконку в папку иконок и вообще ничего не делаю дальше (bower сам подхватывает и перекодирует), в вашем же случае нужно искать css файл, искать класс нужный, менять его даже с автоматическим кодированием или удалять, а если файл довольно объёмный, то и редактор будет неспешно работать

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

                                                                                      В общем на мой взгляд надуманные проблемы, которые уже давно нормально решены.
                                                                                      • 0
                                                                                        Система сборки это не преимущество спрайтов, для base64 все так же настраивается.

                                                                                        Что есть такого у спрайтов, чтобы их до сих пор использовать?

                                                                                        PS: забыл упомянуть — встраивать через data:url можно и jpeg и анимированный gif.
                                                                                        • 0
                                                                                          Простота — настроил и забыл, только спрайты кидаешь в папочку. Я не вижу необходимости в base64 сейчас, зачем менять то, что отлично работает, не создаёт проблем и не требует каких-то ручных вмешательств?
                                                                                          • 0
                                                                                            Вам менять никто ничего не предлагает, тем более если уже настроено и работает. Используйте, если удобно.
                                                                                            Говорю же, каждому своё.
                                                                            • 0
                                                                              В действительности, я не отрицаю использование спрайтов: для каждого инструмента есть своя цель. Когда картинок немного, можно использовать и спрайт. Но чем больше становится спрайт, тем больше он потребляет оперативной памяти (и, мне кажется, здесь имеет место прогрессия). Это происходит из-за того, что спрайт – это повторённое многократно изображение, обрезанное в разных местах. Мало того, что техника довольно изощрённая, так ещё и браузеры, насколько мне известно, не трудятся оптимизировать здесь потребление ОЗУ.

                                                                              Когда я сравнивал производительность спрайтов в сравнении со встроенными изображениями год-два назад, я создал страницу с 50 значками и десятком изображений покрупнее. Результат был одинаковый на всех движках – Webkit@Safari, Gecko@Firefox, IE не трогал – встроенные изображения оказались быстрее.

                                                                              Да, встроенные изображения немного тяжелее, но выигрыш получается комплексным: на потреблении памяти, на количестве запросов (по сравнению с несколькими изображениями), а также отдельный CSS приходит с сервера немного более сжатым (я проверял с помощью встроенного в OS X архиватора).

                                                                              Подсказал мне перейти на встроенные изображения Андрей @ai Ситник, если что. Он, насколько я знаю, также применяет это в своих рабочих проектах.
                                                                              • 0
                                                                                В теории (сейчас лень проверять) data-uri сделает из изображения base64.
                                                                                Следовательно размер увеличится на 1/3 с копейками.

                                                                                С другой стороны, если картинка не очень оптимизированна, то gzip должен выдать меньший результат.
                                                                                Однако моя интуиция не верна:
                                                                                davidbcalhoun.com/2011/when-to-base64-encode-images-and-when-not-to/
                                                                                TL;DR. gzip+base64 дает тот же размер что и бинарка

                                                                                Выводы очевидны
                                                                        • 0
                                                                          «gulp-rigger» — насколько это тормозит сборку, файлы все-таки должны парсится?
                                                                          • 0
                                                                            Практически не тормозит. Он просто копирует содержимое, никак его не обрабатывая. Оверхед только на чтение файла
                                                                          • +3
                                                                            LibSass всем хорош, только больно уж отстаёт от основной ветки. В итоге часть самых вкусностей не работает.

                                                                            Нашёл для себя в качестве альтернативы Stylus, который:
                                                                            а) нативный,
                                                                            б) довольно быстрый,
                                                                            в) более гибкий.

                                                                            Рекомендую.
                                                                            • 0
                                                                              Тоже иногда думаю перейти на него обратно, тем более, что я с ним уже работал. Например, там есть killer feature – можно использовать ранее заданное свойство как переменную, типа того:

                                                                              selector
                                                                                  width: 10px
                                                                                  height: @width
                                                                            • 0
                                                                              Недавно делал нечто подобное, но без bower (мне не нравится, что bower тянет за собой содержимое всего репозитория вместо одного js-файла) и с шаблонизатором Handlebars (ведь даже на фронте часто проще заполнять пространство в цикле, например, а не писать голый html). Так же livereload работает веселее — он не перегружает всю страницу, если изменены стили, а только перегружает сами стили. Ну и я использовал grunt, хотя можно попробовать и на gulp перейти.
                                                                              Ну и вообще у меня как-то попроще получилось, хотя вроде все примерно то же самое)
                                                                              Вот, если кому интересно — github.com/mrTimofey/boilerplate
                                                                              • 0
                                                                                Очень странный довод в пользу отказа от Bower — выше уже всё обсудили много раз. Вообще не важно в каком виде тянутся bower-модули, из них вы собираете общий js/css файл, а всё остальное можно поместить в .gitignore. Зато при каждом разворачивании проекта у вас нужный набор всех зависимостей. Вместо Handlebars я предпочитаю Jade.

                                                                                Если у вас есть общий шаблон, на этом шаблоне построена куча сайтов и в какой-то библиотеке шаблона найдена уязвимость, то вам придётся ручками обновлять библиотеку на каждом сайте. Если сайтов 10, 20, 50 и они на разных хостингах, представьте сколько времени вам понадобиться чтобы обновить библиотеку? В общем тут только поможет опыт коллективной разработки сайтов чуть серьёзнее домашней странички.
                                                                                • 0
                                                                                  Насчет Bower согласен. Стоит его добавить. Но жалко, конечно, что нельзя только нужный файл тянуть… Точнее, я читал, что как-то можно, но это будет не универсально, так как структура файлов проектов не у всех одинаковая.

                                                                                  Jade — да, я его тоже пробовал на этом же boilerplate. Очень удобно, но его можно легко поставить вместо HB добавлением зависимости в package и небольшой правкой кода в engine.js (собственно, для этого я и вынес код для шаблонизатора в отдельный файл). Нужно будет поправить документацию с учетом возможности ставить свой шаблонизатор.
                                                                                • 0
                                                                                  Если хотите устанавливать через bower только самые нужные файлы, можно использовать bower-installer: github.com/blittle/bower-installer
                                                                                  • 0
                                                                                    Чтобы что, просто интересно? «ignore» в конфиге со стандартными настройками вполне хватает, мне кажется.
                                                                                    • +1
                                                                                      Я лишь предложил решение проблемы, описанной mr_T. Так-то, мне тоже вполне достаточно «ignore» в конфиге)
                                                                                • 0
                                                                                  Вдруг кому-то еще для быстрого старта и ознакомления пригодится мой конфиг Gulp-а с комментариями на русском для каждой задачи.
                                                                                  В прошлом году познакомил с Gulp-ом коллег и некоторые уже активно используют, да и мне легче передавать проект.
                                                                                  • 0
                                                                                    здорово по моему
                                                                                    • 0
                                                                                      Обратил внимание, что генерируемый gulp-sourcemaps файл просто огромен (x10 от размера минифицированного файла). В опциях плагина ничего про сжатие карты не нашел, как победить такую ситуацию? И может быть, можно как-то ограничить пользователей от загрузки .map файла, он ведь им по факту не нужен?
                                                                                      • 0
                                                                                        Пользователь его без специального плагина и не получит (вы же его не подключаете как скрипт, я надеюсь).

                                                                                        По мне, так для продакшена надо делать отдельную сборку, с минификацией и без сорсмап.
                                                                                        • 0
                                                                                          По-умолчанию конкретно этот соурсмап генерится прямо в минифицированном файле, в стоке-комментарии. Есть опция генерировать отдельным файлом, но в файле скрипта в комментарии прописывается путь, если файл .map не класть на продакшне, то будет 404.
                                                                                      • 0
                                                                                        Может простой вопрос, но всё же:
                                                                                        почему пути src и watch отличаются?
                                                                                        При том, что watch в итоге запускает билд по src.
                                                                                        • 0
                                                                                          Смотрите — например в стилях:
                                                                                          Мы собираем только 1 файл, в который инклюдим все что нам нужно. Но сборку нам надо запускать при изменениях любого файла стилей. По этому в src один путь, а watch другой
                                                                                        • 0
                                                                                          у вас никогда не возникало проблем с количеством файлов?
                                                                                          использовал ваш код, но с .pipe(reload({stream: true})); картинок копировалось только 48 штук (проверено много раз, и на файлах шрифтов, html файлы не проверял)
                                                                                          пришлось заменить на .on('end', browserSync.reload);, стали копироваться все
                                                                                          • 0
                                                                                            image

                                                                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.