Ещё один пост о сборке front-end проекта

Ещё один пост о сборке front-end проекта

571
ПОДЕЛИТЬСЯ

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

Что умеет делать сборщик:

Собирать front-end проект для development & production окружений.
Собирать по несколько js/css бандлов на проект.
Применять стиль CommonJS модулей в браузере.
Применять ES6-синтаксис.
Спрайты, рисунки и почти все другое.

Вступительное
Чтоб было удобней смотреть за мыслью, сходу кидаю ссылку на репозиторий с шаблоном проекта: github.com/alexfedoseev/js-app-starter

Как его завестиУбедитесь, что установлен npm.
npm -v

Установите нужные глобальные модули (раз ещё не установлены):
npm install -g gulp browserify babel jade stylus http-server

Сделайте форк репозитория.
git clone https://github.com/alexfedoseev/js-app-starter.git

Установите зависимости проекта (исполнять в корне репозитория):
npm install

Соберите проект в development окружении и запустите локальный сервер:
npm start

Откройте браузер и перейдите на lvh.me:3500

В качестве сборщика будем применять Gulp.
Что включает процесс сборки и какие технологии употребляются:

Сборка HTML
Шаблонизатор: Jade
Сборка CSS
Препроцессор: Stylus
Префиксер: Autoprefixer
Сборка JS
Модульная система: Browserify + Babel (ES6 transpiler)
Проверка свойства кода: jsHint
Оптимизация изображений
Оптимизатор: Imagemin
При необходимости: сборка спрайтов, обработка json, копирование фонтов и иных файлов в public папку
Сборщик спрайтов: Spritesmith
Обработка json: gulp-json-editor

При желании хоть какой инструмент можно заменить. Вобще я люблю Slim и Sass, но Ruby к Ruby, a JS к JS: для frontend-проекта будем применять лишь штуки из npm.

Структура проекта
| dist/

| lib/
|— gulp/
|— helpers/
|— tasks/
|— config.js

| node_modules/

| public/
|— css/
|— files/
|— fonts/
|— img/
|— js/
|— json/
|— favicon.ico
|— index.html

| src/
|— css/
|— files/
|— fonts/
|— html/
|— img/
|— js/
|— json/
|— sprite/
|— favicon.ico

| .gitignore
| .npmignore
| gulpfile.js
| npm-shrinkwrap.json
| package.json

Github

.gitignore & .npmignore
Снутри этих файлов находится перечень того, что будет игнорироваться git и npm при коммитах/паблишах.

node_modules/
В эту директорию падают все модули, которые мы установим через npm.

npm-shrinkwrap.json
Я не держу в репозитории содержимое node_modules/. Заместо этого лочу все зависимости через этот файл. Он генерируется автоматом командой: `npm shrinkwrap`.

package.json
Это файл с глобальными опциями проекта. К нему ещё вернемся.

gulpfile.js
Традиционно здесь хранятся все таски для сборки проекта, но в нашем случае он просто описывает значение переменной окружения и пробрасывает нас далее в папку с gulp-тасками.

lib/gulp/
Тут храним все опции и задачки сборщика.

|— config.js
Выносим опции для всех тасков в отдельный файл, чтоб минимизировать правку самих тасков.

|— helpers/
Вспомогательные способы сборщика.

|— tasks/
И сами gulp-таски.

src/
Исходники проекта.

public/
Итог сборки. Полностью всё содержимое данной папки генерируется сборщиком и перед каждой новейшей сборкой она на сто процентов очищается, потому здесь никогда и ничего не храним.

dist/
При этом директория public/ употребляется как хранилище для демки. Раз вы делаете обыденный веб-сайт либо страничку приземления, то оно не пригодится. В данной папке опосля сборки оказываются рядовая и минифицированная версии написанной js-библиотеки. Время от времени я пишу opensource-модули.

Настройка проекта
package.json
Это файл, в котором хранятся глобальные опции проекта.
Подробное описание его внутренностей можно поглядеть здесь: browsenpm.org/package.json
Ниже я остановлюсь лишь на неких принципиальных частях.

},

