Skip to content
This repository was archived by the owner on Nov 30, 2021. It is now read-only.

Commit 48613ff

Browse files
Alistair Lainghannalaakso
andcommitted
Add comments and Refactor lib/extensions.js
Co-authored-by: Hanna Laakso <hanna.laakso@digital.cabinet-office.gov.uk>
1 parent 19ae89f commit 48613ff

2 files changed

Lines changed: 107 additions & 35 deletions

File tree

lib/extensions/extensions.js

Lines changed: 104 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,72 @@
1-
// // Imports
1+
/**
2+
* Extensions.js (Use with caution)
3+
*
4+
* Experimental feature which is likely to change.
5+
* This file returns helper methods to enable services to include
6+
* their own departmental frontend(Styles, Scripts, nunjucks etc)
7+
*
8+
* Module.exports
9+
* getPublicUrls:
10+
* Params: (type | string ) eg. 'scripts', 'stylesheets'
11+
* Description:
12+
* returns array of urls for a type (script, stylesheet, nunjucks etc). do we need to expose this?
13+
* getFileSystemPaths:
14+
* Params: (type | string ) eg. 'scripts', 'stylesheets'
15+
* Description:
16+
* returns array paths to the file in the filesystem for a type (script, stylesheet, nunjucks etc) do we need to expose this?
17+
* getPublicUrlAndFileSystemPaths:
18+
* Params: (type | string ) eg. 'scripts', 'stylesheets'
19+
* Description:
20+
* returns Array of objects, each object is an extension and each obj has the filesystem & public url for the given type
21+
* getAppConfig:
22+
* Params: (type | string ) eg. 'scripts', 'stylesheets'
23+
* Description:
24+
* to do
25+
* getAppViews:
26+
* Params: (type | string ) eg. 'scripts', 'stylesheets'
27+
* Description:
28+
* To do
29+
* setExtensionsByType
30+
* Params: N/A
31+
* Description: only used for test purposes to reset mocked extensions items to ensure they are up-to-date when the tests run
32+
*
33+
* *
34+
*/
35+
36+
// Core dependencies
237
const fs = require('fs')
338
const path = require('path')
39+
40+
// Local dependencies
441
const appConfig = require('../../app/config')
542

