Senior Frontend Developer / Team Lead
0,0
рейтинг
9 сентября 2013 в 19:05

Разработка → Опыт работы с GruntJS

image

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

  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
карма
50,0
рейтинг 0,0
Senior Frontend Developer / Team Lead
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (33)

  • +1
    После года работы с грантом я начал задумываться над написанием своего велосипеда. С одной стороны огромное количество готовых штук это круто, с другой, если требуется нечто сложнее того, что тебе9 дают, уже приходится переписывать плагины… сейчас вместо огромных конфигов я стараюсь писать плагины сборщики под конкретную структуру проекта. Это удобнее и дает больше возможностей.
    • 0
      А можете показать пример? Очень интересно посмотреть
      • 0
        Вы можете посмотреть подобный подход при сборке ангулара.
  • +3
    Пользуйтесь уже тогда моим grunt-bg-shell. Он позволяет запускать shell комманды параллельно в бэкграунде
    • 0
      Да, я уже присматриваюсь к нему.
  • 0
    В нашем проекте относительно много файлов. Примерно тысячи 2. Так вот, если покрыть livereload’ом все (с разными тасками, конечно), то nodejs начинает падать.
    Пробовали увеличить лимит открытых файлов? На Mac по умолчанию 256, на Linux — 1024, на Win — не знаю.
    • +1
      Я обычно использую ивенты ватчера что бы производить ребилд/релоад на всего проекта, а только его части. Это заметно ускоряет сборку.
      • 0
        Ну да. Мы делаем так же. Отдельно смотрим за JS, отдельно за шаблонами, отдельно за Less. Пытались еще и за остальными файлами смотреть, но не получилось.
        • 0
          У меня несколько другая структура проекта. Приложение разбито на модули, при изменении отдельных частей приложения ребилдится только часть оных, по изменению картинки/шаблона копируется только один файл или обрабатывается только один файл… Только less ребилдится целиком.
          А уже для продакшена производится финальные оптимизации, минификация, конкатенация нужных модулей (если они точно должны работать вместе), загрузка шаблонов в JS…
          • 0
            Согласен, так намного лучше.

            Мы работали не с grunt-contrib-watch, а с grunt-contrib-livereload и grunt-regarde. А он не умеет отслеживать в каких файлах произошли изменения. Месяцев 5 назад watch еще не умел делать livereload.
            • 0
              Это да.
  • +3
    делали мы интерактивную книгу одного популярного российского писателя. Книжка написана на JS

    Доставляет. :)
  • 0
    — У меня есть две переменных, как мне их сложить?
    — Я поставил плагин “jquery.math”, очень удобно!


    сильно зависит от типов переменных и задачи :)
  • +1
    «Пример Gruntfile» — это ваш рабочий? Там нет 200 строк кода :)
    Хочется побольше подробностей про Gruntfile.js, плагины которые вы используете, почему выбрали именно их, а от чего отказались, про грабли и наоборот про классные штуки, на которые надо обратить внимание.
    • +11
      Мне начать разбираться с GruntJS помогли 3 ссылки:



      А вот список плагинов, которые я использую для вёрстки:

      • grunt-enb
        Плагин для запуска ENB. Он только для тех, у кого БЭМ головного мозга и кто не любит bem-tools.
      • grunt-favicons
        Генерирует кучу favicons из PNG. Тему раскрывает не полностью, но все равно полезный.
      • grunt-imgo
        Плагин для оптимизации картинок. Сначала требуется установить сам imgo, что само по себе является не самой тривиальной задачей. Но кому надо справится, спасибо Александру banzalik Бойченко за утилиту.
      • grunt-contrib-copy, grunt-rename, grunt-text-replace, grunt-contrib-clean
        Плагины для манипуляций с файлами, тут ничего не выбиралось, брался первый попавшийся и работающий.
      • grunt-prettify
        Плагин для причёсывания HTML. Просто для красоты, чтобы не отдавать разработчикам сжатый в одну строчку HTML.
      • grunt-cssbeautifier
        Плагин для причесывания CSS. Так же для красоты, но может применяться и для исходников. Осторожно, может сломать отступы в каком-нибудь stylus.
      • grunt-csso
        Структурная оптимизация и минификация CSS. Я использую только стуктурную оптимизацию из-за специфики своей работы.
      • grunt-csscomb
        Плагин для изначального CSScomb Славы miripiruni Олиянчука. У меня на Ubuntu работает не очень стабильно, куча php-процессов не завершаются вовремя, выжирают процессор и сильно глушат производительность системы.
        Есть уже JS-версия от Михаила mishanga Трошева, но grunt плагина под неё я не встречал.
      • grunt-autoprefixer
        Мощнейшая штукень от Андрея Iskin Ситника, сама добавляет нужные браузерные префиксы, с гибким конфигом.


      Вроде бы перечислил всё. Свой Gruntfile.js не покажу, потому что он страшный и по большей части слизан с Фоторамы.
      Кстати, осторожнее с Grunt, он реально очень быстро вызывает привыкание.
      • 0
        Просто для интереса, imgo дает какое-то заметный выигрышь по сравнению с imagemin?
        • 0
          imagemin не пользовался, если честно. Настроек и всяких плюшек у imgo на первый взгляд побольше: github.com/imgo/imgo/wiki/imgo-ru

          Хотя надо будет найти время и сравнить их. Возможно, может быть эффективнее их совместное использование в зависимости от размеров и состава картинок.
        • 0
          Сомневаюсь, что будет прямо _заметная_ разница. Скорей всего разница будет исчисляться в процентах\долях процентов.
          • 0
            Я тоже так думаю, но есть много ньюансов. На одном проекте пришлось использовать достаточно большие png c прозрачными областями, во второй библиотеке есть чистка прозрачных областей, которая могла бы помочь, а могла бы навредить. Так, что я допустил бы, что, вероятно, существуют как минимум некоторые специальные случаи, когда разница может быть заметна, пускай и не значительная.

            С другой стороны, с imagemin никаких проблем замечено не было при установке под различные ос (Win/Ubuntu/OS X), так что эта библиотека легко была внедрена в рабочий процесс у всех наших разработчиков.
            • +1
              Один из принципов первой версии imgo — это не навреди. Поэтому с вероятностью 99.9% ничего плохого бы не произошло :)
        • 0
          По сути проще написать простенький плагин, которых будет использовать pngquant и чего еще. Во всяком случае лучше pngquant я пока ничего не нашел для оптимизации PNG. Панда тоже его использует.
          • +1
            Не уловил мысль, чем это проще, использования imagemin?
            • 0
              Вообще да, ничем… Пойду ка выпью кофе, а то что-то не думается.
      • 0
        Есть еще полезные штуки borshik и cssrb.
        С их помощью можно експортировать БЭМ проект, в что-то вроде:
        /css
        /img
        /js
        *.html
        • +1
          Спасибо, про cssrb не слышал.

          Плагины нашел: grunt-borschik, grunt-cssrb. Осталось переписать Gruntfile и посмотреть разницу.

          Хотя для моих задач лучше, наверное, написать свою технологию для enb или bem-tools, которая бы занималась только экспортом… Но это для меня пока что высшая лига.
    • +2
      Вот рабочий Gruntfile:
      gist.github.com/shuvalov-anton/6505312

      Как обычно, что-то писалось в спешке, не судите строго)
  • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Как-то у вас пост на самом интересном обрывается :-)

    Что потом происходит с переменной config? Зачем нужна переменная plugins?
    • 0
      plugins не нужна — исправил. Я по ошибке оставил ее. А что происходит с конфигом можно посмотреть здесь. Я как-то постеснялся весь Gruntfile выкладывать.
  • +1
    Если вы используете imgo, рекомендую еще посмотреть на svgo: github.com/svg/svgo
  • +1
    Про склейку JS здесь, я так понял, упомянут только concat. Это хорошо, если вообще все надо склеить в один файл, но иногда надо отделить мух от котлет. Во времена ruby, использовал sprockets, который позволял просто склеивать js-файлы, определяя зависимости прямо в них, с помощью специального вида комментариев:
    //= require partials/hello
    

    Определять зависимости в файлах оказалось намного удобнее, чем прописывать все файлы для склейки где-либо еще: в Gruntfile.js для concat или в шаблоне, как в usemin, с помощью таких конструкций
    <!-- build:js js/app.js -->
    <script src="js/app.js"></script>
    <script src="js/controllers/thing-controller.js"></script>
    <script src="js/models/thing-model.js"></script>
    <script src="js/views/thing-view.js"></script>
    <!-- endbuild -->
    


    Для, скажем так, веб-приложений, имеет смысл, наверное, смотреть в сторону grunt-contrib-requirejs и r.js, но это зачастую слишком громоздко для большинства стандартных сайтов.

    В итоге наткнулся на barkeep и rig, со вторым все получилось сразу и просто, так что использую его.

    Приблизительный таск в Gruntfile.js
    rig: {
      compile: {
        options: {
          banner: '/* Copyright 2013, XXXXX */\n',
          footer: '\n/* End of source */'
        },
        files: [{
            expand: true,
            cwd: '<%= path.development %>/scripts',
            src: '{desktop,mobile,lib}.js',
            dest: '<%= path.temp %>/scripts'
          }]
      }
    }
    

    Комментарии в файлах имеют вид
    //= partials/_imagemap.js
    //= partials/_player.js
    //= partials/_video.js
    
  • 0
    Ссылку на книгу-то дадите?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.