-
Создание профессиональных сайтов с помощью DocPad
Продолжаю знакомить с open-source наработками, созданными в процессе работы над проектом Emmet. В прошлый раз это был CodeMirror Movie, а в этот раз познакомлю вас с процессом создания сайта документации на основе DocPad.
tl;dr – Как сделать профессиональный высокопроизводительный сайт на DocPad
- Используйте плагин docpad-plugin-menu для автоматической генерации меню сайта.
- Используйте grunt-frontend и docpad-plugin-frontend для сборки CSS и JS ресурсов и правильного кэширования.
- Создайте специальное debug-окружение для плагина
docpad-plugin-frontend
для поиска проблем в исходниках CSS и JS, а не их минифицированных версиях. - Настройте веб-хуки на GitHub и Gith на сервере для автоматической сборки сайта после каждого коммита.
- Настройке nginx для правильного кэширования статических файлов и экономии ресурсов процессора.
DocPad — это генератор статических сайтов, написанный на CoffeeScript. В отличие от сайтов, созданных с использованием обычных CMS вроде Django, Drupal и WordPress, статические сайты потребляют минимальное количество серверных ресурсов, так как представляют собой набор заранее сгенерированных обычных HTML-файлов. То есть кроме обычного веб-сервера вроде nginx или Apache для работы сайта ничего не нужно.
Для документации Emmet это идеальный вариант, позволяющий не только удобно работать над сайтом, но и значительно сократить расходы на хостинг: можно выдерживать относительно высокую посещаемость даже на недорогом хостинге.
Но у DocPad, как и у многих других генераторов, есть ряд недостатков, не позволяющих делать по-настоящему профессиональные и быстрые сайты. Их я и решил исправить, написав несколько плагинов:
- docpad-plugin-menu — автоматическая генерация меню для сайта.
- grunt-frontend — «умная» сборка CSS и JS файлов.
- docpad-plugin-frontend – вывод собранных с помощью
grunt-frontend
CSS и JS файлов с учётом правильного кэширования, а также управление наборами файлов между шаблонами.
Если вы ещё не знакомы с DocPad, рекомендую вам посмотреть и прочитать Введение в DocPad, чтобы вы представляли, о чём в дальнейшем пойдёт речь.
Генерация меню
Плагин docpad-plugin-menu умеет геренировать структурированное меню для всех страниц сайта (то есть для всех файлов из папки
src/documents
). Этот плагин добавляет методgenerateMenu(url)
в объектtemplateData
, в контексте которого отрисовываются все шаблоны проекта. На вход этот метод принимает URL страницы, относительно которого нужно создать меню, на выходе вы получите структуру разделов сайта, которую удобно отрисовывать, например, с помощью partials.Подробнее о возможностях плагина и примерах его использования читайте на основной странице проекта.
Сборка фронт-энд ресурсов
Для удобства разработки я разбиваю CSS и JS файлы на отдельные модули, которые затем склеиваются и минифицируются – это стандартная практика высокопроизводительных сайтов. Для сборки я использую Grunt.js в котором, казалось бы, уже есть все необходимые инструменты для выполнения этих задач.
Но и тут я не нашёл ничего подходящего. Дело в том, что мне важна дата последнего обновления минифицированного файла, потому что я хочу её подставлять в URL файла для эффективного сброса кэша. Поэтому обновлять конечный файл нужно только тогда, когда поменялся один исходных файлов.
Для решения этой задачи я написал свой сборщик: grunt-frontend. Работает он следующим образом. Во время конкатенации и минификации нескольких файлов в один он записывает структуру исходных файлов и их md5-отпечаток в специальный файл
.build-catalog.json
. При следующей сборке плагин смотрит на структуру и содержимое исходных файлов: если ничего не поменялось, то и конечный файл не минифицируется и не обновляется.Это не только сокращает время сборки, но и позволяет сохранить такие важные данные конечного файла как дату обновления и md5-отпечаток. Все эти данные хранятся в
.build-catalog.json
, его желательно хранить вне версионного контроля.Для минификации используются библиотеки CSSO (с автоматическим инлайнингом всех подключённых через
@import
файлов) и UglifyJS.Подробнее об использовании
grunt-frontend
.Управление CSS и JS ресурсами
Очень часто возникает необходимость управлять подключением CSS и JS файлов на различных страницах сайта. Скажем, на всех страницах нужно использовать набор файлов
set1
; для всех внутренних страниц раздела/about/
нужно дополнительно использоватьset2
иset3
, но для страницы/about/contacts/
вместоset2
нужно использоватьset4
(то естьset1
,set4
,set3
, именно в таком порядке). Кроме того, в URL всех ресурсов нужно подставлять дату модификации файла чтобы эффективно сбрасывать кэш.Для решения этих задач был написан плагин docpad-plugin-frontend. Он добавляет метод
assets(prefix)
, который позволяет доставать отсортированный список ресурсов из текущего документа и всей цепочки шаблонов, применяемых к документу. Если в корневой папке проекта существует файл.build-catalog.json
, то плагин считывает его и возвращает список ресурсов с префиксом в виде даты модификации файла.Например, описанную выше задачу с управлением наборов ресурсов можно решить следующим образом. Для основного шаблона
default.html.eco
указываем основной набор файлов в мета-данных:--- js: "/js/fileA.js" ---
В шаблоне
about.html.eco
, который наследуется от основного шаблона и применяется ко всем документам/about/*
, указываем следующие данные:--- layout: default js2: ["/js/fileB.js", "/js/fileC.js"] js3: ["/js/fileD.js", "/js/fileE.js"] ---
В документе
/about/contacts/index.html
перекрываем наборjs2
:--- layout: about js2: "/js/contacts.js" ---
Теперь, при рендеринге страницы
/about/contacts/index.html
, вызовassets('js')
вернёт следующий набор файлов:/js/fileA.js
/js/contacts.js
/js/fileD.js
/js/fileE.js
Как видите, всё довольно просто: придумываем префикс для категории ресурсов, а сами наборы создаём с помощью числовых суффиксов. Далее вызываем
assets()
в шаблоне и передаём ему префикс набора ресурсов: файлы сортируются по суффиксу в порядке возрастания; наборы с одинаковым суффиксом перекрываются.Более подробную информацию о возможностях плагина и примерах использования читайте на главной странице репозитория.
Режим отладки
Очень часто бывает так, что пользователь вашего сайта сообщает вам, что в каком-то браузере возникает ошибка: не работает JavaScript или элементы наехали друг на друга. Но весь ваш CSS и JS код минифициорван и вам довольно сложно найти то самое место в исходных файлах, где эта ошибка возникает.
В будущем эти проблемы можно будет находить с помощью Source Maps, но сейчас далеко не все минификаторы и браузеры их поддерживают.
В плагине
docpad-plugin-frontend
есть специальный режим отладки. Так как структура всех минифицированных файлов хранится в JSON-каталоге, нам не составит труда при необходимости вывести список исходных файлов вместо скомпилированного.Для этого в DocPad я создаю отдельное окружение, в котором указываю опцию
frontendDebug: true
. Если опцияfrontendDebug
равнаtrue
, то методassets()
плагинаdocpad-plugin-frontend
будет по возможности возвращать список исходных файлов вместо минифицированных. Пример настройкиdocpad.coffee
:module.exports = { … environments: debug: frontendDebug: true }
Теперь при запуске
DocPad
в окружениемdebug
, вы получите HTML-страницы с исходными CSS и JS файлами и сможете легко найти ошибку:docpad run --env=debug
Автоматический деплой с GitHub
Я настроил сервер таким образом, чтобы после каждого коммита в ветку
master
сайт автоматически генерировался.Со стороны GitHub я использовал обычный “WebHook”, а на стороне сервера – Gith.
Gith – это удобный веб-сервер для Node.JS, который умеет принимать и фильтровать данные веб-хуков GitHub. Мой сервер, который запускает сборку сайта, выглядит следующим образом:
var childProc = require('child_process'); var path = require('path'); var gith = require('gith').create(3000); gith({ // Слушаем хуки только для ветки "master" branch: 'master' }).on('all', function(payload) { console.log('Run deply script on', new Date()); // Запускаем скрипт сборки сайта var deploy = childProc.spawn('sh', ['/web/deploy.sh']); deploy.stdout.on('data', function(data) { var message = data.toString('utf8'); if (~message.indexOf('subscribe')) { // Docpad может спросить про подписку на рассылку, откажемся deploy.stdin.write('n'); } else if (~message.toLowerCase().indexOf('privacy')) { // Docpad может спросить про политику безопасности, согласимся deploy.stdin.write('y'); } }); deploy.stderr.on('data', function(data) { console.log('Error: ', data.toString('utf8')); }); deploy.on('exit', function(code) { console.log('Deploy complete with exit code ' + code); }); });
Сам скрипт сборки проекта
deploy.sh
выглядит следующим образом:#! /usr/bin/env bash git pull git submodule foreach 'git checkout master && git pull origin master' npm install grunt docpad generate find ./out -type f ( -name '*.html' -o -name '*.css' -o -name '*.js' ) -exec sh -c "gzip -7 -f < {} > {}.gz" ;
Настройка nginx
В качестве веб-сервера я использую nginx, который прекрасно оптимизирован для отдачи статики. В настройках сайта нам нужно сделать следующее:
- Прописать рерайты для статических файлов: отсекать дату модификации в начале пути и посылать правильные кэширующие заголовки.
- Отдавать статику в gzip для снижения объёма передаваемых файлов.
Если посмотреть на скрипт
deploy.sh
, то вы увидите, что в последнем шаге создаются gzip-версии всех HTML, CSS и JS файлов. У nginx есть специальный модуль HttpGzipStaticModule, который может отдавать заранее созданные gzip-версии файлов вместо автоматической генерации для каждого запроса. Этот трюк позволит нам сэкономить процессорные ресурсы. Для того, чтобы воспользоваться этим модулем, его нужно добавить в nginx при компиляции:./configure --with-http_gzip_static_module
Мой конфиг nginx выглядит так:
server { server_name your-server.com; root /path/to/web-site/out; index index.html index.htm; # отсекаем дату модификации со статических ресурсов location ~* ^/d+/(css|js)/ { rewrite ^/(d+)/(.*)$ /$2; } # кэшируем всю статику location ~* .(ico|css|js|gif|jpe?g|png)$ { expires max; access_log off; add_header Pragma public; add_header Cache-Control "public"; } # включаем поддержку статических gzip-версий файлов gzip_static on; }
5 комментариев
1.5 года тишины, с возвращением 🙂
Можно узнать, какую нагрузку могут выдержать сайты на ДокПаде?
ihor, я думаю вопрос некорректен, потому что сгенерированный сайт — всего лишь набор статики, так что все зависит от того чем вы будете ее раздавать.
Это все напоминает StaceyApp — такая php поделка, позволяющая строить простенькие сайты с контентом в файлах в формате markdown. Но вся простота и элегантность заканчивается когда нужен хоть какой-то интерактив кроме навигации между страницами.
Даже на вступительном видео видно как возрастает сложность с добавлением простых элементов типа меню. Большой зоопарк форматов, плагинов. Все надо держать в голове и постараться не расплескать.
Но подход интересный, пошел досматрить.
А вот кстати да. Почему сейчас так популярные эти генераторы статических сайтов? Но ведь в большенстве случаев нужна не просто статика, а ещё и некоторая интерактивность.
Если уж делать генератор то в виде, некой програмки, в которой не нужно будет самому работать с файлами и консолью. Запустил, написал шаблоны, добавляй страницы и всталяй/пиши в них данные. А база пусть сама генерится в файлы, потом жмешь кнопку генерации сайта и он в виде статических файлов в папочке лежит или заливается по фтп прям на хостинг.