Skip to content

Commit a8251a4

Browse files
authored
merge #20
add Font Awesome pro integration and only load defs for icons in use
2 parents 9e124c8 + 76b28a8 commit a8251a4

File tree

9 files changed

+372
-201
lines changed

9 files changed

+372
-201
lines changed

README.adoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,22 @@ In your terminal, execute the following command:
143143
This command installs the dependencies listed in [.path]_package.json_ into the [.path]_node_modules/_ folder inside the project.
144144
This folder does not get included in the UI bundle and should _not_ be committed to the source control repository.
145145

146+
In order for the pro Font Awesome icons work, you must first add the following file to the root folder of the UI project:
147+
148+
..npmrc
149+
----
150+
@fortawesome:registry=https://npm.fontawesome.com/
151+
//npm.fontawesome.com/:_authToken=${FONTAWESOME_NPM_AUTH_TOKEN}
152+
----
153+
154+
Then pass the token to npm when running the `npm install` command:
155+
156+
FONTAWESOME_NPM_AUTH_TOKEN=xxxx npm install
157+
158+
If you don't supply this token when installing packages, the pro icons will not be installed, but the UI preview will still work.
159+
Any icon available in the free collection will still be found, but the pro icons will be missing.
160+
When an icon is missing, a missing icon is shown instead.
161+
146162
=== Preview the UI
147163

148164
The UI project is configured to preview offline.

gulp.d/tasks/build-preview-pages.js