// Frontend зависимости
"dependencies": {
"jquery": "^2.1.3"
… }
}
Github {
// Заглавие проекта
"name": "js-app-starter",

// Версия проекта
// Использую для версионирования модулей / обновления js+css в кэше браузера при обновлении версии сборки
"version": "0.0.1",

// Раз вы пишете js-библиотеку, то здесь указываем путь к файлу,
// который будет отзываться на `require(‘your-lib’)`
"main": "./dist/app.js",

// Настойки browserify
// В данном случае говорим, что необходимо перед сборкой перевоплотить ES6 в ES5
"browserify": {
"transform": [
"babelify"
]
},

// Консольные команды (подробнее ниже)
"scripts": {
"start": "NODE_ENV=development http-server -a lvh.me -p 3500 & gulp",
"build": "NODE_ENV=production gulp build"
},

// Опции jshint (проверка свойства кода)
"lintOptions": {
"esnext": true
… },

// Development зависимости
"devDependencies": {
"gulp": "^3.8.11"

Консольные команды
В package.json мы можем прописать алиасы для консольных команд, которые будем нередко делать в процессе разработки.

"scripts": {
"start": "NODE_ENV=development http-server -a lvh.me -p 3500 & gulp",
"build": "NODE_ENV=production gulp build"
}

Development сборка
Перед началом работы с проектом нам необходимо:

собрать его из исходников (с sourcemaps для дебага)
запустить «наблюдателей», которые будут пересобирать проект при изменении начальных файлов
запустить локальный сервер

# команда, которую исполняем
npm start

# что исполняется на самом деле
NODE_ENV=development http-server -a lvh.me -p 3500 & gulp
Разбираем по частям# устанавливаем переменную окружения
NODE_ENV=development

# запускаем локальный сервер на домене lvh.me и порте 3500
http-server -a lvh.me -p 3500

# запускаем gulp таски
gulp

Production сборка
Когда мы готовы релизить проект — делаем production-сборку.

# нажмите Ctrl+C, чтоб приостановить локальный сервер и наблюдателей, раз они запущены

# команда, которую исполняем
npm run build

# что исполняется на самом деле
NODE_ENV=production gulp build
Разбираем по частям# устанавливаем переменную окружения
NODE_ENV=production

# запускаем gulp-таск `build`
gulp build

Gulp
Перебегаем к Gulp. Структура тасков взята из сборщика от Dan Tello.

Перед тем, как нырнуть, маленький комментарий по порядку выполнения обыденного gulp-таска:

var gulp = require(‘gulp’);

gulp.task(‘task_1’, [‘pre_task_1’, ‘pre_task_2’], function() {
console.log(‘task_1 is done’);
});

// Тут мы объявили `task_1`, который выводит в консоль сообщение `task_1 is done`
// Запускается он командой `gulp task_1`
// Но перед выполнением основного `task_1` должны выполниться задачки `[‘pre_task_1’, ‘pre_task_2’]`
// Принципиально осознавать, что ‘pre_taskПолностьюpre_task_2’ — выполняются асинхронно,
// то есть порядок выполнения не зависит от позиции задачки в массиве,
// а `task_1` стартует лишь опосля того, как отработали 2 pre-задачки — то есть синхронно

Сейчас разберемся что и в каком порядке будем собирать.

Development сборка
Что происходит далее: `npm start` запускает команду `gulp`.

Gulp отыскивает в текущей директории gulpfile.js. Традиционно в него складываются все таски, но тут он просто определит значение переменной окружения и пробросит нас далее в папку с gulp-тасками.

Код с комментами/* file: gulpfile.js */

// модуль, позволяющий включать таски из вложенных директорий
var requireDir = require(‘require-dir’);

// устанавливаем значение глобальной переменной,
// позволяющей различать в тасках development & production окружения
global.devBuild = process.env.NODE_ENV !== ‘production’;

// пробрасываем сборщик в папку с тасками и конфигом
requireDir(‘./lib/gulp/tasks’, { recurse: true });
Github

Опосля того, как нас пробросило в директорию, сборщик отыскивает таск с заглавием `default`, который поначалу запускает «наблюдателей» над исходниками, позже:

очищает папки `public/` & `dist/`
линтит js-файлы
и собирает спрайты

Опосля этого собирается проект (html, css, js и всё остальное).

Код с комментариямиdefault

/* file: lib/gulp/tasks/default.js */

var gulp = require(‘gulp’);

// Запускаем пустой таск `default`, но предварительно исполняем таск `watch`
gulp.task(‘default’, [‘watch’]);

Github

watch

/* file: lib/gulp/tasks/watch.js */

var gulp = require(‘gulp’),
finder = require(‘../helpers/finder’), // хелпер для поиска файлов
config = require(‘../config’); // конфиг

// Запускаем таск `watch`, перед ним исполняем таски `watching` & `build`
gulp.task(‘watch’, [‘watching’, ‘build’], function() {

// Вешаем наблюдателей на все файлы в директориях `css`, `images` & `html`
// При изменении 1-го из файлов в указанной директории gulp выполнит соответственный таск
gulp.watch(finder(config.css.src), [‘css’]);
gulp.watch(finder(config.images.src), [‘images’]);
gulp.watch(finder(config.html.src), [‘html’]);

});

gulp.task(‘watching’, function() {

// Объявляем глобальную переменную `isWatching`,
// которая говорит, что наблюдатели запущены
global.isWatching = true;

});

Github

build

/* file: lib/gulp/tasks/build.js */

var gulp = require(‘gulp’);

// Запускаем таск `build`, перед ним исполняем таски:
// `clean` — перед сборкой очищаем директории `public/` & `dist/`
// `lint` — проходимся jshint по js-файлам (проверка свойства кода)
// `sprite` — собираем спрайты
gulp.task(‘build’, [‘clean’, ‘lint’, ‘sprite’], function() {

// Опосля того, как отработали три таска выше, запускается таск `bundle`
// Вобще способ `gulp.start` deprecated,
// но обычное управление sync/async задачками покажется лишь в Gulp 4.0,
// потому используем пока его
gulp.start(‘bundle’);

});

// Собираем проект
gulp.task(‘bundle’, [‘scripts’, ‘css’, ‘images’, ‘html’, ‘copy’], function() {

// Раз мы в dev-окружении, то опосля сборки выставляем значение переменной `doBeep` = true
// `notifier` хелпер покажет нам уведомления о ошибках либо окончании работы тасков
// (в консоли и всплывающим баннером)
if (devBuild) global.doBeep = true;

});

Github

Production сборка
`npm run build` запускает команду `gulp build`, которая очищает мотивированные папки, линтит js-код, собирает спрайты и опосля этого собирет проект (без sourcemaps). Код с комментами выше. С ней всё проще.

Файл конфигураций gulp-тасков
Все главные конфигурации тасков вынесены в отдельный файл lib/gulp/config.js:

(true — раз пишем модуль, false — раз делаем веб-сайт)
}
];

