Skip to content

Commit 605c36f

Browse files
committed
Experimental populate-icon-defs optimization
I observed the current populate-icon-defs.js script being OOM killed, having used 7.5MB of memory. This version now: * Uses `rg` to do the grepping, which should be fast and highly memory efficient. * Loads only the required icons from npm Local testing shows it emits the same results, though slightly re-ordered.
1 parent 06ac5d8 commit 605c36f

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

scripts/populate-icon-defs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
RG='rg --no-heading --no-messages --only-matching --no-line-number --no-filename --type html'
5+
OUT=$(mktemp)
6+
7+
siteDir=./public
8+
9+
(
10+
# Capture FA5 class usage (fas|far|fal|fab)
11+
$RG 'fa[brs]?\s+fa-[a-z0-9-]+' $siteDir || true
12+
13+
# Capture FA6 class usage (fa-solid|fa-regular|fa-light|fa-brands)
14+
$RG 'fa-(solid|regular|light|brands)\s+fa-[a-z0-9-]+' $siteDir || true
15+
) | sort | uniq > $OUT
16+
17+
node scripts/populate-icon-defs-lean.js $OUT $siteDir
18+
echo DONE

scripts/populate-icon-defs-lean.js

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
'use strict'
2+
3+
// Usage:
4+
// node scripts/populate-icon-defs-lean.js [siteDir=public]
5+
// Notes:
6+
7+
const fs = require('fs')
8+
const ospath = require('path')
9+
10+
11+
const REQUIRED_ICON_NAMES_RX = /\biconNames: *(\[.*?\])/s
12+
13+
function camelCase (str) {
14+
return str.replace(/-(.)/g, (_, l) => l.toUpperCase())
15+
}
16+
17+
function tryResolve (moduleId) {
18+
try { require.resolve(moduleId); return true } catch { return false }
19+
}
20+
21+
function loadStubRequiredIcons (iconDefsFile) {
22+
try {
23+
const contents = fs.readFileSync(iconDefsFile, 'utf8')
24+
const m = contents.match(REQUIRED_ICON_NAMES_RX)
25+
if (!m) return { required: [], comment: null }
26+
const json = m[1].replace(/'/g, '"')
27+
const required = JSON.parse(json)
28+
// Reconstruct a comment to preserve the stub’s intent in the generated file
29+
const comment = `/*! iconNames: [${required.map((s) => `'${s}'`).join(', ')}] */`
30+
return { required, comment }
31+
} catch {
32+
return { required: [], comment: null }
33+
}
34+
}
35+
36+
// v4 shim map: converts 'fa fa-<name>' to real prefix/name
37+
const iconShims = require('@fortawesome/fontawesome-free/js/v4-shims').reduce((accum, it) => {
38+
accum['fa-' + it[0]] = [it[1] || 'fas', 'fa-' + (it[2] || it[0])]
39+
return accum
40+
}, {})
41+
42+
function applyShimIfV4 (prefix, iconName) {
43+
if (prefix !== 'fa') return [prefix, iconName]
44+
const shim = iconShims[iconName]
45+
if (!shim) return [prefix, iconName]
46+
const [realPrefix, realIconName] = shim
47+
return [realPrefix, realIconName]
48+
}
49+
50+
function pkgBaseForPrefix (prefix) {
51+
// Prefer Pro if available, else Free
52+
const pro = {
53+
fa: '@fortawesome/pro-solid-svg-icons/',
54+
fas: '@fortawesome/pro-solid-svg-icons/',
55+
far: '@fortawesome/pro-regular-svg-icons/',
56+
fal: '@fortawesome/pro-light-svg-icons/',
57+
}[prefix]
58+
const free = {
59+
fa: '@fortawesome/free-solid-svg-icons/',
60+
fas: '@fortawesome/free-solid-svg-icons/',
61+
far: '@fortawesome/free-regular-svg-icons/',
62+
fab: '@fortawesome/free-brands-svg-icons/',
63+
}[prefix]
64+
if (pro && tryResolve(pro)) return pro
65+
if (free && tryResolve(free)) return free
66+
return null
67+
}
68+
69+
function loadIcon (pfx, name) {
70+
71+
const camel = camelCase(name) // 'fa-coffee' -> 'faCoffee'
72+
const base = pkgBaseForPrefix(pfx)
73+
if (!base) return null
74+
const modPath = base + camel
75+
if (!tryResolve(modPath)) return null
76+
const mod = require(modPath)
77+
const def = mod[camel]
78+
return def ? { ...def, prefix: pfx } : null
79+
}
80+
81+
function uniq (arr) {
82+
const seen = {}
83+
return arr.filter((it) => {
84+
if (seen[it]) return false
85+
seen[it] = true
86+
return true
87+
})
88+
}
89+
90+
async function main () {
91+
const iconsList = process.argv[2]
92+
if (! iconsList) {
93+
throw new Error("Missing iconsList argument")
94+
}
95+
96+
const siteDir = process.argv[3] || 'public'
97+
98+
const iconDefsFile = ospath.join(siteDir, '_/js/vendor/fontawesome-icon-defs.js')
99+
100+
const iconKeys = fs.readFileSync(iconsList, 'utf8')
101+
.split('\n')
102+
.map((line) => line.trim())
103+
.filter((line) => line.length > 0)
104+
105+
const {
106+
required: stubRequired,
107+
comment: stubComment } = loadStubRequiredIcons(iconDefsFile)
108+
109+
const icons = uniq(
110+
[...iconKeys, ...stubRequired].map(
111+
(iconKey) => {
112+
const [prefix, iconName] = iconKey.split(' ')
113+
const [realPrefix, realIconName] = applyShimIfV4(prefix, iconName)
114+
return [realPrefix,realIconName]
115+
}
116+
))
117+
118+
// Resolve and load per-icon defs
119+
const defs = []
120+
const seen = new Set()
121+
for (let [prefix,iconName] of icons) {
122+
const def = loadIcon(prefix, iconName)
123+
if (def) {
124+
const k = def.prefix + ' ' + iconName
125+
if (!seen.has(k)) { defs.push(def); seen.add(k) }
126+
} else if (prefix === 'fal') {
127+
console.log(`FontAwesome Light icon missing or Pro not installed: ${iconKey}`)
128+
}
129+
else {
130+
console.log(`Icon not found: ${iconKey}`)
131+
}
132+
}
133+
134+
// iconNames: ['far fa-copy', 'fas fa-link', 'fab fa-github', 'fas fa-terminal', 'fal fa-external-link-alt']
135+
fs.writeFileSync(iconDefsFile,
136+
`${stubComment || ''}
137+
window.fontawesomeIconDefs = ${JSON.stringify(defs)}\n`,
138+
'utf8')
139+
140+
console.log(`Wrote ${defs.length} icon defs to ${iconDefsFile}`)
141+
}
142+
143+
main().catch((e) => { console.error(e); process.exit(1) })

0 commit comments

Comments
 (0)