Skip to content

Commit 7623a1d

Browse files
committed
Replace the react-liquid icon injection hack with an esbuild module
1 parent 405dc83 commit 7623a1d

File tree

5 files changed

+88
-12
lines changed

5 files changed

+88
-12
lines changed

.eleventy.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import slugify from 'slugify';
22
import markdownIt from 'markdown-it';
33
import yaml from 'js-yaml';
44
import fs from 'fs';
5+
import path from 'path';
56

67
export default function (eleventyConfig) {
78
// Get baseurl from environment or use default
@@ -49,7 +50,18 @@ export default function (eleventyConfig) {
4950

5051
eleventyConfig.addFilter('markdownify', function (content) {
5152
if (!content) return '';
52-
return md.render(content);
53+
const iconsDir = path.resolve('docs/_includes/icons');
54+
const withIcons = content.replace(
55+
/{%\s+include\s+\/?icons\/([\w-]+)\.svg\s+%}/g,
56+
(match, icon) => {
57+
const iconPath = path.join(iconsDir, `${icon}.svg`);
58+
if (!fs.existsSync(iconPath)) return match;
59+
return fs
60+
.readFileSync(iconPath, { encoding: 'utf8', flag: 'r' })
61+
.trim();
62+
},
63+
);
64+
return md.render(withIcons);
5365
});
5466

5567
// Custom collection for pages by section

docs/assets/js/admin/widgets/ReactLiquid.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Liquid } from 'liquidjs';
44
import { marked } from 'marked';
55
import slugifyLib from 'slugify';
66
import { encode } from 'html-entities';
7+
import icons from 'cfpb-icons';
78

89
const engine = new Liquid({ dynamicPartials: false });
910

@@ -15,7 +16,11 @@ engine.registerFilter('slugify', (val) => {
1516
});
1617
engine.registerFilter('markdownify', (val) => {
1718
if (!val) return '';
18-
return marked.parse(val);
19+
const withIcons = val.replace(
20+
/{%\s+include\s+\/?icons\/([\w-]+)\.svg\s+%}/g,
21+
(match, icon) => icons[`${icon}.svg`] || match,
22+
);
23+
return marked.parse(withIcons);
1924
});
2025

2126
engine.registerFilter('xml_escape', (val) => {
@@ -38,8 +43,35 @@ export default function ReactLiquid({ template, data }) {
3843
const [html, setHtml] = useState('');
3944

4045
useEffect(() => {
46+
const replaceIconsInValue = (value) => {
47+
if (typeof value === 'string') {
48+
return value.replace(
49+
/{%\s+include\s+\/?icons\/([\w-]+)\.svg\s+%}/g,
50+
(match, icon) => icons[`${icon}.svg`] || match,
51+
);
52+
}
53+
if (Array.isArray(value)) {
54+
return value.map((item) => replaceIconsInValue(item));
55+
}
56+
if (value && typeof value === 'object') {
57+
return Object.fromEntries(
58+
Object.entries(value).map(([key, val]) => [
59+
key,
60+
replaceIconsInValue(val),
61+
]),
62+
);
63+
}
64+
return value;
65+
};
66+
67+
const dataWithIcons = replaceIconsInValue(data);
68+
const templateWithIcons = template.replace(
69+
/{%\s+include\s+\/?icons\/([\w-]+)\.svg\s+%}/g,
70+
(match, icon) => icons[`${icon}.svg`] || match,
71+
);
72+
4173
engine
42-
.parseAndRender(template, data)
74+
.parseAndRender(templateWithIcons, dataWithIcons)
4375
.then((rendered) => setHtml(rendered))
4476
.catch((err) => {
4577
console.error('Liquid rendering error:', err);

docs/assets/js/admin/widgets/pagePreviewTemplate.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@ import {
88
import Tabs from '../../../../assets/js/tabs.js';
99
import template from '../../../../_includes/variation-content.html';
1010

11-
// react-liquid (https://github.com/aquibm/react-liquid/) isn't able to `include` other files so we
12-
// replace instances of {% include icons/XXXXX.svg %} with the inlined SVG
13-
const templateWithIcons = template.replace(
14-
/{%\s+include\s+\/?icons\/([\w-]+)\.svg\s+%}/g,
15-
(match, icon) => import(`../../../../_includes/icons/${icon}.svg`),
16-
);
17-
1811
export default class Preview extends Component {
1912
constructor(props) {
2013
super(props);
@@ -55,7 +48,7 @@ export default class Preview extends Component {
5548
// TODO: We're breaking some a11y here by making the whole page clickable.
5649
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
5750
<div ref={this.containerRef} onClick={(event) => this.handleClick(event)}>
58-
<ReactLiquid template={templateWithIcons} data={data} html />
51+
<ReactLiquid template={template} data={data} html />
5952
</div>
6053
);
6154
}

docs/special-pages/home.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,4 @@ All content, apart from CFPB trademarked properties, has been released as open s
141141

142142
<h4 class="h5">Trademark notice</h4>
143143

144-
## <p>The CFPB’s logo and the standard characters Consumer Financial Protection Bureau, CFPB, Know Before You Owe, and Money as You Grow are registered trademarks owned by the CFPB. Nothing on this website shall be construed as granting any license to use any trademark displayed on the website without the express written permission of the CFPB. Your use of these registered trademarks must comply with intellectual property laws. You may not use the CFPB trademarks to state or imply an association with or endorsement of your goods, services, or activities, nor in any manner that infringes upon the CFPB trademarks. Requests to use the CFPB trademarks should be made to the Office of the General Counsel, <a href="mailto:cfpb_ip@cfpb.gov">cfpb_ip@cfpb.gov</a>.</p>
144+
<p>The CFPB’s logo and the standard characters Consumer Financial Protection Bureau, CFPB, Know Before You Owe, and Money as You Grow are registered trademarks owned by the CFPB. Nothing on this website shall be construed as granting any license to use any trademark displayed on the website without the express written permission of the CFPB. Your use of these registered trademarks must comply with intellectual property laws. You may not use the CFPB trademarks to state or imply an association with or endorsement of your goods, services, or activities, nor in any manner that infringes upon the CFPB trademarks. Requests to use the CFPB trademarks should be made to the Office of the General Counsel, <a href="mailto:cfpb_ip@cfpb.gov">cfpb_ip@cfpb.gov</a>.</p>

esbuild/plugins/plugin-esbuild-liquid.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from 'fs';
2+
import path from 'path';
23

34
const pluginEsbuildLiquid = {
45
name: 'liquid',
@@ -12,11 +13,49 @@ const pluginEsbuildLiquid = {
1213
};
1314
});
1415

16+
build.onResolve({ filter: /^cfpb-icons$/ }, () => {
17+
return {
18+
path: 'cfpb-icons',
19+
namespace: 'icons-map',
20+
};
21+
});
22+
23+
build.onLoad({ filter: /.*/, namespace: 'icons-map' }, () => {
24+
const iconsDir = path.resolve(process.cwd(), 'docs/_includes/icons');
25+
const files = fs.existsSync(iconsDir) ? fs.readdirSync(iconsDir) : [];
26+
const icons = {};
27+
28+
for (const file of files) {
29+
if (!file.endsWith('.svg')) continue;
30+
const iconPath = path.join(iconsDir, file);
31+
icons[file] = fs
32+
.readFileSync(iconPath, { encoding: 'utf8', flag: 'r' })
33+
.trim();
34+
}
35+
36+
return {
37+
contents: `export default ${JSON.stringify(icons)};`,
38+
loader: 'js',
39+
};
40+
});
41+
1542
build.onLoad({ filter: /.*\.html$/, namespace: 'liquid' }, async (args) => {
1643
let contents = fs
1744
.readFileSync(args.path, { encoding: 'utf8', flag: 'r' })
1845
.trim();
1946

47+
const iconsDir = path.resolve(process.cwd(), 'docs/_includes/icons');
48+
contents = contents.replace(
49+
/{%\s+include\s+\/?icons\/([\w-]+)\.svg\s+%}/g,
50+
(match, icon) => {
51+
const iconPath = path.join(iconsDir, `${icon}.svg`);
52+
if (!fs.existsSync(iconPath)) return match;
53+
return fs
54+
.readFileSync(iconPath, { encoding: 'utf8', flag: 'r' })
55+
.trim();
56+
},
57+
);
58+
2059
if (build.initialOptions && build.initialOptions.minify) {
2160
contents = contents
2261
.replace(/>[\r\n ]+</g, '><')

0 commit comments

Comments
 (0)