Skip to content

Commit c097ac7

Browse files
authored
fix(APP-993): Prefix SVG asset ids to prevent url(#id) collisions between inlined icons and illustrations (#723)
Signed-off-by: Milos Dzepina <milos@aragon.org>
1 parent 576b552 commit c097ac7

6 files changed

Lines changed: 52 additions & 14 deletions

File tree

.changeset/giant-oranges-like.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@aragon/gov-ui-kit": patch
3+
---
4+
5+
Fix SVG build config with missing `prefixIds` plugin

.storybook/main.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { RollupOptions } from 'rollup';
55
import { mergeConfig } from 'vite';
66
import { viteStaticCopy } from 'vite-plugin-static-copy';
77
import svgr from 'vite-plugin-svgr';
8+
import svgoConfig from '../svgo.config.js';
89

910
const config: StorybookConfig = {
1011
stories: ['../docs/**/*.@(md|mdx)', '../src/**/*.stories.@(js|jsx|ts|tsx)', '../src/**/*.@(md|mdx)'],
@@ -24,7 +25,12 @@ const config: StorybookConfig = {
2425
viteFinal: (viteConfig) => {
2526
// Add source-map-js alias and plugins for importing svg files and copying fonts
2627
const plugins = [
27-
svgr({ include: '**/*.svg' }),
28+
// Run the same SVGO pass (including id namespacing, see svgo.config.js) as the
29+
// published library build, so Storybook previews match production markup.
30+
svgr({
31+
include: '**/*.svg',
32+
svgrOptions: { plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'], svgoConfig },
33+
}),
2834
viteStaticCopy({ targets: [{ src: './src/theme/fonts/*.ttf', dest: './fonts' }] }),
2935
];
3036
const resolve = { alias: { 'source-map-js': 'source-map' } };

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@
104104
"@rollup/plugin-typescript": "^12.3.0",
105105
"@storybook/addon-docs": "^10.4.6",
106106
"@storybook/react-vite": "^10.4.6",
107+
"@svgr/plugin-jsx": "^8.1.0",
108+
"@svgr/plugin-svgo": "^8.1.0",
107109
"@svgr/rollup": "^8.1.0",
108110
"@tailwindcss/postcss": "^4.3.1",
109111
"@tailwindcss/typography": "^0.5.20",
@@ -133,6 +135,7 @@
133135
"rollup-plugin-postcss": "^4.0.2",
134136
"rollup-plugin-visualizer": "^7.0.1",
135137
"storybook": "^10.4.6",
138+
"svgo": "^3.3.3",
136139
"tailwindcss": "^4.3.1",
137140
"ts-jest": "^29.4.11",
138141
"tslib": "^2.8.1",

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rollup.config.mjs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import svgr from '@svgr/rollup';
88
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
99
import postcss from 'rollup-plugin-postcss';
1010
import { visualizer } from 'rollup-plugin-visualizer';
11+
import svgoConfig from './svgo.config.js';
1112

1213
const require = createRequire(import.meta.url);
1314
const tsConfig = require('./tsconfig.json');
@@ -52,19 +53,7 @@ export default [
5253
],
5354
}),
5455
images({ include: ['**/*.png', '**/*.jpg'] }),
55-
svgr({
56-
// Keep the viewBox so icons scale instead of getting clipped when
57-
// rendered at a size other than their intrinsic 16px (e.g. size-3 in
58-
// small buttons). SVGO's preset-default removes it by default.
59-
svgoConfig: {
60-
plugins: [
61-
{
62-
name: 'preset-default',
63-
params: { overrides: { removeViewBox: false } },
64-
},
65-
],
66-
},
67-
}),
56+
svgr({ svgoConfig }),
6857
terser(),
6958
],
7059
},

svgo.config.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Shared SVGO config used by the Rollup library build (rollup.config.mjs), the
2+
// Storybook/dev Vite build (.storybook/main.ts) and the svgIds test, so icon/illustration
3+
// SVGs are optimized identically everywhere and this file is the single source of truth
4+
// for the id collision fix below. Authored as CommonJS so all three can import it.
5+
/** @type {import('svgo').Config} */
6+
module.exports = {
7+
plugins: [
8+
{
9+
name: 'preset-default',
10+
params: {
11+
overrides: {
12+
// Keep the viewBox so icons scale instead of getting clipped when
13+
// rendered at a size other than their intrinsic 16px (e.g. size-3 in
14+
// small buttons). SVGO's preset-default removes it by default.
15+
removeViewBox: false,
16+
},
17+
},
18+
},
19+
// Icons/illustrations are inlined as <svg> markup in the DOM, so `<mask id="a">`,
20+
// `<clipPath id="a">`, etc. resolve against the whole document's id space, not
21+
// per-<svg>. Without this, every source file that (independently) authors the
22+
// same short id collides with every other one as soon as two such components are
23+
// rendered on the same page, and `url(#a)` silently resolves to the wrong element.
24+
'prefixIds',
25+
],
26+
};

0 commit comments

Comments
 (0)