Boilerplate: Angular.js + JADE + Gulp

Изначально я долго и усердно настраивал browserify+angular.js, и был близок к победе, но что-то пошло не так (а именно inject кастомных директив) и я решил немного повременить. Поэтому связка будет немного иной и в консоли придется выполнить на одну команду больше. Так или иначе, этот пост в первую очередь для моего друга-бэкэндера и здесь будет сделан упор на автосборку проекта с помощью таск-менеджера Gulp.
Lexa-007, You’re welcome 😉

Boilerplate = шаблон. Болванка для быстрого старта нового проекта. Берите с гита мою болванку и перейдете в ветку bower

git checkout bower

Если у вас не установлен node.js, вам сюда — http://nodejs.org/download/
Если у вас не установлен Gulp — 1 раз его нужно поставить глобально, с помощью NPM (менеджер пакетов, который вы уже получили вместе с установкой node.js)

npm install -g gulp

Это можно сделать из любой директории, опция -g = global, а следовательно с этого момента вам всюду доступна команда gulp.

Теперь перейдите в папку gulp-experiments и выполните две команды:

npm install
bower install

Первая команда тянет все зависимости проекта указанные в файле package.json, вторая делает тоже самое, только из файла bower.json
Чтобы добавлять новые зависимости в проект, вам достаточно устанавливать что-либо с опцией —save-dev, например

bower install jquery --save-dev

Для создания файлов package.json и bower.json вы можете использовать команды

npm init
bower init

Сейчас их выполнять не нужно, но чтобы понять о чем идет речь, я рекомендую вам выполнить npm init в любой другой директории с кодом под версионным контролем git (так как в этом случае автоматически подтянется адрес удаленного репозитория и прочее).
Теперь напишите в консоли

gulp build

Должно получится примерно следующее:
gulp build
offtopic: Обратите внимание, я пишу g build, так как у меня настроен алиас g = gulp

О чем речь на экране терминала? Все очень просто: gulp запустил (асинхронно) несколько задач и написал вам об этом, а также о том, что закончил их выполнение, что есть синтаксическая ошибка в .jade файле шаблона и что запущен локальный web-сервер. В общем, для отладки информации предостаточно. Все ошибки в процессе работы gulp будут сыпаться на экран терминала, где он запущен.

Углубимся в теорию. Как работает gulp? Он ищет в корне файлик gulpfile.js и исполняет его. Если заглянуть в файл, можно увидеть 2 разных куска кода. Первый — строчки с require, второй — gulp.task кусочки. Так как статья по большему счету для новичков мира node.js — расскажу и про require 😉

Помните вы выполнили команду npm install? Отлично! Теперь на вашем компьютере в папке с проектом появилась папка node_modules/ (для мира node.js, и не тлько, ваша папка = «.») поэтому правильно будет написать — у вас теперь есть ./node_modules/ папка, где лежат все модули которые скачал менеджер пакетов NPM.

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

npm install gulp-uglify --save-dev

а затем подключаем uglify в файлике. Я не открою вам Америку, если скажу что в переменную uglify мы просто подключили функцию из ./node_modules/gulp-uglify
gulp-uglify
Я специально вставил код картинкой, чтобы вы не лазили в него =) Сейчас это совсем не нужно, для справедливости только скажу, что в переменную uglify передался последний вызов return, и то что вы также легко можете писать свои функции, обернуть их в

module.exports = function (name) {
  return console.log('Hi, ' + name);
}

и в коде вызвать проще простого

var greetings = require('my-module-name');
greetins('Max')

Этот подход как раз используется в browserify, поэтому я очень рассчитываю что еще напишу подробнее про это, а пока вернемся к gulp.

Мы разобрались с первой частью, require = подключение npm-модулей. Перейдем ко второй — gulp.task

Собственно, каждая задача описывается внутри gulp.task

Каждая строчка в файле, почти всегда возвращает поток (stream, опять же магия node.js) и получается что наш файл(ы), изначально указанный в gulp.src проходит через несколько фильтров. Разберем сразу большой пример, представьте: мы сунули все наши скрипты в первый фильтр — конкатенация, на выходе (не дав опомнится :D) окунили сконкатенированный буфер данных в фильтр минификации и на выходе выдали всего 1 файлик, причем положили его туда, куда вежливо указали в gulp.dest

Поверьте, это круто, когда Gulp за вас делает несколько операций и ложит все необходимые файлы, например в папку builds/development. Потом мы эту папку заливаем на production, где абсолютно нет нужды держать npm-пакеты, кучу серверных скриптов и так далее, так как нам нужно всего на всего отдать HTML разметку и JavaScript код.

Вот мы и подошли к тому, с чего я должен был начать статью для привлечения внимания:
Gulp во время разработки в моем шаблоне:
1) Дает нам локальный веб-сервер, который перезагружается после изменения js, html, css
2) Превращает JADE-шаблоны в HTML-код
3) Превращает SCSS код в CSS
4) Склеивает все js файлы в два файлика: libs (здесь jquery, lodash, angular и так далее), и scipts — скрипты, которые написали мы сами.
5) Минифицирует оба файла из пункта 4
6) Для angular.js добавляет $scope.inject правила
7) Проверяет наш код с помощью JS Lint
8) *и делает все остальное, что вы захотите

