Twitter GitHub

Оптимизируем WordPress (и не только) с помощью Grunt’а

С самого момента установки WordPress’а и темы для него, меня как разработчика, очень сильно напрягло, что ни сам WordPress, ни тема никак не оптимизируют количество подгружаемых файлов стилей и скриптов — никакой минификации, никакой конкатенации, только пачка запросов к серверу на 5 файлов стилей и 12 скриптов. Все это меня удручало, но как-то все было не того.

Но вот вчера я наткнулся на отличную статью на Хабре про Grunt, в конце которой оказалась ссылка на еще более полезную статью Артёма Сапегина про Grunt как систему сборки для фронтенд-разработчиков. После ознакомления с указанными статьями я подумал, что это просто-таки отличное решение моей проблемы с Wordpress’ом и погрузился в изучение Grunt’а.

Вот что из этого получилось… Мой Gruntfile.coffee:

'use strict'

module.exports = (grunt)->
  grunt.initConfig
    concat:
      js:
        src: [
          'wp-includes/js/jquery/jquery.js',
          'wp-content/themes/memoir/includes/js/jquery.easing.1.3.js',
          'wp-content/themes/memoir/includes/fancybox/jquery.mousewheel-3.0.4.pack.js',
          'wp-content/themes/memoir/includes/fancybox/jquery.fancybox-1.3.4.pack.js',
          'wp-content/themes/memoir/includes/js/jquery.mobilemenu.js',
          'wp-content/themes/memoir/includes/js/jquery.flexslider-min.js',
          'wp-content/themes/memoir/includes/js/jquery.fitvids.js',
          'wp-content/themes/memoir/includes/js/social-likes.min.js',
          'wp-content/themes/memoir/includes/js/highlight.pack.js',
          'wp-content/themes/memoir/includes/js/highlight.my.js',
          'wp-content/themes/memoir/includes/js/memoir.js',
          'wp-includes/js/comment-reply.js'
        ]
        dest: 'wp-content/themes/memoir/combined.js'

    uglify:
      js:
        src: '<%= concat.js.dest %>'
        dest: 'wp-content/themes/memoir/combined.min.js'

    cssmin:
      css:
        src: [
          'wp-content/themes/memoir/style.css',
          'wp-content/themes/memoir/includes/fancybox/jquery.fancybox-1.3.4.css',
          'wp-content/themes/memoir/includes/js/flexslider.css',
          'wp-content/themes/memoir/includes/js/social-likes.css',
          'wp-content/themes/memoir/includes/js/highlight.css'
        ]
        dest: 'wp-content/themes/memoir/combined.css'

    shell:
      gzipJS:
        command: 'gzip --best -f -c "<%= uglify.js.dest %>" > "<%= uglify.js.dest %>.gz"'
      gzipCSS:
        command: 'gzip --best -f -c "<%= cssmin.css.dest %>" > "<%= cssmin.css.dest %>.gz"'

    copy:
      templates:
        options:
          processContent: grunt.template.process
        files:
          'wp-content/themes/memoir/header.php': 'wp-content/themes/memoir/header.tpl.php'
          'wp-content/themes/memoir/footer.php': 'wp-content/themes/memoir/footer.tpl.php'

  grunt.loadNpmTasks 'grunt-contrib-concat'
  grunt.loadNpmTasks 'grunt-contrib-cssmin'
  grunt.loadNpmTasks 'grunt-contrib-uglify'
  grunt.loadNpmTasks 'grunt-contrib-copy'
  grunt.loadNpmTasks 'grunt-shell'

  grunt.registerTask 'default', ['concat', 'uglify', 'cssmin', 'shell', 'copy']
  grunt.registerTask 'js', ['concat:js', 'uglify', 'shell:gzipJS', 'copy']
  grunt.registerTask 'css', ['cssmin', 'shell:gzipCSS', 'copy']

Я пишу на JS только в CoffeeScript, потому что попробовав его один раз, приобрел перманентное отвращение к избыточности синтаксиса JS. В файле в общем-то все очевидно, подробнее о синтаксисе можете прочитать в вышеупомянутых статьях, там же про установку Grunt’а и его плагинов. Еще хорошо помогает официальный сайт Grunt’а :)

