diff --git a/browser-tests/Dockerfile b/browser-tests/Dockerfile index 0cf27cd52..ff030b557 100644 --- a/browser-tests/Dockerfile +++ b/browser-tests/Dockerfile @@ -12,6 +12,6 @@ COPY ./browser-tests/snapshot-tests ./snapshot-tests COPY ./browser-tests/playwright.config.ts ./ COPY ./browser-tests/tsconfig.json ./ COPY ../views/ipv/page ../views/ipv/page -COPY ../src/test-utils/pages-and-contexts.ts ../src/test-utils/pages-and-contexts.ts +COPY ../src/config/pages-and-contexts.ts ../src/config/pages-and-contexts.ts CMD ["npm run test"] diff --git a/browser-tests/data/pagesAndContexts.ts b/browser-tests/data/pagesAndContexts.ts index b7d281f44..fea0ad5e2 100644 --- a/browser-tests/data/pagesAndContexts.ts +++ b/browser-tests/data/pagesAndContexts.ts @@ -1,4 +1,4 @@ -import {pagesAndContexts} from "../../src/test-utils/pages-and-contexts"; +import {pagesAndContexts} from "../../src/config/pages-and-contexts"; type TestFn = (pageName: string, context: string | undefined, language: string, url: string) => void; diff --git a/browser-tests/snapshot-tests/snapshot.spec.ts b/browser-tests/snapshot-tests/snapshot.spec.ts index 176c06406..9ad9043e5 100644 --- a/browser-tests/snapshot-tests/snapshot.spec.ts +++ b/browser-tests/snapshot-tests/snapshot.spec.ts @@ -4,7 +4,7 @@ import fs from "fs"; import { iteratePagesAndContexts } from "../data/pagesAndContexts"; -import { pagesAndContexts } from "../../src/test-utils/pages-and-contexts"; +import { pagesAndContexts } from "../../src/config/pages-and-contexts"; test.describe.parallel("Snapshot tests", () => { test.setTimeout(120000); diff --git a/src/app.ts b/src/app.ts index 108d90805..9e06bd1f0 100644 --- a/src/app.ts +++ b/src/app.ts @@ -19,7 +19,7 @@ import config from "./config/config"; import { setLocals } from "./lib/locals"; import { loggerMiddleware, logger } from "./lib/logger"; import { i18nextConfigurationOptions } from "./config/i18next"; -import { configureNunjucks } from "./config/nunjucks"; +import { configureNunjucks, preloadTemplates } from "./config/nunjucks"; import serverErrorHandler from "./handlers/internal-server-error-handler"; import journeyEventErrorHandler from "./handlers/journey-event-error-handler"; import pageNotFoundHandler from "./handlers/page-not-found-handler"; @@ -110,7 +110,9 @@ app.get("/healthcheck", (req, res) => { app.use(setLocals); app.use(cspHandler); -app.set("view engine", configureNunjucks(app, APP_VIEWS)); + +const nunjucksEnv = configureNunjucks(app, APP_VIEWS); +app.set("view engine", nunjucksEnv); i18next .use(Backend) @@ -121,6 +123,8 @@ i18next app.use(i18nextMiddleware.handle(i18next)); +preloadTemplates(nunjucksEnv); // Must come after nunjucks and i18n + app.use(cookieParser()); // Generate a new session ID asynchronously if no session cookie diff --git a/src/app/development/middleware.test.ts b/src/app/development/middleware.test.ts index 4c241cb50..224704979 100644 --- a/src/app/development/middleware.test.ts +++ b/src/app/development/middleware.test.ts @@ -17,7 +17,7 @@ const pagesAndContextsStub = { }, }; const middleware = proxyquire("./middleware", { - "../../test-utils/pages-and-contexts": pagesAndContextsStub, + "../../config/pages-and-contexts": pagesAndContextsStub, "fs/promises": fsReadDirStub, }); diff --git a/src/app/development/middleware.ts b/src/app/development/middleware.ts index 1a7a6caac..5ff60fa8c 100644 --- a/src/app/development/middleware.ts +++ b/src/app/development/middleware.ts @@ -10,7 +10,7 @@ import { generateQrCodeImageData } from "../shared/qrCodeHelper"; import { getAppStoreRedirectUrl } from "../shared/appDownloadHelper"; import PAGES from "../../constants/ipv-pages"; import { getIpvPageTemplatePath, getTemplatePath } from "../../lib/paths"; -import { pagesAndContexts } from "../../test-utils/pages-and-contexts"; +import { pagesAndContexts } from "../../config/pages-and-contexts"; interface RadioOption { text: string; diff --git a/src/config/nunjucks.ts b/src/config/nunjucks.ts index d5edb784d..b78f2a221 100644 --- a/src/config/nunjucks.ts +++ b/src/config/nunjucks.ts @@ -4,6 +4,9 @@ import i18next from "i18next"; import nunjucks, { Environment } from "nunjucks"; import { kebabCaseToPascalCase } from "../app/shared/stringHelper"; import config from "./config"; +import { logger } from "../lib/logger"; +import { getIpvPageTemplatePath } from "../lib/paths"; +import { pagesAndContexts } from "./pages-and-contexts"; interface FilterContext { ctx: { @@ -85,3 +88,21 @@ export const configureNunjucks = ( nunjucksEnv.addGlobal("addLanguageParam", addLanguageParam); return nunjucksEnv; }; + +// Usually nunjucks will load templates on-demand (and are then cached indefinitely). +// However, this can trigger overload-protection as it's a synchronous blocking call. +// By pre-rendering each template once, we can force all the templates to be loaded at startup. +export const preloadTemplates = (nunjucksEnv: Environment): void => { + if (config.TEMPLATE_CACHING) { + logger.info("Preloading templates"); + const dummyContext = { + i18n: { + language: "en", + }, + }; + + Object.keys(pagesAndContexts).forEach((pageId) => { + nunjucksEnv.render(getIpvPageTemplatePath(pageId), dummyContext); + }); + } +}; diff --git a/src/test-utils/pages-and-contexts.ts b/src/config/pages-and-contexts.ts similarity index 100% rename from src/test-utils/pages-and-contexts.ts rename to src/config/pages-and-contexts.ts