diff --git a/README.md b/README.md index cd602702..3c014f24 100644 --- a/README.md +++ b/README.md @@ -397,6 +397,8 @@ change an icon color to "currentColor" in order to inherit from text color. | ------- | --------------------------------- | ------------------------------------ | | `[]` | `--replace-attr-values ` | `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. diff --git a/packages/babel-plugin-replace-jsx-attribute-value/README.md b/packages/babel-plugin-replace-jsx-attribute-value/README.md index 5d98b258..3ba0a3dd 100644 --- a/packages/babel-plugin-replace-jsx-attribute-value/README.md +++ b/packages/babel-plugin-replace-jsx-attribute-value/README.md @@ -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 } + ] + } ] ] } diff --git a/packages/babel-plugin-replace-jsx-attribute-value/src/index.js b/packages/babel-plugin-replace-jsx-attribute-value/src/index.js index 53918450..ee870099 100644 --- a/packages/babel-plugin-replace-jsx-attribute-value/src/index.js +++ b/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 diff --git a/packages/babel-plugin-replace-jsx-attribute-value/src/index.test.js b/packages/babel-plugin-replace-jsx-attribute-value/src/index.test.js index 56537c2a..9d6b5312 100644 --- a/packages/babel-plugin-replace-jsx-attribute-value/src/index.test.js +++ b/packages/babel-plugin-replace-jsx-attribute-value/src/index.test.js @@ -14,10 +14,14 @@ describe('plugin', () => { it('should replace attribute values', () => { expect( testPlugin('
', { - values: { - cool: 'not cool', - }, + values: [{ value: 'cool', newValue: 'not cool' }], }), ).toMatchInlineSnapshot(`"
;"`) + + expect( + testPlugin('
', { + values: [{ value: 'cool', newValue: 'props.color', literal: true }], + }), + ).toMatchInlineSnapshot(`"
;"`) }) }) diff --git a/packages/babel-preset/src/index.js b/packages/babel-preset/src/index.js index 78c05371..cc1c40a7 100644 --- a/packages/babel-preset/src/index.js +++ b/packages/babel-preset/src/index.js @@ -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 } }) } @@ -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) { diff --git a/packages/babel-preset/src/index.test.js b/packages/babel-preset/src/index.test.js index f5794050..857ff534 100644 --- a/packages/babel-preset/src/index.test.js +++ b/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 '.' @@ -26,7 +27,7 @@ describe('preset', () => { ).toMatchInlineSnapshot(` "import React from \\"react\\"; -const SvgComponent = () => ; +const SvgComponent = () => ; export default SvgComponent;" `) @@ -47,6 +48,26 @@ const SvgComponent = ({ title }) => {title}; +export default SvgComponent;" +`) + }) + + it('should handle replaceAttrValues', () => { + expect( + testPreset('', { + replaceAttrValues: { + '#000': 'black', + '#fff': '{props.white}', + }, + state: { + componentName: 'SvgComponent', + }, + }), + ).toMatchInlineSnapshot(` +"import React from \\"react\\"; + +const SvgComponent = () => ; + export default SvgComponent;" `) }) diff --git a/packages/core/src/__snapshots__/convert.test.js.snap b/packages/core/src/__snapshots__/convert.test.js.snap index 1e8ed37a..e23891ce 100644 --- a/packages/core/src/__snapshots__/convert.test.js.snap +++ b/packages/core/src/__snapshots__/convert.test.js.snap @@ -207,6 +207,27 @@ export default ForwardRef " `; +exports[`convert config should support options { replaceAttrValues: { none: '{black}' } } 1`] = ` +"import React from 'react' + +const SvgComponent = props => ( + + + + + +) + +export default SvgComponent +" +`; + exports[`convert config should support options { replaceAttrValues: { none: 'black' } } 1`] = ` "import React from 'react' @@ -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 => ( + + + + + +) + +export default SvgComponent +" +`; + exports[`convert config should support options { svgo: false } 1`] = ` "import React from 'react' diff --git a/packages/core/src/convert.test.js b/packages/core/src/convert.test.js index d56f7138..afdf2406 100644 --- a/packages/core/src/convert.test.js +++ b/packages/core/src/convert.test.js @@ -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 => { diff --git a/yarn.lock b/yarn.lock index a5450ef9..c2b62136 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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== @@ -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==