Интересных момента тут два:

  1.   shell:
          gzipJS:
            command: 'gzip --best -f -c "<%= uglify.js.dest %>" > "<%= uglify.js.dest %>.gz"'
          gzipCSS:
            command: 'gzip --best -f -c "<%= cssmin.compress.dest %>" > "<%= cssmin.compress.dest %>.gz"'

    Я не нашел (может плохо искал) плагина для gzip’а файлов. Есть плагин grunt-contrib-compress, но в нем нельзя менять силу сжатия. Поэтому решил эту проблему вот так — банальным вызовом gzip в шелле. Отвечает за это плагин grunt-shell, который на самом деле открывает просто безграничные возможности для творчества :)

    В целом этот шаг не обязателен, просто у меня в nginx активирована директива gzip_static, чтобы при наличии готовых *.gz файлов nginx брал сразу их, а не занимался компрессией сам.

  2.   copy:
          templates:
            options:
              processContent: grunt.template.process
            files:
              'wp-content/themes/memoir/header.php': 'wp-content/themes/memoir/header.tpl.php'
              'wp-content/themes/memoir/footer.php': 'wp-content/themes/memoir/footer.tpl.php'

    Тут мы копируем шаблоны шапки и подвала темы, попутно прогоняя их через Lo-Dash шаблонизатор Grunt’а, чтобы иметь возможность делать вот такие штуки:

    <link rel="stylesheet" type="text/css" href="<?php bloginfo('stylesheet_directory'); ?>/combined.css?<%= grunt.template.today('yyddmmHHMMss') %>" media="screen" />
    <script type="text/javascript" src="<?php bloginfo('stylesheet_directory'); ?>/combined.min.js?<%= grunt.template.today('yyddmmHHMMss') %>"></script>

    Это решает проблему с обновлением кэшированных стилей и скриптов на клиенте.

Дефолтное подключение файлов стилей и скриптов естественно из темы выпиливаем (не забывая проверить файл functions.php, а заодно и все остальные *.php, на предмет подключения файлов; ключевые слова для поиска: wp_enqueue_style и wp_enqueue_script) и заменяем примером выше.

Теперь при обновлении стилей и/или скриптов темы мне достаточно выполнить:

$ grunt

Или только для скриптов:

$ grunt js

Или только для стилей:

$ grunt css

После чего залить файлы combined.* + шаблоны header.php и footer.php, и все готово. Теперь посетитель получает один файл стилей и один файл скриптов, который в лучших традициях сайтостроения находится в самом низу шаблона. YSlow после всех этих манипуляций выдал оценку в 85 баллов.

Ну и конечно так можно и нужно оптимизировать не только WordPress, но и любой другой сайт, если у него нет встроенных механизмов минификации и конкатенации файлов стилей и скриптов.

Мой package.json, если кому-то интересно:

{
  "name": "svyatov.ru",
  "version": "1.0.0",
  "devDependencies": {
    "grunt": "~0.4.0",
    "grunt-contrib-concat": "~0.1.3",
    "grunt-contrib-cssmin": "~0.4.1",
    "grunt-contrib-uglify": "~0.1.1",
    "grunt-shell": "~0.2.1",
    "grunt-contrib-copy": "~0.4.0"
  }
}

[update 06.03.2013]

Спасибо комментарию Артёма, что направил ход мыслей в более правильное русло, а заодно вооружил отличным плагином. Используя плагин Артёма, я упростил схему до следующего вида…

Плагин grunt-contrib-copy я заменил плагином grunt-fingerprint:

fingerprint:
  assets:
    src: 'wp-content/themes/memoir/combined.*'
    filename: 'wp-content/themes/memoir/assets-fingerprint.php',
    template: "<?php define('ASSETS_FINGERPRINT', '<%= fingerprint %>');"

В header.php стили теперь подключаются так:

<?php require_once(get_template_directory().'/assets-fingerprint.php'); ?>
<link rel="stylesheet" type="text/css" href="<?php bloginfo('stylesheet_directory'); ?>/combined.css?<?php echo ASSETS_FINGERPRINT; ?>" media="screen" />

Аналогично подключаются скрипты в footer.php:

<?php require_once(get_template_directory().'/assets-fingerprint.php'); ?>
<script type="text/javascript" src="<?php bloginfo('stylesheet_directory'); ?>/combined.min.js?<?php echo ASSETS_FINGERPRINT; ?>"></script>

После обновления заливать теперь приходтся только файлы combined.* и assets-fingerprint.php, что проще и удобнее, а кроме того не нужно держать лишних шаблонов.

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

  • Спасибо за ссылку на мою статью! Я далаю примерно так же, но вместо шаблонов использую свой же плагин fingerprint — он позволяет записать в файл метку времени самого свежего файла из сборки. Потом можно будет подключить этот файл в теме и добавить метку к подключамым файлам.

    • Leonid Svyatov says

      Спасибо за наводку! Воспользовался плагином и обновил пост.

  • Позабавила матрёшка: TPL > PHP > HTM.