module.exports = {

/* здесь опции тасков */

};
Github /* file: lib/gulp/config.js */

var pkg = require(‘../../package.json’), // импортируем package.json
bundler = require(‘./helpers/bundler’); // импортируем хелпер для созлания бандлов

/* Настраиваем пути */

var _src = ‘./src/’, // путь до исходников
_dist = ‘./dist/’, // куда будем сохранять дистрибутив будущей библиотеки
_public = ‘./public/’; // куда будем сохранять веб-сайт либо примеры использования библиотеки

var _js = ‘js/’, // папка с javascript файлами
_css = ‘css/’, // папка с css
_img = ‘img/’, // папка с картинами
_html = ‘html/’; // папка с html

/*
* Настраиваем js / css бандлы
*
* Пример: app.js, app.css — веб-сайт
* admin.js, admin.css — админка
*
* Пример: your-lib.js — модуль без зависимостей
* your-lib.jquery.js — модуль в формате jquery-плагина
*
*/

var bundles = [
{
name : ‘app’, // заглавие бандла
global : ‘app’, // раз пишем модуль, это имя объекта, экспортируемого в глобальное место имён
compress : true, // минифицируем? saveToDist : true // сохраняем в папку `/dist`?

Сборка HTML
Для шаблонизации используем Jade. Он дозволяет делать вставки партиалов, применять inline-javascript, переменные, миксины и ещё много различных крутых штук.

GulpКонфиг

locals: { // переменные, которые мы передаем в шаблоны
pkgVersion: pkg.version // сохраняем версию релиза в переменную `pkgVersion`
}
}
}

Github /* file: lib/gulp/config.js */

