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
237const fs = require ( 'fs' )
338const path = require ( 'path' )
39+
40+ // Local dependencies
441const 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 !== '..'
946const 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' }
1570const 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`
2980const 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)
3985const 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+ // ]}
50111const 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
59123let 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
77150const 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}
0 commit comments