Skip to content

Commit c3f7655

Browse files
Merge pull request #2306 from alphagov/frontend-v5-compatibility-colin
Locate GOV.UK Frontend using `require.resolve()`
2 parents 06bec62 + 4a40024 commit c3f7655

10 files changed

Lines changed: 94 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Fixes
6+
7+
- [#2306: Locate GOV.UK Frontend using `require.resolve()`](https://github.com/alphagov/govuk-prototype-kit/pull/2306)
8+
59
## 13.13.3
610

711
### Fixes

lib/build.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const sass = require('sass')
1111
// local dependencies
1212
const plugins = require('./plugins/plugins')
1313
const {
14+
packageDir,
1415
projectDir,
1516
appSassDir,
1617
libSassDir,
@@ -21,9 +22,10 @@ const {
2122
backupNunjucksDir,
2223
appViewsDir
2324
} = require('./utils/paths')
24-
const { recursiveDirectoryContentsSync, getInternalGovukFrontendDir } = require('./utils')
25+
const { recursiveDirectoryContentsSync } = require('./utils')
2526
const { startPerformanceTimer, endPerformanceTimer } = require('./utils/performance')
2627
const { verboseLog } = require('./utils/verboseLogger')
28+
const { govukFrontendPaths } = require('./govukFrontendPaths')
2729
const { hasRestartedAfterError } = require('./sync-changes')
2830

2931
let nodemonInstance
@@ -90,11 +92,12 @@ function sassInclude (filePath) {
9092
function sassKitFrontendDependency () {
9193
const timer = startPerformanceTimer()
9294

93-
const internalGovUkFrontendDir = getInternalGovukFrontendDir()
94-
const internalGovUkFrontendConfig = fse.readJsonSync(path.join(internalGovUkFrontendDir, 'govuk-prototype-kit.config.json'))
95+
// Find GOV.UK Frontend (via internal package, project fallback)
96+
const govukFrontendInternal = govukFrontendPaths([packageDir, projectDir])
9597

96-
const govukFrontendSass = internalGovUkFrontendConfig.sass
97-
.map(sassPath => path.join(internalGovUkFrontendDir, sassPath))
98+
// Get GOV.UK Frontend (internal) stylesheets
99+
const govukFrontendSass = (govukFrontendInternal.config?.sass || [])
100+
.map(sassPath => path.join(govukFrontendInternal.baseDir, sassPath))
98101

99102
const fileContents = sassVariables('/manage-prototype/dependencies') +
100103
govukFrontendSass

lib/errorServer.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ const fs = require('fs')
44
const { getNunjucksAppEnv } = require('./nunjucks/nunjucksConfiguration')
55
const { getErrorModel } = require('./utils/errorModel')
66
const { verboseLog } = require('./utils/verboseLogger')
7+
const { packageDir, projectDir } = require('./utils/paths')
8+
const { govukFrontendPaths } = require('./govukFrontendPaths')
79
const syncChanges = require('./sync-changes')
810
const { flagError } = require('./sync-changes')
9-
const { packageDir } = require('./utils/paths')
10-
const { getInternalGovukFrontendDir } = require('./utils')
1111

1212
function runErrorServer (error) {
1313
flagError(error)
@@ -45,6 +45,9 @@ function runErrorServer (error) {
4545
}
4646
}
4747

48+
// Find GOV.UK Frontend (via internal package, project fallback)
49+
const govukFrontendInternal = govukFrontendPaths([packageDir, projectDir])
50+
4851
/**
4952
* @type {http.RequestListener}
5053
*/
@@ -79,7 +82,7 @@ function runErrorServer (error) {
7982

8083
// Route GOV.UK Frontend to internal package
8184
const modulesDir = filePath.startsWith('govuk-frontend/') && knownRoute.startsWith('/manage-prototype/')
82-
? path.dirname(getInternalGovukFrontendDir())
85+
? path.dirname(govukFrontendInternal.baseDir)
8386
: path.join(process.cwd(), 'node_modules')
8487

8588
try {
@@ -99,15 +102,21 @@ function runErrorServer (error) {
99102
res.setHeader('Content-Type', 'text/html')
100103
res.writeHead(500)
101104

105+
// Get GOV.UK Frontend (internal) views
106+
const govukFrontendNunjucksPaths = (govukFrontendInternal.config?.nunjucksPaths || [])
107+
.map(nunjucksPath => path.join(govukFrontendInternal.baseDir, nunjucksPath))
108+
102109
const fileContentsParts = []
103110

104111
try {
105112
const nunjucksAppEnv = getNunjucksAppEnv([
106113
path.join(__dirname, 'nunjucks'),
107-
path.join(packageDir, 'node_modules', 'govuk-frontend'),
108-
path.join(process.cwd(), 'node_modules', 'govuk-frontend')
114+
...govukFrontendNunjucksPaths
109115
])
110-
res.end(nunjucksAppEnv.render('views/error-handling/server-error', getErrorModel(error)))
116+
res.end(nunjucksAppEnv.render('views/error-handling/server-error', {
117+
govukFrontendInternal, // Add GOV.UK Frontend paths to Nunjucks context
118+
...getErrorModel(error)
119+
}))
111120
} catch (ignoreThisError) {
112121
console.log(JSON.stringify({ ignoreThisError }, null, 2))
113122
fileContentsParts.push('<h1 class="govuk-heading-l">There is an error</h1>')

lib/govukFrontendPaths.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// core dependencies
2+
const path = require('path')
3+
4+
// npm dependencies
5+
const fse = require('fs-extra')
6+
7+
/**
8+
* Find GOV.UK Frontend via search paths
9+
*
10+
* @param {string[]} searchPaths - Search paths for `require.resolve()`
11+
* @returns {{ baseDir: string, includePath: string, assetPath: string, config: { [key: string]: unknown } }}
12+
*/
13+
function govukFrontendPaths (searchPaths = []) {
14+
/**
15+
* GOV.UK Frontend paths normalised
16+
*
17+
* v4.x - `/path/to/node_modules/govuk-frontend/govuk/all.js`
18+
* v5.x - `/path/to/node_modules/govuk-frontend/dist/govuk/all.bundle.js`
19+
*/
20+
const entryPath = require.resolve('govuk-frontend', { paths: searchPaths })
21+
const dependencyPath = path.join('node_modules/govuk-frontend')
22+
const baseDir = path.join(entryPath.split(dependencyPath)[0], dependencyPath)
23+
const includeDir = path.dirname(entryPath)
24+
25+
return {
26+
baseDir,
27+
28+
includePath: `/${path.relative(baseDir, includeDir)}`,
29+
assetPath: `/${path.relative(baseDir, path.join(includeDir, 'assets'))}`,
30+
31+
// GOV.UK Frontend plugin config
32+
config: fse.readJsonSync(path.join(baseDir, 'govuk-prototype-kit.config.json'), {
33+
throws: false
34+
})
35+
}
36+
}
37+
38+
module.exports = {
39+
govukFrontendPaths
40+
}

lib/manage-prototype-handlers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,10 @@ async function getTemplatesHandler (req, res) {
232232
const availableTemplates = getPluginTemplates()
233233

234234
const commonTemplatesPackageName = '@govuk-prototype-kit/common-templates'
235-
const govUkFrontendPackageName = 'govuk-frontend'
235+
const govukFrontendPackageName = 'govuk-frontend'
236236
let commonTemplatesDetails
237237
const installedPlugins = (await getInstalledPackages()).map((pkg) => pkg.packageName)
238-
if (installedPlugins.includes(govUkFrontendPackageName) && !installedPlugins.includes(commonTemplatesPackageName)) {
238+
if (installedPlugins.includes(govukFrontendPackageName) && !installedPlugins.includes(commonTemplatesPackageName)) {
239239
commonTemplatesDetails = {
240240
pluginDisplayName: plugins.preparePackageNameForDisplay(commonTemplatesPackageName),
241241
installLink: `${contextPath}/plugins/install?package=${encodeURIComponent(commonTemplatesPackageName)}&returnTo=templates`

lib/manage-prototype-routes.js

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ const {
2626
pluginCacheMiddleware,
2727
postPluginsHandler
2828
} = require('./manage-prototype-handlers')
29-
const path = require('path')
30-
const { getInternalGovukFrontendDir } = require('./utils')
29+
const { packageDir, projectDir } = require('./utils/paths')
30+
const { govukFrontendPaths } = require('./govukFrontendPaths')
3131

3232
const router = require('../index').requests.setupRouter(contextPath)
3333

@@ -79,14 +79,10 @@ router.post('/plugins/:mode', postPluginsModeMiddleware)
7979

8080
router.post('/plugins/:mode', csrfProtection, postPluginsModeHandler)
8181

82-
const partialGovukFrontendUrls = [
83-
'govuk/assets',
84-
'govuk/all.js',
85-
'govuk-prototype-kit/init.js'
86-
]
87-
partialGovukFrontendUrls.forEach(url => {
88-
router.use(`/dependencies/govuk-frontend/${url}`, express.static(path.join(getInternalGovukFrontendDir(), url)))
89-
})
82+
// Find GOV.UK Frontend (via internal package, project fallback)
83+
router.use('/dependencies/govuk-frontend', express.static(
84+
govukFrontendPaths([packageDir, projectDir]).baseDir)
85+
)
9086

9187
setKitRestarted(true)
9288

lib/nunjucks/govuk-prototype-kit/layouts/govuk-branded.njk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{%- set assetPath = assetPath | default('/plugin-assets/govuk-frontend/govuk/assets') -%}
1+
{%- set assetPath = assetPath | default('/plugin-assets/govuk-frontend' + govukFrontend.assetPath) -%}
22

33
{% extends "govuk/template.njk" %}
44

lib/nunjucks/views/manage-prototype/layout.njk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{%- set assetPath = '/manage-prototype/dependencies/govuk-frontend/govuk/assets' -%}
1+
{%- set assetPath = '/manage-prototype/dependencies/govuk-frontend' + govukFrontendInternal.assetPath -%}
22
{% extends "govuk-prototype-kit/layouts/govuk-branded.njk" %}
33

44
{% block pageTitle %}

lib/utils/index.js

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ const filters = require('../filters/api')
1717
const functions = require('../functions/api')
1818
const plugins = require('../plugins/plugins')
1919
const routes = require('../routes/api')
20-
const { appDir, projectDir, packageDir } = require('./paths')
21-
const fse = require('fs-extra')
20+
const { appDir, projectDir } = require('./paths')
2221
const { asyncSeriesMap } = require('./asyncSeriesMap')
2322

2423
// Tweak the Markdown renderer
@@ -254,17 +253,6 @@ async function searchAndReplaceFiles (dir, searchText, replaceText, extensions)
254253
return modifiedFiles.flat().filter(Boolean)
255254
}
256255

257-
let internalGovukFrontendDir
258-
259-
function getInternalGovukFrontendDir () {
260-
if (!internalGovukFrontendDir) {
261-
const packageDirFrontend = path.join(packageDir, 'node_modules', 'govuk-frontend')
262-
const projectDirFrontend = path.join(projectDir, 'node_modules', 'govuk-frontend')
263-
internalGovukFrontendDir = fse.pathExistsSync(packageDirFrontend) ? packageDirFrontend : projectDirFrontend
264-
}
265-
return internalGovukFrontendDir
266-
}
267-
268256
function sortByObjectKey (key) {
269257
return function (a, b) {
270258
if (a[key] > b[key]) {
@@ -291,6 +279,5 @@ module.exports = {
291279
sessionFileStoreQuietLogFn,
292280
searchAndReplaceFiles,
293281
recursiveDirectoryContentsSync,
294-
getInternalGovukFrontendDir,
295282
sortByObjectKey
296283
}

server.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ const bodyParser = require('body-parser')
88
const cookieParser = require('cookie-parser')
99
const dotenv = require('dotenv')
1010
const express = require('express')
11-
const fse = require('fs-extra')
1211
const { expressNunjucks, getNunjucksAppEnv, stopWatchingNunjucks } = require('./lib/nunjucks/nunjucksConfiguration')
1312

1413
// We want users to be able to keep api keys, config variables and other
@@ -20,11 +19,11 @@ dotenv.config()
2019
const { projectDir, packageDir, finalBackupNunjucksDir } = require('./lib/utils/paths')
2120
const config = require('./lib/config.js').getConfig()
2221
const packageJson = require('./package.json')
22+
const { govukFrontendPaths } = require('./lib/govukFrontendPaths')
2323
const utils = require('./lib/utils')
2424
const sessionUtils = require('./lib/session.js')
2525
const plugins = require('./lib/plugins/plugins.js')
2626
const routesApi = require('./lib/routes/api.js')
27-
const { getInternalGovukFrontendDir } = require('./lib/utils')
2827

2928
const app = express()
3029
routesApi.setApp(app)
@@ -40,6 +39,12 @@ if (isSecure) {
4039
app.set('trust proxy', 1) // needed for secure cookies on heroku
4140
}
4241

42+
// Find GOV.UK Frontend (via project, internal package fallback)
43+
const govukFrontend = govukFrontendPaths([projectDir, packageDir])
44+
45+
// Find GOV.UK Frontend (via internal package, project fallback)
46+
const govukFrontendInternal = govukFrontendPaths([packageDir, projectDir])
47+
4348
// Add variables that are available in all views
4449
app.locals.asset_path = '/public/'
4550
app.locals.useAutoStoreData = config.useAutoStoreData
@@ -56,6 +61,11 @@ if (plugins.legacyGovukFrontendFixesNeeded()) {
5661
app.locals.pluginConfig = plugins.getAppConfig({
5762
scripts: utils.prototypeAppScripts
5863
})
64+
65+
// Add GOV.UK Frontend paths to Nunjucks locals
66+
app.locals.govukFrontend = govukFrontend
67+
app.locals.govukFrontendInternal = govukFrontendInternal
68+
5969
// keep extensionConfig around for backwards compatibility
6070
// TODO: remove in v14
6171
app.locals.extensionConfig = app.locals.pluginConfig
@@ -70,16 +80,15 @@ app.use(cookieParser())
7080
// static assets to prevent unauthorised access
7181
app.use(require('./lib/authentication.js')())
7282

73-
// Get internal govuk-frontend views
74-
const internalGovUkFrontendDir = getInternalGovukFrontendDir()
75-
const internalGovUkFrontendConfig = fse.readJsonSync(path.join(internalGovUkFrontendDir, 'govuk-prototype-kit.config.json'))
76-
const internalGovUkFrontendViews = internalGovUkFrontendConfig.nunjucksPaths.map(viewPath => path.join(internalGovUkFrontendDir, viewPath))
83+
// Get GOV.UK Frontend (internal) views
84+
const govukFrontendNunjucksPaths = (govukFrontendInternal.config?.nunjucksPaths || [])
85+
.map(nunjucksPath => path.join(govukFrontendInternal.baseDir, nunjucksPath))
7786

7887
// Set up App
7988
const appViews = [
8089
path.join(projectDir, '/app/views/')
8190
].concat(plugins.getAppViews([
82-
...internalGovUkFrontendViews,
91+
...govukFrontendNunjucksPaths,
8392
finalBackupNunjucksDir
8493
]))
8594

0 commit comments

Comments
 (0)