html: {
src: _src + _html, // путь до jade-исходников
dest: _public, // куда сохраняем собранное
params: { // характеристики для jade
pretty: devBuild, // убиваем отступы в html?

Таск

/* file: lib/gulp/tasks/html.js */

var gulp = require(‘gulp’),
jade = require(‘gulp-jade’),
jadeInherit = require(‘gulp-jade-inheritance’),
gulpif = require(‘gulp-if’),
changed = require(‘gulp-changed’),
filter = require(‘gulp-filter’),
notifier = require(‘../helpers/notifier’),
config = require(‘../config’).html;

gulp.task(‘html’, function(cb) {

// берём все jade-файлы из директории src/html
gulp.src(config.src + ‘*.jade’)
// раз dev-сборка, то watcher пересобирает лишь модифицированные файлы
.pipe(gulpif(devBuild, changed(config.dest)))
// корректно обрабатываем зависимости
.pipe(jadeInherit({basedir: config.src}))
// отфильтровываем не-партиалы (без `_` сначала)
.pipe(filter(function(file) {
return !//_/.test(file.path) || !/^_/.test(file.relative);
}))
// преобразуем jade в html
.pipe(jade(config.params))
// пишем html-файлы
.pipe(gulp.dest(config.dest))
// по окончании запускаем функцию
.on(‘end’, function() {
notifier(‘html’); // уведомление (в консоли + всплывашка)
cb(); // gulp-callback, сигнализирующий о завершении таска
});

});

Github
ИсходникиСтруктура папки src/html

|— _head.jade

Github | src
|— html
|— index.jade # скелет странички
|— components/ # составляющие странички
|— _header.jade
|— helpers/ # переменные, миксины
|— _params.jade
|— _mixins.jade
|— meta/ # содержимое head, коды аналитики и пр.

Все партиалы снабжаем префиксом `_` (нижнее подчеркивание), чтоб при сборке мы могли их отфильтровать и игнорировать.

helpers/_variables.jade
Сохраняем нужные характеристики в переменные. К примеру, раз у нас телефон стоит в пары местах странички, то его лучше сохранить в переменную и в шаблонах применять конкретно её.

/* file: src/html/helpers/_variables.jade */

— var release = pkgVersion // переменная из gulp-конфига
— var phone = ‘8 800 CALL-ME-NOW’ // телефон

Github

helpers/_mixins.jade
Нередко используемые блоки можно обернуть в mixin.

/* file: src/html/helpers/_mixins.jade */

mixin phoneLink(phoneString)
— var cleanPhone = phoneString.replace(/(|)|s|-/g, »)
a(href="tel:#{cleanPhone}")= phoneString

// в верстке вставляем
// +phoneLink(phone)

Github

index.jade
Скелет главной странички.

/* file: src/html/index.jade */

include helpers/_variables // импортируем переменные
include helpers/_mixins // импортируем миксины

doctype html
html

head
include meta/_head

body
include components/_header
include components/_some_component
include components/_footer

Github

meta/_head.jade
Содержимое head.

Github /* file: src/html/meta/_head.jade */

meta(charset="utf-8")

… // Используем версию сборки, раз необходимо обновить js/css в кэше браузеров
link(rel="stylesheet" href="css/app.min.css?v=#{release}")
script(src="js/app.min.js?v=#{release}")

Сборка JavaScript
C ним мы можем применять стиль подключения CommonJS модулей конкретно в браузере. В качестве модульной системы используем Browserify. И перед сборкой мы проходимся jsHint для проверки свойства кода. Не считая этого мы сейчас можем применять ES6-синтаксис: Babel преобразует его в ES5 перед тем, как Browserify соберет js.

В этом случае я заменяю Browserify на конкатенацию и пишу обёртку руками. У Browserify есть один минус: раз вы пишете библиотеку с наружными зависимостями (к примеру jQuery-плагин), то он не сумеет сделать правильную UMD-обертку.

О бандлахНа проекте может появиться необходимость сформировывать несколько наборов js/css.

Либо библиотеку в 2 вариантах: без зависимостей и в формате jQuery-плагина. Для этого в настройках сборщика мы создаем массив: Эти сборки необходимо делить. К примеру вы пишите фронт + админку.

}
];

/* Для веб-сайта / странички приземления */
var bundles = [
{
name : ‘app’, // заглавие бандла
global : false, // ничем отсвечивать не нужно
compress : true, // минифицируем? (неминифицированная версия сохранятся постоянно)
saveToDist : true // сохраняем в папку `/dist`? /* file: lib/gulp/config.js */

/* Для библиотеки */
var bundles = [
{
name : ‘myLib’, // заглавие бандла
global : ‘myLib’, // это имя объекта, экспортируемого в глобальное место имён
compress : true, // минифицируем? },
name : ‘admin’,
global : false,
compress : true,
saveToDist : false
}
];

Github saveToDist : false // сохраняем в папку `/dist`?

Их структуру я покажу чуток ниже. js/css cборщики будут находить в папке с js/css исходниками соответственный end-point файл (`app.js` либо `app.styl`). Через этот end-point файл мы управляем всеми зависимостями бандла.

Перед передачей бандлов сборщику, мы предварительно пропускаем массив через хелпер `bundler`, который сформировывает объект с опциями.GulpКонфиг

/* file: lib/gulp/config.js */

scripts: {
bundles: bundler(bundles, _js, _src, _dist, _public), // пакуем бандлы
banner: ‘/** ‘ + pkg.name + ‘ v’ + pkg.version + ‘ **/n’, // задаем формат баннера для min.js
extensions: [‘.jsx’], // указываем доп расширения
lint: { // характеристики для jshint
options: pkg.lintOptions,
dir: _src + _js
}
}

Github

Таск

debug: devBuild
});