При этом, Gulp делает это быстро!

Разберем простой «таск»

gulp.task('copyToBuild:bootstrap', function() {
  return gulp.src('src/vendor/css/bootstrap.min.css')
    .pipe(gulp.dest(outputDir + '/css'));
});

Перевод: возьми файл стилей bootstrap и скопируй его по пути, указанному в gulp.dest, а конкретно для нашего случая — ‘builds/development/css’ (так как в самом начале мы определили outputDir = ‘builds/delopment’)

А теперь взгляните на «сложный таск»

gulp.task('compress', function() {
  gulp.src(['./src/js/**/*.js']) //возьми все js-файлы из всех папок, внутри ./src/js/
    .pipe(changed(outputDir + '/js')) //но дальше пропусти только те, в которых были изменения
    .pipe(sourcemaps.init()) //инициализируй sourcemap
    .pipe(concat('scripts.js')) //соедини все файлы в один - scripts.js
    .pipe(ngAnnotate()) //добавь $scope.inject
    .pipe(uglify()) //минифицируй
    .pipe(sourcemaps.write()) //запиши sourcemap
    .pipe(gulp.dest(outputDir + '/js')); //положи scripts.js в 'builds/delvelopment/js' 
});

Хм, по-моему не стало сложнее, логично что больше задач — больше строчек.
offtopic: sourcemap — удобная фича для отладки минифицированного кода, $scope.inject — включение зависимостей angular.js

Да, это то, о чем я и говорил! Gulp сам все сделает, его нужно только 1 раз научить.

Пытливый читатель, может спросить — а где обещанный локальный сервер и LiveReload (перезагрузка страницы браузера, если в коде произошли изменения).
На что я отвечу — gulp-connect

В gulpfile.js легко найти строчку по старту сервера:

gulp.task('connect', function() {
  connect.server({
    //root: outputDir, //по умолчанию = ./
    port: 8001,
    livereload: true
  });
});

Тогда тот же самый пытливый читатель, скажет — а как «оно» понимает? Давайте взглянем на задачу с превращением JADE в html

gulp.task('jade', function() {
  return gulp.src('src/templates/**/*.jade') // бери все файлы .jade из всех папок в src/templates
    .pipe(jade()) // превращай их в html
    .pipe(gulp.dest(outputDir)) // копируй все файлы в builds/development
    .pipe(connect.reload()); // перезагрузи страницу в браузере
});

Ничего лишнего, не правда ли?

Но, вернемся к задаче про javascript. Так как в таске «compress» нет строки connect.reload, у вас могут возникнуть подозрения. Минуточку терпения, статья уже кончается =)
Для проверки синтаксиса мы используем JS Lint, он будет ругаться, например, если вы объявили переменную и не использовали ее, или забыли поставить там где нужно ;
Вот задача для gulp

gulp.task('lint', function() {
    var files = [
      './src/js/**/*.js', //бери все .js файлы, что лежат в src/js
      //'!./src/js/modules/signalr.js' - не бери этот файл
    ];
    files.push('./gulpfile.js'); //добавь туда gulpfile - мини-хак
    return gulp.src(files) //бери файлы из переменной
      .pipe(jshint('.jshintrc')) //проверяй их на основе конфигурационного файла .jshintrc
      .pipe(jshint.reporter('jshint-stylish')); //и выводи уведомления на основе reporter "jshint stylish" который мы стянули из NPM
});

Вуаля, файлы проверяются на синтаксические ошибки, но… всего 1 раз. Для того, чтобы Gulp проверял их постоянно, при каждом изменении, мы будем использовать встроенное решение — gulp.watch!

gulp.task('watch', function() {
  gulp.watch('src/templates/**/*.jade', ['jade']); // смотри за всеми .jade файлами из папки ... и выполняй таск jade
  gulp.watch('src/js/**/*.js', ['js','lint', 'compress']); // смотри за всеми .js файлами из папки ... и выполняй таски js, lint, compress
  gulp.watch('src/js/views/**/*.jade', ['jadeAngularTmpl']); // таск для преоброзования view, используемых в angular.js
  gulp.watch('src/sass/**/*.scss', ['sass']); //следи за изменениями в scss файлах и запускай таск sass
});

Паззл почти собрался — при изменении кода в .js файлах запускается три задачи. В двух из них нет connect.reload, давайте взглянем на третью

gulp.task('js', function() {
  return gulp.src([
      'src/js/**/*.js',
    ])
    .pipe(connect.reload());
});

Таск по сути ни делает ничего, кроме подачи сигнала — «перезагрузи страницу».

Вот и все про Gulp на сегодня 😉
gulp logo

comments powered by HyperComments
При копировании материалов обратная ссылка на play-stop.ru желательна обязательна!