Skip to content

Commit

Permalink
feat: allow dynamic properties in replaceAttrValues option
Browse files Browse the repository at this point in the history
Closes #205
  • Loading branch information
gregberge committed Nov 4, 2018
1 parent a48ba7b commit 15f55fe
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 44 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -397,6 +397,8 @@ change an icon color to "currentColor" in order to inherit from text color.
| ------- | --------------------------------- | ------------------------------------ |
| `[]` | `--replace-attr-values <old=new>` | `replaceAttrValues: { old: 'new' }>` |

> 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.
### SVG props

Add props to the root SVG tag.
Expand Down
7 changes: 6 additions & 1 deletion packages/babel-plugin-replace-jsx-attribute-value/README.md
Expand Up @@ -15,7 +15,12 @@ npm install --save-dev @svgr/babel-plugin-replace-jsx-attribute-value
"plugins": [
[
"@svgr/babel-plugin-replace-jsx-attribute-value",
{ "values": { "#000": "currentColor" } }
{
"values": [
{ "value": "#000", "newValue": "#fff" },
{ "value": "blue", "newValue": "props.color", "literal": true }
]
}
]
]
}
Expand Down
48 changes: 35 additions & 13 deletions packages/babel-plugin-replace-jsx-attribute-value/src/index.js
@@ -1,15 +1,37 @@
const plugin = ({ types: t }, opts) => ({
visitor: {
JSXAttribute(path) {
const value = path.get('value')
if (!value.isStringLiteral()) return

Object.keys(opts.values).forEach(key => {
if (!value.isStringLiteral({ value: key })) return
value.replaceWith(t.stringLiteral(opts.values[key]))
})
const addJSXAttribute = ({ types: t, template }, opts) => {
function getAttributeValue(value, literal) {
if (typeof value === 'string' && literal) {
return t.jsxExpressionContainer(template.ast(value).expression)
}

if (typeof value === 'string') {
return t.stringLiteral(value)
}

if (typeof value === 'boolean') {
return t.jsxExpressionContainer(t.booleanLiteral(value))
}

if (typeof value === 'number') {
return t.jsxExpressionContainer(t.numericLiteral(value))
}

return null
}

return {
visitor: {
JSXAttribute(path) {
const valuePath = path.get('value')
if (!valuePath.isStringLiteral()) return

opts.values.forEach(({ value, newValue, literal }) => {
if (!valuePath.isStringLiteral({ value })) return
valuePath.replaceWith(getAttributeValue(newValue, literal))
})
},
},
},
})
}
}

export default plugin
export default addJSXAttribute
Expand Up @@ -14,10 +14,14 @@ describe('plugin', () => {
it('should replace attribute values', () => {
expect(
testPlugin('<div something="cool" />', {
values: {
cool: 'not cool',
},
values: [{ value: 'cool', newValue: 'not cool' }],
}),
).toMatchInlineSnapshot(`"<div something=\\"not cool\\" />;"`)

expect(
testPlugin('<div something="cool" />', {
values: [{ value: 'cool', newValue: 'props.color', literal: true }],
}),
).toMatchInlineSnapshot(`"<div something={props.color} />;"`)
})
})
23 changes: 18 additions & 5 deletions packages/babel-preset/src/index.js
Expand Up @@ -7,13 +7,23 @@ import svgEmDimensions from '@svgr/babel-plugin-svg-em-dimensions'
import transformReactNativeSVG from '@svgr/babel-plugin-transform-react-native-svg'
import transformSvgComponent from '@svgr/babel-plugin-transform-svg-component'

function getAttributeValue(value) {
const literal =
typeof value === 'string' && value.startsWith('{') && value.endsWith('}')
return { value: literal ? value.slice(1, -1) : value, literal }
}

function propsToAttributes(props) {
return Object.keys(props).map(name => {
const value = props[name]
const literal =
typeof value === 'string' && value.startsWith('{') && value.endsWith('}')
const { literal, value } = getAttributeValue(props[name])
return { name, literal, value }
})
}

return { name, value: value.slice(1, -1), literal }
function replaceMapToValues(replaceMap) {
return Object.keys(replaceMap).map(value => {
const { literal, value: newValue } = getAttributeValue(replaceMap[value])
return { value, newValue, literal }
})
}

Expand Down Expand Up @@ -65,7 +75,10 @@ const plugin = (api, opts) => {
]

if (opts.replaceAttrValues) {
plugins.push([replaceJSXAttributeValue, { values: opts.replaceAttrValues }])
plugins.push([
replaceJSXAttributeValue,
{ values: replaceMapToValues(opts.replaceAttrValues) },
])
}

if (opts.icon && opts.dimensions) {
Expand Down
23 changes: 22 additions & 1 deletion packages/babel-preset/src/index.test.js
@@ -1,3 +1,4 @@
/* eslint-disable import/no-extraneous-dependencies */
import { transform } from '@babel/core'
import preset from '.'

Expand Down Expand Up @@ -26,7 +27,7 @@ describe('preset', () => {
).toMatchInlineSnapshot(`
"import React from \\"react\\";
const SvgComponent = () => <svg foo=\\"a\\" x={y} />;
const SvgComponent = () => <svg foo=\\"bar\\" x={y} />;
export default SvgComponent;"
`)
Expand All @@ -47,6 +48,26 @@ const SvgComponent = ({
title
}) => <svg><title>{title}</title></svg>;
export default SvgComponent;"
`)
})

it('should handle replaceAttrValues', () => {
expect(
testPreset('<svg a="#000" b="#fff" />', {
replaceAttrValues: {
'#000': 'black',
'#fff': '{props.white}',
},
state: {
componentName: 'SvgComponent',
},
}),
).toMatchInlineSnapshot(`
"import React from \\"react\\";
const SvgComponent = () => <svg a=\\"black\\" b={props.white} />;
export default SvgComponent;"
`)
})
Expand Down
42 changes: 42 additions & 0 deletions packages/core/src/__snapshots__/convert.test.js.snap
Expand Up @@ -207,6 +207,27 @@ export default ForwardRef
"
`;

exports[`convert config should support options { replaceAttrValues: { none: '{black}' } } 1`] = `
"import React from 'react'
const SvgComponent = props => (
<svg width={88} height={88} {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill={black}
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37L37 51M51 51L37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;

exports[`convert config should support options { replaceAttrValues: { none: 'black' } } 1`] = `
"import React from 'react'
Expand All @@ -228,6 +249,27 @@ export default SvgComponent
"
`;

exports[`convert config should support options { svgProps: { a: 'b', b: '{props.b}' } } 1`] = `
"import React from 'react'
const SvgComponent = props => (
<svg width={88} height={88} a=\\"b\\" b={props.b} {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37L37 51M51 51L37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;

exports[`convert config should support options { svgo: false } 1`] = `
"import React from 'react'
Expand Down
38 changes: 19 additions & 19 deletions packages/core/src/convert.test.js
Expand Up @@ -288,25 +288,25 @@ describe('convert', () => {

describe('config', () => {
const configs = [
[{ dimensions: false }],
[{ expandProps: false }],
[{ expandProps: 'start' }],
[{ icon: true }],
[{ native: true }],
[{ native: true, icon: true }],
[{ native: true, expandProps: false }],
[{ native: true, ref: true }],
[{ ref: true }],
[{ replaceAttrValues: { none: 'black' } }],
[{ svgo: false }],
[{ prettier: false }],
[
{
template: ({ template }) =>
template.ast`const noop = () => null; export default noop;`,
},
],
[{ titleProp: true }],
{ dimensions: false },
{ expandProps: false },
{ expandProps: 'start' },
{ icon: true },
{ native: true },
{ native: true, icon: true },
{ native: true, expandProps: false },
{ native: true, ref: true },
{ ref: true },
{ svgProps: { a: 'b', b: '{props.b}' } },
{ replaceAttrValues: { none: 'black' } },
{ replaceAttrValues: { none: '{black}' } },
{ svgo: false },
{ prettier: false },
{
template: ({ template }) =>
template.ast`const noop = () => null; export default noop;`,
},
{ titleProp: true },
]

test.each(configs)('should support options %o', async config => {
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Expand Up @@ -6997,7 +6997,7 @@ preserve@^0.2.0:
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=

prettier@^1.0.0:
prettier@^1.14.3:
version "1.14.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895"
integrity sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg==
Expand Down Expand Up @@ -8230,7 +8230,7 @@ supports-color@^5.3.0:
dependencies:
has-flag "^3.0.0"

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

0 comments on commit 15f55fe

Please sign in to comment.