Home

Awesome

express-dom

Express middleware for (pre)rendering web pages with playwright.

Uses system-installed chromium or google-chrome.

Synopsis

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:

// 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:

Configuration functions can setup a handler instance, valid for all requests on that handler.

Routers can change settings depending on the current request.

Plugins can change page settings before it 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 can be asynchronous and are run serially.

Options

dom holds some global settings:

Middleware settings:

Handler properties:

Phase settings:

Default offline settings:

Default online settings:

Mind that policies of the requesting phase are obtained from settings of the responding phase: route handler cannot change policies of current phase.

tracker

If phase setting track is true, the default tracker waits for async operations:

Otherwise, track can be a custom async function that is evaluated by the default tracker to determine when the page has settled.

When track is false, the idle event just wait for first batch of files to be loaded.

Route settings

Route-dependent configuration can be done by passing to dom():

Phase settings

Configuration depending on the route and the phase can be set using a router function accepting (phase, req, res) as argument.

dom().route((phase, req, res) => {
  // change phase.settings.plugins depending on req and phase.online/offline/visible
})

phase has the following properties:

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>');
  }
}));

Page settings and plugins

Plugins are asynchronous functions, executed in order.

dom.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.online.plugins.delete('html').add('fragment').add('html');
app.get('*.html', dom(), express.static(app.get('views')));

page is a playwright page instance, with additional page.location, a URL instance that can be modified synchronously.

Bundled plugins

This is a limited list of plugins, some are used by default:

Compatibility with caching proxies

express-dom currently uses Sec-Purpose request header, and set Vary: Sec-Purpose response headers, so all proxies should be okay with that.

Logs

Backend

express-dom installs playwright-core and expects a system-installed chrome browser to be available.

License

MIT License, see LICENSE file.