С каждым годом происходит развитие технологий, используемых разработчиками 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.

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


  1. Fen1kz
    28.04.2018 13:17

    Классная штука. Помню писал свой блог на нем, так начал с него и постепенно, случайно (!) заменил галпом. Сейчас проверил — воистину, весь блог написан на галпе, а металсмит до сих пор в зависимостях.


    1. justboris
      28.04.2018 18:08
      +1

      Из этого следует резонный вопрос: а зачем нужен metalsmith, если все равно он заменился на gulp? Может проще с gulp и начинать?


      1. Fen1kz
        28.04.2018 22:16

        Скорее наоборот — проще начать с metalsmith, особенно если с галпом не оч.