diff --git a/scripts/populate-icon-defs.js b/scripts/populate-icon-defs.js
index 8cb5f35ef2..d94c9060ca 100644
--- a/scripts/populate-icon-defs.js
+++ b/scripts/populate-icon-defs.js
@@ -1,15 +1,42 @@
'use strict'
+// populate-icon-defs.js
+// =============
+//
+// Scans the Antora output in public/**/*.html for all uses of Fontawesome icons like:
+//
+//
+//
+// Collates these together, and writes a file to
+//
+// public/_/js/vendor/populate-icon-defs.js
+//
+// that contains the full SVG icon definitions for each of these icons.
+// This is used by the UI to substitue the icon images at runtime.
+//
+// NOTE: the docs-ui/ bundle contains a default version of this file, which is only periodically
+// updated.
+// This script will however work on the *actual* output, so will honour newly added icons.
+//
// Prerequisite:
+// =============
//
-// $ npm --no-package-lock i
+// $ npm ci
//
// Usage:
+// =============
//
-// $ node populate-icon-defs.js ../public
+// $ node scripts/populate-icon-defs.js public
//
-const { promises: fsp } = require('fs')
+
+// NOTE: original version of script was async, refactored to synchronous to simplify debugging
+// Against a problematically large input that crashed our staging build, the dumb sync version
+// takes around 7 seconds.
+// We could reintroduce async code here to optimize, in due course.
+
+const fs = require('fs')
const ospath = require('path')
+
const iconPacks = {
fas: (() => {
try {
@@ -34,69 +61,130 @@ const iconPacks = {
})(),
fab: require('@fortawesome/free-brands-svg-icons'),
}
+
iconPacks.fa = iconPacks.fas
const iconShims = require('@fortawesome/fontawesome-free/js/v4-shims').reduce((accum, it) => {
accum['fa-' + it[0]] = [it[1] || 'fas', 'fa-' + (it[2] || it[0])]
return accum
}, {})
+// define patterns/regular expressions used in the scanning
const ICON_SIGNATURE_CS = ' {
- return dirents.reduce(async (accum, dirent) => {
- const entries = dirent.isDirectory()
- ? await runOnHtmlFiles(ospath.join(dir, dirent.name), fn)
- : (dirent.name.endsWith('.html') ? await fn(ospath.join(dir, dirent.name)) : undefined)
- return entries && entries.length ? (await accum).concat(entries) : accum
- }, [])
- })
+ const ret = new Set()
+ const files = findHtmlFiles(dir)
+ for (const path of files) {
+ const val = fn(path)
+ if (val) {
+ for (const item of val) {
+ ret.add(item)
+ }
+ }
+ }
+ return ret
+}
+
+// return a list of all HTML files (recursive, e.g. **/*.html)
+function findHtmlFiles (dir) {
+ const ret = []
+
+ for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
+ if (dirent.isDirectory()) {
+ const files = findHtmlFiles(ospath.join(dir, dirent.name))
+ ret.push(...files)
+
+ } else if (dirent.name.endsWith('.html')) {
+ ret.push(ospath.join(dir, dirent.name))
+ }
+ }
+ return ret
}
function camelCase (str) {
return str.replace(/-(.)/g, (_, l) => l.toUpperCase())
}
+// Return all icon names
+// e.g. for example, an HTML file that contained these icon definitions
+//
+//
+//
+//
+// Would return ["fas fa-copy", "far fa-save"]
+
+function getScannedNames(path) {
+ const contents = fs.readFileSync(path)
+ if (contents.includes(ICON_SIGNATURE_CS)) {
+ return contents.toString()
+ .match(ICON_RX)
+ .map((it) => it.substr(10))
+ }
+ else {
+ return undefined
+ }
+}
+
function scanForIconNames (dir) {
- return runOnHtmlFiles(dir, (path) =>
- fsp.readFile(path).then((contents) =>
- contents.includes(ICON_SIGNATURE_CS)
- ? contents.toString().match(ICON_RX).map((it) => it.substr(10))
- : undefined
- )
- ).then((scanResult) => [...new Set(scanResult)])
+ const scanResult = runOnHtmlFiles(dir, getScannedNames)
+ return [...scanResult] // Set to array
}
-;(async () => {
+// On running the script, execute the following immediately invoked function expression (IIFE)
+;(() => {
+
const siteDir = process.argv[2] || 'public'
+
+ let iconNames = scanForIconNames(siteDir)
+
const iconDefsFile = ospath.join(siteDir, '_/js/vendor/fontawesome-icon-defs.js')
- const iconDefs = await scanForIconNames(siteDir).then((iconNames) =>
- fsp.readFile(iconDefsFile, 'utf8').then((contents) => {
- try {
- const requiredIconNames = JSON.parse(contents.match(REQUIRED_ICON_NAMES_RX)[1].replace(/'/g, '"'))
- iconNames = [...new Set(iconNames.concat(requiredIconNames))]
- } catch (e) {}
- }).then(() =>
- iconNames.reduce((accum, iconKey) => {
- const [iconPrefix, iconName] = iconKey.split(' ').slice(0, 2)
- let iconDef = (iconPacks[iconPrefix] || {})[camelCase(iconName)]
- if (iconDef) {
- return accum.set(iconKey, { ...iconDef, prefix: iconPrefix })
- } else if (iconPrefix === 'fa') {
- const [realIconPrefix, realIconName] = iconShims[iconName] || []
- if (
- realIconName &&
- !accum.has((iconKey = `${realIconPrefix} ${realIconName}`)) &&
- (iconDef = (iconPacks[realIconPrefix] || {})[camelCase(realIconName)])
- ) {
- return accum.set(iconKey, { ...iconDef, prefix: realIconPrefix })
- }
+ // first we read the stub file. This starts with a comment with a list of icons that must *always* be included
+ // e.g.
+ // /*! iconNames: ['far fa-copy', 'fas fa-link', 'fab fa-github', 'fas fa-terminal', 'fal fa-external-link-alt'] */
+
+ let contents = fs.readFileSync(iconDefsFile, 'utf8')
+ let firstLine = contents.substr(0, contents.indexOf("\n"));
+
+ try {
+ const requiredIconNames = JSON.parse(firstLine.match(REQUIRED_ICON_NAMES_RX)[1].replace(/'/g, '"'))
+ console.log(requiredIconNames)
+ iconNames = [...new Set(iconNames.concat(requiredIconNames))]
+ } catch (e) {
+ // we didn't get a valid list of requiredIconNames, so don't write it back out to the new file
+ firstLine = undefined
+ }
+
+ const iconDefs = new Map()
+
+ for (const iconKey of iconNames) {
+ const [iconPrefix, iconName] = iconKey.split(' ').slice(0, 2)
+ let iconDef = (iconPacks[iconPrefix] || {})[camelCase(iconName)]
+
+ if (iconDef) {
+ iconDefs.set(iconKey, { ...iconDef, prefix: iconPrefix })
+ }
+ else if (iconPrefix === 'fa') {
+ const [realIconPrefix, realIconName] = iconShims[iconName] || []
+ if (realIconName) {
+ const realIconKey = `${realIconPrefix} ${realIconName}`
+ if (
+ !iconDefs.has(realIconKey) &&
+ (iconDef = (iconPacks[realIconPrefix] || {})[camelCase(realIconName)]))
+ {
+ iconDefs.set(realIconKey, { ...iconDef, prefix: realIconPrefix })
}
- return accum
- }, new Map())
- )
- )
- await fsp.writeFile(iconDefsFile, `window.FontAwesomeIconDefs = ${JSON.stringify([...iconDefs.values()])}\n`, 'utf8')
+ }
+ }
+ }
+
+ // update the contents to the collated example, and write it out
+ contents =
+ `${firstLine ? firstLine + "\n" : ''}window.FontAwesomeIconDefs = ${JSON.stringify([...iconDefs.values()])}\n`
+
+ fs.writeFileSync(iconDefsFile, contents, 'utf8')
})()