Генератор статических сайтов metalsmith
С каждым годом происходит развитие технологий, используемых разработчиками front-end. И здесь речь идет не только о конкретных фреймворках и архитектурных паттернах для реализации клиентской логики в браузерах, но и о различных альтернативных инструментах, таких как, например, генераторы статических сайтов. Их основной целью является упрощение процесса создания статических сайтов. Безусловно они не являются универсальным инструментом, но в некоторых случаях они подходят как нельзя лучше:
- Прототип web-интерфейса
- Блог с редко обновляемым контентом
- Отдельная статическая часть другого web-приложения
- Сайт-визитка или landing-page
- Онлайн-документация
Более подробный сравнительный анализ различных генераторов можно найти в этой статье, а также посмотреть рейтинг, основанный на активности соответствующих проектов на github. Мы же рассмотрим хоть и не самый популярный, но, тем не менее, элегантный и простой представитель данного класса инструментов — metalsmith.
Metalsmith
Данный генератор целиком и полностью написан на javascript, исходный код можно найти в официальном репозитории. Концепцию работы можно разделить на несколько этапов: считывание файлов-шаблонов из папки с исходниками сайта, последовательную обработку их плагинами и запись результатов в целевую директорию.
На данный момент существует порядка 670 различных плагинов. Если вы все-таки чего-то не нашли, написать свой плагин совершенно не составляет труда благодаря простому и понятному plugin-API. Например, плагин выводящий имена файлов на каком-то конкретном шаге может выглядеть следующим образом:
function plugin() {
return (files, metalsmith, done) => {
Object.keys(files).forEach(name => console.log(name));
done();
};
}
Хотя, если вам необходимо (например для отладки), вы можете воспользоваться моим плагином — metalsmith-inspect-files, который визуализирует дерево файлов и каталогов в момент своего использования в цепочке плагинов.
Делаем простой блог
Для того, чтобы продемонстрировать возможности инструмента мы сделаем с его помощью простой статический блог, весь код доступен в репозитории. Конфигурация для сборки находится либо в metalsmith.json файле, либо непосредственно в скрипте, причем последнее, на мой взгляд наиболее предпочтительно, потому что позволяет более гибко конфигурировать процесс сборки:
const Metalsmith = require('metalsmith');
const timer = require('./plugins/timer');
const jade = require('metalsmith-jade');
const layouts = require('metalsmith-layouts');
const permalinks = require('metalsmith-permalinks');
const collections = require('metalsmith-collections');
const less = require('metalsmith-less');
const ignore = require('metalsmith-ignore');
const cleanCss = require('metalsmith-clean-css');
const metalsmithInspectFiles = require('metalsmith-inspect-files');
const partial = require('metalsmith-partial');
Metalsmith(__dirname)
.source('./source')
.metadata({ // глобальные переменные доступные в каждом шаблоне
title: 'Example blog',
layout: 'index.jade',
menuLinks: [
{title:'Home', url: '/'},
{title:'Articles', url: '/articles/'},
{title:'About', url: '/about/'}
]
})
.destination('./build')
.clean(true)
.use(collections({ // коллекции страниц определяемые шаблоном
articles: {
pattern: [
'articles/**',
'!articles/index.jade'
],
sortBy: 'title'
},
}))
.use(partial({ // шаблоны вставки внутри других шаблонов
directory: './partials',
engine: 'jade'
}))
.use(jade({ // шаблонизатор
useMetadata: true
}))
.use(permalinks({ // красивые url /about.html => /about/index.html
relative: false
}))
.use(layouts({ // шаблон-каркас одинаковый для всех страниц
engine: 'jade',
default: 'index.jade',
pattern: '**/*.html'
}))
.use(less()) // компиляция less в css
.use(cleanCss()) // сжатие css
.use(ignore([ // фильтрация файлов
'**/*.less'
]))
.use(metalsmithInspectFiles())
.build((err, files) => {
if (err) { throw err; }
timer('Build time: ')();
});
Наш блог состоит из нескольких отдельных обособленных страниц и одной коллекции однотипных страниц. Структура исходных директорий (без внешних директорий layouts и partials):
|-articles
| |-article-one.jade
| |-article-three.jade
| |-article-two.jade
| -index.jade
|-assets
| |-images
| | -favicon.png
| |-js
| | -turbolinks.min.js
| -stylesheets
| -main.less
|-about.jade
-index.jade
А так выглядит результат работы (содержимое папочки build):
|-about
| -index.html
|-articles
| |-article-one
| | -index.html
| |-article-three
| | -index.html
| |-article-two
| | -index.html
| -index.html
|-assets
| |-images
| | -favicon.png
| |-js
| | -turbolinks.min.js
| -stylesheets
| -main.css
-index.html
Сразу заметно работу плагина metalsmith-permalinks, который преобразует файлы *.html, чье имя отлично от index.html, в соответствующие директории с index.html, для того, чтобы url этих страниц выглядел более приятно. Основными особенностями любого генератора статических сайтов являются шаблоны (layouts и partials), как средство для соблюдения принципа DRY (Don’t repeat yourself). В качестве языка шаблонизации использован jade (или pug), популярный среди представителей javascript-коммьюнити. Так в нашем случае выглядит основной layout, представляющий каркас страницы:
doctype html
html
head
meta(charset='utf-8')
meta(name='description' content='Simple metalsmith blog')
meta(name='viewport' content='width=device-width, initial-scale=1.0')
title=title
link(href='/assets/stylesheets/main.css', rel='stylesheet')
link(rel="icon" href="/assets/images/favicon.png")
body
.container
nav!=partial('menu.jade', {menuLinks, currentPath: path})
section.page-content!=contents
footer!=partial('footer.jade')
script(src='/assets/js/turbolinks.min.js')
Он в свою очередь использует шаблоны-вставки (partials), функциональность которых реализуется плагином metalsmith-partial (обратите внимание на явную передачу переменных для отрисовки шаблона). Включаемый шаблон меню выглядит следующим образом:
ul.menu
each page in menuLinks
- isActive = ('/'+currentPath+'/').startsWith(page.url) && (page.url != '/' || page.url === currentPath + '/')
li(class=(isActive ? 'active' : ''))
a(href=page.url)=page.title
Для итерации по страницам коллекции (articles) используется плагин metalsmith-collections, сами страницы для генерации списка доступны в переменной-массиве с именем коллекции.
Turbolinks
Библиотека turbolinks позволяет 'оживить' содержимое, отдаваемое сервером со статикой, добавив SPA-поведение при переходе по ссылкам внутри нашего сайта, загружая посредством AJAX содержимое каждой новой страницы и заменяя им текущее. В случае, если по какой-то причине запрос оказался долгим (более 500 мс), будет показан progress-bar, который сообщит пользователю о том, что переход на другую страницу происходит (стандартные средства, с помощью которых браузер показывает это при обыкновенном переходе по ссылке, не будут задействованы по причине отсутствия перезагрузки страницы), хотя и медленно. Turbolinks в данном примере подключается в виде минифицированного файла-дистрибутива, так сказать «for the sake of simplicity». В случае, если javascript-часть сайта не такая простая, следует использовать что-то в духе metalsmith-webpack-2 или gulp-metalsmith.
Полезные ссылки и выводы
Несмотря на то, что metalsmith не самый популярный из генераторов, на мой взгляд, он заслуживает внимания, потому как прост и гибок (практически любой сторонний инструмент может быть внедрен в plugin-pipeline с помощью нескольких строк кода), а значит, может быть использован для создания различных сайтов, хотя, безусловно, он не является 'серебряной пулей' и имеет свои недостатки. Например у большинства плагинов, отсутствует детальная документация поэтому для того, чтобы понять детали их работы, частенько приходится изучать исходники, благо они почти всегда являются простыми фасадом, предназначенным для адаптации других популярных инструментов с хорошей документацией под metalsmith plugin-API.