Todo relatório migrado precisa de um engine para transformar o HTML Vash em PDF.
Vantagem central: a API de entrada é a mesma em todos — trocar de engine é trocar o require, sem reescrever o controller:
await reportEngine.renderReportPdf(model, res, {
reportDir: __dirname,
pdf: { /* margens, página, etc. */ }
});
Escolha o engine conforme o ambiente da aplicação hospedeira. As dependências não vêm embutidas neste pacote: instale-as na sua app.
| Engine | Módulo | Quando usar | Status |
|---|---|---|---|
| Devbox | report-engine-devbox |
App com adapter res.pdf |
Pronto |
| Puppeteer | report-engine-puppeteer |
Node genérico / Chromium | Pronto |
| wkhtmltopdf | report-engine-wkhtmltopdf |
Binário wkhtmltopdf no PATH | Pronto |
| WeasyPrint | report-engine-weasyprint |
Python 3 + WeasyPrint | Pronto |
| Dependência | Tipo | Instalação | Link |
|---|---|---|---|
vash |
pacote npm | npm install vash |
https://www.npmjs.com/package/vash |
Templates report.html / report_header.html / report_footer.html são compilados com Vash dentro desses engines.
report-engine-devbox)| Dependência | Tipo | Como obter |
|---|---|---|
res.pdf |
API do framework hospedeiro | Já existe em apps Devbox / stacks com esse adapter |
| templates Vash | no framework | em geral o próprio res.pdf renderiza Vash |
Não exige puppeteer nem o binário wkhtmltopdf.
# apenas o runtime CrystalNode
npm install @crystal-node/runtime
const reportEngine = require('@crystal-node/runtime/report-engine-devbox');
report-engine-puppeteer)| Dependência | Tipo | Instalação | Link |
|---|---|---|---|
puppeteer |
pacote npm (baixa Chromium) | npm install puppeteer |
https://www.npmjs.com/package/puppeteer |
vash |
pacote npm | npm install vash |
https://www.npmjs.com/package/vash |
# individual
npm install @crystal-node/engine-puppeteer @crystal-node/controller puppeteer vash
# ou meta-pacote
npm install @crystal-node/runtime puppeteer vash
Em CI/Docker, o Puppeteer pode precisar de libs do sistema (fonte, sandbox). Consulte:
https://pptr.dev/troubleshooting
Alternativa mais leve (Chromium externo): puppeteer-core — nesse caso você precisa passar o browser/executablePath via opções do engine (puppeteer / puppeteerLaunch). Link: https://www.npmjs.com/package/puppeteer-core
const reportEngine = require('@crystal-node/runtime/report-engine-puppeteer');
await reportEngine.renderReportPdf(model, res, {
reportDir: __dirname,
pdf: {
marginTop: 28,
marginBottom: 14,
marginLeft: 0,
marginRight: 0,
pageWidth: '215.9mm',
pageHeight: '279.4mm'
},
// opcional: escala se o HTML veio com outro coeficiente de twips
// twipsToPxCoefficient: 1.25,
// debug: true
});
Debug:
REPORT_ENGINE_PUPPETEER_DEBUG=1
Artefatos em <reportDir>/puppeteer-debug/<timestamp>/.
report-engine-wkhtmltopdf)| Dependência | Tipo | Instalação | Link |
|---|---|---|---|
wkhtmltopdf |
binário do sistema (não é pacote npm obrigatório) | instalar no SO / PATH | https://wkhtmltopdf.org/downloads.html |
vash |
pacote npm | npm install vash |
https://www.npmjs.com/package/vash |
npm install @crystal-node/engine-wkhtmltopdf @crystal-node/controller vash
# ou: npm install @crystal-node/runtime vash
# + instalar o binário wkhtmltopdf no sistema
Caminho do binário (opcional):
wkhtmltopdfPath em renderReportPdfWKHTMLTOPDF_PATHconst reportEngine = require('@crystal-node/runtime/report-engine-wkhtmltopdf');
await reportEngine.renderReportPdf(model, res, {
reportDir: __dirname,
pdf: {
marginTop: 28,
marginBottom: 14,
marginLeft: 0,
marginRight: 0,
pageWidth: '215.9mm',
pageHeight: '279.4mm',
dpi: 96
},
// wkhtmltopdfPath: '/usr/local/bin/wkhtmltopdf',
// debug: true
});
Pacotes npm auxiliares (opcionais, se preferir gerenciar via npm):
https://www.npmjs.com/package/wkhtmltopdf — este runtime chama o binário diretamente; o wrapper npm não é obrigatório.
report-engine-weasyprint)| Dependência | Tipo | Link |
|---|---|---|
| WeasyPrint | CLI Python (weasyprint ou py -m weasyprint) |
https://doc.courtbouillon.org/weasyprint/ |
| vash | pacote npm (render dos templates) | https://www.npmjs.com/package/vash |
Status: implementado. Header/footer via position: running() + @page margins; numeração com counter(page) / counter(pages). Binário: options.weasyprintPath → WEASYPRINT_PATH → weasyprint → fallback python -m weasyprint.
const reportEngine = require('@crystal-node/runtime/report-engine-weasyprint');
await reportEngine.renderReportPdf(model, res, {
reportDir: __dirname,
pdf: { marginTop: 26, marginBottom: 30, pageWidth: '210mm', pageHeight: '297mm' },
weasyprintPath: process.env.WEASYPRINT_PATH // opcional
});
A troca é só o require — o restante do controller permanece igual:
// Pacotes individuais (recomendado)
const reportEngine = require('@crystal-node/engine-devbox/report-engine-devbox');
const reportEngine = require('@crystal-node/engine-puppeteer');
const reportEngine = require('@crystal-node/engine-wkhtmltopdf');
const reportEngine = require('@crystal-node/engine-weasyprint');
// Ou via meta-pacote / subpaths (compat 1.0)
const reportEngine = require('@crystal-node/runtime/report-engine-devbox');
const reportEngine = require('@crystal-node/runtime/report-engine-puppeteer');
const reportEngine = require('@crystal-node/runtime/report-engine-wkhtmltopdf');
const reportEngine = require('@crystal-node/runtime/report-engine-weasyprint');
Também disponível pelo entrypoint principal:
const {
reportEngineDevbox,
reportEnginePuppeteer,
reportEngineWkhtmltopdf,
reportEngineWeasyprint
} = require('@crystal-node/runtime');
Se report_header.html / report_footer.html existirem e tiverem conteúdo, os engines os usam automaticamente.
Numeração de página nos templates (marcadores compatíveis):
@{
crystalFunctions = Object.assign({}, crystalFunctions, {
pageNumber: function () { return crystalFunctions.pdfPageChrome('page'); },
totalPageCount: function () { return crystalFunctions.pdfPageChrome('sitepages'); }
});
}
data-cr-pdf-chrome via script + ?page= / ?topage= (não depende do token [page] do binário)pageNumber / totalPages do headerTemplate/footerTemplateweasy-page-number / weasy-pages-number + counter(page|pages)data-cr-suppress-if)Secções de page header/footer cuja fórmula Crystal cita PageNumber / TotalPageCount saem com
data-cr-suppress-if="page >= …" (sem @if Vash). Avaliação:
| Engine | Mecanismo |
|---|---|
| wkhtmltopdf / devbox | Script no HTML + query ?page= / ?topage= |
| Puppeteer / WeasyPrint | pdf-chrome-suppress.js filtra por página; faixas + merge via pdf-lib |
Instale pdf-lib no projeto hospedeiro se for usar Puppeteer ou WeasyPrint com esses marcadores.
report-engine-devboxres.pdf → report-engine-puppeteer (mais fácil de instalar via npm)report-engine-wkhtmltopdfreport-engine-weasyprint (CLI WeasyPrint + peer pdf-lib se houver suppress por página)