pdf-node
Version:
A modern, feature-rich PDF generation library for Node.js with TypeScript support. Convert HTML to PDF with Handlebars templates, buffers, and streams.
166 lines (165 loc) • 4.35 kB
JavaScript
import * as pdf from 'html-pdf';
import Handlebars from 'handlebars';
import * as ejs from 'ejs';
import handleError from 'cli-error-handler';
const templateCache = {};
export {Handlebars, ejs};
/**
* Generates a PDF from an HTML template and data
* @param options PDF generation options
* @returns Promise that resolves with the generated PDF (as buffer or file info)
* @throws {Error} If required options are missing or PDF generation fails
*/
export function generatePDF(options) {
return new Promise(async (resolve, reject) => {
if (!options || !options.html || !options.data) {
reject(new Error('Some, or all, options are missing.'));
return;
}
if (options.type !== undefined && options.type !== 'pdf') {
reject(new Error('Only PDF file type is supported'));
return;
}
const pdfOptions = options.pdfOptions || {};
const useCache = options.cacheTemplate !== false; // Default to true
const engine = options.engine || 'handlebars';
// Get compiled template
let html;
try {
html = await compileTemplate(
options.html,
options.data,
engine,
useCache
);
} catch (error) {
handleError('Error compiling template', error);
reject(error);
return;
}
// Check if buffer output is requested
if (options.buffer === true) {
// Output will be PDF buffer (useful for APIs/web services)
pdf.create(html, pdfOptions).toBuffer(function (err, buffer) {
if (err) {
handleError('error in creating buffer', err);
reject(err);
return;
}
console.log(
'PDF buffer generated, size:',
buffer.length,
'bytes'
);
resolve({
buffer: buffer,
size: buffer.length,
type: 'application/pdf'
});
});
} else {
// Output will be PDF file (default behavior)
if (!options.path) {
reject(
new Error(
'Path is required when buffer option is not set to true.'
)
);
return;
}
const filepath = options.path;
pdf.create(html, pdfOptions).toFile(filepath, function (err, res) {
if (err) {
handleError('error in creating file', err);
reject(err);
return;
}
console.log('file generated:', res.filename);
resolve(res);
});
}
});
}
/**
* Adds a page break to the PDF
* @returns HTML string with a page break
*/
export function addNewPage() {
return '<div style="page-break-after: always;"></div>';
}
/**
* Compiles a template with the specified engine
* @param template The template string
* @param data Data to inject into the template
* @param engine Template engine to use ('handlebars', 'ejs', or 'html')
* @param useCache Whether to cache the compiled template
* @returns Compiled HTML string
*/
async function compileTemplate(
template,
data,
engine = 'handlebars',
useCache = true
) {
const cacheKey = useCache ? `${engine}:${template}` : null;
// Return cached template if available
if (cacheKey && templateCache[cacheKey]) {
const cached = templateCache[cacheKey];
if (typeof cached === 'function') {
return cached(data);
}
return cached;
}
// Compile and cache the template
let compiled;
switch (engine) {
case 'ejs':
compiled = async templateData => {
try {
// Use the promise-based renderFile with a fake filename
return await ejs.render(template, templateData, {
async: true
});
} catch (err) {
throw err instanceof Error ? err : new Error(String(err));
}
};
break;
case 'html':
// Simple variable replacement for HTML mode
compiled = template.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
return data[key.trim()] || '';
});
break;
case 'handlebars':
default:
const handlebarsTemplate = Handlebars.compile(template);
compiled = templateData =>
Promise.resolve(handlebarsTemplate(templateData));
break;
}
// Cache the compiled template if needed
if (useCache && cacheKey) {
templateCache[cacheKey] = compiled;
}
return typeof compiled === 'function' ? compiled(data) : compiled;
}
/**
* Clears the template cache
*/
export function clearTemplateCache() {
Object.keys(templateCache).forEach(key => {
delete templateCache[key];
});
}
const pdfNode = {
generatePDF,
addNewPage,
clearTemplateCache,
// Export template engines for advanced usage
engines: {
handlebars: Handlebars,
ejs
}
};
export default pdfNode;