Опыт работы с GruntJS

в 15:05, , рубрики: grunt.js, gruntjs, javascript, node.js, метки: , ,

Привет. Мы, наконец, закончили работу над одной интерактивной книгой, и сейчас очень хочется рассказать об одном из самых интересных инструментов, из тех, что мы использовали — о 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js