Привет. Мы, наконец, закончили работу над одной интерактивной книгой, и сейчас очень хочется рассказать об одном из самых интересных инструментов, из тех, что мы использовали — о GruntJS.
Немного о проекте
Собственно, делали мы интерактивную книгу одного популярного российского писателя. Книжка написана на JS, шаблонах ECT-JS и LESS. Сборкой, конкатенацией, минификацией и деплоем занимается Grunt, книжка работает на iPad’е под Phonegap.
Технически, мы делали прототип — мы активно изучали и применяли различные технологии. С чем-то получилось круто, с чем-то не очень. Как бы то ни было, книга работает, и ее даже можно скачать в App Store.
Думаю, что этого достаточно. Теперь можно перейти к GruntJS…
Grunt Головного Мозга
— У меня есть две переменных, как мне их сложить?
— Я поставил плагин “jquery.math”, очень удобно!
Расскажу немного утрированную, но поучительную историю. Основным форматом графики в нашем проекте был PNG24 с прозрачностью. Проект занимал примерно 500Мб. Мы решили это оптимизировать. Конечно же с помощью плагина для GruntJS…
Вменяемого результата так и не было. 500Мб не лезло ни в какие рамки. С трудом сжали до 450Mб. В итоге, после нескольких дней поисков, сбросили 250Мб. Вот так:
find . -name "*.png" | xargs pngquant -f -v --ext .png --quality 0-90
GruntJS — потрясающая штука, но вызывает привыкание. Конечно, эта история немного преувеличена, но я действительно видел, как люди делают такие вещи. Напоминает плагины к jQuery. Все-таки, порой проще обойтись однострочником на баше, или подключить проверенные временем консольные утилиты.
Вообще, можно выполнять bash скрипты и команды через grunt. Для этого есть grunt-shell.
grunt.initConfig({
shell: {
compressPNG: {
options: {
stdout: true
},
command: 'find . -name "*.png" | xargs pngquant -f -v --ext .png --quality 0-90'
}
}
});
grunt.loadNpmTasks('grunt-shell');
grunt.registerTask('compress', ['shell: compressPNG']);
Конфиг для Gruntfile.js
В нашем проекте Gruntfile.js содержал порядка 200 строк кода, где была перемешана логика и параметры. Мы решили сделать отдельный конфиг для Gruntfile. В своем проекте для этой цели я создал файл config.json, в котором я храню:
- Директорию исходников
- Директорию для деплоя
- Переменные для шаблонизатора (например, название книги)
- Игнор-лист (файлы, которые не попадают в деплой)
- Список LESS-файлов, и целевой файл
- Список JS-файлов и целевой файл
- Параметры для деплоя на сервер для тестирования
Пользы от этого много. Во-первых, добавлять файлы становится значительно проще, во-вторых, небольшие изменения в конфиге может внести совершенно не знакомый с Grunt’ом человек. Ну и в третих, логика не перемешивается с параметрами.
Выглядит конфиг так:
{
"src": "_src",
"dst": "www",
"port": 8000,
"variables": { "bookname": "Чапаев и Пустота"},
"ignore": [ "**/*.ect", "**/*.md", "**/*.less"],
"less": {
"www/app/assets/css/app.min.css": ["_src/app/assets/less/main.less"],
"www/content/assets/css/content.min.css": ["_src/content/assets/less/main.less","_src/content/widgets/**/widget.less"]
}
},
"js": {
"www/app/assets/js/app.min.js": [
"_src/app/assets/js/utilities.js",
"_src/app/assets/js/modules/*.js",
"_src/app/assets/js/setups/*.js",
"_src/content/widgets/**/*.js"
"_src/app/assets/js/init.js",
]
}
}
Пример Gruntfile
module.exports = function (grunt) {
// Подключаем config.json
var config = grunt.file.readJSON('config.json') || grunt.fatal('config.json not found');
// Разбираемся с паттернами в игнор-листе
config.ignore = getIgnorePatterns(config);
var tasks = {
clean: {
dst: path.join(config.dst, '**/*'),
ignore: config.ignore
},
livereload: {…},
regarde: {…},
copy: {…},
ect: {…},
less: {…},
uglify: {…},
rsync: {…}
};
grunt.initConfig(tasks);
grunt.registerTask('lvrld', ['livereload-start', 'connect', 'regarde']);
grunt.registerTask('main', ['clean:dst', 'copy:main', 'clean:ignore','uglify:main', 'ect', 'less:browser']);
grunt.registerTask('browser', ['main', 'lvrld']);
grunt.registerTask('phonegap', ['clean:dst', 'copy:main', 'clean:ignore', 'uglify:main', 'less:phonegap', 'ect']);
grunt.registerTask('deploy', ['main', 'rsync:deploy']);
grunt.registerTask('default', ['browser']);
var plugins = ['grunt-rsync', 'grunt-ect-templates', 'grunt-remove-logging', 'grunt-contrib-copy', 'grunt-contrib-concat', 'grunt-contrib-clean', 'grunt-contrib-less', 'grunt-contrib-uglify', 'grunt-contrib-livereload', 'grunt-contrib-connect', 'grunt-regarde']
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
};
Удобная отладка на устройствах
Отлаживать код на паре iPad'ов и MacBook'е очень неудобно. Приходиться постоянно обновлять страницу руками. Это ужасно.
Здесь Grunt может помочь. Если запустить grunt-contrib-watch, настроить livereload, и подключить все устройства по IP, то при изменении кода все гаджеты будут сами перезагружать страницу. Это потрясающе! Серьезно, если постоянно перезагружать страницу даже на трех устройствах, то уже через час я хочу разнести все в щепки.
Вообще, здесь есть проблема. В нашем проекте относительно много файлов. Примерно тысячи 2. Так вот, если покрыть livereload’ом все (с разными тасками, конечно), то nodejs начинает падать. Приходится отказываться от полного покрытия. У нас под нож пошли картинки.
Написание плагина
Вобщем-то, шаблонизатор я выбрал достаточно просто — взял первый попавшийся, с layout и partials. Первым попавшимся оказался ECTJS. Сейчас я бы взял jade, но тогда взял этот. Не найдя подходящий плагин для шаблонизатора, я решил написать свой.
Вообще, писать плагин для gruntjs достаточно просто. Практически все что может понадобиться, написано в инструкции. В целом, алгоритм такой:
1. grunt-init gruntplugin
создаст структуту проекта
2. Пишем код, подключаем модули…
3. Когда нужно — подсматриваем в API GruntJS
Вобщем-то это все, чем я хотел поделиться.
Автор: asheee