Lines changed: 130 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,28 @@
33
const asciidoctor = require('asciidoctor.js')()
44
const fs = require('fs-extra')
55
const handlebars = require('handlebars')
6+
const iconPacks = {
7+
fas: (() => {
8+
try {
9+
return require('@fortawesome/pro-solid-svg-icons')
10+
} catch (e) {
11+
return require('@fortawesome/free-solid-svg-icons')
12+
}
13+
})(),
14+
far: (() => {
15+
try {
16+
return require('@fortawesome/pro-regular-svg-icons')
17+
} catch (e) {
18+
return require('@fortawesome/free-regular-svg-icons')
19+
}
20+
})(),
21+
fab: require('@fortawesome/free-brands-svg-icons'),
22+
}
23+
iconPacks.fa = iconPacks.fas
24+
const iconShims = require('@fortawesome/fontawesome-free/js/v4-shims').reduce((accum, it) => {
25+
accum[it[0]] = [it[1] || 'fas', it[2] || it[0]]
26+
return accum
27+
}, {})
628
const { obj: map } = require('through2')
729
const merge = require('merge-stream')
830
const ospath = require('path')
@@ -21,73 +43,83 @@ module.exports = (src, previewSrc, previewDest, sink = () => map()) => (done) =>
2143
),
2244
])
2345
.then(([baseUiModel, { layouts }]) => [{ ...baseUiModel, env: process.env }, layouts])
24-
.then(([baseUiModel, layouts]) =>
46+
.then(([baseUiModel, layouts, iconDefs = new Map()]) =>
2547
vfs
2648
.src('**/*.adoc', { base: previewSrc, cwd: previewSrc })
2749
.pipe(
28-
map((file, enc, next) => {
29-
const siteRootPath = path.relative(ospath.dirname(file.path), ospath.resolve(previewSrc))
30-
const uiModel = { ...baseUiModel }
31-
uiModel.siteRootPath = siteRootPath
32-
uiModel.siteRootUrl = path.join(siteRootPath, 'index.html')
33-
uiModel.uiRootPath = path.join(siteRootPath, '_')
34-
if (file.stem === '404') {
35-
uiModel.page = { layout: '404', title: 'Page Not Found' }
36-
} else {
37-
const pageModel = (uiModel.page = { ...uiModel.page })
38-
const doc = asciidoctor.load(file.contents, { safe: 'safe', attributes: ASCIIDOC_ATTRIBUTES })
39-
const attributes = doc.getAttributes()
40-
pageModel.layout = doc.getAttribute('page-layout', 'default')
41-
pageModel.title = doc.getDocumentTitle()
42-
pageModel.url = '/' + file.relative.slice(0, -5) + '.html'
43-
if (file.stem === 'home') pageModel.home = true
44-
const componentName = doc.getAttribute('page-component-name', pageModel.src.component)
45-
const versionString = doc.getAttribute(
46-
'page-version',
47-
doc.hasAttribute('page-component-name') ? undefined : pageModel.src.version
48-
)
49-
let component
50-
let componentVersion
51-
if (componentName) {
52-
component = pageModel.component = uiModel.site.components[componentName]
53-
componentVersion = pageModel.componentVersion = versionString
54-
? component.versions.find(({ version }) => version === versionString)
55-
: component.latest
50+
map(
51+
(file, enc, next) => {
52+
const siteRootPath = path.relative(ospath.dirname(file.path), ospath.resolve(previewSrc))
53+
const uiModel = {
54+
...baseUiModel,
55+
preview: true,
56+
siteRootPath,
57+
siteRootUrl: path.join(siteRootPath, 'index.html'),
58+
uiRootPath: path.join(siteRootPath, '_'),
59+
}
60+
if (file.stem === '404') {
61+
uiModel.page = { layout: '404', title: 'Page Not Found' }
5662
} else {
57-
component = pageModel.component = Object.values(uiModel.site.components)[0]
58-
componentVersion = pageModel.componentVersion = component.latest
63+
const pageModel = (uiModel.page = { ...uiModel.page })
64+
const doc = asciidoctor.load(file.contents, { safe: 'safe', attributes: ASCIIDOC_ATTRIBUTES })
65+
const attributes = doc.getAttributes()
66+
pageModel.layout = doc.getAttribute('page-layout', 'default')
67+
pageModel.title = doc.getDocumentTitle()
68+
pageModel.url = '/' + file.relative.slice(0, -5) + '.html'
69+
if (file.stem === 'home') pageModel.home = true
70+
const componentName = doc.getAttribute('page-component-name', pageModel.src.component)
71+
const versionString = doc.getAttribute(
72+
'page-version',
73+
doc.hasAttribute('page-component-name') ? undefined : pageModel.src.version
74+
)
75+
let component
76+
let componentVersion
77+
if (componentName) {
78+
component = pageModel.component = uiModel.site.components[componentName]
79+
componentVersion = pageModel.componentVersion = versionString
80+
? component.versions.find(({ version }) => version === versionString)
81+
: component.latest
82+
} else {
83+
component = pageModel.component = Object.values(uiModel.site.components)[0]
84+
componentVersion = pageModel.componentVersion = component.latest
85+
}
86+
pageModel.module = 'ROOT'
87+
pageModel.relativeSrcPath = file.relative
88+
pageModel.version = componentVersion.version
89+
pageModel.displayVersion = componentVersion.displayVersion
90+
pageModel.editUrl = pageModel.origin.editUrlPattern.replace('%s', file.relative)
91+
pageModel.navigation = componentVersion.navigation || []
92+
pageModel.breadcrumbs = findNavPath(pageModel.url, pageModel.navigation)
93+
if (pageModel.component.versions.length > 1) {
94+
pageModel.versions = pageModel.component.versions.map(
95+
({ version, displayVersion, url }, idx, arr) => {
96+
const pageVersion = { version, displayVersion: displayVersion || version, url }
97+
if (version === component.latest.version) pageVersion.latest = true
98+
if (idx === arr.length - 1) {
99+
delete pageVersion.url
100+
pageVersion.missing = true
101+
}
102+
return pageVersion
103+
}
104+
)
105+
}
106+
pageModel.attributes = Object.entries({ ...attributes, ...componentVersion.asciidoc.attributes })
107+
.filter(([name, val]) => name.startsWith('page-'))
108+
.reduce((accum, [name, val]) => ({ ...accum, [name.substr(5)]: val }), {})
109+
pageModel.contents = Buffer.from(doc.convert())
59110
}
60-
pageModel.module = 'ROOT'
61-
pageModel.relativeSrcPath = file.relative
62-
pageModel.version = componentVersion.version
63-
pageModel.displayVersion = componentVersion.displayVersion
64-
pageModel.editUrl = pageModel.origin.editUrlPattern.replace('%s', file.relative)
65-
pageModel.navigation = componentVersion.navigation || []
66-
pageModel.breadcrumbs = findNavPath(pageModel.url, pageModel.navigation)
67-
if (pageModel.component.versions.length > 1) {
68-
pageModel.versions = pageModel.component.versions.map(({ version, displayVersion, url }, idx, arr) => {
69-
const pageVersion = { version, displayVersion: displayVersion || version, url }
70-
if (version === component.latest.version) pageVersion.latest = true
71-
if (idx === arr.length - 1) {
72-
delete pageVersion.url
73-
pageVersion.missing = true
74-
}
75-
return pageVersion
76-
})
111+
file.extname = '.html'
112+
try {
113+
file.contents = Buffer.from(layouts.get(uiModel.page.layout)(uiModel))
114+
registerIconDefs(iconDefs, file)
115+
next(null, file)
116+
} catch (e) {
117+
next(transformHandlebarsError(e, uiModel.page.layout))
77118
}
78-
pageModel.attributes = Object.entries({ ...attributes, ...componentVersion.asciidoc.attributes })
79-
.filter(([name, val]) => name.startsWith('page-'))
80-
.reduce((accum, [name, val]) => ({ ...accum, [name.substr(5)]: val }), {})
81-
pageModel.contents = Buffer.from(doc.convert())
82-
}
83-
file.extname = '.html'
84-
try {
85-
file.contents = Buffer.from(layouts.get(uiModel.page.layout)(uiModel))
86-
next(null, file)
87-
} catch (e) {
88-
next(transformHandlebarsError(e, uiModel.page.layout))
89-
}
90-
})
119+
},
120+
// NOTE parallel build overwrites default fontawesome-icon-defs.js, so we must use an alternate path
121+
() => writeIconDefs(iconDefs, ospath.join(previewDest, 'fontawesome-icon-defs.js'))
122+
)
91123
)
92124
.pipe(vfs.dest(previewDest))
93125
.on('error', (e) => done)
@@ -183,6 +215,37 @@ function findNavPath (currentUrl, node = [], current_path = [], root = true) {
183215
if (root) return []
184216
}
185217

218+
function registerIconDefs (iconDefs, file) {
219+
const contents = file.contents
220+
if (!contents.includes('<i class="fa')) return
221+
const stringContents = contents.toString()
222+
const iconNames = stringContents.match(/<i class="fa[brs]? fa-[^" ]+/g).map((it) => it.substr(10).replace('fa-', ''))
223+
if (!iconNames.length) return
224+
;[...new Set(iconNames)].reduce((accum, iconKey) => {
225+
if (!accum.has(iconKey)) {
226+
const [iconPrefix, iconName] = iconKey.split(' ').slice(0, 2)
227+
let iconDef = (iconPacks[iconPrefix] || {})['fa' + camelCase(iconName)]
228+
if (iconDef) {
229+
return accum.set(iconKey, { ...iconDef, prefix: iconPrefix })
230+
} else if (iconPrefix === 'fa') {
231+
const [realIconPrefix, realIconName] = iconShims[iconName] || []
232+
if (
233+
!accum.has((iconKey = `${realIconPrefix} ${realIconName}`)) &&
234+
realIconName &&
235+
(iconDef = (iconPacks[realIconPrefix] || {})['fa' + camelCase(realIconName)])
236+
) {
237+
return accum.set(iconKey, { ...iconDef, prefix: realIconPrefix })
238+
}
239+
}
240+
}
241+
return accum
242+
}, iconDefs)
243+
}
244+
245+
async function writeIconDefs (iconDefs, to) {
246+
return fs.writeFile(to, `window.FontAwesomeIconDefs = ${JSON.stringify([...iconDefs.values()])}\n`, 'utf8')
247+
}
248+
186249
function relativize (url) {
187250
return url ? (url.charAt() === '#' ? url : url.slice(1)) : '#'
188251
}
@@ -211,3 +274,9 @@ function toPromise (stream) {
211274
.on('finish', () => resolve(data))
212275
)
213276
}
277+
278+
function camelCase (str) {
279+
return str.replace(/(?:^|-)(.)/g, function (_, l) {
280+
return l.toUpperCase()
281+
})
282+
}

0 commit comments

Comments
 (0)