From 3c0b7793afe082146f12dc463ab9b20e465dcd7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Berg=C3=A9?= Date: Sat, 13 Nov 2021 09:29:23 +0100 Subject: [PATCH] feat: allow to specify icon size BREAKING CHANGE: using --icon as latest arg now requires "--" Fix #571 --- .../babel-plugin-svg-em-dimensions/README.md | 4 +- .../src/index.test.ts | 24 ++++- .../src/index.ts | 35 +++++-- packages/babel-preset/src/index.test.ts | 44 ++++++++- packages/babel-preset/src/index.ts | 15 ++- .../cli/src/__snapshots__/index.test.ts.snap | 44 ++++++++- packages/cli/src/index.test.ts | 4 +- packages/cli/src/index.ts | 11 ++- .../src/__snapshots__/transform.test.ts.snap | 96 +++++++++++++++---- packages/core/src/config.ts | 2 +- packages/core/src/transform.test.ts | 4 +- 11 files changed, 237 insertions(+), 46 deletions(-) diff --git a/packages/babel-plugin-svg-em-dimensions/README.md b/packages/babel-plugin-svg-em-dimensions/README.md index 215c1d02..b7759641 100644 --- a/packages/babel-plugin-svg-em-dimensions/README.md +++ b/packages/babel-plugin-svg-em-dimensions/README.md @@ -12,7 +12,9 @@ npm install --save-dev @svgr/babel-plugin-svg-em-dimensions ```json { - "plugins": ["@svgr/babel-plugin-svg-em-dimensions"] + "plugins": [ + ["@svgr/babel-plugin-svg-em-dimensions", { "width": 24, "height": 24 }] + ] } ``` diff --git a/packages/babel-plugin-svg-em-dimensions/src/index.test.ts b/packages/babel-plugin-svg-em-dimensions/src/index.test.ts index e687647d..5e9d96d6 100644 --- a/packages/babel-plugin-svg-em-dimensions/src/index.test.ts +++ b/packages/babel-plugin-svg-em-dimensions/src/index.test.ts @@ -1,9 +1,9 @@ import { transform } from '@babel/core' -import plugin from '.' +import plugin, { Options } from '.' -const testPlugin = (code: string) => { +const testPlugin = (code: string, options?: Options) => { const result = transform(code, { - plugins: ['@babel/plugin-syntax-jsx', plugin], + plugins: ['@babel/plugin-syntax-jsx', [plugin, options]], configFile: false, }) @@ -11,7 +11,7 @@ const testPlugin = (code: string) => { } describe('plugin', () => { - it('should replace width / height attributes', () => { + it('replaces width / height attributes', () => { expect( testPlugin(''), ).toMatchInlineSnapshot( @@ -19,9 +19,23 @@ describe('plugin', () => { ) }) - it('should add theme if they are not present', () => { + it('adds theme if they are not present', () => { expect(testPlugin('')).toMatchInlineSnapshot( `";"`, ) }) + + it('accepts numeric values', () => { + expect( + testPlugin('', { width: 24, height: 24 }), + ).toMatchInlineSnapshot(`";"`) + }) + + it('accepts string values', () => { + expect( + testPlugin('', { width: '2em', height: '2em' }), + ).toMatchInlineSnapshot( + `";"`, + ) + }) }) diff --git a/packages/babel-plugin-svg-em-dimensions/src/index.ts b/packages/babel-plugin-svg-em-dimensions/src/index.ts index 0b24accc..7ef835df 100644 --- a/packages/babel-plugin-svg-em-dimensions/src/index.ts +++ b/packages/babel-plugin-svg-em-dimensions/src/index.ts @@ -1,10 +1,26 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { types as t, NodePath } from '@babel/core' +import { types as t, NodePath, ConfigAPI } from '@babel/core' const elements = ['svg', 'Svg'] -const value = t.stringLiteral('1em') -const plugin = () => ({ +export interface Options { + width: number | string + height: number | string +} + +const getValue = (raw: undefined | number | string) => { + if (raw === undefined) return t.stringLiteral('1em') + switch (typeof raw) { + case 'number': + return t.jsxExpressionContainer(t.numericLiteral(raw)) + case 'string': + return t.stringLiteral(raw) + default: + return t.stringLiteral('1em') + } +} + +const plugin = (_: ConfigAPI, opts: Options) => ({ visitor: { JSXOpeningElement(path: NodePath) { if ( @@ -14,7 +30,11 @@ const plugin = () => ({ ) return - const requiredAttributes = ['width', 'height'] + const values = { + width: getValue(opts.width), + height: getValue(opts.height), + } + const requiredAttributes = Object.keys(values) path.get('attributes').forEach((attributePath) => { if (!attributePath.isJSXAttribute()) return @@ -25,14 +45,17 @@ const plugin = () => ({ if (index === -1) return const valuePath = attributePath.get('value') - valuePath.replaceWith(value) + valuePath.replaceWith(values[namePath.node.name as 'width' | 'height']) requiredAttributes.splice(index, 1) }) path.pushContainer( 'attributes', requiredAttributes.map((attr) => - t.jsxAttribute(t.jsxIdentifier(attr), value), + t.jsxAttribute( + t.jsxIdentifier(attr), + values[attr as 'width' | 'height'], + ), ), ) }, diff --git a/packages/babel-preset/src/index.test.ts b/packages/babel-preset/src/index.test.ts index cd703f29..90073f5a 100644 --- a/packages/babel-preset/src/index.test.ts +++ b/packages/babel-preset/src/index.test.ts @@ -17,7 +17,7 @@ const testPreset = (code: string, options: Partial) => { } describe('preset', () => { - it('should handle svgProps', () => { + it('handles svgProps', () => { expect( testPreset('', { svgProps: { @@ -34,7 +34,7 @@ describe('preset', () => { `) }) - it('should handle titleProp', () => { + it('handles titleProp', () => { expect( testPreset('', { titleProp: true, @@ -50,7 +50,7 @@ describe('preset', () => { export default SvgComponent;" `) }) - it('should handle titleProp and fallback on existing title', () => { + it('handles titleProp and fallback on existing title', () => { // testing when existing title has string as chilren expect( testPreset(`Hello`, { @@ -83,7 +83,7 @@ describe('preset', () => { `) }) - it('should handle replaceAttrValues', () => { + it('handles replaceAttrValues', () => { expect( testPreset('', { replaceAttrValues: { @@ -100,7 +100,7 @@ describe('preset', () => { `) }) - it('should handle expandProps & icon & dimensions', () => { + it('handles expandProps & icon & dimensions', () => { expect( testPreset('', { expandProps: 'end', @@ -115,4 +115,38 @@ describe('preset', () => { export default SvgComponent;" `) }) + + it('handles custom icon size', () => { + expect( + testPreset('', { + expandProps: 'end', + icon: 24, + dimensions: true, + }), + ).toMatchInlineSnapshot(` + "import * as React from \\"react\\"; + + const SvgComponent = props => ; + + export default SvgComponent;" + `) + }) + + it('defaults to 24 on native', () => { + expect( + testPreset('', { + expandProps: 'end', + icon: true, + native: true, + dimensions: true, + }), + ).toMatchInlineSnapshot(` + "import * as React from \\"react\\"; + import Svg from \\"react-native-svg\\"; + + const SvgComponent = props => ; + + export default SvgComponent;" + `) + }) }) diff --git a/packages/babel-preset/src/index.ts b/packages/babel-preset/src/index.ts index 66501915..5405a032 100644 --- a/packages/babel-preset/src/index.ts +++ b/packages/babel-preset/src/index.ts @@ -20,7 +20,7 @@ export interface Options extends TransformOptions { titleProp?: boolean expandProps?: boolean | 'start' | 'end' dimensions?: boolean - icon?: boolean + icon?: boolean | string | number native?: boolean svgProps?: { [key: string]: string } replaceAttrValues?: { [key: string]: string } @@ -96,7 +96,18 @@ const plugin = (_: ConfigAPI, opts: Options) => { const plugins: any[] = [ [transformSvgComponent, opts], - ...(opts.icon && opts.dimensions ? [svgEmDimensions] : []), + ...(opts.icon !== false && opts.dimensions + ? [ + [ + svgEmDimensions, + opts.icon !== true + ? { width: opts.icon, height: opts.icon } + : opts.native + ? { width: 24, height: 24 } + : {}, + ], + ] + : []), [ removeJSXAttribute, { elements: ['svg', 'Svg'], attributes: toRemoveAttributes }, diff --git a/packages/cli/src/__snapshots__/index.test.ts.snap b/packages/cli/src/__snapshots__/index.test.ts.snap index 7004f9df..dc21e590 100644 --- a/packages/cli/src/__snapshots__/index.test.ts.snap +++ b/packages/cli/src/__snapshots__/index.test.ts.snap @@ -203,6 +203,46 @@ export default SvgFile " `; +exports[`cli should support various args: --icon 2em 1`] = ` +"import * as React from 'react' + +const SvgFile = (props) => ( + + + +) + +export default SvgFile + +" +`; + +exports[`cli should support various args: --icon 24 1`] = ` +"import * as React from 'react' + +const SvgFile = (props) => ( + + + +) + +export default SvgFile + +" +`; + exports[`cli should support various args: --jsx-runtime automatic 1`] = ` "const SvgFile = (props) => ( @@ -250,8 +290,8 @@ import Svg, { Path } from 'react-native-svg' const SvgFile = (props) => ( { ['--expand-props none'], ['--expand-props start'], ['--icon'], + ['--icon 24'], + ['--icon 2em'], ['--native'], ['--native --icon'], ['--native --expand-props none'], @@ -135,7 +137,7 @@ describe('cli', () => { ])( 'should support various args', async (args) => { - const result = await cli(`${args} __fixtures__/simple/file.svg`) + const result = await cli(`${args} -- __fixtures__/simple/file.svg`) expect(result).toMatchSnapshot(args) }, 10000, diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 8c8aec1a..bee4a0b9 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -68,6 +68,11 @@ const parseTemplate = (name: string) => (arg: string) => { } } +const parseIconSize = (arg: string) => { + const num = Number(arg) + return Number.isNaN(num) ? arg : num +} + export interface Options extends Config { configFile?: string runtimeConfig?: boolean @@ -100,7 +105,11 @@ program '--filename-case ', 'specify filename case ("pascal", "kebab", "camel") (default: "pascal")', ) - .option('--icon', 'use "1em" as width and height') + .option( + '--icon [size]', + 'specify width and height (default to "1em" or 24dp (native))', + parseIconSize, + ) .option( '--jsx-runtime ', 'specify JSX runtime ("automatic", "classic", "classic-preact") (default: "classic")', diff --git a/packages/core/src/__snapshots__/transform.test.ts.snap b/packages/core/src/__snapshots__/transform.test.ts.snap index e4a58455..98693bb2 100644 --- a/packages/core/src/__snapshots__/transform.test.ts.snap +++ b/packages/core/src/__snapshots__/transform.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`convert config should support options {"dimensions":false} 1`] = ` +exports[`convert config accepts options {"dimensions":false} 1`] = ` "import * as React from 'react' const SvgComponent = (props) => ( @@ -21,7 +21,7 @@ export default SvgComponent " `; -exports[`convert config should support options {"expandProps":"start"} 1`] = ` +exports[`convert config accepts options {"expandProps":"start"} 1`] = ` "import * as React from 'react' const SvgComponent = (props) => ( @@ -42,7 +42,7 @@ export default SvgComponent " `; -exports[`convert config should support options {"expandProps":false} 1`] = ` +exports[`convert config accepts options {"expandProps":false} 1`] = ` "import * as React from 'react' const SvgComponent = () => ( @@ -63,7 +63,7 @@ export default SvgComponent " `; -exports[`convert config should support options {"exportType":"named"} 1`] = ` +exports[`convert config accepts options {"exportType":"named"} 1`] = ` "import * as React from 'react' const SvgComponent = (props) => ( @@ -84,7 +84,61 @@ export { SvgComponent as ReactComponent } " `; -exports[`convert config should support options {"icon":true} 1`] = ` +exports[`convert config accepts options {"icon":"2em"} 1`] = ` +"import * as React from 'react' + +const SvgComponent = (props) => ( + + + + + +) + +export default SvgComponent +" +`; + +exports[`convert config accepts options {"icon":24} 1`] = ` +"import * as React from 'react' + +const SvgComponent = (props) => ( + + + + + +) + +export default SvgComponent +" +`; + +exports[`convert config accepts options {"icon":true} 1`] = ` "import * as React from 'react' const SvgComponent = (props) => ( @@ -111,7 +165,7 @@ export default SvgComponent " `; -exports[`convert config should support options {"memo":true} 1`] = ` +exports[`convert config accepts options {"memo":true} 1`] = ` "import * as React from 'react' import { memo } from 'react' @@ -134,7 +188,7 @@ export default Memo " `; -exports[`convert config should support options {"namedExport":"Component","state":{"caller":{"previousExport":"export default \\"logo.svg\\";"}}} 1`] = ` +exports[`convert config accepts options {"namedExport":"Component","state":{"caller":{"previousExport":"export default \\"logo.svg\\";"}}} 1`] = ` "import * as React from 'react' const SvgComponent = (props) => ( @@ -155,7 +209,7 @@ export default SvgComponent " `; -exports[`convert config should support options {"native":true,"expandProps":false} 1`] = ` +exports[`convert config accepts options {"native":true,"expandProps":false} 1`] = ` "import * as React from 'react' import Svg, { G, Path } from 'react-native-svg' @@ -177,14 +231,14 @@ export default SvgComponent " `; -exports[`convert config should support options {"native":true,"icon":true} 1`] = ` +exports[`convert config accepts options {"native":true,"icon":true} 1`] = ` "import * as React from 'react' import Svg, { G, Path } from 'react-native-svg' const SvgComponent = (props) => ( ; @@ -265,7 +319,7 @@ const SvgComponent = props => ( @@ -315,7 +369,7 @@ export default SvgComponent " `; -exports[`convert config should support options {"replaceAttrValues":{"none":"black"}} 1`] = ` +exports[`convert config accepts options {"replaceAttrValues":{"none":"black"}} 1`] = ` "import * as React from 'react' const SvgComponent = (props) => ( @@ -336,7 +390,7 @@ export default SvgComponent " `; -exports[`convert config should support options {"svgProps":{"a":"b","b":"{props.b}"}} 1`] = ` +exports[`convert config accepts options {"svgProps":{"a":"b","b":"{props.b}"}} 1`] = ` "import * as React from 'react' const SvgComponent = (props) => ( @@ -364,7 +418,7 @@ export default SvgComponent " `; -exports[`convert config should support options {"svgo":false} 1`] = ` +exports[`convert config accepts options {"svgo":false} 1`] = ` "import * as React from 'react' const SvgComponent = (props) => ( @@ -399,7 +453,7 @@ export default SvgComponent " `; -exports[`convert config should support options {"titleProp":true} 1`] = ` +exports[`convert config accepts options {"titleProp":true} 1`] = ` "import * as React from 'react' const SvgComponent = ({ title, titleId, ...props }) => ( @@ -427,7 +481,7 @@ export default SvgComponent " `; -exports[`convert config should support options {} 1`] = ` +exports[`convert config accepts options {} 1`] = ` "const noop = () => null export default noop diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 113e38ab..028f9e68 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -11,7 +11,7 @@ export interface Config { titleProp?: boolean expandProps?: boolean | 'start' | 'end' dimensions?: boolean - icon?: boolean + icon?: boolean | string | number native?: boolean svgProps?: { [key: string]: string diff --git a/packages/core/src/transform.test.ts b/packages/core/src/transform.test.ts index 33e1a0f2..82fd5bf7 100644 --- a/packages/core/src/transform.test.ts +++ b/packages/core/src/transform.test.ts @@ -300,6 +300,8 @@ describe('convert', () => { { expandProps: false }, { expandProps: 'start' }, { icon: true }, + { icon: 24 }, + { icon: '2em' }, { native: true }, { native: true, icon: true }, { native: true, expandProps: false }, @@ -323,7 +325,7 @@ describe('convert', () => { { exportType: 'named' }, ] - test.each(configs)('should support options %j', async (config) => { + test.each(configs)('accepts options %j', async (config) => { const result = await convertWithAllPlugins(svgBaseCode, config) expect(result).toMatchSnapshot() })