Pull to refresh

Начинаем использовать Handlebars для создания статических сайтов

Предупреждение: Матёрый фронтендщик скорее всего не увидит для себя здесь ничего нового. Код в статье представляется без каких-либо гарантий и не претендует на идеологическую верность.


На днях мой друг верстальщик, работая над очередным статическим сайтом, рассказал, что всё собирается начать пользоваться шаблонизатором. Посматривал он в сторону Handlebars, но нигде не видел простой, готовой тулзы. Которая позволяла бы собирать шаблоны в статический html, могла бы работать через FileWatcher Webstorm'а и не требовала бы развёртывания окружения с сотнями пакетов, гульпом и прочими вебпаками.


Другу надо помогать, поэтому я написал скрипт, который бы отвечал его запросам. Заодно решил выложить результат на Хабр с мини-инструкцией для тех, кто хотел бы начать пользоваться шаблонизатором для вёрстки своих одностраничников, но не знает что почём.


Все инструкции по пользованию шаблонизатором можно найти на официальном сайте Handlebars, а также можно прочесть старый обзор на Хабре, поэтому приступим сразу к сути.


  1. Первым делом устанавливаем NodeJS, если ещё не установлена. Я использовал самую свежую версию (v14) и тестировал скрипт только на ней, впрочем и на v12 должно завестись.
  2. Далее устанавливаем глобально пакет handlebars npm install -g handlebars
  3. Создаём папку проекта. В ней создаём (index или_другое_название).hbs, который будет служить корневым шаблоном и будет преобразован в html.
  4. Копируем в папку проекта наш скрипт и правим раздел config:
    • entryPoints — корневые шаблоны, указываем здесь наш 'index.hbs'
    • partials — по ходу работы мы можем создать шаблоны, которые сами не компилируются в html, но могут быть использованы внутри корневых шаблонов, например какие-нибудь повторно используемые блоки
    • data — здесь указываются значения, которые Hundlebars будет подставлять в шаблоны (смотри документацию)
    • helpers — для подключения своих хелперов
    • setup — для тонкого конфигурирования handlebars, чтобы не лезть в глубь скрипта
  5. Для преобразования шаблонов в статичный html открываем терминал в папке проекта и выполняем node handlebars.compile.js, ну или настраиваем вотчер в вашей_версии_Idea.

Пример в картинках

Корневой шаблон



Partial-шаблон



Раздел конфигурации в handlebars.compile.js



Пример настройки файлвотчера (увы, для каждого проекта отдельно)



Что получилось



И как это выглядит



Ниже под спойлером код самого скрипта. Не стал заливать его на гит: по-хорошему можно его довести до ума и сделать npm-пакет, но пускай этим занимается кто-нибудь другой, более сведущий в javascript-разработке


handlebars.compile.js
const config = {
  entryPoints: [
  ],
  partials: [
  ],
  data: {
  },
  helpers: [
  ],
  setup: (handlebars) =>
  {
  }
};

(async function doWork(configuration) {
  const {promises: _fs} = require("fs");
  const _path = require( 'path');

  const getBaseFileName = (filePath) => _path.basename(filePath, _path.extname(filePath));

  async function requireGlobal(id)
  {
      const getNpmRoot = new Promise((resolve, reject) => {
          require("child_process").exec("npm root -g", (err, stdout, stderr) => {
              if(err) { reject(err.message); }
              else if(stderr) { reject(stderr); }
              else { resolve(stdout.trim()); }
          });
      });

      try
      {
        const npmRoot = await getNpmRoot;
        const packagePath = _path.resolve(`${npmRoot}/${id}`);
        return require(packagePath);
      }
      catch(e)
      {
        throw `Can not find global installed ${id}. Exception: ${e.message}`;
      }

  }

  function addHelper(helper, hbs)
  {
    if (helper && typeof helper.register === 'function')
    {
      helper.register(hbs);
    }
    else
    {
      console.error(`WARNING: helper have not a 'register' function, cannot add`);
    }
  }

  function resolveFile(filePath)
  {
    const absolutePath = _path.resolve(__dirname, filePath);
    console.info(`Load file from ${absolutePath}`);
    return _fs.readFile(absolutePath, "utf8");
  }

  async function addPartial(input, hbs)
  {
    const partial = await resolveFile(input);
    hbs.registerPartial(getBaseFileName(input), partial);
  }

  async function renderTemplate(input, context, hbs)
  {
    const template = await resolveFile(input);
    const hbsRender = hbs.compile(template);
    const htmlContents = hbsRender(context);
    const entryPointDir = _path.dirname(input);
    const output = _path.resolve(entryPointDir, `${getBaseFileName(input)}.html`);
    await _fs.writeFile(output, htmlContents, 'utf8');
    console.info(`Wrote ${output}`);
  }

  async function compile({
    entryPoints = ['index.hbs'],
    partials = [],
    data = {},
    helpers = [],
    setup = (handlebars) => {}
  })
  {

    const Handlebars = await requireGlobal('handlebars');

    setup(Handlebars);

    helpers.forEach(helper => addHelper(helper, Handlebars));

    await Promise.all(partials.map( fileName => addPartial(fileName, Handlebars)));
    await Promise.all(entryPoints.map( fileName => renderTemplate(fileName, data, Handlebars)));
  }

  try
  {
    await compile(configuration);
    console.info("Done!")
  }
  catch (e)
  {
    console.error(e);
  }
})(config);
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.