Начинаем использовать Handlebars для создания статических сайтов
Предупреждение: Матёрый фронтендщик скорее всего не увидит для себя здесь ничего нового. Код в статье представляется без каких-либо гарантий и не претендует на идеологическую верность.
На днях мой друг верстальщик, работая над очередным статическим сайтом, рассказал, что всё собирается начать пользоваться шаблонизатором. Посматривал он в сторону Handlebars, но нигде не видел простой, готовой тулзы. Которая позволяла бы собирать шаблоны в статический html, могла бы работать через FileWatcher Webstorm'а и не требовала бы развёртывания окружения с сотнями пакетов, гульпом и прочими вебпаками.
Другу надо помогать, поэтому я написал скрипт, который бы отвечал его запросам. Заодно решил выложить результат на Хабр с мини-инструкцией для тех, кто хотел бы начать пользоваться шаблонизатором для вёрстки своих одностраничников, но не знает что почём.
Все инструкции по пользованию шаблонизатором можно найти на официальном сайте Handlebars, а также можно прочесть старый обзор на Хабре, поэтому приступим сразу к сути.
- Первым делом устанавливаем NodeJS, если ещё не установлена. Я использовал самую свежую версию (v14) и тестировал скрипт только на ней, впрочем и на v12 должно завестись.
- Далее устанавливаем глобально пакет handlebars
npm install -g handlebars
- Создаём папку проекта. В ней создаём (index или_другое_название).hbs, который будет служить корневым шаблоном и будет преобразован в html.
- Копируем в папку проекта наш скрипт и правим раздел config:
- entryPoints — корневые шаблоны, указываем здесь наш 'index.hbs'
- partials — по ходу работы мы можем создать шаблоны, которые сами не компилируются в html, но могут быть использованы внутри корневых шаблонов, например какие-нибудь повторно используемые блоки
- data — здесь указываются значения, которые Hundlebars будет подставлять в шаблоны (смотри документацию)
- helpers — для подключения своих хелперов
- setup — для тонкого конфигурирования handlebars, чтобы не лезть в глубь скрипта
- Для преобразования шаблонов в статичный html открываем терминал в папке проекта и выполняем
node handlebars.compile.js
, ну или настраиваем вотчер в вашей_версии_Idea.
Корневой шаблон
Partial-шаблон
Раздел конфигурации в handlebars.compile.js
Пример настройки файлвотчера (увы, для каждого проекта отдельно)
Что получилось
И как это выглядит
Ниже под спойлером код самого скрипта. Не стал заливать его на гит: по-хорошему можно его довести до ума и сделать npm-пакет, но пускай этим занимается кто-нибудь другой, более сведущий в javascript-разработке
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);