From 003fbb03f16f48122b8e85ff30126a9363f0d293 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Fri, 13 Oct 2023 09:19:49 +0100 Subject: [PATCH] Move Nunjucks functions into `@govuk-frontend/lib/nunjucks` But leaves component-specific `render()` and `renderPreview()` functions in `@govuk-frontend/lib/components` --- .../src/common/nunjucks/index.mjs | 2 +- .../components/components.template.test.js | 2 +- .../src/govuk/macros/i18n.unit.test.mjs | 2 +- .../govuk-frontend/src/govuk/template.test.js | 2 +- shared/helpers/nunjucks.js | 9 +- shared/lib/components.js | 122 ++---------------- shared/lib/index.js | 4 +- shared/lib/nunjucks.js | 119 +++++++++++++++++ shared/tasks/components.mjs | 3 +- 9 files changed, 142 insertions(+), 123 deletions(-) create mode 100644 shared/lib/nunjucks.js diff --git a/packages/govuk-frontend-review/src/common/nunjucks/index.mjs b/packages/govuk-frontend-review/src/common/nunjucks/index.mjs index d1a53413b8..6ec86bf826 100644 --- a/packages/govuk-frontend-review/src/common/nunjucks/index.mjs +++ b/packages/govuk-frontend-review/src/common/nunjucks/index.mjs @@ -1,7 +1,7 @@ import { join } from 'path' import { paths } from '@govuk-frontend/config' -import { nunjucksEnv } from '@govuk-frontend/lib/components' +import { nunjucksEnv } from '@govuk-frontend/lib/nunjucks' import * as filters from './filters/index.mjs' import * as globals from './globals/index.mjs' diff --git a/packages/govuk-frontend/src/govuk/components/components.template.test.js b/packages/govuk-frontend/src/govuk/components/components.template.test.js index 7c596f532d..294bea3bc6 100644 --- a/packages/govuk-frontend/src/govuk/components/components.template.test.js +++ b/packages/govuk-frontend/src/govuk/components/components.template.test.js @@ -4,9 +4,9 @@ const { paths } = require('@govuk-frontend/config') const { getComponentsFixtures, getComponentNames, - nunjucksEnv, render } = require('@govuk-frontend/lib/components') +const { nunjucksEnv } = require('@govuk-frontend/lib/nunjucks') const { HtmlValidate } = require('html-validate') describe('Components', () => { diff --git a/packages/govuk-frontend/src/govuk/macros/i18n.unit.test.mjs b/packages/govuk-frontend/src/govuk/macros/i18n.unit.test.mjs index 182674ca9f..986b4d0e12 100644 --- a/packages/govuk-frontend/src/govuk/macros/i18n.unit.test.mjs +++ b/packages/govuk-frontend/src/govuk/macros/i18n.unit.test.mjs @@ -1,4 +1,4 @@ -import { renderMacro } from '@govuk-frontend/lib/components' +import { renderMacro } from '@govuk-frontend/lib/nunjucks' describe('i18n.njk', () => { describe('govukI18nAttributes', () => { diff --git a/packages/govuk-frontend/src/govuk/template.test.js b/packages/govuk-frontend/src/govuk/template.test.js index 6b323a7424..ef4a951d4b 100644 --- a/packages/govuk-frontend/src/govuk/template.test.js +++ b/packages/govuk-frontend/src/govuk/template.test.js @@ -1,7 +1,7 @@ const crypto = require('crypto') const { renderTemplate } = require('@govuk-frontend/helpers/nunjucks') -const { nunjucksEnv } = require('@govuk-frontend/lib/components') +const { nunjucksEnv } = require('@govuk-frontend/lib/nunjucks') describe('Template', () => { describe('with default nunjucks configuration', () => { diff --git a/shared/helpers/nunjucks.js b/shared/helpers/nunjucks.js index 98cd30984f..f1b90e4c0a 100644 --- a/shared/helpers/nunjucks.js +++ b/shared/helpers/nunjucks.js @@ -1,4 +1,5 @@ const components = require('@govuk-frontend/lib/components') +const nunjucks = require('@govuk-frontend/lib/nunjucks') const cheerio = require('cheerio') /** @@ -20,7 +21,7 @@ function render(componentName, options) { * @returns {import('cheerio').CheerioAPI} Nunjucks template output */ function renderTemplate(templatePath, options) { - return cheerio.load(components.renderTemplate(templatePath, options)) + return cheerio.load(nunjucks.renderTemplate(templatePath, options)) } module.exports = { @@ -29,7 +30,7 @@ module.exports = { } /** - * @typedef {import('@govuk-frontend/lib/components').MacroOptions} MacroOptions - * @typedef {import('@govuk-frontend/lib/components').MacroRenderOptions} MacroRenderOptions - * @typedef {import('@govuk-frontend/lib/components').TemplateRenderOptions} TemplateRenderOptions + * @typedef {import('@govuk-frontend/lib/nunjucks').MacroOptions} MacroOptions + * @typedef {import('@govuk-frontend/lib/nunjucks').MacroRenderOptions} MacroRenderOptions + * @typedef {import('@govuk-frontend/lib/nunjucks').TemplateRenderOptions} TemplateRenderOptions */ diff --git a/shared/lib/components.js b/shared/lib/components.js index a02b4fdcb9..4720a06071 100644 --- a/shared/lib/components.js +++ b/shared/lib/components.js @@ -1,37 +1,10 @@ const { dirname, join } = require('path') -const nunjucks = require('nunjucks') const { outdent } = require('outdent') const { getListing, getDirectories } = require('./files') -const { packageTypeToPath, componentNameToMacroName } = require('./names') - -// Nunjucks default environment -const env = nunjucksEnv() - -/** - * Nunjucks environment factory - * - * @param {string[]} [searchPaths] - Nunjucks search paths (optional) - * @param {import('nunjucks').ConfigureOptions} [nunjucksOptions] - Nunjucks options (optional) - * @param {import('./names').PackageOptions} [packageOptions] - Package options (optional) - * @returns {import('nunjucks').Environment} Nunjucks environment - */ -function nunjucksEnv(searchPaths = [], nunjucksOptions = {}, packageOptions) { - const packagePath = dirname( - packageTypeToPath('govuk-frontend', packageOptions) - ) - - // Add to Nunjucks search paths (without 'govuk' suffix) - searchPaths.push(join(packagePath, '../')) - - // Nunjucks environment - return nunjucks.configure(searchPaths, { - trimBlocks: true, // automatically remove trailing newlines from a block/tag - lstripBlocks: true, // automatically remove leading whitespace from a block/tag, - ...nunjucksOptions - }) -} +const { componentNameToMacroName, packageTypeToPath } = require('./names') +const { renderMacro, renderTemplate } = require('./nunjucks') /** * Load single component fixtures @@ -147,28 +120,6 @@ function render(componentName, options) { return renderMacro(macroName, macroPath, options) } -/** - * Render macro HTML - * - * @param {string} macroName - The name of the macro - * @param {string} macroPath - The path to the file containing the macro - * @param {MacroRenderOptions} [options] - Nunjucks macro render options - * @returns {string} HTML rendered by the macro - */ -function renderMacro(macroName, macroPath, options) { - const paramsFormatted = JSON.stringify(options?.context ?? {}, undefined, 2) - - let macroString = `{%- from "${macroPath}" import ${macroName} -%}` - - // If we're nesting child components or text, pass the children to the macro - // using the 'caller' Nunjucks feature - macroString += options?.callBlock - ? `{%- call ${macroName}(${paramsFormatted}) -%}${options.callBlock}{%- endcall -%}` - : `{{- ${macroName}(${paramsFormatted}) -}}` - - return renderString(macroString, options) -} - /** * Render component preview on boilerplate page * @@ -220,39 +171,6 @@ function renderPreview(componentName, options) { }) } -/** - * Render string - * - * @param {string} string - Nunjucks string to render - * @param {MacroRenderOptions | TemplateRenderOptions} [options] - Nunjucks render options - * @returns {string} HTML rendered from the Nunjucks string - */ -function renderString(string, options) { - const nunjucksEnv = options?.env ?? env - return nunjucksEnv.renderString(string, options?.context ?? {}) -} - -/** - * Render template HTML - * - * @param {string} templatePath - Nunjucks template path - * @param {TemplateRenderOptions} [options] - Nunjucks template render options - * @returns {string} HTML rendered by template.njk - */ -function renderTemplate(templatePath, options = {}) { - let viewString = `{% extends "${templatePath}" %}` - - for (const [name, content] of Object.entries(options?.blocks ?? {})) { - viewString += outdent` - - {% block ${name} -%} - ${content} - {%- endblock %}` - } - - return renderString(viewString, options) -} - module.exports = { getComponentFixtures, getComponentsFixtures, @@ -260,12 +178,8 @@ module.exports = { getComponentNames, getComponentNamesFiltered, getExamples, - nunjucksEnv, render, - renderMacro, - renderPreview, - renderString, - renderTemplate + renderPreview } /** @@ -301,12 +215,6 @@ module.exports = { * @property {MacroOptions} options - Nunjucks macro options (or params) */ -/** - * Nunjucks macro options - * - * @typedef {{ [param: string]: unknown }} MacroOptions - */ - /** * Component fixture * (used by the Design System website) @@ -314,24 +222,6 @@ module.exports = { * @typedef {Required & { html: string }} ComponentFixture */ -/** - * Nunjucks macro render options - * - * @typedef {object} MacroRenderOptions - * @property {MacroOptions} [context] - Nunjucks context object (optional) - * @property {string} [callBlock] - Nunjucks macro `caller()` content (optional) - * @property {import('nunjucks').Environment} [env] - Nunjucks environment (optional) - */ - -/** - * Nunjucks template render options - * - * @typedef {object} TemplateRenderOptions - * @property {object} [context] - Nunjucks context object (optional) - * @property {{ [blockName: string]: string }} [blocks] - Nunjucks blocks (optional) - * @property {import('nunjucks').Environment} [env] - Nunjucks environment (optional) - */ - /** * Component fixtures * (used by the Design System website) @@ -341,3 +231,9 @@ module.exports = { * @property {ComponentFixture[]} fixtures - Component fixtures with Nunjucks macro options (or params) * @property {string} [previewLayout] - Nunjucks layout for component preview */ + +/** + * @typedef {import('./nunjucks').MacroOptions} MacroOptions + * @typedef {import('./nunjucks').MacroRenderOptions} MacroRenderOptions + * @typedef {import('./nunjucks').TemplateRenderOptions} TemplateRenderOptions + */ diff --git a/shared/lib/index.js b/shared/lib/index.js index 93421849a1..a6e0ea34b3 100644 --- a/shared/lib/index.js +++ b/shared/lib/index.js @@ -1,10 +1,12 @@ const components = require('./components') const names = require('./names') +const nunjucks = require('./nunjucks') /** * Shared library */ module.exports = { components, - names + names, + nunjucks } diff --git a/shared/lib/nunjucks.js b/shared/lib/nunjucks.js new file mode 100644 index 0000000000..6d82e35d2c --- /dev/null +++ b/shared/lib/nunjucks.js @@ -0,0 +1,119 @@ +const { join, dirname } = require('path') + +const nunjucks = require('nunjucks') +const { outdent } = require('outdent') + +const { packageTypeToPath } = require('./names') + +// Nunjucks default environment +const env = nunjucksEnv() + +/** + * Nunjucks environment factory + * + * @param {string[]} [searchPaths] - Nunjucks search paths (optional) + * @param {import('nunjucks').ConfigureOptions} [nunjucksOptions] - Nunjucks options (optional) + * @param {import('./names').PackageOptions} [packageOptions] - Package options (optional) + * @returns {import('nunjucks').Environment} Nunjucks environment + */ +function nunjucksEnv(searchPaths = [], nunjucksOptions = {}, packageOptions) { + const packagePath = dirname( + packageTypeToPath('govuk-frontend', packageOptions) + ) + + // Add to Nunjucks search paths (without 'govuk' suffix) + searchPaths.push(join(packagePath, '../')) + + // Nunjucks environment + return nunjucks.configure(searchPaths, { + trimBlocks: true, // automatically remove trailing newlines from a block/tag + lstripBlocks: true, // automatically remove leading whitespace from a block/tag, + ...nunjucksOptions + }) +} + +/** + * Render macro HTML + * + * @param {string} macroName - The name of the macro + * @param {string} macroPath - The path to the file containing the macro + * @param {MacroRenderOptions} [options] - Nunjucks macro render options + * @returns {string} HTML rendered by the macro + */ +function renderMacro(macroName, macroPath, options) { + const paramsFormatted = JSON.stringify(options?.context ?? {}, undefined, 2) + + let macroString = `{%- from "${macroPath}" import ${macroName} -%}` + + // If we're nesting child components or text, pass the children to the macro + // using the 'caller' Nunjucks feature + macroString += options?.callBlock + ? `{%- call ${macroName}(${paramsFormatted}) -%}${options.callBlock}{%- endcall -%}` + : `{{- ${macroName}(${paramsFormatted}) -}}` + + return renderString(macroString, options) +} + +/** + * Render string + * + * @param {string} string - Nunjucks string to render + * @param {MacroRenderOptions | TemplateRenderOptions} [options] - Nunjucks render options + * @returns {string} HTML rendered from the Nunjucks string + */ +function renderString(string, options) { + const nunjucksEnv = options?.env ?? env + return nunjucksEnv.renderString(string, options?.context ?? {}) +} + +/** + * Render template HTML + * + * @param {string} templatePath - Nunjucks template path + * @param {TemplateRenderOptions} [options] - Nunjucks template render options + * @returns {string} HTML rendered by template.njk + */ +function renderTemplate(templatePath, options = {}) { + let viewString = `{% extends "${templatePath}" %}` + + for (const [name, content] of Object.entries(options?.blocks ?? {})) { + viewString += outdent` + + {% block ${name} -%} + ${content} + {%- endblock %}` + } + + return renderString(viewString, options) +} + +module.exports = { + nunjucksEnv, + renderMacro, + renderString, + renderTemplate +} + +/** + * Nunjucks macro options + * + * @typedef {{ [param: string]: unknown }} MacroOptions + */ + +/** + * Nunjucks macro render options + * + * @typedef {object} MacroRenderOptions + * @property {MacroOptions} [context] - Nunjucks context object (optional) + * @property {string} [callBlock] - Nunjucks macro `caller()` content (optional) + * @property {import('nunjucks').Environment} [env] - Nunjucks environment (optional) + */ + +/** + * Nunjucks template render options + * + * @typedef {object} TemplateRenderOptions + * @property {object} [context] - Nunjucks context object (optional) + * @property {{ [blockName: string]: string }} [blocks] - Nunjucks blocks (optional) + * @property {import('nunjucks').Environment} [env] - Nunjucks environment (optional) + */ diff --git a/shared/tasks/components.mjs b/shared/tasks/components.mjs index ddeb8a7157..0974f4513f 100644 --- a/shared/tasks/components.mjs +++ b/shared/tasks/components.mjs @@ -1,7 +1,8 @@ import { basename, dirname, join } from 'path' -import { nunjucksEnv, render } from '@govuk-frontend/lib/components' +import { render } from '@govuk-frontend/lib/components' import { getListing, getYaml } from '@govuk-frontend/lib/files' +import { nunjucksEnv } from '@govuk-frontend/lib/nunjucks' import { files } from './index.mjs'