Skip to content

Commit

Permalink
Move Nunjucks functions into @govuk-frontend/lib/nunjucks
Browse files Browse the repository at this point in the history
But leaves component-specific `render()` and `renderPreview()` functions in `@govuk-frontend/lib/components`
  • Loading branch information
colinrotherham committed Oct 13, 2023
1 parent 130b1f5 commit 003fbb0
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 123 deletions.
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { renderMacro } from '@govuk-frontend/lib/components'
import { renderMacro } from '@govuk-frontend/lib/nunjucks'

describe('i18n.njk', () => {
describe('govukI18nAttributes', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/govuk-frontend/src/govuk/template.test.js
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down
9 changes: 5 additions & 4 deletions shared/helpers/nunjucks.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const components = require('@govuk-frontend/lib/components')
const nunjucks = require('@govuk-frontend/lib/nunjucks')
const cheerio = require('cheerio')

/**
Expand All @@ -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 = {
Expand All @@ -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
*/
122 changes: 9 additions & 113 deletions shared/lib/components.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -220,52 +171,15 @@ 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,
getComponentFiles,
getComponentNames,
getComponentNamesFiltered,
getExamples,
nunjucksEnv,
render,
renderMacro,
renderPreview,
renderString,
renderTemplate
renderPreview
}

/**
Expand Down Expand Up @@ -301,37 +215,13 @@ 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)
*
* @typedef {Required<ComponentExample> & { 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)
Expand All @@ -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
*/
4 changes: 3 additions & 1 deletion shared/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const components = require('./components')
const names = require('./names')
const nunjucks = require('./nunjucks')

/**
* Shared library
*/
module.exports = {
components,
names
names,
nunjucks
}
119 changes: 119 additions & 0 deletions shared/lib/nunjucks.js
Original file line number Diff line number Diff line change
@@ -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)
*/
3 changes: 2 additions & 1 deletion shared/tasks/components.mjs
Original file line number Diff line number Diff line change
@@ -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'

Expand Down

0 comments on commit 003fbb0

Please sign in to comment.