15 января 2016 г.

Приручаем хрюшку


Немного ранее я писала статью по настройке окружения для начала разработки на Angular JS.

Сейчас предлагаю немного подробнее разобраться с такой штукой, как Grunt. Сам по себе он ничего практически не умеет. Он умеет делать только одно — запускать таски.


Навигация

  1. Структура проекта
  2. Установка Grunt
  3. Запуск сервера и LiveReload
  4. Таски для dev
    1. Copy
    2. Clean
    3. Wiredep

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


Итак, у нас есть такая структура проекта:


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


Описание каталогов:

  • app: исходные файлы проекта — скрипты, стили, вёрстка
  • dist: собранный проект, который можно уже использовать на production — используется минификация, обфускация и подобное
  • server: проект, который собран частично — из этого каталога запускается develop server
  • tmp: тут по названию понятно

Установка Grunt


Как я упоминала ранее, Grunt сам по себе ничего не делает, а просто запускает задачи, прописанные и настроенные в Gruntfile.js. Предположим, что Node.js у вас уже установлен. Если нет — то можете посмотреть в этой статье (шаг первый).

В систему нужно установить сам Grunt, вернее инструменты командной строки для него. Это делается только один раз на систему.
npm install -g grunt-cli
Устанавливаем Grunt как зависимость для нашего проекта. А вот это уже для каждого проекта.
npm install grunt --save-dev
Все запускаемые таски для Grunt являются модулями для Node.js. Поэтому, чтобы их можно было использовать, их надо устанавливать через npm install.

В первую очередь устанавливаем task loader. Это специальный модуль, который позволяет импортировать в Grunt все нужные модули тасков в одну строчку. Подробнее можно посмотреть тут
npm install load-grunt-tasks --save-dev
Ну вот, теперь можно начать настраивать наш Gruntfile.js. Для начала зарегистрируем свою задачу и назовём её server. В итоге наш файл будет выглядеть следующим образом:

module.exports = function (grunt) {
// Волшебная строчка. Она творит магию.
require('load-grunt-tasks')(grunt);
grunt.initConfig({});
// Регестрируем таску, которую можно вызвать с консоли.
// По сути, под одним названием мы запускаем другие в указанном порядке.
grunt.registerTask('server', []);
};
view raw Gruntfile.js hosted with ❤ by GitHub


Теперь можно даже запустить нашу таску, которая ничего не делает ) Из каталога, где находится наш Gruntfile.js открываем консоль и пишем:
grunt server
В Grunt есть два вида задач, скажем так.

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

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

Запуск сервера и Live Reload


Теперь создадим 2 таски. Первая запускает сервер (называется connect), а вторая отслеживает изменения в файлах проекта и автоматически перезагружает страницу в браузере (работает по сокету, называется watch).
npm install grunt-contrib-connect serve-static --save-dev
npm install grunt-contrib-watch --save-dev
Теперь добавляем необходимую конфигурацию для них в Gruntfile.js.

module.exports = function (grunt) {
// Волшебная строчка. Она творит магию.
require('load-grunt-tasks')(grunt);
var serveStatic = require('serve-static');
grunt.initConfig({
// Эта таска отслеживает изменения в файлах проекта
// и запускает другие таски при необходимости.
watch: {
// При изменении файлов открытая страничка
// в браузере перезагрузится автоматически.
livereload: {
options: {
livereload: '<%= connect.options.livereload %>'
},
files: [
'app/**'
]
}
},
// Эта таска запускает сервер.
connect: {
options: {
port: 9000,
hostname: 'localhost',
livereload: 35729,
middleware: function (connect) {
return [
serveStatic('app'),
connect().use('/bower_components', serveStatic('./bower_components'))
];
}
},
livereload: {
options: {
open: false
}
}
}
});
// Регестрируем таску, которую можно вызвать с консоли.
// По сути, под одним названием мы запускаем другие в указанном порядке.
grunt.registerTask('server', [
'connect',
'watch'
]);
};
view raw Gruntfile.js hosted with ❤ by GitHub


Наш супер проект доступен по адресу http://localhost:9000

В данном случае используется grunt-contrib-connect версии 0.11.x. Для его работы нужен serve-static. Объясню подробнее про строки 32 и 33.