// сборка
var build = function() {

return (
// browserify-сборка
pack.bundle()
// превращаем browserify-сборку в vinyl
.pipe(source(bundle.destFile))
// эта штука нужна, чтоб нормально работал `require` собранной библиотеки
.pipe(derequire())
// раз dev-свита, то сохрани неминифицированную версию в `public/` (для чего — не помню))
.pipe(gulpif(devBuild, gulp.dest(bundle.destPublicDir)))
// раз сохраняем в папку `dist` — сохраняем
.pipe(gulpif(bundle.saveToDist, gulp.dest(bundle.destDistDir)))
// это для обычной работы sourcemaps при минификации
.pipe(gulpif(bundle.compress, buffer()))
// раз dev-свита и нужна минификация — инициализируем sourcemaps
.pipe(gulpif(bundle.compress && devBuild, sourcemaps.init({loadMaps: true})))
// минифицируем
.pipe(gulpif(bundle.compress, uglify()))
// к минифицированной версии добавляем суффикс `.min`
.pipe(gulpif(bundle.compress, rename({suffix: ‘.min’})))
// раз собираем для production — добавляем баннер с заглавием и версией релиза
.pipe(gulpif(!devBuild, header(config.banner)))
// пишем sourcemaps
.pipe(gulpif(bundle.compress && devBuild, sourcemaps.write(‘./’)))
// сохраняем минифицированную версию в `/dist`
.pipe(gulpif(bundle.saveToDist, gulp.dest(bundle.destDistDir)))
// и в `public`
.pipe(gulp.dest(bundle.destPublicDir))
// в конце исполняем callback handleQueue (определен ниже)
.on(‘end’, handleQueue)
);

};

// раз необходимы watchers
if (global.isWatching) {
// оборачиваем browserify-сборку в watchify
pack = watchify(pack);
// при обновлении файлов из сборки — пересобираем бандл
pack.on(‘update’, build);
}

// в конце сборки бандла
var handleQueue = function() {
// сообщаем, что всё собрали
notifier(bundle.destFile);
// раз есть очередь
if (queue) {
// уменьшаем на 1
queue—;
// раз бандлов больше нет, то сообщаем, что таск завершен
if (queue === 0) cb();
}
};

return build();
};

// запускаем массив бандлов в цикл
config.bundles.forEach(buildThis);

});

Github /* file: lib/gulp/tasks/scripts.js */

var gulp = require(‘gulp’),
browserify = require(‘browserify’),
watchify = require(‘watchify’),
uglify = require(‘gulp-uglify’),
sourcemaps = require(‘gulp-sourcemaps’),
derequire = require(‘gulp-derequire’),
source = require(‘vinyl-source-stream’),
buffer = require(‘vinyl-buffer’),
rename = require(‘gulp-rename’),
header = require(‘gulp-header’),
gulpif = require(‘gulp-if’),
notifier = require(‘../helpers/notifier’),
config = require(‘../config’).scripts;

