Изначально я долго и усердно настраивал 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
Должно получится примерно следующее:
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
Я специально вставил код картинкой, чтобы вы не лазили в него =) Сейчас это совсем не нужно, для справедливости только скажу, что в переменную 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 на сегодня 😉