@pattern-lab/engine-handlebars
Version:
The Handlebars engine for Pattern Lab / Node
163 lines (139 loc) • 4.9 kB
JavaScript
;
/*
* handlebars pattern engine for patternlab-node
*
* Geoffrey Pursell, Brian Muenzenmeyer, and the web community.
* Licensed under the MIT license.
*
* Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice.
*
*/
/*
* ENGINE SUPPORT LEVEL:
*
* Full. Partial calls and lineage hunting are supported. Handlebars does not
* support the mustache-specific syntax extensions, style modifiers and pattern
* parameters, because their use cases are addressed by the core Handlebars
* feature set. It also does not support verbose partial syntax, because it
* seems like it can't tolerate slashes in partial names. But honestly, did you
* really want to use the verbose syntax anyway? I don't.
*
*/
const fs = require('fs-extra');
const path = require('path');
const Handlebars = require('handlebars');
const glob = require('glob');
// regexes, stored here so they're only compiled once
const findPartialsRE = /{{#?>\s*([\w-\/.]+)(?:.|\s+)*?}}/g;
const findListItemsRE =
/({{#( )?)(list(I|i)tems.)(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)( )?}}/g;
const findAtPartialBlockRE = /{{#?>\s*@partial-block\s*}}/g;
function escapeAtPartialBlock(partialString) {
const partial = partialString.replace(
findAtPartialBlockRE,
'{{> @partial-block }}'
);
return partial;
}
function loadHelpers(helpers) {
helpers.forEach((globPattern) => {
glob.sync(globPattern).forEach((filePath) => {
require(path.join(process.cwd(), filePath))(Handlebars);
});
});
}
const engine_handlebars = {
engine: Handlebars,
engineName: 'handlebars',
engineFileExtension: ['.hbs', '.handlebars'],
// partial expansion is only necessary for Mustache templates that have
// style modifiers or pattern parameters (I think)
expandPartials: false,
// render it
renderPattern: function renderPattern(pattern, data, partials) {
if (partials) {
Handlebars.registerPartial(partials);
}
const compiled = Handlebars.compile(escapeAtPartialBlock(pattern.template));
return Promise.resolve(compiled(data));
},
registerPartial: function (pattern) {
// register exact partial name
Handlebars.registerPartial(pattern.patternPartial, pattern.template);
Handlebars.registerPartial(pattern.verbosePartial, pattern.template);
},
// find and return any {{> template-name }} within pattern
findPartials: function findPartials(pattern) {
const matches = pattern.template.match(findPartialsRE);
return matches;
},
// returns any patterns that match {{> value(foo:"bar") }} or {{>
// value:mod(foo:"bar") }} within the pattern
findPartialsWithPatternParameters: function () {
// TODO: make the call to this from oPattern objects conditional on their
// being implemented here.
return [];
},
findListItems: function (pattern) {
const matches = pattern.template.match(findListItemsRE);
return matches;
},
// given a pattern, and a partial string, tease out the "pattern key" and
// return it.
findPartial: function (partialString) {
const partial = partialString.replace(findPartialsRE, '$1');
return partial;
},
spawnFile: function (config, fileName) {
const paths = config.paths;
const metaFilePath = path.resolve(paths.source.meta, fileName);
try {
fs.statSync(metaFilePath);
} catch (err) {
//not a file, so spawn it from the included file
const metaFileContent = fs.readFileSync(
path.resolve(__dirname, '..', '_meta/', fileName),
'utf8'
);
fs.outputFileSync(metaFilePath, metaFileContent);
}
},
/**
* Checks to see if the _meta directory has engine-specific head and foot files,
* spawning them if not found.
*
* @param {object} config - the global config object from core, since we won't
* assume it's already present
*/
spawnMeta: function (config) {
this.spawnFile(config, '_head.hbs');
this.spawnFile(config, '_foot.hbs');
},
/**
* Accept a Pattern Lab config object from the core and use the settings to
* load helpers.
*
* @param {object} config - the global config object from core
*/
usePatternLabConfig: function (config) {
let helpers;
try {
// Look for helpers in the config
helpers = config.engines.handlebars.extend;
if (typeof helpers === 'string') {
helpers = [helpers];
}
} catch (error) {
// Look for helpers in default location
const configPath = 'patternlab-handlebars-config.js';
if (fs.existsSync(path.join(process.cwd(), configPath))) {
helpers = [configPath];
}
}
// Load helpers if they were found
if (helpers) {
loadHelpers(helpers);
}
},
};
module.exports = engine_handlebars;