С самого момента установки 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’а :)
Интересных момента тут два:
-
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 брал сразу их, а не занимался компрессией сам. -
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
, что проще и удобнее, а кроме того не нужно держать лишних шаблонов.
Спасибо за ссылку на мою статью! Я далаю примерно так же, но вместо шаблонов использую свой же плагин fingerprint — он позволяет записать в файл метку времени самого свежего файла из сборки. Потом можно будет подключить этот файл в теме и добавить метку к подключамым файлам.
Спасибо за наводку! Воспользовался плагином и обновил пост.
Позабавила матрёшка: TPL > PHP > HTM.