-
Notifications
You must be signed in to change notification settings - Fork 95
Update icon definitions script #844
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,15 +1,42 @@ | ||||||
| 'use strict' | ||||||
|
|
||||||
| // populate-icon-defs.js | ||||||
| // ============= | ||||||
| // | ||||||
| // Scans the Antora output in public/**/*.html for all uses of Fontawesome icons like: | ||||||
| // | ||||||
| // <i class="fas fa-copy"></i> | ||||||
| // | ||||||
| // 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 = '<i class="fa' | ||||||
| const ICON_RX = /<i class="fa[brs]? fa-[^" ]+/g | ||||||
| const REQUIRED_ICON_NAMES_RX = /\biconNames: *(\[.*?\])/ | ||||||
|
|
||||||
| // on all *.html files under dir, run the provided function fn, and collate all results in a Set. | ||||||
| // e.g. all values will be unique | ||||||
|
|
||||||
| function runOnHtmlFiles (dir, fn) { | ||||||
| return fsp.readdir(dir, { withFileTypes: true }).then((dirents) => { | ||||||
| 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 | ||||||
| // | ||||||
| // <i class="fas fa-copy"></i> | ||||||
| // <i class="far fa-save"></i> | ||||||
| // | ||||||
| // 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")); | ||||||
|
||||||
| let firstLine = contents.substr(0, contents.indexOf("\n")); | |
| let firstLine = contents.substring(0, contents.indexOf("\n")); |
Copilot
AI
Sep 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this debug console.log statement as it appears to be leftover from debugging and should not be in production code.
| console.log(requiredIconNames) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
substring()instead of the deprecatedsubstr()method. Replace withit.substring(10).