6-
// Utils
7-
const pathFromRoot = (...all) => path.join.apply(null, [__dirname, '..', '..'].concat(all))
8-
const pathToHookFile = packageName => pathFromRoot('node_modules', packageName, 'govuk-prototype-kit.config.json')
43+
// Generic utilities
44+
const removeDuplicates = arr => [...new Set(arr)]
45+
const filterOutParentAndEmpty = part => part && part !== '..'
946
const objectMap = (object, mapFn) => Object.keys(object).reduce((result, key) => {
1047
result[key] = mapFn(object[key], key)
1148
return result
1249
}, {})
13-
const removeDuplicates = arr => [...new Set(arr)]
14-
const filterOutParentAndEmpty = part => part && part !== '..'
50+
51+
// File utilities
52+
const getPathFromProjectRoot = (...all) => {
53+
return path.join.apply(null, [__dirname, '..', '..'].concat(all))
54+
}
55+
const pathToPackageConfigFile = packageName => getPathFromProjectRoot('node_modules', packageName, 'govuk-prototype-kit.config.json')
56+
57+
const readJsonFile = (filePath) => {
58+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
59+
}
60+
const getPackageConfig = packageName => {
61+
if (fs.existsSync(pathToPackageConfigFile(packageName))) {
62+
return readJsonFile(pathToPackageConfigFile(packageName))
63+
} else {
64+
return {}
65+
}
66+
}
67+
68+
// Handle errors to do with extension paths
69+
// Example of `subject`: { packageName: 'govuk-frontend', item: '/all.js' }
1570
const throwIfBadFilepath = subject => {
1671
if (('' + subject.item).indexOf('\\') > -1) {
1772
throw new Error(`Can't use backslashes in extension paths - "${subject.packageName}" used "${subject.item}".`)
@@ -21,23 +76,14 @@ const throwIfBadFilepath = subject => {
2176
}
2277
}
2378

24-
function readJsonFile (filePath) {
25-
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
26-
}
27-
28-
// Reused Functions
79+
// Check for `baseExtensions` in config.js. If it's not there, default to `govuk-frontend`
2980
const getBaseExtensions = () => appConfig.baseExtensions || ['govuk-frontend']
3081

31-
const lookupConfigForPackage = packageName => {
32-
if (fs.existsSync(pathToHookFile(packageName))) {
33-
return readJsonFile(pathToHookFile(packageName))
34-
} else {
35-
return {}
36-
}
37-
}
38-
82+
// Get all npm dependencies
83+
// Get baseExtensions in the order defined in `baseExtensions` in config.js
84+
// Then place baseExtensions before npm dependencies (and remove duplicates)
3985
const getPackageNamesInOrder = () => {
40-
const dependencies = readJsonFile(pathFromRoot('package.json')).dependencies || {}
86+
const dependencies = readJsonFile(getPathFromProjectRoot('package.json')).dependencies || {}
4187
const allNpmDependenciesInAlphabeticalOrder = Object.keys(dependencies).sort()
4288
const installedBaseExtensions = getBaseExtensions()
4389
.filter(packageName => allNpmDependenciesInAlphabeticalOrder.includes(packageName))
@@ -47,32 +93,59 @@ const getPackageNamesInOrder = () => {
4793

4894
// Extensions provide items such as sass scripts, asset paths etc.
4995
// This function groups them by type in a format which can used by getList
96+
// Example of return
97+
// {
98+
// nunjucksPaths: [
99+
// { packageName: 'govuk-frontend', item: '/' },
100+
// { packageName: 'govuk-frontend', item: '/components'}
101+
// ],
102+
// scripts: [
103+
// { packageName: 'govuk-frontend', item: '/all.js' }
104+
// ]
105+
// assets: [
106+
// { packageName: 'govuk-frontend', item: '/assets' }
107+
// ],
108+
// sass: [
109+
// { packageName: 'govuk-frontend', item: '/all.scss' }
110+
// ]}
50111
const getExtensionsByType = () => {
51112
return getPackageNamesInOrder()
52113
.reduce((accum, packageName) => Object.assign({}, accum, objectMap(
53-
lookupConfigForPackage(packageName),
114+
getPackageConfig(packageName),
54115
(listOfItemsForType, type) => (accum[type] || [])
55-
.concat([].concat(listOfItemsForType).map(item => ({ packageName, item })))
116+
.concat([].concat(listOfItemsForType).map(item => ({
117+
packageName,
118+
item
119+
})))
56120
)), {})
57121
}
58122

59123
let extensionsByType
60124

61-
const recache = () => {
125+
const setExtensionsByType = () => {
62126
extensionsByType = getExtensionsByType()
63127
}
64128

65-
recache()
129+
setExtensionsByType()
66130

67131
// The hard-coded reference to govuk-frontend allows us to soft launch without a breaking change. After a hard launch
68132
// govuk-frontend assets will be served on /extension-assets/govuk-frontend
69-
const getPublicUrl = config => (config.item === '/assets' && config.packageName === 'govuk-frontend') ? '/assets' : ['', 'extension-assets', config.packageName]
70-
.concat(config.item.split('/').filter(filterOutParentAndEmpty))
71-
.map(encodeURIComponent)
72-
.join('/')
133+
const getPublicUrl = config => {
134+
if (config.item === '/assets' && config.packageName === 'govuk-frontend') {
135+
return '/assets'
136+
} else {
137+
return ['', 'extension-assets', config.packageName]
138+
.concat(config.item.split('/').filter(filterOutParentAndEmpty))
139+
.map(encodeURIComponent)
140+
.join('/')
141+
}
142+
}
73143

74-
const getFileSystemPath = config => throwIfBadFilepath(config) || pathFromRoot('node_modules', config.packageName,
75-
config.item.split('/').filter(filterOutParentAndEmpty).join(path.sep))
144+
const getFileSystemPath = config => {
145+
return throwIfBadFilepath(config) || getPathFromProjectRoot('node_modules',
146+
config.packageName,
147+
config.item.split('/').filter(filterOutParentAndEmpty).join(path.sep))
148+
}
76149

77150
const getPublicUrlAndFileSystemPath = config => ({
78151
fileSystemPath: getFileSystemPath(config),
@@ -86,7 +159,6 @@ const self = module.exports = {
86159
getPublicUrls: type => getList(type).map(getPublicUrl),
87160
getFileSystemPaths: type => getList(type).map(getFileSystemPath),
88161
getPublicUrlAndFileSystemPaths: type => getList(type).map(getPublicUrlAndFileSystemPath),
89-
90162
getAppConfig: _ => ({
91163
scripts: self.getPublicUrls('scripts'),
92164
stylesheets: self.getPublicUrls('stylesheets')
@@ -96,5 +168,5 @@ const self = module.exports = {
96168
.reverse()
97169
.concat(additionalViews || []),
98170

99-
recache
171+
setExtensionsByType // exposed only for testing purposes
100172
}

lib/extensions/extensions.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('extensions', () => {
2727
}
2828
}
2929
setupFakeFilesystem()
30-
extensions.recache()
30+
extensions.setExtensionsByType()
3131
})
3232
afterEach(() => {
3333
jest.clearAllMocks()
@@ -444,7 +444,7 @@ describe('extensions', () => {
444444
const existingPackageJson = JSON.parse(testScope.fileSystem['/package.json'])
445445
existingPackageJson.dependencies[packageName] = version
446446
testScope.fileSystem['/package.json'] = JSON.stringify(existingPackageJson)
447-
extensions.recache()
447+
extensions.setExtensionsByType()
448448
}
449449

450450
const mockUninstallExtension = (packageName) => {
@@ -454,7 +454,7 @@ describe('extensions', () => {
454454
}
455455
delete existingPackageJson.dependencies[packageName]
456456
testScope.fileSystem['/package.json'] = JSON.stringify(existingPackageJson)
457-
extensions.recache()
457+
extensions.setExtensionsByType()
458458
}
459459

460460
const mockExtensionConfig = (packageName, config = {}, version) => {

0 commit comments

Comments
 (0)