gulp.task(‘scripts’, function(cb) {

// считаем кол-во бандлов
var queue = config.bundles.length;

// так как бандлов может быть несколько, оборачиваем сборщик в функцию,
// которая в качестве аргумента воспринимает bundle-объект с параметрами
// позднее запустим её в цикл
var buildThis = function(bundle) {

// отдаем bundle browserify
var pack = browserify({
// это для sourcemaps
cache: {}, packageCache: {}, fullPaths: devBuild,
// путь до end-point (app.js)
entries: bundle.src,
// раз пишем модуль, то через этот параметр
// browserify обернет всё в UMD-обертку
// и при подключении объект будет доступен как bundle.global
standalone: bundle.global,
// доп расширения
extensions: config.extensions,
// пишем sourcemaps?
ИсходникиСтруктура папки src/js

| src/
|— js/
|— components/ # код компонентов
|— helpers/ # js-хелперы
|— app.js # end-point бандла

Github

app.js
Имя файла обязано совпадать с именованием бандла. Через этот файл мы рулим всеми зависимостями и порядком выполнения js-компонентов.

/* file: src/js/app.js */

/* Vendor */
import $ from ‘jquery’;

/* Components */
import myComponent from ‘./components/my-component’;

/* App */

$(document).ready(() => {

myComponent();

});

Github
Что делать, раз зависимости нет в npmВ таковых вариантах используем browserify-shim: плагин, который дозволяет превращать обыденные библиотеки в CommonJS-совместимые модули. Итак, у нас есть jQuery-плагин `maskedinput`, которого нет в npm.

Добавляем в `package.json` преобразование и выставляем опции для зависимости:

/* file: package.json */

"browserify": {
"transform": [
"babelify",
"browserify-shim" // добавляем преобразование
]
},

// у `browserify-shim` много вариантов подключения библиотек
// смотрите доки на github: https://github.com/thlorenz/browserify-shim
"browser": {
"maskedinput": "./path/to/jquery.maskedinput.js"
},
"browserify-shim": {
"maskedinput": {
"exports": "maskedinput",
"depends": [
"jquery:jQuery"
]
}
}

Опосля этого мы можем подключать модуль:
require(‘maskedinput’);

Сборка CSS
В качестве препроцессора используем Stylus. Плюс проходимся по css автопрефиксером, чтоб не прописывать вендорные префиксы руками.

GulpКонфиг

/* file: lib/gulp/config.js */

css: {
bundles: bundler(bundles, _css, _src, _dist, _public), // пакуем бандлы
src: _src + _css, // указываем где лежать исходники для watcher
params: {}, // раз необходимы опции для stylus — указываем здесь
autoprefixer: { // настраиваем autoprefixer
browsers: [‘> 1%’, ‘last 2 versions’], // подо что ставим префиксы
cascade: false // прекрасно не нужно, всё равно минифицируем
},
compress: {} // раз необходимы опции минификации — указываем здесь
}

Github

Таск

/* file: lib/gulp/tasks/css.js */

var gulp = require(‘gulp’),
process = require(‘gulp-stylus’),
prefix = require(‘gulp-autoprefixer’),
compress = require(‘gulp-minify-css’),
gulpif = require(‘gulp-if’),
rename = require(‘gulp-rename’),
notifier = require(‘../helpers/notifier’),
config = require(‘../config’).css;

/* Логика css-таска повторяет логику js-таска */

gulp.task(‘css’, function(cb) {

var queue = config.bundles.length;

var buildThis = function(bundle) {

var build = function() {
return (
gulp.src(bundle.src)
.pipe(process(config.params))
.pipe(prefix(config.autoprefixer))
.pipe(gulpif(bundle.compress, compress(config.compress)))
.pipe(gulpif(bundle.compress, rename({suffix: ‘.min’})))
.pipe(gulp.dest(bundle.destPublicDir))
.on(‘end’, handleQueue)
);
};

var handleQueue = function() {
notifier(bundle.destFile);
if (queue) {
queue—;
if (queue === 0) cb();
}
};

return build();
};

config.bundles.forEach(buildThis);

});

Github
ИсходникиСтруктура папки src/css

| src/
|— css/
|— components/ # стили компонентов
|— header.styl
|— footer.styl
|— globals/
|— fonts.styl # подключаем фонты
|— global.styl # глобальные опции проекта
|— normalize.styl # нормализуем / ресетим
|— variables.styl # переменные
|— z-index.styl # z-индексы проекта
|— helpers/
|— classes.styl # вспомогательные классы
|— mixins.styl # и миксины
|— sprite/
|— sprite.json # json, генерируемый gulp.spritesmith
|— sprite.styl # создаем из json css-классы
|— vendor/ # вендорные css складываем сюда
|— app.styl # end-point бандла

Github

app.styl
Через этот файл мы рулим порядком подключения css-компонентов. Имя файла обязано совпадать с именованием бандла.

/* file: src/css/app.styl */

@import "helpers/mixins"
@import "helpers/classes"
@import "globals/variables"
@import "globals/normalize"
@import "globals/z-index"
@import "globals/fonts"
@import "globals/global"
@import "sprite/sprite"
@import "vendor/*"
@import "components/*"

Github

Исходники лежат в репозитории: github.com/alexfedoseev/js-app-starter Все другие таски — рисунки, спрайты, чистка и пр. — не требуют доп комментариев (на самом деле я просто утомился уже строчить).

Раз есть косяки либо дополнения — буду рад обратной связи через комменты здесь либо issues / pull requests на Github. Удач! habrahabr.ru