serveStatic('app')
указывает, с какого каталога нужно смотреть файлы (относительно base, по умолчанию он смотрит на тот каталог, в котором находится Gruntfile.js). Так что когда запустите http://localhost:9000, то отобразится ./server/index.html.

connect().use('/bower_components', serveStatic('./bower_components'))
тут немногосложнее. Каталог bower_components находится в том же каталоге, что и Gruntfile.js, так что по хорошему к нему надо обращаться как ../bower_components. Но это не получится, т.к. мы попадаем на путь выше базового. Но достучаться до него нам надо (о самом bower будет чуть позже). Так что тут получается такой «маппинг», спрашиваем путь ./bower_components, а получаем содержимое ../bower_components.

Узнать про настройки можно в описании в репозитории проектов:
https://github.com/gruntjs/grunt-contrib-connect
https://github.com/gruntjs/grunt-contrib-watch

Таски для dev


Copy


Чем отличается dev от production? В production обычно необходимо сделать минификацию и обфускацию скриптов и css. В то же время для разработки нам это не надо, т.к. нужно всё это дебажить. Поэтому мы сделаем 2 основные таски:
  • server для dev
  • build для production
Т.к. часто используется less, который надо компилировать всегда (и для dev, и для production), а так же будут специальные настройки в самом index.html (об этом будет чуть позже), то для dev server и production build нам понадобятся несколько дополнительных каталогов в проекте. Они были описаны выше. А сейчас добавим настройки в Gruntfile.js, чтобы он знал про них. Добавим такую штуку:

var appConfig = {
app: require('./bower.json').appPath || 'app',
dist: 'dist',
tmp: 'tmp',
server: 'server',
bower: 'bower_components'
};
view raw Gruntfile.js hosted with ❤ by GitHub


Не забывайте, что это всё обычный JavaScript, который выполняется в среде Node.js. Итак. Теперь нам нужно скопировать рабочие файлы из нашего проекта в каталог server. Пока что будем копировать как есть. Для этого есть таска grunt-contrib-copy. Устанавливаем и настраиваем:
npm install grunt-contrib-copy --save-dev

module.exports = function (grunt) {
// Autoload grunt tasks
require('load-grunt-tasks')(grunt);
var serveStatic = require('serve-static');
var appConfig = {
app: require('./bower.json').appPath || 'app',
dist: 'dist',
tmp: 'tmp',
server: 'server',
bower: 'bower_components'
};
grunt.initConfig({
config: appConfig,
// Copy source files into another directory
copy: {
server: { // subtask for dev
files: [{
expand: true,
cwd: '<%= config.app %>',
src: ['**', '!**/*.less'],
dest: '<%= config.server %>/'
}]
}
},
// Watching project source files
watch: {
livereload: {
options: {
livereload: '<%= connect.options.livereload %>'
},
files: [
'<%= config.server %>/**'
]
}
},
// Start server
connect: {
options: {
port: 9000,
hostname: 'localhost',
livereload: 35729,
middleware: function (connect) {
return [
serveStatic(appConfig.server),
connect().use('/bower_components', serveStatic(appConfig.bower))
];
}
},
livereload: {
options: {
open: false
}
}
}
});
/* Run server
- copy sources into server directory
the remaining tasks use the files from the server directory
- start grunt server
- start watching project source files
*/
grunt.registerTask('server', [
'copy:server',
'connect',
'watch'
]);
};
view raw Gruntfile.js hosted with ❤ by GitHub


Теперь немного пройдёмся по настройкам.
cwd: '<%= config.app %>' // откуда копируем
Тут надо подробнее объяснить, что означает '<%= config.app %>'. Если посмотрите на строку 17, то увидите, что мы создали ссылку на наш список каталогов (config). Таким образом наша строка зарезолвится в значение, которое указано в config.app (т.е. в 'app'). Аналогично можно ссылаться на значения, которые указаны в конфигурациях тасков ('<%= connect.options.port %>' например).
dest: '<%= config.server %>/' // куда копируем
Тут аналогично, ничего нового не добавилось.
src: ['**', '!**/*.less'] // что копируем
А тут тоже подробнее объясню. Фактически это обозначает «скопировать все файлы, кроме файлов с расширением '.less' и не важно, в каком каталоге они находятся». Для указания паттернов путей или файлов используется такая штука как node-glob.

Clean


