Skip to content

Commit 15f55fe

Browse files
committed
feat: allow dynamic properties in replaceAttrValues option
Closes #205
1 parent a48ba7b commit 15f55fe

File tree

9 files changed

+153
-44
lines changed

9 files changed

+153
-44
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,8 @@ change an icon color to "currentColor" in order to inherit from text color.
397397
| ------- | --------------------------------- | ------------------------------------ |
398398
| `[]` | `--replace-attr-values <old=new>` | `replaceAttrValues: { old: 'new' }>` |
399399

400+
> You can specify dynamic property using curly braces: `{ '#000': "{props.color}" }` or `--replace-attr-values #000={props.color}`. It is particulary useful with a custom template.
401+
400402
### SVG props
401403

402404
Add props to the root SVG tag.

packages/babel-plugin-replace-jsx-attribute-value/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ npm install --save-dev @svgr/babel-plugin-replace-jsx-attribute-value
1515
"plugins": [
1616
[
1717
"@svgr/babel-plugin-replace-jsx-attribute-value",
18-
{ "values": { "#000": "currentColor" } }
18+
{
19+
"values": [
20+
{ "value": "#000", "newValue": "#fff" },
21+
{ "value": "blue", "newValue": "props.color", "literal": true }
22+
]
23+
}
1924
]
2025
]
2126
}
Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,37 @@
1-
const plugin = ({ types: t }, opts) => ({
2-
visitor: {
3-
JSXAttribute(path) {
4-
const value = path.get('value')
5-
if (!value.isStringLiteral()) return
6-
7-
Object.keys(opts.values).forEach(key => {
8-
if (!value.isStringLiteral({ value: key })) return
9-
value.replaceWith(t.stringLiteral(opts.values[key]))
10-
})
1+
const addJSXAttribute = ({ types: t, template }, opts) => {
2+
function getAttributeValue(value, literal) {
3+
if (typeof value === 'string' && literal) {
4+
return t.jsxExpressionContainer(template.ast(value).expression)
5+
}
6+
7+
if (typeof value === 'string') {
8+
return t.stringLiteral(value)
9+
}
10+
11+
if (typeof value === 'boolean') {
12+
return t.jsxExpressionContainer(t.booleanLiteral(value))
13+
}
14+
15+
if (typeof value === 'number') {
16+
return t.jsxExpressionContainer(t.numericLiteral(value))
17+
}
18+
19+
return null
20+
}
21+
22+
return {
23+
visitor: {
24+
JSXAttribute(path) {
25+
const valuePath = path.get('value')
26+
if (!valuePath.isStringLiteral()) return
27+
28+
opts.values.forEach(({ value, newValue, literal }) => {
29+
if (!valuePath.isStringLiteral({ value })) return
30+
valuePath.replaceWith(getAttributeValue(newValue, literal))
31+
})
32+
},
1133
},
12-
},
13-
})
34+
}
35+
}
1436

15-
export default plugin
37+
export default addJSXAttribute

packages/babel-plugin-replace-jsx-attribute-value/src/index.test.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ describe('plugin', () => {
1414
it('should replace attribute values', () => {
1515
expect(
1616
testPlugin('<div something="cool" />', {
17-
values: {
18-
cool: 'not cool',
19-
},
17+
values: [{ value: 'cool', newValue: 'not cool' }],
2018
}),
2119
).toMatchInlineSnapshot(`"<div something=\\"not cool\\" />;"`)
20+
21+
expect(
22+
testPlugin('<div something="cool" />', {
23+
values: [{ value: 'cool', newValue: 'props.color', literal: true }],
24+
}),
25+
).toMatchInlineSnapshot(`"<div something={props.color} />;"`)
2226
})
2327
})

packages/babel-preset/src/index.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,23 @@ import svgEmDimensions from '@svgr/babel-plugin-svg-em-dimensions'
77
import transformReactNativeSVG from '@svgr/babel-plugin-transform-react-native-svg'
88
import transformSvgComponent from '@svgr/babel-plugin-transform-svg-component'
99

10+
function getAttributeValue(value) {
11+
const literal =
12+
typeof value === 'string' && value.startsWith('{') && value.endsWith('}')
13+
return { value: literal ? value.slice(1, -1) : value, literal }
14+
}
15+
1016
function propsToAttributes(props) {
1117
return Object.keys(props).map(name => {
12-
const value = props[name]
13-
const literal =
14-
typeof value === 'string' && value.startsWith('{') && value.endsWith('}')
18+
const { literal, value } = getAttributeValue(props[name])
19+
return { name, literal, value }
20+
})
21+
}
1522

16-
return { name, value: value.slice(1, -1), literal }
23+
function replaceMapToValues(replaceMap) {
24+
return Object.keys(replaceMap).map(value => {
25+
const { literal, value: newValue } = getAttributeValue(replaceMap[value])
26+
return { value, newValue, literal }
1727
})
1828
}
1929

@@ -65,7 +75,10 @@ const plugin = (api, opts) => {
6575
]
6676

6777
if (opts.replaceAttrValues) {
68-
plugins.push([replaceJSXAttributeValue, { values: opts.replaceAttrValues }])
78+
plugins.push([
79+
replaceJSXAttributeValue,
80+
{ values: replaceMapToValues(opts.replaceAttrValues) },
81+
])
6982
}
7083

7184
if (opts.icon && opts.dimensions) {

packages/babel-preset/src/index.test.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
12
import { transform } from '@babel/core'
23
import preset from '.'
34

@@ -26,7 +27,7 @@ describe('preset', () => {
2627
).toMatchInlineSnapshot(`
2728
"import React from \\"react\\";
2829
29-
const SvgComponent = () => <svg foo=\\"a\\" x={y} />;
30+
const SvgComponent = () => <svg foo=\\"bar\\" x={y} />;
3031
3132
export default SvgComponent;"
3233
`)
@@ -47,6 +48,26 @@ const SvgComponent = ({
4748
title
4849
}) => <svg><title>{title}</title></svg>;
4950
51+
export default SvgComponent;"
52+
`)
53+
})
54+
55+
it('should handle replaceAttrValues', () => {
56+
expect(
57+
testPreset('<svg a="#000" b="#fff" />', {
58+
replaceAttrValues: {
59+
'#000': 'black',
60+
'#fff': '{props.white}',
61+
},
62+
state: {
63+
componentName: 'SvgComponent',
64+
},
65+
}),
66+
).toMatchInlineSnapshot(`
67+
"import React from \\"react\\";
68+
69+
const SvgComponent = () => <svg a=\\"black\\" b={props.white} />;
70+
5071
export default SvgComponent;"
5172
`)
5273
})

packages/core/src/__snapshots__/convert.test.js.snap

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,27 @@ export default ForwardRef
207207
"
208208
`;
209209

210+
exports[`convert config should support options { replaceAttrValues: { none: '{black}' } } 1`] = `
211+
"import React from 'react'
212+
213+
const SvgComponent = props => (
214+
<svg width={88} height={88} {...props}>
215+
<g
216+
stroke=\\"#063855\\"
217+
strokeWidth={2}
218+
fill={black}
219+
fillRule=\\"evenodd\\"
220+
strokeLinecap=\\"square\\"
221+
>
222+
<path d=\\"M51 37L37 51M51 51L37 37\\" />
223+
</g>
224+
</svg>
225+
)
226+
227+
export default SvgComponent
228+
"
229+
`;
230+
210231
exports[`convert config should support options { replaceAttrValues: { none: 'black' } } 1`] = `
211232
"import React from 'react'
212233
@@ -228,6 +249,27 @@ export default SvgComponent
228249
"
229250
`;
230251

252+
exports[`convert config should support options { svgProps: { a: 'b', b: '{props.b}' } } 1`] = `
253+
"import React from 'react'
254+
255+
const SvgComponent = props => (
256+
<svg width={88} height={88} a=\\"b\\" b={props.b} {...props}>
257+
<g
258+
stroke=\\"#063855\\"
259+
strokeWidth={2}
260+
fill=\\"none\\"
261+
fillRule=\\"evenodd\\"
262+
strokeLinecap=\\"square\\"
263+
>
264+
<path d=\\"M51 37L37 51M51 51L37 37\\" />
265+
</g>
266+
</svg>
267+
)
268+
269+
export default SvgComponent
270+
"
271+
`;
272+
231273
exports[`convert config should support options { svgo: false } 1`] = `
232274
"import React from 'react'
233275

packages/core/src/convert.test.js

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -288,25 +288,25 @@ describe('convert', () => {
288288

289289
describe('config', () => {
290290
const configs = [
291-
[{ dimensions: false }],
292-
[{ expandProps: false }],
293-
[{ expandProps: 'start' }],
294-
[{ icon: true }],
295-
[{ native: true }],
296-
[{ native: true, icon: true }],
297-
[{ native: true, expandProps: false }],
298-
[{ native: true, ref: true }],
299-
[{ ref: true }],
300-
[{ replaceAttrValues: { none: 'black' } }],
301-
[{ svgo: false }],
302-
[{ prettier: false }],
303-
[
304-
{
305-
template: ({ template }) =>
306-
template.ast`const noop = () => null; export default noop;`,
307-
},
308-
],
309-
[{ titleProp: true }],
291+
{ dimensions: false },
292+
{ expandProps: false },
293+
{ expandProps: 'start' },
294+
{ icon: true },
295+
{ native: true },
296+
{ native: true, icon: true },
297+
{ native: true, expandProps: false },
298+
{ native: true, ref: true },
299+
{ ref: true },
300+
{ svgProps: { a: 'b', b: '{props.b}' } },
301+
{ replaceAttrValues: { none: 'black' } },
302+
{ replaceAttrValues: { none: '{black}' } },
303+
{ svgo: false },
304+
{ prettier: false },
305+
{
306+
template: ({ template }) =>
307+
template.ast`const noop = () => null; export default noop;`,
308+
},
309+
{ titleProp: true },
310310
]
311311

312312
test.each(configs)('should support options %o', async config => {

yarn.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6997,7 +6997,7 @@ preserve@^0.2.0:
69976997
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
69986998
integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
69996999

7000-
prettier@^1.0.0:
7000+
prettier@^1.14.3:
70017001
version "1.14.3"
70027002
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895"
70037003
integrity sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg==
@@ -8230,7 +8230,7 @@ supports-color@^5.3.0:
82308230
dependencies:
82318231
has-flag "^3.0.0"
82328232

8233-
svgo@^1.0.0:
8233+
svgo@^1.1.1:
82348234
version "1.1.1"
82358235
resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985"
82368236
integrity sha512-GBkJbnTuFpM4jFbiERHDWhZc/S/kpHToqmZag3aEBjPYK44JAN2QBjvrGIxLOoCyMZjuFQIfTO2eJd8uwLY/9g==

0 commit comments

Comments
 (0)