express-dom
Version:
Prepare, render web pages - express middleware
193 lines (135 loc) • 5.77 kB
Markdown
Express middleware for (pre)rendering web pages using real browsers.
## Synopsis
```js
const express = require('express');
const app = express();
const dom = require('express-dom');
app.get('*.html', dom(), express.static('public/'));
```
To do rendering outside express middleware:
```js
// obj can be a url string, or an IncomingMessage,
// or an object with { url, status?, headers?, body? } properties
// status defaults to 200, headers to { Content-Type: 'text/html' }
const res = await dom()(obj);
const { statusCode, headers, body } = res;
```
A page is requested by a browser for three purposes:
- offline: hidden offline web page changed by outside scripts.
This phase can't fetch resources and doesn't run page own scripts.
- online: hidden online web page built by its own scripts. Typical prerendering,
can be done on the server, or delegated to the user browser.
- visible: fully rendered page, usually happens on the user browser,
or on server for pdf/png rendering. See also express-dom-pdf.
Each phase has its own set of plugins, named after the `plugins` settings map.
Plugins can change page settings before the page is loaded,
and can run scripts when the page is 'idle'.
A phase is skipped if it has no registered plugins.
The 'idle' event is emitted on the `page` instance after DOMContentLoaded,
and after requests have settled and custom or default tracker has resolved.
The listeners of the 'idle' event are asynchronous and run serially.
dom.defaults holds defaults settings, see source code.
Mind that policies of the requesting phase are obtained from settings of the responding phase: route handler cannot change policies of current phase.
CSP names that end with `-src` can be written without that suffix.
The tracker is experimental, it is best to use a custom tracker.
Set `[phase].track` setting to a custom async function that resolves when the page is ready.
If phase setting `track` is true, the default tracker waits for async operations:
- loading of script/link nodes
- DOMContentLoaded listeners
- fetch, xhr calls
- timeouts (capped by page timeout)
- animation frame requests
- microtasks
When `track` is false, the idle event just wait for first batch of files to be loaded.
Change `dom.defaults` for all instances and phases, otherwise,
pass an object to `dom(opts)` that will be merged with the defaults.
It is also possible to pass a function to `dom(mycustomconfig)` that
will receive the `(opts, { plugins, routers })` as argument.
Configuration depending on the route and the phase can be set using a router function accepting (phase, req, res) as argument.
```js
dom().route((phase, req, res) => {
// change phase.settings.plugins depending on req and phase.online/offline/visible
}) // returns the express middleware
```
phase has the following properties:
- visible, online, offline: booleans, purpose of the requesting phase
- settings: current phase settings
- policies: requesting phase policies
- location: parse url of the current phase
```js
app.get('*.html', dom().route((phase, req, res) => {
if (phase.visible && req.query.url) {
// overwrite default location
location.href = req.query.url;
} else if (phase.online) {
res.type('html');
res.send('<html><script src="asset.js"></script></html>');
}
}));
```
- `dom().route(dom.defaults.routers.png)` to setup png rendering
- see also [express-dom-pdf plugin](https://github.com/kapouer/express-dom-pdf)
Plugins are asynchronous functions, executed in order.
```js
dom.defaults.plugins.fragment = async (page, settings, req, res) => {
settings.timeout = 30000;
page.on('idle', async () => {
const html = await page.evaluate(sel => {
return document.querySelector(sel)?.outerHTML;
}, req.query.fragment);
if (html) {
res.type('html');
res.send(html);
} else {
// html plugin will send page content
}
});
};
dom.defaults.online.plugins.delete('html').add('fragment').add('html');
app.get('*.html', dom(), express.static(app.get('views')));
```
`page` is a puppeteer page instance, with additional
`page.location`, a URL instance that can be modified
synchronously.
This is a limited list of plugins, some are used by default:
- console
Report browser console to node console.
Depends on settings.log value.
- hidden
Force `document.visibilityState == "hidden"`.
Adds user stylesheet to keep rendering to minimum;
Honors `settings.hidden` boolean, if set by a previous plugin.
- cookies
If online `settings.online.cookies` is true, copy all cookies,
else only copy cookies with names in this Set.
Defaults to an empty Set.
- equivs
Parse `meta[http-equiv]` tags and set response headers accordingly.
Supports http-equiv="Status".
Removes the meta nodes, except when the names are listed in the
`settings.equivs` array.
- languages
Pass current request `Accept-Language` to page route headers.
Sets `Content-Language` to the lang attribute found on documentElement.
- preloads
Parse `link[rel=preload]` tags and set 'Link' response header.
- referrer, referer
Sets headers.referer to express req.get('referrer')
- redirect
catch navigation requests and instead sends a 302 redirection
express-dom currently uses `Sec-Purpose` request header, and set `Vary: Sec-Purpose` response headers, so all proxies should be okay with that.
- `DEBUG=express-dom`
express-dom installs puppeteer-core.
It is best to install chromium and firefox (on linux, using the package manager).
## License
MIT License, see LICENSE file.