From dc6a3e1c77a74c68fb0926df16fe2769ead2d0e9 Mon Sep 17 00:00:00 2001 From: alexpozzi Date: Mon, 19 Jul 2021 17:23:44 +0200 Subject: [PATCH] Add the ability to configure a post render script --- .dockerignore | 1 + Dockerfile | 1 + docker-compose.test.yaml | 2 ++ fixtures/nginx/html/post-render-script.html | 10 ++++++++ fixtures/scripts/postRender.js | 11 +++++++++ scripts/postRender.js | 23 +++++++++++++++++ src/e2e.test.js | 20 +++++++++++++++ src/worker/renderers/chrome/renderer.js | 26 ++++++++++++++++++++ src/worker/renderers/chrome/renderer.test.js | 2 ++ 9 files changed, 96 insertions(+) create mode 100644 fixtures/nginx/html/post-render-script.html create mode 100644 fixtures/scripts/postRender.js create mode 100644 scripts/postRender.js diff --git a/.dockerignore b/.dockerignore index f36a349e..44c764ce 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,6 +2,7 @@ * !src/ +!scripts/ !.babelrc !package.json !yarn.lock diff --git a/Dockerfile b/Dockerfile index 03bf8e49..c94d8f1a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,7 @@ RUN chmod +x /usr/local/bin/dumb-init WORKDIR /app COPY --chown=1000:1000 src/ src/ +COPY --chown=1000:1000 scripts/ scripts/ COPY --chown=1000:1000 package.json package.json COPY --chown=1000:1000 yarn.lock yarn.lock COPY --chown=1000:1000 .babelrc .babelrc diff --git a/docker-compose.test.yaml b/docker-compose.test.yaml index 6f8827c3..42c49d33 100644 --- a/docker-compose.test.yaml +++ b/docker-compose.test.yaml @@ -25,6 +25,8 @@ services: - WORKER_ENABLED=1 - QUEUE_REDIS_DSN=redis://redis:6379 - WORKER_RENDERER_REDIRECTIONS=http://external-nginx|http://nginx + volumes: + - ./fixtures/scripts/postRender.js:/app/scripts/postRender.js:ro redis: image: redis:6.2.2-buster diff --git a/fixtures/nginx/html/post-render-script.html b/fixtures/nginx/html/post-render-script.html new file mode 100644 index 00000000..41a6aab0 --- /dev/null +++ b/fixtures/nginx/html/post-render-script.html @@ -0,0 +1,10 @@ + + + + + A static HTML page (post render script) + + +

A static HTML page (post render script)

+ + diff --git a/fixtures/scripts/postRender.js b/fixtures/scripts/postRender.js new file mode 100644 index 00000000..77f23d17 --- /dev/null +++ b/fixtures/scripts/postRender.js @@ -0,0 +1,11 @@ + +postRenderFunction = () => { + if ('/post-render-script.html' === window.location.pathname) { + const paragraphElement = document.createElement('p') + const textNode = document.createTextNode("This element has been created by the post render script."); + + paragraphElement.appendChild(textNode) + + document.body.append(paragraphElement) + } +} diff --git a/scripts/postRender.js b/scripts/postRender.js new file mode 100644 index 00000000..f57d229c --- /dev/null +++ b/scripts/postRender.js @@ -0,0 +1,23 @@ +/** + * Post render script executed by Puppeteer's evaluate method before returning + * the rendered html page. See https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#pageevaluatepagefunction-args) + * for more information. + * + * In order to use a custom post render script you have to mount your own + * script as a Docker volume to `/app/scripts/postRender.js` location. + * Your custom script will just have to assign the function you want to execute + * to the `postRenderFunction` variable. + * + * Example: + * ```javascript + * // Adds the serialized Redux store + * postRenderFunction = function () { + * var preloadedState = yourReduxStore.getState(); + * var script = document.createElement('script'); + * script.type = 'text/javascript'; + * script.text = 'window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/ { `)) }) + it(`asserts that a static page with post render script is rendered`, async () => { + const res = await fetch(`http://manager:8080/render?url=http://nginx/post-render-script.html`) + const content = await res.text() + + expect(res.status).toBe(200) + expect(trimStringForComparison(content)).toBe(trimStringForComparison(` + + + + + A static HTML page (post render script) + + +

A static HTML page (post render script)

+

This element has been created by the post render script.

+ + + `)) + }) + it(`asserts that a dynamic page is rendered`, async () => { const res = await fetch(`http://manager:8080/render?url=http://nginx/dynamic.html`) const content = await res.text() diff --git a/src/worker/renderers/chrome/renderer.js b/src/worker/renderers/chrome/renderer.js index d7ab2094..9db04398 100644 --- a/src/worker/renderers/chrome/renderer.js +++ b/src/worker/renderers/chrome/renderer.js @@ -1,7 +1,31 @@ import browserRequestHandler from './browserRequestHandler' import { formatException } from './../../../logger' +import fs from 'fs' import getBrowserProvider from './browserProvider' +import path from 'path' import { reduce } from 'ramda' +import vm from 'vm' + +const POST_RENDER_SCRIPT_PATH = path.join(process.cwd(), 'scripts/postRender.js') + +const resolvePostRenderFunction = () => { + const context = { postRenderFunction: () => {} } + + const fileStats = fs.statSync(POST_RENDER_SCRIPT_PATH, { throwIfNoEntry: false }) + if (fileStats && fileStats.isFile()) { + const script = new vm.Script( + fs.readFileSync( + POST_RENDER_SCRIPT_PATH, + { encoding: 'utf8', flag: 'r' }, + ), + ) + + vm.createContext(context) + script.runInContext(context) + } + + return context.postRenderFunction +} // renderPageContent :: (Configuration, Logger, BrowserInstance, String) -> RenderedPage const renderPageContent = async (configuration, logger, browserInstance, url) => { @@ -34,6 +58,8 @@ const renderPageContent = async (configuration, logger, browserInstance, url) => timeout: configuration.worker.renderer.timeout, }) + await page.evaluate(resolvePostRenderFunction()) + return await page.content() } diff --git a/src/worker/renderers/chrome/renderer.test.js b/src/worker/renderers/chrome/renderer.test.js index 25673471..34834180 100644 --- a/src/worker/renderers/chrome/renderer.test.js +++ b/src/worker/renderers/chrome/renderer.test.js @@ -35,6 +35,7 @@ describe('worker :: renderer', () => { on: jest.fn(), goto: jest.fn(), content: jest.fn(() => Promise.resolve('My page content')), + evaluate: jest.fn(), } getBrowserProvider.mockReturnValueOnce(({ @@ -58,6 +59,7 @@ describe('worker :: renderer', () => { waitUntil: 'networkidle0', timeout: 20000, }) + expect(pageMock.evaluate).toHaveBeenCalledTimes(1) expect(browserCleanupMock).toHaveBeenCalledTimes(1) })