Ще один пост про складання front-end проекту

Js app starter

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

Що вміє робити складальник:
  • Збирати 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/
Іноді я пишу opensource-модулі. У цій папці після складання виявляються звичайна і минифицированная версії написаної js-бібліотеки. При цьому директорія public/ використовується як сховище для демки. Якщо ви робите звичайний сайт або сторінку приземлення, то воно не знадобиться.

Налаштування проекту

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

{
// Назва проекту
"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
...
},

// Frontend залежності
"dependencies": {
"jquery": "^2.1.3"
...
},

// Development залежності
"devDependencies": {
"gulp": "^3.8.11"
...
}
}
Github

Консольні команди
В 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_1' & '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` 
    // При зміні одного з файлів у вказаній директорії gulp виконає відповідний таск
    gulp.watch(finder(config.css.src), ['css']);
    gulp.watch(finder(config.images.src), ['type']);
    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:

/* 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`? (true - якщо пишемо модуль, false - якщо робимо сайт)
}
];


module.exports = {

/* тут налаштування тасков */

};
Github

Збірка HTML
Для шаблонізації використовуємо Jade. Він дозволяє робити вставки партиалов, використовувати inline-javascript, змінні, міксини і ще багато різних крутих штук.

GulpКонфіг

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

html: {
src: _src + _html, // шлях до jade-исходников
dest: _public, // куди зберігаємо зібране
params: { // параметри для jade
pretty: devBuild, // вбиваємо відступи в html?
locals: { // змінні, які ми передаємо в шаблони
pkgVersion: pkg.version // зберігаємо версію релізу в змінну `pkgVersion`
}
}
}

Github

Таск

/* 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

| src
|-- html
|-- index.jade # скелет сторінки
|-- components/ # компоненти сторінки
|-- _header.jade
|-- helpers/ # змінні, міксини
|-- _params.jade
|-- _mixins.jade
|-- meta/ # вміст head, коди аналітики та ін.
|-- _head.jade

Github

Всі партиалы постачаємо префіксом `_` (нижнє підкреслювання), щоб при збірці ми могли їх фільтрувати і ігнорувати.

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.

/* 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}")

...

Github


Збірка JavaScript
У якості модульної системи використовуємо Browserify. З ним ми можемо використовувати стиль підключення CommonJS модулів безпосередньо в браузері. Крім цього ми тепер можемо використовувати ES6-синтаксис: Babel перетворює його в ES5 перед тим, як Browserify збере js. І перед складанням ми проходимся jsHint для перевірки якості коду.

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

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

Наприклад, ви пишіть фронт + адмінку. Або бібліотеку в 2 варіантах: без залежностей і у форматі jQuery-плагіна. Ці збірки потрібно розділяти. Для цього в налаштуваннях складальника ми створюємо масив:

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

/* Для бібліотеки */
var bundles = [
{
name : 'myLib', // назва пакету
global : 'myLib', // назва об'єкта, що експортується в глобальний простір імен
compress : true, // минифицируем? (неминифицированная версія збережуться завжди)
saveToDist : true // зберігаємо в папці `/dist`?
}
];


/* Для сайту / сторінки приземлення */
var bundles = [
{
name : 'app', // назва пакету
global : false, // нічим не відсвічувати треба
compress : true, // минифицируем?
saveToDist : false // зберігаємо в папці `/dist`?
},
name : 'admin',
global : false,
compress : true,
saveToDist : false
}
];

Github

js/css збирачі будуть шукати в папці з 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

Таск

/* 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?
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
ДжерелоСтруктура папок 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. Успіхів!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.