From fbb463a2083312c50ce17da95ece57bd84bb3c9a Mon Sep 17 00:00:00 2001 From: avali4907 Date: Thu, 26 May 2022 13:55:19 -0700 Subject: [PATCH 01/23] added notes --- package.json | 3 ++- packages/babel-plugin-svg-dynamic-title/src/index.ts | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 12d7a3c9..315561f3 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "format": "prettier --write \"**/*.{js,json,md}\"", "lint": "eslint .", "release": "lerna publish --conventional-commits && conventional-github-releaser --preset angular", - "test": "jest --runInBand" + "test": "jest --runInBand", + "test:c": "jest packages/babel-plugin-svg-dynamic-title --runInBand" }, "devDependencies": { "@babel/core": "^7.16.0", diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.ts b/packages/babel-plugin-svg-dynamic-title/src/index.ts index 6673f511..ec200cd4 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.ts @@ -39,13 +39,18 @@ const addTitleIdAttribute = ( return attributes } +// Aveline-art note: this is the plugin that goes into babel; it has a specific format, and is an object with visitor key. +// Babel first converts the data into an AST tree. Then the plugins are applied to the tree in order to change the tree. Finaly the AST is changed back into its original data form. +// Plugins uses visitors, which searches for a type of tree node. If the right one is found, it is transformed by the plugin. const plugin = () => ({ visitor: { + // Aveline-art: the identifier, which takes a node JSXElement(path: NodePath) { if (!elements.length) return const openingElement = path.get('openingElement') const openingElementName = openingElement.get('name') + // Aveline-art: check that the parent element is an svg if ( !elements.some((element) => openingElementName.isJSXIdentifier({ name: element }), @@ -54,6 +59,7 @@ const plugin = () => ({ return } + // Aveline-art: a helper function, not used yet const getTitleElement = ( existingTitle?: t.JSXElement, ): t.JSXExpressionContainer => { @@ -91,15 +97,17 @@ const plugin = () => ({ return t.jsxExpressionContainer(conditionalTitle) } - // store the title element + // store the title element, Aveline-art is null for now let titleElement: t.JSXExpressionContainer | null = null + // aveline-art check whether a title is in the svgs children const hasTitle = path.get('children').some((childPath) => { if (childPath.node === titleElement) return false if (!childPath.isJSXElement()) return false const name = childPath.get('openingElement').get('name') if (!name.isJSXIdentifier()) return false if (name.node.name !== 'title') return false + // aveline-art verified that there is a title element, now run the childpath through the helper function, then replace the current AST node with new childpath titleElement = getTitleElement(childPath.node) childPath.replaceWith(titleElement) return true From f53e565146e03aaea0c8d8b9770fcfb0d664b4b7 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Thu, 26 May 2022 16:21:06 -0700 Subject: [PATCH 02/23] generalized titleplugin --- package.json | 3 +- .../src/index.test.ts | 77 ++++++++++++++++--- .../src/index.ts | 66 ++++++++-------- 3 files changed, 100 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 315561f3..f39f7a0e 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "lint": "eslint .", "release": "lerna publish --conventional-commits && conventional-github-releaser --preset angular", "test": "jest --runInBand", - "test:c": "jest packages/babel-plugin-svg-dynamic-title --runInBand" + "test:c": "jest packages/babel-plugin-svg-dynamic-title --runInBand", + "test:cw": "jest packages/babel-plugin-svg-dynamic-title --runInBand --watch" }, "devDependencies": { "@babel/core": "^7.16.0", diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts index 32cb9dbb..5d82f269 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts @@ -1,30 +1,40 @@ import { transform } from '@babel/core' import plugin from '.' -const testPlugin = (code: string) => { +const testTagPlugin = (code: string, tag: string) => { const result = transform(code, { - plugins: ['@babel/plugin-syntax-jsx', plugin], + plugins: ['@babel/plugin-syntax-jsx', () => plugin(tag)], configFile: false, }) return result?.code } -describe('plugin', () => { +const testTitlePlugin = (code: string) => { + return testTagPlugin(code, 'title') +} + +const testDescPlugin = (code: string) => { + return testTagPlugin(code, 'desc') +} + +describe('title plugin', () => { it('should add title attribute if not present', () => { - expect(testPlugin('')).toMatchInlineSnapshot( + expect(testTitlePlugin('')).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) }) it('should add title element and fallback to existing title', () => { // testing when the existing title contains a simple string - expect(testPlugin(`Hello`)).toMatchInlineSnapshot( + expect( + testTitlePlugin(`Hello`), + ).toMatchInlineSnapshot( `"{title === undefined ? Hello : title ? {title} : null};"`, ) // testing when the existing title contains an JSXExpression expect( - testPlugin(`{"Hello"}`), + testTitlePlugin(`{"Hello"}`), ).toMatchInlineSnapshot( `"{title === undefined ? {\\"Hello\\"} : title ? {title} : null};"`, ) @@ -32,25 +42,72 @@ describe('plugin', () => { it('should preserve any existing title attributes', () => { // testing when the existing title contains a simple string expect( - testPlugin(`Hello`), + testTitlePlugin(`Hello`), ).toMatchInlineSnapshot( `"{title === undefined ? Hello : title ? {title} : null};"`, ) }) it('should support empty title', () => { - expect(testPlugin('')).toMatchInlineSnapshot( + expect(testTitlePlugin('')).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) }) it('should support self closing title', () => { - expect(testPlugin('')).toMatchInlineSnapshot( + expect(testTitlePlugin('')).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) }) it('should work if an attribute is already present', () => { - expect(testPlugin('')).toMatchInlineSnapshot( + expect(testTitlePlugin('')).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) }) }) + +describe('desc plugin', () => { + it('should add desc attribute if not present', () => { + expect(testDescPlugin('')).toMatchInlineSnapshot( + `"{desc ? {desc} : null};"`, + ) + }) + + it('should add desc element and fallback to existing desc', () => { + // testing when the existing desc contains a simple string + expect( + testDescPlugin(`Hello`), + ).toMatchInlineSnapshot( + `"{desc === undefined ? Hello : desc ? {desc} : null};"`, + ) + // testing when the existing desc contains an JSXExpression + expect( + testDescPlugin(`{"Hello"}`), + ).toMatchInlineSnapshot( + `"{desc === undefined ? {\\"Hello\\"} : desc ? {desc} : null};"`, + ) + }) + it('should preserve any existing desc attributes', () => { + // testing when the existing desc contains a simple string + expect( + testDescPlugin(`Hello`), + ).toMatchInlineSnapshot( + `"{desc === undefined ? Hello : desc ? {desc} : null};"`, + ) + }) + it('should support empty desc', () => { + expect(testDescPlugin('')).toMatchInlineSnapshot( + `"{desc ? {desc} : null};"`, + ) + }) + it('should support self closing desc', () => { + expect(testDescPlugin('')).toMatchInlineSnapshot( + `"{desc ? {desc} : null};"`, + ) + }) + + it('should work if an attribute is already present', () => { + expect(testDescPlugin('')).toMatchInlineSnapshot( + `"{desc ? {desc} : null};"`, + ) + }) +}) diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.ts b/packages/babel-plugin-svg-dynamic-title/src/index.ts index ec200cd4..1d7bb807 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.ts @@ -3,25 +3,27 @@ import { NodePath, types as t } from '@babel/core' const elements = ['svg', 'Svg'] -const createTitleElement = ( +const createTagElement = ( + tag: string, children: t.JSXExpressionContainer[] = [], attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[] = [], ) => { - const title = t.jsxIdentifier('title') + const eleName = t.jsxIdentifier(tag) return t.jsxElement( - t.jsxOpeningElement(title, attributes), - t.jsxClosingElement(title), + t.jsxOpeningElement(eleName, attributes), + t.jsxClosingElement(eleName), children, ) } -const createTitleIdAttribute = () => +const createTagIdAttribute = (tag: string) => t.jsxAttribute( t.jsxIdentifier('id'), - t.jsxExpressionContainer(t.identifier('titleId')), + t.jsxExpressionContainer(t.identifier(`${tag}Id`)), ) -const addTitleIdAttribute = ( +const addTagIdAttribute = ( + tag: string, attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[], ) => { const existingId = attributes.find( @@ -29,28 +31,23 @@ const addTitleIdAttribute = ( ) as t.JSXAttribute | undefined if (!existingId) { - return [...attributes, createTitleIdAttribute()] + return [...attributes, createTagIdAttribute(tag)] } existingId.value = t.jsxExpressionContainer( t.isStringLiteral(existingId.value) - ? t.logicalExpression('||', t.identifier('titleId'), existingId.value) - : t.identifier('titleId'), + ? t.logicalExpression('||', t.identifier(`${tag}Id`), existingId.value) + : t.identifier(`${tag}Id`), ) return attributes } -// Aveline-art note: this is the plugin that goes into babel; it has a specific format, and is an object with visitor key. -// Babel first converts the data into an AST tree. Then the plugins are applied to the tree in order to change the tree. Finaly the AST is changed back into its original data form. -// Plugins uses visitors, which searches for a type of tree node. If the right one is found, it is transformed by the plugin. -const plugin = () => ({ +const plugin = (tag: string) => ({ visitor: { - // Aveline-art: the identifier, which takes a node JSXElement(path: NodePath) { if (!elements.length) return const openingElement = path.get('openingElement') const openingElementName = openingElement.get('name') - // Aveline-art: check that the parent element is an svg if ( !elements.some((element) => openingElementName.isJSXIdentifier({ name: element }), @@ -59,23 +56,24 @@ const plugin = () => ({ return } - // Aveline-art: a helper function, not used yet - const getTitleElement = ( + const getTagElement = ( existingTitle?: t.JSXElement, ): t.JSXExpressionContainer => { - const titleExpression = t.identifier('title') + const tagExpression = t.identifier(tag) if (existingTitle) { - existingTitle.openingElement.attributes = addTitleIdAttribute( + existingTitle.openingElement.attributes = addTagIdAttribute( + tag, existingTitle.openingElement.attributes, ) } const conditionalTitle = t.conditionalExpression( - titleExpression, - createTitleElement( - [t.jsxExpressionContainer(titleExpression)], + tagExpression, + createTagElement( + tag, + [t.jsxExpressionContainer(tagExpression)], existingTitle ? existingTitle.openingElement.attributes - : [createTitleIdAttribute()], + : [createTagIdAttribute(tag)], ), t.nullLiteral(), ) @@ -86,7 +84,7 @@ const plugin = () => ({ t.conditionalExpression( t.binaryExpression( '===', - titleExpression, + tagExpression, t.identifier('undefined'), ), existingTitle, @@ -97,28 +95,26 @@ const plugin = () => ({ return t.jsxExpressionContainer(conditionalTitle) } - // store the title element, Aveline-art is null for now - let titleElement: t.JSXExpressionContainer | null = null + // store the title element + let tagElement: t.JSXExpressionContainer | null = null - // aveline-art check whether a title is in the svgs children const hasTitle = path.get('children').some((childPath) => { - if (childPath.node === titleElement) return false + if (childPath.node === tagElement) return false if (!childPath.isJSXElement()) return false const name = childPath.get('openingElement').get('name') if (!name.isJSXIdentifier()) return false - if (name.node.name !== 'title') return false - // aveline-art verified that there is a title element, now run the childpath through the helper function, then replace the current AST node with new childpath - titleElement = getTitleElement(childPath.node) - childPath.replaceWith(titleElement) + if (name.node.name !== tag) return false + tagElement = getTagElement(childPath.node) + childPath.replaceWith(tagElement) return true }) // create a title element if not already create - titleElement = titleElement || getTitleElement() + tagElement = tagElement || getTagElement() if (!hasTitle) { // path.unshiftContainer is not working well :( // path.unshiftContainer('children', titleElement) - path.node.children.unshift(titleElement) + path.node.children.unshift(tagElement) path.replaceWith(path.node) } }, From 9b058f9ac39aeeeef812629aec4c8d5d9e674bab Mon Sep 17 00:00:00 2001 From: avali4907 Date: Thu, 26 May 2022 16:43:15 -0700 Subject: [PATCH 03/23] added new npm script --- package.json | 4 ++-- packages/babel-plugin-svg-dynamic-title/src/index.test.ts | 6 +++--- packages/babel-preset/src/index.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index f39f7a0e..2ce00350 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "lint": "eslint .", "release": "lerna publish --conventional-commits && conventional-github-releaser --preset angular", "test": "jest --runInBand", - "test:c": "jest packages/babel-plugin-svg-dynamic-title --runInBand", - "test:cw": "jest packages/babel-plugin-svg-dynamic-title --runInBand --watch" + "test:c": "jest packages/babel-plugin-transform-svg-component --runInBand", + "test:cw": "jest packages/babel-plugin-transform-svg-component --runInBand --watch" }, "devDependencies": { "@babel/core": "^7.16.0", diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts index 5d82f269..5e384a2c 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts @@ -1,7 +1,7 @@ import { transform } from '@babel/core' import plugin from '.' -const testTagPlugin = (code: string, tag: string) => { +const testPlugin = (code: string, tag: string) => { const result = transform(code, { plugins: ['@babel/plugin-syntax-jsx', () => plugin(tag)], configFile: false, @@ -11,11 +11,11 @@ const testTagPlugin = (code: string, tag: string) => { } const testTitlePlugin = (code: string) => { - return testTagPlugin(code, 'title') + return testPlugin(code, 'title') } const testDescPlugin = (code: string) => { - return testTagPlugin(code, 'desc') + return testPlugin(code, 'desc') } describe('title plugin', () => { diff --git a/packages/babel-preset/src/index.ts b/packages/babel-preset/src/index.ts index 5405a032..02bf228c 100644 --- a/packages/babel-preset/src/index.ts +++ b/packages/babel-preset/src/index.ts @@ -127,7 +127,7 @@ const plugin = (_: ConfigAPI, opts: Options) => { } if (opts.titleProp) { - plugins.push(svgDynamicTitle) + plugins.push(() => svgDynamicTitle('title')) } if (opts.native) { From 0f0edcb3ae2015301c03f57eebdc629510a704f2 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Thu, 26 May 2022 22:52:07 -0700 Subject: [PATCH 04/23] added desc prop to transform-svg --- .../src/__snapshots__/index.test.ts.snap | 130 +++++++++++++++++- .../src/index.test.ts | 46 ++++++- .../src/types.ts | 1 + .../src/variables.ts | 90 ++++++++---- 4 files changed, 236 insertions(+), 31 deletions(-) diff --git a/packages/babel-plugin-transform-svg-component/src/__snapshots__/index.test.ts.snap b/packages/babel-plugin-transform-svg-component/src/__snapshots__/index.test.ts.snap index 587508f1..7029e262 100644 --- a/packages/babel-plugin-transform-svg-component/src/__snapshots__/index.test.ts.snap +++ b/packages/babel-plugin-transform-svg-component/src/__snapshots__/index.test.ts.snap @@ -97,6 +97,29 @@ const SvgComponent = () => ; export default SvgComponent;" `; +exports[`plugin javascript with "descProp" adds "desc" and "descId" prop 1`] = ` +"import * as React from \\"react\\"; + +const SvgComponent = ({ + desc, + descId +}) => ; + +export default SvgComponent;" +`; + +exports[`plugin javascript with "descProp" and "expandProps" adds "desc", "descId" props and expands props 1`] = ` +"import * as React from \\"react\\"; + +const SvgComponent = ({ + desc, + descId, + ...props +}) => ; + +export default SvgComponent;" +`; + exports[`plugin javascript with "expandProps" add props 1`] = ` "import * as React from \\"react\\"; @@ -194,7 +217,21 @@ const ForwardRef = forwardRef(SvgComponent); export default ForwardRef;" `; -exports[`plugin javascript with "titleProp" adds "titleProp" and "titleId" prop 1`] = ` +exports[`plugin javascript with "titleProp" "descProp" and "expandProps" adds "title", "titleId", "desc", "descId" props and expands props 1`] = ` +"import * as React from \\"react\\"; + +const SvgComponent = ({ + title, + titleId, + desc, + descId, + ...props +}) => ; + +export default SvgComponent;" +`; + +exports[`plugin javascript with "titleProp" adds "title" and "titleId" prop 1`] = ` "import * as React from \\"react\\"; const SvgComponent = ({ @@ -205,7 +242,20 @@ const SvgComponent = ({ export default SvgComponent;" `; -exports[`plugin javascript with "titleProp" and "expandProps" adds "titleProp", "titleId" props and expands props 1`] = ` +exports[`plugin javascript with "titleProp" and "descProp" adds "title", "titleId", "desc", and "descId prop 1`] = ` +"import * as React from \\"react\\"; + +const SvgComponent = ({ + title, + titleId, + desc, + descId +}) => ; + +export default SvgComponent;" +`; + +exports[`plugin javascript with "titleProp" and "expandProps" adds "title", "titleId" props and expands props 1`] = ` "import * as React from \\"react\\"; const SvgComponent = ({ @@ -325,6 +375,38 @@ const SvgComponent = () => ; export default SvgComponent;" `; +exports[`plugin typescript with "descProp" adds "desc" and "descId" prop 1`] = ` +"import * as React from \\"react\\"; +interface SVGRProps { + desc?: string; + descId?: string; +} + +const SvgComponent = ({ + desc, + descId +}: SVGRProps) => ; + +export default SvgComponent;" +`; + +exports[`plugin typescript with "descProp" and "expandProps" adds "desc", "descId" props and expands props 1`] = ` +"import * as React from \\"react\\"; +import { SVGProps } from \\"react\\"; +interface SVGRProps { + desc?: string; + descId?: string; +} + +const SvgComponent = ({ + desc, + descId, + ...props +}: SVGProps & SVGRProps) => ; + +export default SvgComponent;" +`; + exports[`plugin typescript with "expandProps" add props 1`] = ` "import * as React from \\"react\\"; import { SVGProps } from \\"react\\"; @@ -423,7 +505,28 @@ const ForwardRef = forwardRef(SvgComponent); export default ForwardRef;" `; -exports[`plugin typescript with "titleProp" adds "titleProp" and "titleId" prop 1`] = ` +exports[`plugin typescript with "titleProp" "descProp" and "expandProps" adds "title", "titleId", "desc", "descId" props and expands props 1`] = ` +"import * as React from \\"react\\"; +import { SVGProps } from \\"react\\"; +interface SVGRProps { + title?: string; + titleId?: string; + desc?: string; + descId?: string; +} + +const SvgComponent = ({ + title, + titleId, + desc, + descId, + ...props +}: SVGProps & SVGRProps) => ; + +export default SvgComponent;" +`; + +exports[`plugin typescript with "titleProp" adds "title" and "titleId" prop 1`] = ` "import * as React from \\"react\\"; interface SVGRProps { title?: string; @@ -438,7 +541,26 @@ const SvgComponent = ({ export default SvgComponent;" `; -exports[`plugin typescript with "titleProp" and "expandProps" adds "titleProp", "titleId" props and expands props 1`] = ` +exports[`plugin typescript with "titleProp" and "descProp" adds "title", "titleId", "desc", and "descId prop 1`] = ` +"import * as React from \\"react\\"; +interface SVGRProps { + title?: string; + titleId?: string; + desc?: string; + descId?: string; +} + +const SvgComponent = ({ + title, + titleId, + desc, + descId +}: SVGRProps) => ; + +export default SvgComponent;" +`; + +exports[`plugin typescript with "titleProp" and "expandProps" adds "title", "titleId" props and expands props 1`] = ` "import * as React from \\"react\\"; import { SVGProps } from \\"react\\"; interface SVGRProps { diff --git a/packages/babel-plugin-transform-svg-component/src/index.test.ts b/packages/babel-plugin-transform-svg-component/src/index.test.ts index cce4d490..7272ff0d 100644 --- a/packages/babel-plugin-transform-svg-component/src/index.test.ts +++ b/packages/babel-plugin-transform-svg-component/src/index.test.ts @@ -57,7 +57,7 @@ describe('plugin', () => { }) describe('with "titleProp"', () => { - it('adds "titleProp" and "titleId" prop', () => { + it('adds "title" and "titleId" prop', () => { const { code } = testPlugin(language)('', { titleProp: true, }) @@ -66,7 +66,7 @@ describe('plugin', () => { }) describe('with "titleProp" and "expandProps"', () => { - it('adds "titleProp", "titleId" props and expands props', () => { + it('adds "title", "titleId" props and expands props', () => { const { code } = testPlugin(language)('', { ...defaultOptions, expandProps: true, @@ -76,6 +76,48 @@ describe('plugin', () => { }) }) + describe('with "descProp"', () => { + it('adds "desc" and "descId" prop', () => { + const { code } = testPlugin(language)('', { + descProp: true, + }) + expect(code).toMatchSnapshot() + }) + }) + + describe('with "descProp" and "expandProps"', () => { + it('adds "desc", "descId" props and expands props', () => { + const { code } = testPlugin(language)('', { + ...defaultOptions, + expandProps: true, + descProp: true, + }) + expect(code).toMatchSnapshot() + }) + }) + + describe('with "titleProp" and "descProp"', () => { + it('adds "title", "titleId", "desc", and "descId prop', () => { + const { code } = testPlugin(language)('', { + titleProp: true, + descProp: true, + }) + expect(code).toMatchSnapshot() + }) + }) + + describe('with "titleProp" "descProp" and "expandProps"', () => { + it('adds "title", "titleId", "desc", "descId" props and expands props', () => { + const { code } = testPlugin(language)('', { + ...defaultOptions, + expandProps: true, + titleProp: true, + descProp: true, + }) + expect(code).toMatchSnapshot() + }) + }) + describe('with "expandProps"', () => { it('add props', () => { const { code } = testPlugin(language)('', { diff --git a/packages/babel-plugin-transform-svg-component/src/types.ts b/packages/babel-plugin-transform-svg-component/src/types.ts index 8c1c6651..c171a5bb 100644 --- a/packages/babel-plugin-transform-svg-component/src/types.ts +++ b/packages/babel-plugin-transform-svg-component/src/types.ts @@ -35,6 +35,7 @@ export interface JSXRuntimeImport { export interface Options { typescript?: boolean titleProp?: boolean + descProp?: boolean expandProps?: boolean | 'start' | 'end' ref?: boolean template?: Template diff --git a/packages/babel-plugin-transform-svg-component/src/variables.ts b/packages/babel-plugin-transform-svg-component/src/variables.ts index 844e6383..7250a01b 100644 --- a/packages/babel-plugin-transform-svg-component/src/variables.ts +++ b/packages/babel-plugin-transform-svg-component/src/variables.ts @@ -122,21 +122,70 @@ export const getVariables = ({ ) } - if (opts.titleProp) { - const prop = t.objectPattern([ - t.objectProperty( - t.identifier('title'), - t.identifier('title'), - false, - true, - ), - t.objectProperty( - t.identifier('titleId'), - t.identifier('titleId'), - false, - true, - ), - ]) + if (opts.titleProp || opts.descProp) { + const properties = [] + const propertySignatures = [] + if (opts.titleProp) { + properties.push( + t.objectProperty( + t.identifier('title'), + t.identifier('title'), + false, + true, + ), + t.objectProperty( + t.identifier('titleId'), + t.identifier('titleId'), + false, + true, + ), + ) + + if (opts.typescript) { + propertySignatures.push( + tsOptionalPropertySignature( + t.identifier('title'), + t.tsTypeAnnotation(t.tsStringKeyword()), + ), + tsOptionalPropertySignature( + t.identifier('titleId'), + t.tsTypeAnnotation(t.tsStringKeyword()), + ), + ) + } + } + + if (opts.descProp) { + properties.push( + t.objectProperty( + t.identifier('desc'), + t.identifier('desc'), + false, + true, + ), + t.objectProperty( + t.identifier('descId'), + t.identifier('descId'), + false, + true, + ), + ) + + if (opts.typescript) { + propertySignatures.push( + tsOptionalPropertySignature( + t.identifier('desc'), + t.tsTypeAnnotation(t.tsStringKeyword()), + ), + tsOptionalPropertySignature( + t.identifier('descId'), + t.tsTypeAnnotation(t.tsStringKeyword()), + ), + ) + } + } + + const prop = t.objectPattern(properties) props.push(prop) if (opts.typescript) { interfaces.push( @@ -144,16 +193,7 @@ export const getVariables = ({ t.identifier('SVGRProps'), null, null, - t.tSInterfaceBody([ - tsOptionalPropertySignature( - t.identifier('title'), - t.tsTypeAnnotation(t.tsStringKeyword()), - ), - tsOptionalPropertySignature( - t.identifier('titleId'), - t.tsTypeAnnotation(t.tsStringKeyword()), - ), - ]), + t.tSInterfaceBody(propertySignatures), ), ) prop.typeAnnotation = t.tsTypeAnnotation( From a4f88e01706bdc3b4f5a76bd9572d18fb8360215 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Thu, 26 May 2022 23:51:29 -0700 Subject: [PATCH 05/23] added new tests --- package.json | 4 +- packages/babel-preset/src/index.test.ts | 49 +++++++++++++++++++ packages/babel-preset/src/index.ts | 16 ++++++ packages/core/src/config.ts | 2 + packages/plugin-jsx/src/index.ts | 1 + .../components/playground/config/settings.js | 7 +++ 6 files changed, 77 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2ce00350..6c8438a9 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "lint": "eslint .", "release": "lerna publish --conventional-commits && conventional-github-releaser --preset angular", "test": "jest --runInBand", - "test:c": "jest packages/babel-plugin-transform-svg-component --runInBand", - "test:cw": "jest packages/babel-plugin-transform-svg-component --runInBand --watch" + "test:c": "jest packages/babel-preset --runInBand", + "test:cw": "jest packages/babel-preset --runInBand --watch" }, "devDependencies": { "@babel/core": "^7.16.0", diff --git a/packages/babel-preset/src/index.test.ts b/packages/babel-preset/src/index.test.ts index 90073f5a..bf09b86d 100644 --- a/packages/babel-preset/src/index.test.ts +++ b/packages/babel-preset/src/index.test.ts @@ -83,6 +83,55 @@ describe('preset', () => { `) }) + it('handles descProp', () => { + expect( + testPreset('', { + descProp: true, + }), + ).toMatchInlineSnapshot(` + "import * as React from \\"react\\"; + + const SvgComponent = ({ + desc, + descId + }) => {desc ? {desc} : null}; + + export default SvgComponent;" + `) + }) + it('handles descProp and fallback on existing desc', () => { + // testing when existing desc has string as chilren + expect( + testPreset(`Hello`, { + descProp: true, + }), + ).toMatchInlineSnapshot(` + "import * as React from \\"react\\"; + + const SvgComponent = ({ + desc, + descId + }) => {desc === undefined ? Hello : desc ? {desc} : null}; + + export default SvgComponent;" + `) + // testing when existing desc has JSXExpression as children + expect( + testPreset(`{"Hello"}`, { + descProp: true, + }), + ).toMatchInlineSnapshot(` + "import * as React from \\"react\\"; + + const SvgComponent = ({ + desc, + descId + }) => {desc === undefined ? {\\"Hello\\"} : desc ? {desc} : null}; + + export default SvgComponent;" + `) + }) + it('handles replaceAttrValues', () => { expect( testPreset('', { diff --git a/packages/babel-preset/src/index.ts b/packages/babel-preset/src/index.ts index 02bf228c..0b9b0496 100644 --- a/packages/babel-preset/src/index.ts +++ b/packages/babel-preset/src/index.ts @@ -18,6 +18,7 @@ import transformSvgComponent, { export interface Options extends TransformOptions { ref?: boolean titleProp?: boolean + descProp?: boolean expandProps?: boolean | 'start' | 'end' dimensions?: boolean icon?: boolean | string | number @@ -76,6 +77,17 @@ const plugin = (_: ConfigAPI, opts: Options) => { ] } + if (opts.descProp) { + toAddAttributes = [ + ...toAddAttributes, + { + name: 'aria-describedby', + value: 'descId', + literal: true, + }, + ] + } + if (opts.expandProps) { toAddAttributes = [ ...toAddAttributes, @@ -130,6 +142,10 @@ const plugin = (_: ConfigAPI, opts: Options) => { plugins.push(() => svgDynamicTitle('title')) } + if (opts.descProp) { + plugins.push(() => svgDynamicTitle('desc')) + } + if (opts.native) { plugins.push(transformReactNativeSVG) } diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 028f9e68..9468ca8b 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -9,6 +9,7 @@ import type { State } from './state' export interface Config { ref?: boolean titleProp?: boolean + descProp?: boolean expandProps?: boolean | 'start' | 'end' dimensions?: boolean icon?: boolean | string | number @@ -59,6 +60,7 @@ export const DEFAULT_CONFIG: Config = { template: undefined, index: false, titleProp: false, + descProp: false, runtimeConfig: true, namedExport: 'ReactComponent', exportType: 'default', diff --git a/packages/plugin-jsx/src/index.ts b/packages/plugin-jsx/src/index.ts index 57e9c844..9d198df0 100644 --- a/packages/plugin-jsx/src/index.ts +++ b/packages/plugin-jsx/src/index.ts @@ -38,6 +38,7 @@ const jsxPlugin: Plugin = (code, config, state) => { const svgPresetOptions: SvgrPresetOptions = { ref: config.ref, titleProp: config.titleProp, + descProp: config.descProp, expandProps: config.expandProps, dimensions: config.dimensions, icon: config.icon, diff --git a/website/src/components/playground/config/settings.js b/website/src/components/playground/config/settings.js index 75f3f39a..5192fbfe 100644 --- a/website/src/components/playground/config/settings.js +++ b/website/src/components/playground/config/settings.js @@ -67,6 +67,13 @@ export const settings = [ group: 'global', default: false, }, + { + label: 'Desc prop', + name: 'descProp', + type: 'boolean', + group: 'global', + default: false, + }, { label: 'Expand props', name: 'expandProps', From 0d824e3603fd8ef24b1f2abd6a23fdf519b9c742 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 00:11:24 -0700 Subject: [PATCH 06/23] added tests to core --- packages/core/src/transform.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/core/src/transform.test.ts b/packages/core/src/transform.test.ts index bfea9b33..387a2029 100644 --- a/packages/core/src/transform.test.ts +++ b/packages/core/src/transform.test.ts @@ -317,6 +317,7 @@ describe('convert', () => { tpl`const noop = () => null; export default noop;`, }, { titleProp: true }, + { descProp: true }, { memo: true }, { namedExport: 'Component', @@ -340,5 +341,16 @@ describe('convert', () => { await convertWithAllPlugins(svg, { titleProp: true }), ).toMatchSnapshot() }) + + it('descProp: without desc added', async () => { + const svg = ` + + + +` + expect( + await convertWithAllPlugins(svg, { descProp: true }), + ).toMatchSnapshot() + }) }) }) From 8cfff2caca2271509fe98761f567cae0d067a908 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 00:11:54 -0700 Subject: [PATCH 07/23] added tests to npm --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6c8438a9..37561712 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "lint": "eslint .", "release": "lerna publish --conventional-commits && conventional-github-releaser --preset angular", "test": "jest --runInBand", - "test:c": "jest packages/babel-preset --runInBand", - "test:cw": "jest packages/babel-preset --runInBand --watch" + "test:c": "jest packages/core --runInBand", + "test:cw": "jest packages/core --runInBand --watch" }, "devDependencies": { "@babel/core": "^7.16.0", From 452de9057e96ce43b578f923b0dff7ef38ca496c Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 00:17:00 -0700 Subject: [PATCH 08/23] updated snapshot --- packages/core/src/__snapshots__/config.test.ts.snap | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/core/src/__snapshots__/config.test.ts.snap b/packages/core/src/__snapshots__/config.test.ts.snap index 4f146232..d5886aa2 100644 --- a/packages/core/src/__snapshots__/config.test.ts.snap +++ b/packages/core/src/__snapshots__/config.test.ts.snap @@ -2,6 +2,7 @@ exports[`svgo async #loadConfig [async] should load config using filePath 1`] = ` Object { + "descProp": false, "dimensions": true, "expandProps": "end", "exportType": "default", @@ -32,6 +33,7 @@ Object { exports[`svgo async #loadConfig [async] should not load config with "runtimeConfig: false 1`] = ` Object { + "descProp": false, "dimensions": true, "expandProps": "end", "exportType": "default", @@ -63,6 +65,7 @@ Object { exports[`svgo async #loadConfig [async] should use default config without state.filePath 1`] = ` Object { + "descProp": false, "dimensions": false, "expandProps": "end", "exportType": "default", @@ -87,6 +90,7 @@ Object { exports[`svgo async #loadConfig [async] should work with custom config path 1`] = ` Object { + "descProp": false, "dimensions": true, "expandProps": "end", "exportType": "default", @@ -117,6 +121,7 @@ Object { exports[`svgo sync #loadConfig [sync] should load config using filePath 1`] = ` Object { + "descProp": false, "dimensions": true, "expandProps": "end", "exportType": "default", @@ -147,6 +152,7 @@ Object { exports[`svgo sync #loadConfig [sync] should not load config with "runtimeConfig: false 1`] = ` Object { + "descProp": false, "dimensions": true, "expandProps": "end", "exportType": "default", @@ -178,6 +184,7 @@ Object { exports[`svgo sync #loadConfig [sync] should use default config without state.filePath 1`] = ` Object { + "descProp": false, "dimensions": false, "expandProps": "end", "exportType": "default", @@ -202,6 +209,7 @@ Object { exports[`svgo sync #loadConfig [sync] should work with custom config path 1`] = ` Object { + "descProp": false, "dimensions": true, "expandProps": "end", "exportType": "default", From 00665767c48baa3a98c20d86123de8728101b2cc Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 00:21:28 -0700 Subject: [PATCH 09/23] added new snapshots --- .../src/__snapshots__/transform.test.ts.snap | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/core/src/__snapshots__/transform.test.ts.snap b/packages/core/src/__snapshots__/transform.test.ts.snap index c85ba403..1c7355a3 100644 --- a/packages/core/src/__snapshots__/transform.test.ts.snap +++ b/packages/core/src/__snapshots__/transform.test.ts.snap @@ -1,5 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`convert config accepts options {"descProp":true} 1`] = ` +"import * as React from 'react' + +const SvgComponent = ({ desc, descId, ...props }) => ( + + {desc ? {desc} : null} + + + + +) + +export default SvgComponent +" +`; + exports[`convert config accepts options {"dimensions":false} 1`] = ` "import * as React from 'react' @@ -489,6 +517,28 @@ export default noop " `; +exports[`convert config descProp: without desc added 1`] = ` +"import * as React from 'react' + +const SvgComponent = ({ desc, descId, ...props }) => ( + + {desc ? {desc} : null} + + +) + +export default SvgComponent +" +`; + exports[`convert config titleProp: without title added 1`] = ` "import * as React from 'react' From 8fc965a05f8a8bdb0fba55e37781497320487a54 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 09:28:53 -0700 Subject: [PATCH 10/23] added desc to documentation --- packages/cli/README.md | 1 + website/pages/docs/options.mdx | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/packages/cli/README.md b/packages/cli/README.md index 6cc42ff9..be7b1bba 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -35,6 +35,7 @@ Options: --index-template specify a custom index.js template to use --no-index disable index file generation --title-prop create a title element linked with props + --desc-prop create a desc element linked with props --prettier-config Prettier config --no-prettier disable Prettier --svgo-config SVGO config diff --git a/website/pages/docs/options.mdx b/website/pages/docs/options.mdx index 825e1a20..89b68350 100644 --- a/website/pages/docs/options.mdx +++ b/website/pages/docs/options.mdx @@ -178,6 +178,14 @@ Add title tag via title property. If titleProp is set to true and no title is pr | ------- | -------------- | -------------------- | | `false` | `--title-prop` | `titleProp: boolean` | +## Description + +Add desc tag via title property. If descProp is set to true and no description is provided (`desc={undefined}`) at render time, this will fallback to an existing desc element in the svg if exists. + +| Default | CLI Override | API Override | +| ------- | ------------- | ------------------- | +| `false` | `--desc-prop` | `descProp: boolean` | + ## Template Specify a template file (CLI) or a template function (API) to use. For an example of template, see [the default one](https://github.com/gregberge/svgr/blob/main/packages/babel-plugin-transform-svg-component/src/defaultTemplate.ts). From c8469a8a2796dfcce529912446829732dfc8d7aa Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 09:29:17 -0700 Subject: [PATCH 11/23] added new tests --- package.json | 4 ++-- packages/cli/src/index.test.ts | 2 ++ packages/cli/src/index.ts | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 37561712..ca69d8e7 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "lint": "eslint .", "release": "lerna publish --conventional-commits && conventional-github-releaser --preset angular", "test": "jest --runInBand", - "test:c": "jest packages/core --runInBand", - "test:cw": "jest packages/core --runInBand --watch" + "test:c": "jest packages/cli --runInBand", + "test:cw": "jest packages/cli --runInBand --watch" }, "devDependencies": { "@babel/core": "^7.16.0", diff --git a/packages/cli/src/index.test.ts b/packages/cli/src/index.test.ts index 95750846..5de680fd 100644 --- a/packages/cli/src/index.test.ts +++ b/packages/cli/src/index.test.ts @@ -131,9 +131,11 @@ describe('cli', () => { ['--no-svgo'], ['--no-prettier'], ['--title-prop'], + ['--desc-prop'], ['--typescript'], ['--typescript --ref'], ['--typescript --ref --title-prop'], + ['--typescript --ref --desc-prop'], ])( 'should support various args', async (args) => { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index bee4a0b9..bbdb4647 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -146,6 +146,7 @@ program ) .option('--no-index', 'disable index file generation') .option('--title-prop', 'create a title element linked with props') + .option('--desc-prop', 'create a desc element linked with props') .option( '--prettier-config ', 'Prettier config', From 9f160c0f6c00bf291c8d86b71f4e5776240bec78 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 09:38:44 -0700 Subject: [PATCH 12/23] added new snapshots --- .../cli/src/__snapshots__/index.test.ts.snap | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/packages/cli/src/__snapshots__/index.test.ts.snap b/packages/cli/src/__snapshots__/index.test.ts.snap index a2c6a585..fb97ba86 100644 --- a/packages/cli/src/__snapshots__/index.test.ts.snap +++ b/packages/cli/src/__snapshots__/index.test.ts.snap @@ -155,6 +155,27 @@ export default SvgFile " `; +exports[`cli should support various args: --desc-prop 1`] = ` +"import * as React from 'react' + +const SvgFile = ({ desc, descId, ...props }) => ( + + {desc ? {desc} : null} + + +) + +export default SvgFile + +" +`; + exports[`cli should support various args: --expand-props none 1`] = ` "import * as React from 'react' @@ -484,6 +505,37 @@ export default SvgFile " `; +exports[`cli should support various args: --typescript --ref --desc-prop 1`] = ` +"import * as React from 'react' +import { SVGProps, Ref, forwardRef } from 'react' +interface SVGRProps { + desc?: string; + descId?: string; +} + +const SvgFile = ( + { desc, descId, ...props }: SVGProps & SVGRProps, + ref: Ref, +) => ( + + {desc ? {desc} : null} + + +) + +const ForwardRef = forwardRef(SvgFile) +export default ForwardRef + +" +`; + exports[`cli should support various args: --typescript --ref --title-prop 1`] = ` "import * as React from 'react' import { SVGProps, Ref, forwardRef } from 'react' From 29c881290d42ee9a0430a682f85d8c1e3ea50a2a Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 09:47:09 -0700 Subject: [PATCH 13/23] added default to dynamic title plugin --- packages/babel-plugin-svg-dynamic-title/README.md | 4 ++++ packages/babel-plugin-svg-dynamic-title/src/index.test.ts | 6 +++--- packages/babel-plugin-svg-dynamic-title/src/index.ts | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/babel-plugin-svg-dynamic-title/README.md b/packages/babel-plugin-svg-dynamic-title/README.md index efe47ac1..904db858 100644 --- a/packages/babel-plugin-svg-dynamic-title/README.md +++ b/packages/babel-plugin-svg-dynamic-title/README.md @@ -16,6 +16,10 @@ npm install --save-dev @svgr/babel-plugin-svg-dynamic-title } ``` +## Note + +This plugin handles both the titleProp and descProp options. By default, it will handle titleProp only. + ## License MIT diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts index 5e384a2c..b04b07c6 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts @@ -1,9 +1,9 @@ import { transform } from '@babel/core' import plugin from '.' -const testPlugin = (code: string, tag: string) => { +const testPlugin = (code: string, tag: string = 'title') => { const result = transform(code, { - plugins: ['@babel/plugin-syntax-jsx', () => plugin(tag)], + plugins: ['@babel/plugin-syntax-jsx', tag ? () => plugin(tag) : plugin], configFile: false, }) @@ -11,7 +11,7 @@ const testPlugin = (code: string, tag: string) => { } const testTitlePlugin = (code: string) => { - return testPlugin(code, 'title') + return testPlugin(code) } const testDescPlugin = (code: string) => { diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.ts b/packages/babel-plugin-svg-dynamic-title/src/index.ts index 1d7bb807..845f22f0 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.ts @@ -4,7 +4,7 @@ import { NodePath, types as t } from '@babel/core' const elements = ['svg', 'Svg'] const createTagElement = ( - tag: string, + tag: string = 'title', children: t.JSXExpressionContainer[] = [], attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[] = [], ) => { From 8eabbf5f5f67237a82cc76ebc2cf6aac0a3d5b91 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 09:49:22 -0700 Subject: [PATCH 14/23] removed redundant typing --- packages/babel-plugin-svg-dynamic-title/src/index.test.ts | 2 +- packages/babel-plugin-svg-dynamic-title/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts index b04b07c6..212f0007 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts @@ -1,7 +1,7 @@ import { transform } from '@babel/core' import plugin from '.' -const testPlugin = (code: string, tag: string = 'title') => { +const testPlugin = (code: string, tag = 'title') => { const result = transform(code, { plugins: ['@babel/plugin-syntax-jsx', tag ? () => plugin(tag) : plugin], configFile: false, diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.ts b/packages/babel-plugin-svg-dynamic-title/src/index.ts index 845f22f0..c5a197df 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.ts @@ -4,7 +4,7 @@ import { NodePath, types as t } from '@babel/core' const elements = ['svg', 'Svg'] const createTagElement = ( - tag: string = 'title', + tag = 'title', children: t.JSXExpressionContainer[] = [], attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[] = [], ) => { From 97614d60569baf0215bb24bd3026b8d3c3a2a7b4 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 10:10:54 -0700 Subject: [PATCH 15/23] refactored code --- package.json | 4 +- .../src/variables.ts | 65 ++++++------------- packages/babel-preset/src/index.ts | 2 +- 3 files changed, 23 insertions(+), 48 deletions(-) diff --git a/package.json b/package.json index ca69d8e7..12d7a3c9 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,7 @@ "format": "prettier --write \"**/*.{js,json,md}\"", "lint": "eslint .", "release": "lerna publish --conventional-commits && conventional-github-releaser --preset angular", - "test": "jest --runInBand", - "test:c": "jest packages/cli --runInBand", - "test:cw": "jest packages/cli --runInBand --watch" + "test": "jest --runInBand" }, "devDependencies": { "@babel/core": "^7.16.0", diff --git a/packages/babel-plugin-transform-svg-component/src/variables.ts b/packages/babel-plugin-transform-svg-component/src/variables.ts index 7250a01b..64a1481b 100644 --- a/packages/babel-plugin-transform-svg-component/src/variables.ts +++ b/packages/babel-plugin-transform-svg-component/src/variables.ts @@ -125,62 +125,39 @@ export const getVariables = ({ if (opts.titleProp || opts.descProp) { const properties = [] const propertySignatures = [] - if (opts.titleProp) { - properties.push( - t.objectProperty( - t.identifier('title'), - t.identifier('title'), - false, - true, - ), - t.objectProperty( - t.identifier('titleId'), - t.identifier('titleId'), - false, - true, - ), + const createProperty = (attr: string) => { + return t.objectProperty( + t.identifier(attr), + t.identifier(attr), + false, + true, ) + } + const createSignature = (attr: string) => { + return tsOptionalPropertySignature( + t.identifier(attr), + t.tsTypeAnnotation(t.tsStringKeyword()), + ) + } + + if (opts.titleProp) { + properties.push(createProperty('title'), createProperty('titleId')) if (opts.typescript) { propertySignatures.push( - tsOptionalPropertySignature( - t.identifier('title'), - t.tsTypeAnnotation(t.tsStringKeyword()), - ), - tsOptionalPropertySignature( - t.identifier('titleId'), - t.tsTypeAnnotation(t.tsStringKeyword()), - ), + createSignature('title'), + createSignature('titleId'), ) } } if (opts.descProp) { - properties.push( - t.objectProperty( - t.identifier('desc'), - t.identifier('desc'), - false, - true, - ), - t.objectProperty( - t.identifier('descId'), - t.identifier('descId'), - false, - true, - ), - ) + properties.push(createProperty('desc'), createProperty('descId')) if (opts.typescript) { propertySignatures.push( - tsOptionalPropertySignature( - t.identifier('desc'), - t.tsTypeAnnotation(t.tsStringKeyword()), - ), - tsOptionalPropertySignature( - t.identifier('descId'), - t.tsTypeAnnotation(t.tsStringKeyword()), - ), + createSignature('desc'), + createSignature('descId'), ) } } diff --git a/packages/babel-preset/src/index.ts b/packages/babel-preset/src/index.ts index 0b9b0496..2433aee0 100644 --- a/packages/babel-preset/src/index.ts +++ b/packages/babel-preset/src/index.ts @@ -139,7 +139,7 @@ const plugin = (_: ConfigAPI, opts: Options) => { } if (opts.titleProp) { - plugins.push(() => svgDynamicTitle('title')) + plugins.push(svgDynamicTitle) } if (opts.descProp) { From 7a2da65e727f043014e4eb0068eb11f8cbb51d70 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 10:17:32 -0700 Subject: [PATCH 16/23] corrected bug --- packages/babel-plugin-svg-dynamic-title/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.ts b/packages/babel-plugin-svg-dynamic-title/src/index.ts index c5a197df..cecb36a2 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.ts @@ -4,7 +4,7 @@ import { NodePath, types as t } from '@babel/core' const elements = ['svg', 'Svg'] const createTagElement = ( - tag = 'title', + tag: string, children: t.JSXExpressionContainer[] = [], attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[] = [], ) => { @@ -41,7 +41,7 @@ const addTagIdAttribute = ( return attributes } -const plugin = (tag: string) => ({ +const plugin = (tag = 'title') => ({ visitor: { JSXElement(path: NodePath) { if (!elements.length) return From 97e521ce9328ccaee63e969056a824634010200d Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 10:59:16 -0700 Subject: [PATCH 17/23] corrected bug --- .../src/index.ts | 18 +++++++++++------- packages/babel-preset/src/index.ts | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.ts b/packages/babel-plugin-svg-dynamic-title/src/index.ts index cecb36a2..59498f90 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.ts @@ -1,8 +1,12 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { NodePath, types as t } from '@babel/core' +import { ConfigAPI, NodePath, types as t } from '@babel/core' const elements = ['svg', 'Svg'] +export interface Options { + tag: string +} + const createTagElement = ( tag: string, children: t.JSXExpressionContainer[] = [], @@ -41,7 +45,7 @@ const addTagIdAttribute = ( return attributes } -const plugin = (tag = 'title') => ({ +const plugin = (_: ConfigAPI, opts: Options = { tag: 'title' }) => ({ visitor: { JSXElement(path: NodePath) { if (!elements.length) return @@ -59,21 +63,21 @@ const plugin = (tag = 'title') => ({ const getTagElement = ( existingTitle?: t.JSXElement, ): t.JSXExpressionContainer => { - const tagExpression = t.identifier(tag) + const tagExpression = t.identifier(opts.tag) if (existingTitle) { existingTitle.openingElement.attributes = addTagIdAttribute( - tag, + opts.tag, existingTitle.openingElement.attributes, ) } const conditionalTitle = t.conditionalExpression( tagExpression, createTagElement( - tag, + opts.tag, [t.jsxExpressionContainer(tagExpression)], existingTitle ? existingTitle.openingElement.attributes - : [createTagIdAttribute(tag)], + : [createTagIdAttribute(opts.tag)], ), t.nullLiteral(), ) @@ -103,7 +107,7 @@ const plugin = (tag = 'title') => ({ if (!childPath.isJSXElement()) return false const name = childPath.get('openingElement').get('name') if (!name.isJSXIdentifier()) return false - if (name.node.name !== tag) return false + if (name.node.name !== opts.tag) return false tagElement = getTagElement(childPath.node) childPath.replaceWith(tagElement) return true diff --git a/packages/babel-preset/src/index.ts b/packages/babel-preset/src/index.ts index 2433aee0..455c1191 100644 --- a/packages/babel-preset/src/index.ts +++ b/packages/babel-preset/src/index.ts @@ -143,7 +143,7 @@ const plugin = (_: ConfigAPI, opts: Options) => { } if (opts.descProp) { - plugins.push(() => svgDynamicTitle('desc')) + plugins.push(svgDynamicTitle, { tag: 'desc' }) } if (opts.native) { From a62037a60977c3e3de759df179a7be44264c10e0 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 11:22:20 -0700 Subject: [PATCH 18/23] refactored code --- .../src/index.test.ts | 54 ++++++++++--------- packages/babel-preset/src/index.ts | 4 +- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts index 212f0007..ba37cf1e 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts @@ -1,26 +1,18 @@ import { transform } from '@babel/core' -import plugin from '.' +import plugin, { Options } from '.' -const testPlugin = (code: string, tag = 'title') => { +const testPlugin = (code: string, options: Options) => { const result = transform(code, { - plugins: ['@babel/plugin-syntax-jsx', tag ? () => plugin(tag) : plugin], + plugins: ['@babel/plugin-syntax-jsx', [plugin, options]], configFile: false, }) return result?.code } -const testTitlePlugin = (code: string) => { - return testPlugin(code) -} - -const testDescPlugin = (code: string) => { - return testPlugin(code, 'desc') -} - describe('title plugin', () => { it('should add title attribute if not present', () => { - expect(testTitlePlugin('')).toMatchInlineSnapshot( + expect(testPlugin('', { tag: 'title' })).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) }) @@ -28,13 +20,13 @@ describe('title plugin', () => { it('should add title element and fallback to existing title', () => { // testing when the existing title contains a simple string expect( - testTitlePlugin(`Hello`), + testPlugin(`Hello`, { tag: 'title' }), ).toMatchInlineSnapshot( `"{title === undefined ? Hello : title ? {title} : null};"`, ) // testing when the existing title contains an JSXExpression expect( - testTitlePlugin(`{"Hello"}`), + testPlugin(`{"Hello"}`, { tag: 'title' }), ).toMatchInlineSnapshot( `"{title === undefined ? {\\"Hello\\"} : title ? {title} : null};"`, ) @@ -42,24 +34,30 @@ describe('title plugin', () => { it('should preserve any existing title attributes', () => { // testing when the existing title contains a simple string expect( - testTitlePlugin(`Hello`), + testPlugin(`Hello`, { tag: 'title' }), ).toMatchInlineSnapshot( `"{title === undefined ? Hello : title ? {title} : null};"`, ) }) it('should support empty title', () => { - expect(testTitlePlugin('')).toMatchInlineSnapshot( + expect( + testPlugin('', { tag: 'title' }), + ).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) }) it('should support self closing title', () => { - expect(testTitlePlugin('')).toMatchInlineSnapshot( + expect( + testPlugin('', { tag: 'title' }), + ).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) }) it('should work if an attribute is already present', () => { - expect(testTitlePlugin('')).toMatchInlineSnapshot( + expect( + testPlugin('', { tag: 'title' }), + ).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) }) @@ -67,7 +65,7 @@ describe('title plugin', () => { describe('desc plugin', () => { it('should add desc attribute if not present', () => { - expect(testDescPlugin('')).toMatchInlineSnapshot( + expect(testPlugin('', { tag: 'desc' })).toMatchInlineSnapshot( `"{desc ? {desc} : null};"`, ) }) @@ -75,13 +73,13 @@ describe('desc plugin', () => { it('should add desc element and fallback to existing desc', () => { // testing when the existing desc contains a simple string expect( - testDescPlugin(`Hello`), + testPlugin(`Hello`, { tag: 'desc' }), ).toMatchInlineSnapshot( `"{desc === undefined ? Hello : desc ? {desc} : null};"`, ) // testing when the existing desc contains an JSXExpression expect( - testDescPlugin(`{"Hello"}`), + testPlugin(`{"Hello"}`, { tag: 'desc' }), ).toMatchInlineSnapshot( `"{desc === undefined ? {\\"Hello\\"} : desc ? {desc} : null};"`, ) @@ -89,24 +87,30 @@ describe('desc plugin', () => { it('should preserve any existing desc attributes', () => { // testing when the existing desc contains a simple string expect( - testDescPlugin(`Hello`), + testPlugin(`Hello`, { tag: 'desc' }), ).toMatchInlineSnapshot( `"{desc === undefined ? Hello : desc ? {desc} : null};"`, ) }) it('should support empty desc', () => { - expect(testDescPlugin('')).toMatchInlineSnapshot( + expect( + testPlugin('', { tag: 'desc' }), + ).toMatchInlineSnapshot( `"{desc ? {desc} : null};"`, ) }) it('should support self closing desc', () => { - expect(testDescPlugin('')).toMatchInlineSnapshot( + expect( + testPlugin('', { tag: 'desc' }), + ).toMatchInlineSnapshot( `"{desc ? {desc} : null};"`, ) }) it('should work if an attribute is already present', () => { - expect(testDescPlugin('')).toMatchInlineSnapshot( + expect( + testPlugin('', { tag: 'desc' }), + ).toMatchInlineSnapshot( `"{desc ? {desc} : null};"`, ) }) diff --git a/packages/babel-preset/src/index.ts b/packages/babel-preset/src/index.ts index 455c1191..c79cfb4d 100644 --- a/packages/babel-preset/src/index.ts +++ b/packages/babel-preset/src/index.ts @@ -139,11 +139,11 @@ const plugin = (_: ConfigAPI, opts: Options) => { } if (opts.titleProp) { - plugins.push(svgDynamicTitle) + plugins.push([svgDynamicTitle, { tag: 'title' }]) } if (opts.descProp) { - plugins.push(svgDynamicTitle, { tag: 'desc' }) + plugins.push([svgDynamicTitle, { tag: 'desc' }]) } if (opts.native) { From ffbe5dfe232fee4dc36f1678f27201deac071deb Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 11:26:33 -0700 Subject: [PATCH 19/23] removed default title --- packages/babel-plugin-svg-dynamic-title/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.ts b/packages/babel-plugin-svg-dynamic-title/src/index.ts index 59498f90..a9eb995a 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.ts @@ -45,7 +45,7 @@ const addTagIdAttribute = ( return attributes } -const plugin = (_: ConfigAPI, opts: Options = { tag: 'title' }) => ({ +const plugin = (_: ConfigAPI, opts: Options) => ({ visitor: { JSXElement(path: NodePath) { if (!elements.length) return From 42a4c73e9bbc230b59f158dc5daa58f08891718e Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 11:36:24 -0700 Subject: [PATCH 20/23] restricted type to string unions --- packages/babel-plugin-svg-dynamic-title/src/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.ts b/packages/babel-plugin-svg-dynamic-title/src/index.ts index a9eb995a..5b99a91b 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.ts @@ -4,11 +4,11 @@ import { ConfigAPI, NodePath, types as t } from '@babel/core' const elements = ['svg', 'Svg'] export interface Options { - tag: string + tag: 'title' | 'desc' } const createTagElement = ( - tag: string, + tag: Options['tag'], children: t.JSXExpressionContainer[] = [], attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[] = [], ) => { @@ -20,14 +20,14 @@ const createTagElement = ( ) } -const createTagIdAttribute = (tag: string) => +const createTagIdAttribute = (tag: Options['tag']) => t.jsxAttribute( t.jsxIdentifier('id'), t.jsxExpressionContainer(t.identifier(`${tag}Id`)), ) const addTagIdAttribute = ( - tag: string, + tag: Options['tag'], attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[], ) => { const existingId = attributes.find( From 524782c4d5c8b81f7ecbcac15b20bfd75a186374 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 11:46:55 -0700 Subject: [PATCH 21/23] corrected docs --- website/pages/docs/options.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/pages/docs/options.mdx b/website/pages/docs/options.mdx index 89b68350..f59cb5ab 100644 --- a/website/pages/docs/options.mdx +++ b/website/pages/docs/options.mdx @@ -180,7 +180,7 @@ Add title tag via title property. If titleProp is set to true and no title is pr ## Description -Add desc tag via title property. If descProp is set to true and no description is provided (`desc={undefined}`) at render time, this will fallback to an existing desc element in the svg if exists. +Add desc tag via desc property. If descProp is set to true and no description is provided (`desc={undefined}`) at render time, this will fallback to an existing desc element in the svg if exists. | Default | CLI Override | API Override | | ------- | ------------- | ------------------- | From 5baa7e50af78829dac5790bbafbf214d7ac8b4a1 Mon Sep 17 00:00:00 2001 From: avali4907 Date: Fri, 27 May 2022 13:29:32 -0700 Subject: [PATCH 22/23] streamlined state --- .../src/index.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.ts b/packages/babel-plugin-svg-dynamic-title/src/index.ts index 5b99a91b..dc3d2c86 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { ConfigAPI, NodePath, types as t } from '@babel/core' +import { NodePath, types as t } from '@babel/core' const elements = ['svg', 'Svg'] @@ -7,6 +7,10 @@ export interface Options { tag: 'title' | 'desc' } +interface State { + opts: Options +} + const createTagElement = ( tag: Options['tag'], children: t.JSXExpressionContainer[] = [], @@ -45,9 +49,10 @@ const addTagIdAttribute = ( return attributes } -const plugin = (_: ConfigAPI, opts: Options) => ({ +const plugin = () => ({ visitor: { - JSXElement(path: NodePath) { + JSXElement(path: NodePath, state: State) { + const tag = state.opts.tag if (!elements.length) return const openingElement = path.get('openingElement') @@ -63,21 +68,21 @@ const plugin = (_: ConfigAPI, opts: Options) => ({ const getTagElement = ( existingTitle?: t.JSXElement, ): t.JSXExpressionContainer => { - const tagExpression = t.identifier(opts.tag) + const tagExpression = t.identifier(tag) if (existingTitle) { existingTitle.openingElement.attributes = addTagIdAttribute( - opts.tag, + tag, existingTitle.openingElement.attributes, ) } const conditionalTitle = t.conditionalExpression( tagExpression, createTagElement( - opts.tag, + tag, [t.jsxExpressionContainer(tagExpression)], existingTitle ? existingTitle.openingElement.attributes - : [createTagIdAttribute(opts.tag)], + : [createTagIdAttribute(tag)], ), t.nullLiteral(), ) @@ -107,7 +112,7 @@ const plugin = (_: ConfigAPI, opts: Options) => ({ if (!childPath.isJSXElement()) return false const name = childPath.get('openingElement').get('name') if (!name.isJSXIdentifier()) return false - if (name.node.name !== opts.tag) return false + if (name.node.name !== tag) return false tagElement = getTagElement(childPath.node) childPath.replaceWith(tagElement) return true From 9d6cf65439cad003e5458f57f35e53a63bbb009f Mon Sep 17 00:00:00 2001 From: avali4907 Date: Sat, 28 May 2022 08:09:00 -0700 Subject: [PATCH 23/23] introduced default tag --- .../src/index.test.ts | 16 ++++++++-------- .../babel-plugin-svg-dynamic-title/src/index.ts | 12 +++++++----- packages/babel-preset/src/index.ts | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts index ba37cf1e..4753c5dc 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.test.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.test.ts @@ -1,7 +1,7 @@ import { transform } from '@babel/core' import plugin, { Options } from '.' -const testPlugin = (code: string, options: Options) => { +const testPlugin = (code: string, options: Options = {tag: 'title'}) => { const result = transform(code, { plugins: ['@babel/plugin-syntax-jsx', [plugin, options]], configFile: false, @@ -12,7 +12,7 @@ const testPlugin = (code: string, options: Options) => { describe('title plugin', () => { it('should add title attribute if not present', () => { - expect(testPlugin('', { tag: 'title' })).toMatchInlineSnapshot( + expect(testPlugin('')).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) }) @@ -20,13 +20,13 @@ describe('title plugin', () => { it('should add title element and fallback to existing title', () => { // testing when the existing title contains a simple string expect( - testPlugin(`Hello`, { tag: 'title' }), + testPlugin(`Hello`), ).toMatchInlineSnapshot( `"{title === undefined ? Hello : title ? {title} : null};"`, ) // testing when the existing title contains an JSXExpression expect( - testPlugin(`{"Hello"}`, { tag: 'title' }), + testPlugin(`{"Hello"}`), ).toMatchInlineSnapshot( `"{title === undefined ? {\\"Hello\\"} : title ? {title} : null};"`, ) @@ -34,21 +34,21 @@ describe('title plugin', () => { it('should preserve any existing title attributes', () => { // testing when the existing title contains a simple string expect( - testPlugin(`Hello`, { tag: 'title' }), + testPlugin(`Hello`), ).toMatchInlineSnapshot( `"{title === undefined ? Hello : title ? {title} : null};"`, ) }) it('should support empty title', () => { expect( - testPlugin('', { tag: 'title' }), + testPlugin(''), ).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) }) it('should support self closing title', () => { expect( - testPlugin('', { tag: 'title' }), + testPlugin(''), ).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) @@ -56,7 +56,7 @@ describe('title plugin', () => { it('should work if an attribute is already present', () => { expect( - testPlugin('', { tag: 'title' }), + testPlugin(''), ).toMatchInlineSnapshot( `"{title ? {title} : null};"`, ) diff --git a/packages/babel-plugin-svg-dynamic-title/src/index.ts b/packages/babel-plugin-svg-dynamic-title/src/index.ts index dc3d2c86..220c5962 100644 --- a/packages/babel-plugin-svg-dynamic-title/src/index.ts +++ b/packages/babel-plugin-svg-dynamic-title/src/index.ts @@ -3,8 +3,10 @@ import { NodePath, types as t } from '@babel/core' const elements = ['svg', 'Svg'] +type tag = 'title' | 'desc' + export interface Options { - tag: 'title' | 'desc' + tag: tag | null } interface State { @@ -12,7 +14,7 @@ interface State { } const createTagElement = ( - tag: Options['tag'], + tag: tag, children: t.JSXExpressionContainer[] = [], attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[] = [], ) => { @@ -24,14 +26,14 @@ const createTagElement = ( ) } -const createTagIdAttribute = (tag: Options['tag']) => +const createTagIdAttribute = (tag: tag) => t.jsxAttribute( t.jsxIdentifier('id'), t.jsxExpressionContainer(t.identifier(`${tag}Id`)), ) const addTagIdAttribute = ( - tag: Options['tag'], + tag: tag, attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[], ) => { const existingId = attributes.find( @@ -52,7 +54,7 @@ const addTagIdAttribute = ( const plugin = () => ({ visitor: { JSXElement(path: NodePath, state: State) { - const tag = state.opts.tag + const tag = state.opts.tag || 'title' if (!elements.length) return const openingElement = path.get('openingElement') diff --git a/packages/babel-preset/src/index.ts b/packages/babel-preset/src/index.ts index c79cfb4d..aa8faf58 100644 --- a/packages/babel-preset/src/index.ts +++ b/packages/babel-preset/src/index.ts @@ -139,7 +139,7 @@ const plugin = (_: ConfigAPI, opts: Options) => { } if (opts.titleProp) { - plugins.push([svgDynamicTitle, { tag: 'title' }]) + plugins.push(svgDynamicTitle) } if (opts.descProp) {