Любому опытному разработчику ясно, что все эти папочки с билдами, временными файлами нужно очищать. Сейчас мы такую штуку сделаем. Устанавливаем и настраиваем, как всегда.
npm install grunt-contrib-clean --save-dev

...
// Cleanup directories
clean: {
server: '<%= config.server %>'
},
...
/* Run server
- clean server directory
- copy sources into server directory
the remaining tasks use the files from the server directory
- start grunt server
- start watching project source files
*/
grunt.registerTask('server', [
'clean:server',
'copy:server',
'connect',
'watch'
]);
view raw Gruntfile.js hosted with ❤ by GitHub


Я уже не добавляла сюда весь файл, а только изменения. Теперь каждый раз при запуске сервера, наш каталог будет очищаться и туда скопируются файлы с нуля. Это очень полезно, чтобы не оставались устаревшие файлы, из-за которых потом могут быть проблемы (вы внесли нужные изменения, а оно не работает оО).

Wiredep


Вот это будет очень интересная таска. При разработке UI части проекта, могут использоваться различные сторонние js-библиотеки или темы со стилями. Ручками их скачивать и добавлять в проект слишком муторно (все программисты ленивые же).


Wiredep аботает вместе с bower. Bower — система для управления зависимостями в js-проектах (например, стили bootstrap или библиотека jquery).

npm install grunt-wiredep --save-dev

// Include bower dependencies into index.html
wiredep: {
server: {
src: ['<%= config.server %>/index.html'],
ignorePath: /\.\.\//
}
},
/* Run server
- clean server directory
- copy sources into server directory
the remaining tasks use the files from the server directory
- include bower dependencies
- start grunt server
- start watching project source files
*/
grunt.registerTask('server', [
'clean:server',
'copy:server',
'wiredep:server',
'connect',
'watch'
]);
view raw Gruntfile.js hosted with ❤ by GitHub


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

bower install jquery --save
bower install bootstrap --save

Wiredep для включения зависимостей в html-файл использует специальные комментарии. Выше в настройке самой таски указан файл, который надо обработать (src: ['<%= config.server %>/index.html']). Вот таким образом он будет сейчас выглядеть:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Grunt Example</title>
<!-- bower:css -->
<!-- endbower -->
<!-- bower:js -->
<!-- endbower -->
</head>
<body>
<h1>Grunt Example</h1>
<div class="text-muted">This is text with bootstrap style!</div>
</body>
</html>
view raw index.html hosted with ❤ by GitHub


Запускаем сервер
grunt server

Смотрим в server/index.html:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Grunt Example</title>
<!-- bower:css -->
<!-- endbower -->
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
<!-- endbower -->
</head>
<body>
<h1>Grunt Example</h1>
<div class="text-muted">This is text with bootstrap style!</div>
</body>
</html>
view raw index.html hosted with ❤ by GitHub


Упс… Скрипты есть, а куда же подевались стили? Вообще да, bootstrap немного странно работает с bower. И тут надо кое-что подправить. Wiredep в процессе работы смотрит в файл bower.json. Вот его то мы и подправим.

"dependencies": {
"bootstrap": "3.3.6",
"jquery": "2.2.0"
},
"overrides": {
"bootstrap": {
"main": [
"./dist/css/bootstrap.min.css"
]
}
}
}
view raw bower.json hosted with ❤ by GitHub


Перезапускаем наш сервер, и теперь server/index.html выглядит так:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Grunt Example</title>
<!-- bower:css -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css" />
<!-- endbower -->
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
<!-- endbower -->
</head>
<body>
<h1>Grunt Example</h1>
<div class="text-muted">This is text with bootstrap style!</div>
</body>
</html>
view raw index.html hosted with ❤ by GitHub


Что мы тут сделали? Обычно bower зависимости поставляются не только в виде готового для употребления js- или css-файла, но и с исходниками или другими сопутствующими файлами (например, шрифтами). Естественно из всего этого добра нам в html-файл нужно включить только часть. Вот именно эту информацию для wiredep’а мы и подправили. Кстати, теперь у нас не включился bootstrap’овский js-файл (будет вам небольшое домашнее задание исправить это ;)

Вообще это не всё. Даже для dev ещё много чего есть. Об этом я буду писать позже. А потом ещё и для production. Так что не расслабляйтесь (а точнее мне надо не лениться и писать дальше).

Комментариев нет:

Отправить комментарий