Skip to content

Instantly share code, notes, and snippets.

@ba55ie
Last active April 10, 2020 13:08
Show Gist options
  • Save ba55ie/21045ca9f29f08fa4092798c0f31e3c1 to your computer and use it in GitHub Desktop.
Save ba55ie/21045ca9f29f08fa4092798c0f31e3c1 to your computer and use it in GitHub Desktop.
Basic Rollup plugin for nested handlebars. Quick 'n dirty, no configuration options, you have to add them yourself
import path from 'path';
import fs from 'fs';
import Handlebars from 'handlebars';
class ImportScanner extends Handlebars.Visitor {
constructor() {
super();
this.partials = new Set();
}
PartialStatement(partial) {
if (partial.name.type === 'SubExpression') {
throw new Error('Dynamic partial resolution is not supported');
}
this.partials.add(partial.name.original);
return super.PartialStatement(partial);
}
}
export default function handlebarsNested() {
// Template cache
const cache = new Map();
function getCompiledPartial(partial, dir) {
const source = fs.readFileSync(path.join(__dirname, dir, `${partial}.hbs`), 'utf-8');
const compiled = Handlebars.precompile(source);
return [partial, compiled];
}
// Recursive function for getting nested partials
function getPartials(source, dir, list = new Set()) {
const tree = Handlebars.parse(source);
const scanner = new ImportScanner();
scanner.accept(tree);
if (scanner.partials.size) {
// We have found partials!
for (const partial of scanner.partials) {
// Add it to the list
list.add(partial);
// Get the source
const src = fs.readFileSync(path.join(__dirname, dir, `${partial}.hbs`), 'utf-8');
// And check if we have more nested partial in this one
getPartials(src, dir, list);
}
}
return list;
}
// Convert to ESM and register partial
function toEsm(source, id) {
const dir = path.dirname(id);
const name = path.basename(id, '.hbs');
// Get nested partials
const partials = getPartials(source, dir);
const children = [];
for (const partial of partials) {
children.push(getCompiledPartial(partial, dir));
}
// Create a tree
const tree = Handlebars.parse(source);
const { code, map } = Handlebars.precompile(tree, { srcName: id });
// Import this (partial) template and nested templates
const body = `
import Handlebars from 'handlebars/runtime.js';
const template = Handlebars.template(${code});
Handlebars.registerPartial('${name}', template);
${children.map(([p, s]) => `Handlebars.registerPartial('${p}', Handlebars.template(${s}));`).join('\n')}
export default (data, options) => template(data, options);
`;
return {
code: body,
map,
};
}
return {
name: 'handlebars-nested',
transform(source, id) {
if (id.endsWith('.hbs')) {
// Get from cache when avalaible
if (cache.has(id)) {
return cache.get(id);
}
const output = toEsm(source, id);
cache.set(id, output);
return output;
}
return null;
},
};
}
@ba55ie
Copy link
Author

ba55ie commented Apr 10, 2020

Source maps only work for the first imported template, not for the nested partials.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment