-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
124 lines (98 loc) · 3.56 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
const { Cluster } = require('puppeteer-cluster');
const { inlineSource } = require('inline-source');
const debug = require('debug')('imaworldhealth:coral');
const DEFAULTS = {
preferCSSPageSize : true,
format : 'A4',
swallowErrors : true,
};
// launch cluster
let cluster;
/**
* PDF rendering is extremely resource-intensive if we do not reuse browser instances
* On a RPi V4, we have a 17 second startup by launching a new browser each time. By
* reusing the same chromium instance, we shave that to sub-second timing.
*/
const launch = async () => {
debug('#launch(): setting up puppeteer cluster');
const options = {
concurrency : Cluster.CONCURRENCY_CONTEXT, // incognito windows
maxConcurrency : 2,
};
const puppeteerOptions = { headless: 'new' };
if (process.env.CHROME_OPTIONS) {
Object.assign(puppeteerOptions, {
args : process.env.CHROME_OPTIONS.split(' '),
});
debug('#launch() using extra launch arguments:', process.env.CHROME_OPTIONS);
}
if (process.env.PUPPETEER_EXECUTABLE_PATH) {
Object.assign(puppeteerOptions, {
executablePath : process.env.PUPPETEER_EXECUTABLE_PATH,
});
debug('#launch() using exeuctable path:', process.env.PUPPETEER_EXECUTABLE_PATH);
}
// merge in options if they were triggered
if (Object.entries(puppeteerOptions).length > 0) {
options.puppeteerOptions = puppeteerOptions;
}
cluster = await Cluster.launch(options);
debug('#launch(): configuring PDF rendering task');
await cluster.task(async ({ page, data }) => {
if (data.options.filename) {
debug(`#task(): rendering PDF w/ filename: ${data.options.filename}`);
}
const content = data.html.trim();
await page.setContent(content);
debug(`#task(): rendering content length of ${content.length}`);
// FIXME(@jniles) - for some reason, puppeteer seems to be inconsistent on the
// kind of page rendering sizes, but this seems to work for making pages landscaped.
// See: https://github.com/puppeteer/puppeteer/issues/3834#issuecomment-549007667
if (data.options.orientation === 'landscape') {
await page.addStyleTag(
{ content : '@page { size: A4 landscape; }' },
);
}
const pdf = await page.pdf(data.options);
debug('#task(): pdf rendered.');
return pdf;
});
debug('#launch(): rendering task configured');
return cluster;
};
/**
* @function render
*
* @description
* Takes an HTML source file, inlines the assets, spins up a new puppeteer instance, and
* renders them together into a PDF.
*
* @param {String} html - the html template
* @param {Object} options - options to merge into the HTML
*
* @returns {Promise} a PDF of the HTML source
*/
async function render(html, options = {}) {
const opts = { ...options, ...DEFAULTS };
let inlined = html;
if (!options.skipRendering) {
debug('#render(): HTML render skipping is disabled. Using inline-source to render HTML.');
inlined = await inlineSource(html, {
attribute : false, rootpath : '/', compress : false, swallowErrors : opts.swallowErrors,
});
}
if (!cluster) { cluster = await launch(); }
// make sure cluster is setup
const pdf = await cluster.execute({ options : opts, html : inlined });
return pdf;
}
// make sure cluster is terminated correctly on exit
process.on('beforeExit', async () => {
debug('cleaning up subprocesses');
await cluster.idle();
await cluster.close();
debug('successfully closed all subprocesses.');
});
// force-close all subprocesses on exit.
process.on('exit', () => cluster && cluster.close());
module.exports = render;