diff --git a/__fixtures__/simple-existing/File..js b/__fixtures__/simple-existing/File..js
new file mode 100644
index 00000000..7158f5b0
--- /dev/null
+++ b/__fixtures__/simple-existing/File..js
@@ -0,0 +1,11 @@
+import * as React from 'react'
+
+function SvgFile(props) {
+ return (
+
+ )
+}
+
+export default SvgFile
diff --git a/__fixtures__/simple-existing/index..js b/__fixtures__/simple-existing/index..js
new file mode 100644
index 00000000..e2a362ff
--- /dev/null
+++ b/__fixtures__/simple-existing/index..js
@@ -0,0 +1 @@
+export { default as File. } from './File.'
\ No newline at end of file
diff --git a/packages/babel-plugin-transform-svg-component/src/__snapshots__/index.test.js.snap b/packages/babel-plugin-transform-svg-component/src/__snapshots__/index.test.js.snap
new file mode 100644
index 00000000..7ed69aaa
--- /dev/null
+++ b/packages/babel-plugin-transform-svg-component/src/__snapshots__/index.test.js.snap
@@ -0,0 +1,280 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` 1`] = `
+"import * as React from 'react';
+
+const MyComponent = () => {};
+
+export default MyComponent;"
+`;
+
+exports[` 2`] = `
+"import * as React from 'react';
+
+const MyComponent = () => {};
+
+export default MyComponent;"
+`;
+
+exports[`plugin javascript custom templates support basic template 1`] = `
+"import * as React from 'react';
+
+const MyComponent = () => ;
+
+export default MyComponent;"
+`;
+
+exports[`plugin javascript custom templates supports TypeScript template 1`] = `
+"import * as React from 'react';
+
+const MyComponent = (props: React.SVGProps) => ;
+
+export default MyComponent;"
+`;
+
+exports[`plugin javascript custom templates supports template that does not return an array 1`] = `";"`;
+
+exports[`plugin javascript transforms whole program 1`] = `
+"import * as React from \\"react\\";
+
+function SvgComponent() {
+ return ;
+}
+
+export default SvgComponent;"
+`;
+
+exports[`plugin javascript with "expandProps" add props 1`] = `
+"import * as React from \\"react\\";
+
+function SvgComponent(props) {
+ return ;
+}
+
+export default SvgComponent;"
+`;
+
+exports[`plugin javascript with "memo" option wrap component in "React.memo" 1`] = `
+"import * as React from \\"react\\";
+
+function SvgComponent() {
+ return ;
+}
+
+const MemoSvgComponent = React.memo(SvgComponent);
+export default MemoSvgComponent;"
+`;
+
+exports[`plugin javascript with "native" option adds import from "react-native-svg" 1`] = `
+"import * as React from \\"react\\";
+import Svg from \\"react-native-svg\\";
+
+function SvgComponent() {
+ return ;
+}
+
+export default SvgComponent;"
+`;
+
+exports[`plugin javascript with "native.expo" option adds import from "react-native-svg" & from "expo" 1`] = `
+"import * as React from \\"react\\";
+import \\"expo\\";
+
+function SvgComponent() {
+ return ;
+}
+
+export default SvgComponent;"
+`;
+
+exports[`plugin javascript with "ref" and "expandProps" option expands props 1`] = `
+"import * as React from \\"react\\";
+
+function SvgComponent({
+ svgRef,
+ ...props
+}) {
+ return ;
+}
+
+const ForwardRef = React.forwardRef((props, ref) => );
+export default ForwardRef;"
+`;
+
+exports[`plugin javascript with "ref" option adds ForwardRef component 1`] = `
+"import * as React from \\"react\\";
+
+function SvgComponent({
+ svgRef
+}) {
+ return ;
+}
+
+const ForwardRef = React.forwardRef((props, ref) => );
+export default ForwardRef;"
+`;
+
+exports[`plugin javascript with "titleProp" adds "titleProp" and "titleId" prop 1`] = `
+"import * as React from \\"react\\";
+
+function SvgComponent({
+ title,
+ titleId
+}) {
+ return ;
+}
+
+export default SvgComponent;"
+`;
+
+exports[`plugin javascript with both "memo" and "ref" option wrap component in "React.memo" and "React.forwardRef" 1`] = `
+"import * as React from \\"react\\";
+
+function SvgComponent({
+ svgRef
+}) {
+ return ;
+}
+
+const MemoSvgComponent = React.memo(SvgComponent);
+const ForwardRef = React.forwardRef((props, ref) => );
+export default ForwardRef;"
+`;
+
+exports[`plugin typescript custom templates support basic template 1`] = `
+"import * as React from 'react';
+
+const MyComponent = () => ;
+
+export default MyComponent;"
+`;
+
+exports[`plugin typescript custom templates supports TypeScript template 1`] = `
+"import * as React from 'react';
+
+const MyComponent = (props: React.SVGProps) => ;
+
+export default MyComponent;"
+`;
+
+exports[`plugin typescript custom templates supports template that does not return an array 1`] = `";"`;
+
+exports[`plugin typescript transforms whole program 1`] = `
+"import * as React from \\"react\\";
+
+function SvgComponent() {
+ return ;
+}
+
+export default SvgComponent;"
+`;
+
+exports[`plugin typescript with "expandProps" add props 1`] = `
+"import * as React from \\"react\\";
+
+function SvgComponent(props: React.SVGProps) {
+ return ;
+}
+
+export default SvgComponent;"
+`;
+
+exports[`plugin typescript with "memo" option wrap component in "React.memo" 1`] = `
+"import * as React from \\"react\\";
+
+function SvgComponent() {
+ return ;
+}
+
+const MemoSvgComponent = React.memo(SvgComponent);
+export default MemoSvgComponent;"
+`;
+
+exports[`plugin typescript with "native" option adds import from "react-native-svg" 1`] = `
+"import * as React from \\"react\\";
+import Svg from \\"react-native-svg\\";
+
+function SvgComponent() {
+ return ;
+}
+
+export default SvgComponent;"
+`;
+
+exports[`plugin typescript with "native.expo" option adds import from "react-native-svg" & from "expo" 1`] = `
+"import * as React from \\"react\\";
+import \\"expo\\";
+
+function SvgComponent() {
+ return ;
+}
+
+export default SvgComponent;"
+`;
+
+exports[`plugin typescript with "ref" and "expandProps" option expands props 1`] = `
+"import * as React from \\"react\\";
+interface SVGRProps {
+ svgRef?: React.Ref
+}
+
+function SvgComponent({
+ svgRef,
+ ...props
+}: React.SVGProps & SVGRProps) {
+ return ;
+}
+
+const ForwardRef = React.forwardRef((props, ref) => );
+export default ForwardRef;"
+`;
+
+exports[`plugin typescript with "ref" option adds ForwardRef component 1`] = `
+"import * as React from \\"react\\";
+interface SVGRProps {
+ svgRef?: React.Ref
+}
+
+function SvgComponent({
+ svgRef
+}: SVGRProps) {
+ return ;
+}
+
+const ForwardRef = React.forwardRef((props, ref) => );
+export default ForwardRef;"
+`;
+
+exports[`plugin typescript with "titleProp" adds "titleProp" and "titleId" prop 1`] = `
+"import * as React from \\"react\\";
+interface SVGRProps {
+ title?: String,
+ titleId?: String,
+}
+
+function SvgComponent({
+ title,
+ titleId
+}: SVGRProps) {
+ return ;
+}
+
+export default SvgComponent;"
+`;
+
+exports[`plugin typescript with both "memo" and "ref" option wrap component in "React.memo" and "React.forwardRef" 1`] = `
+"import * as React from \\"react\\";
+interface SVGRProps {
+ svgRef?: React.Ref
+}
+
+function SvgComponent({
+ svgRef
+}: SVGRProps) {
+ return ;
+}
+
+const MemoSvgComponent = React.memo(SvgComponent);
+const ForwardRef = React.forwardRef((props, ref) => );
+export default ForwardRef;"
+`;
diff --git a/packages/babel-plugin-transform-svg-component/src/index.js b/packages/babel-plugin-transform-svg-component/src/index.js
index 408b9cc3..7603a6ac 100644
--- a/packages/babel-plugin-transform-svg-component/src/index.js
+++ b/packages/babel-plugin-transform-svg-component/src/index.js
@@ -1,16 +1,24 @@
-import { getProps, getImport, getExport } from './util'
+import { getProps, getImport, getExport, getInterface } from './util'
function defaultTemplate(
{ template },
opts,
- { imports, componentName, props, jsx, exports },
+ { imports, interfaces, componentName, props, jsx, exports },
) {
- return template.ast`${imports}
+ const plugins = ['jsx']
+ if (opts.typescript) {
+ plugins.push('typescript')
+ }
+ const typeScriptTpl = template.smart({ plugins })
+ return typeScriptTpl.ast`${imports}
+
+${interfaces}
+
function ${componentName}(${props}) {
return ${jsx};
}
${exports}
-`
+ `
}
const plugin = (api, opts) => ({
@@ -20,6 +28,7 @@ const plugin = (api, opts) => ({
const template = opts.template || defaultTemplate
const body = template(api, opts, {
componentName: t.identifier(opts.state.componentName),
+ interfaces: getInterface(api, opts),
props: getProps(api, opts),
imports: getImport(api, opts),
exports: getExport(api, opts),
diff --git a/packages/babel-plugin-transform-svg-component/src/index.test.js b/packages/babel-plugin-transform-svg-component/src/index.test.js
index e9f126a2..311007eb 100644
--- a/packages/babel-plugin-transform-svg-component/src/index.test.js
+++ b/packages/babel-plugin-transform-svg-component/src/index.test.js
@@ -1,9 +1,12 @@
import { transform } from '@babel/core'
import plugin from '.'
-const testPlugin = (code, options) => {
+const testPlugin = language => (code, options) => {
const result = transform(code, {
- plugins: ['@babel/plugin-syntax-jsx', [plugin, options]],
+ plugins: [
+ '@babel/plugin-syntax-jsx',
+ [plugin, { ...options, typescript: language === 'typescript' }],
+ ],
configFile: false,
})
@@ -11,159 +14,150 @@ const testPlugin = (code, options) => {
}
describe('plugin', () => {
- it('should transform whole program', () => {
- const { code } = testPlugin('', {
- state: { componentName: 'SvgComponent' },
+ describe.each(['javascript', 'typescript'])('%s', language => {
+ it('transforms whole program', () => {
+ const { code } = testPlugin(language)('', {
+ state: { componentName: 'SvgComponent' },
+ })
+ expect(code).toMatchSnapshot()
})
- expect(code).toMatchInlineSnapshot(`
- "import * as React from \\"react\\";
- function SvgComponent() {
- return ;
- }
-
- export default SvgComponent;"
- `)
- })
-
- it('should add import for react-native-svg', () => {
- const { code } = testPlugin('', {
- state: { componentName: 'SvgComponent' },
- native: true,
+ describe('with "native" option', () => {
+ it('adds import from "react-native-svg"', () => {
+ const { code } = testPlugin(language)('', {
+ state: { componentName: 'SvgComponent' },
+ native: true,
+ })
+ expect(code).toMatchSnapshot()
+ })
})
- expect(code).toMatchInlineSnapshot(`
- "import * as React from \\"react\\";
- import Svg from \\"react-native-svg\\";
-
- function SvgComponent() {
- return ;
- }
-
- export default SvgComponent;"
- `)
- })
- it('should import for expo', () => {
- const { code } = testPlugin('', {
- state: { componentName: 'SvgComponent' },
- native: { expo: true },
+ describe('with "native.expo" option', () => {
+ it('adds import from "react-native-svg" & from "expo"', () => {
+ const { code } = testPlugin(language)('', {
+ state: { componentName: 'SvgComponent' },
+ native: { expo: true },
+ })
+ expect(code).toMatchSnapshot()
+ })
})
- expect(code).toMatchInlineSnapshot(`
- "import * as React from \\"react\\";
- import \\"expo\\";
- function SvgComponent() {
- return ;
- }
-
- export default SvgComponent;"
- `)
- })
-
- it('should support custom template', () => {
- const { code } = testPlugin('', {
- template: (
- { template },
- opts,
- { jsx },
- ) => template.ast`import * as React from 'react';
- const MyComponent = () => ${jsx}
- export default MyComponent
-`,
- state: { componentName: 'SvgComponent' },
+ describe('with "ref" option', () => {
+ it('adds ForwardRef component', () => {
+ const { code } = testPlugin(language)('', {
+ state: { componentName: 'SvgComponent' },
+ ref: true,
+ })
+ expect(code).toMatchSnapshot()
+ })
})
- expect(code).toMatchInlineSnapshot(`
- "import * as React from 'react';
-
- const MyComponent = () => ;
-
- export default MyComponent;"
- `)
- })
- it('should support custom typescript template', () => {
- const { code } = testPlugin('', {
- template: ({ template }, opts, { jsx }) => {
- const typescriptTemplate = template.smart({ plugins: ['typescript'] })
- return typescriptTemplate.ast`
- import * as React from 'react';
- const MyComponent = (props: React.SVGProps) => ${jsx};
- export default MyComponent;
- `
- },
- state: { componentName: 'SvgComponent' },
+ describe('with "titleProp"', () => {
+ it('adds "titleProp" and "titleId" prop', () => {
+ const { code } = testPlugin(language)('', {
+ state: { componentName: 'SvgComponent' },
+ titleProp: true,
+ })
+ expect(code).toMatchSnapshot()
+ })
})
- expect(code).toMatchInlineSnapshot(`
- "import * as React from 'react';
- const MyComponent = (props: React.SVGProps) => ;
-
- export default MyComponent;"
- `)
- })
-
- it('should handle template that does not return an array', () => {
- const { code } = testPlugin('', {
- template: ({ template }, opts, { jsx }) => template.ast`${jsx}`,
- state: { componentName: 'SvgComponent' },
+ describe('with "expandProps"', () => {
+ it('add props', () => {
+ const { code } = testPlugin(language)('', {
+ state: { componentName: 'SvgComponent' },
+ expandProps: true,
+ })
+ expect(code).toMatchSnapshot()
+ })
})
- expect(code).toMatchInlineSnapshot(`";"`)
- })
- it('should work with ref', () => {
- const { code } = testPlugin('', {
- state: { componentName: 'SvgComponent' },
- ref: true,
+ describe('with "ref" and "expandProps" option', () => {
+ it('expands props', () => {
+ const { code } = testPlugin(language)('', {
+ state: { componentName: 'SvgComponent' },
+ expandProps: true,
+ ref: true,
+ })
+ expect(code).toMatchSnapshot()
+ })
})
- expect(code).toMatchInlineSnapshot(`
- "import * as React from \\"react\\";
-
- function SvgComponent({
- svgRef
- }) {
- return ;
- }
-
- const ForwardRef = React.forwardRef((props, ref) => );
- export default ForwardRef;"
- `)
- })
- it('should work with memo', () => {
- const { code } = testPlugin('', {
- state: { componentName: 'SvgComponent' },
- memo: true,
+ describe('with "memo" option', () => {
+ it('wrap component in "React.memo"', () => {
+ const { code } = testPlugin(language)('', {
+ state: { componentName: 'SvgComponent' },
+ memo: true,
+ })
+ expect(code).toMatchSnapshot()
+ })
})
- expect(code).toMatchInlineSnapshot(`
- "import * as React from \\"react\\";
-
- function SvgComponent() {
- return ;
- }
-
- const MemoSvgComponent = React.memo(SvgComponent);
- export default MemoSvgComponent;"
- `)
- })
- it('should work with memo + ref', () => {
- const { code } = testPlugin('', {
- state: { componentName: 'SvgComponent' },
- memo: true,
- ref: true,
+ describe('with both "memo" and "ref" option', () => {
+ it('wrap component in "React.memo" and "React.forwardRef"', () => {
+ const { code } = testPlugin(language)('', {
+ state: { componentName: 'SvgComponent' },
+ memo: true,
+ ref: true,
+ })
+ expect(code).toMatchSnapshot()
+ })
})
- expect(code).toMatchInlineSnapshot(`
- "import * as React from \\"react\\";
-
- function SvgComponent({
- svgRef
- }) {
- return ;
- }
- const MemoSvgComponent = React.memo(SvgComponent);
- const ForwardRef = React.forwardRef((props, ref) => );
- export default ForwardRef;"
- `)
+ describe('custom templates', () => {
+ it('support basic template', () => {
+ const { code } = testPlugin(language)('', {
+ template: (
+ { template },
+ opts,
+ { jsx },
+ ) => template.ast`import * as React from 'react';
+ const MyComponent = () => ${jsx}
+ export default MyComponent
+ `,
+ state: { componentName: 'SvgComponent' },
+ })
+ expect(code).toMatchSnapshot()
+ })
+
+ describe('it supports JSX template', () => {
+ const { code } = testPlugin(language)('', {
+ template: ({ template }, opts, { jsx }) => {
+ const jsxTemplate = template.smart({ plugins: ['jsx'] })
+ return jsxTemplate.ast`import * as React from 'react';
+ const MyComponent = () => {${jsx}}
+ export default MyComponent
+ `
+ },
+ state: { componentName: 'SvgComponent' },
+ })
+ expect(code).toMatchSnapshot()
+ })
+
+ it('supports TypeScript template', () => {
+ const { code } = testPlugin(language)('', {
+ template: ({ template }, opts, { jsx }) => {
+ const typescriptTemplate = template.smart({
+ plugins: ['typescript'],
+ })
+ return typescriptTemplate.ast`
+ import * as React from 'react';
+ const MyComponent = (props: React.SVGProps) => ${jsx};
+ export default MyComponent;
+ `
+ },
+ state: { componentName: 'SvgComponent' },
+ })
+ expect(code).toMatchSnapshot()
+ })
+
+ it('supports template that does not return an array', () => {
+ const { code } = testPlugin(language)('', {
+ template: ({ template }, opts, { jsx }) => template.ast`${jsx}`,
+ state: { componentName: 'SvgComponent' },
+ })
+ expect(code).toMatchSnapshot()
+ })
+ })
})
})
diff --git a/packages/babel-plugin-transform-svg-component/src/util.js b/packages/babel-plugin-transform-svg-component/src/util.js
index 6a69a556..375bc333 100644
--- a/packages/babel-plugin-transform-svg-component/src/util.js
+++ b/packages/babel-plugin-transform-svg-component/src/util.js
@@ -1,3 +1,74 @@
+function typeAnnotation(typeAnnotation) {
+ return {
+ type: 'TypeAnnotation',
+ typeAnnotation,
+ }
+}
+
+function genericTypeAnnotation(id, typeParameters = null) {
+ return { type: 'GenericTypeAnnotation', id, typeParameters }
+}
+
+function typeParameters(params) {
+ return {
+ type: 'TypeParameterInstantiation',
+ params,
+ }
+}
+
+function qualifiedTypeIdentifier(qualification, id) {
+ return { type: 'QualifiedTypeIdentifier', qualification, id }
+}
+
+function intersectionTypeAnnotation(types) {
+ return { type: 'IntersectionTypeAnnotation', types }
+}
+
+function interfaceDeclaration(id, body) {
+ return {
+ type: 'InterfaceDeclaration',
+ id,
+ typeParameters: null,
+ extends: [],
+ implements: [],
+ mixins: [],
+ body,
+ }
+}
+
+function objectTypeAnnotation(properties) {
+ return {
+ type: 'ObjectTypeAnnotation',
+ properties,
+ }
+}
+
+function objectTypeProperty(key, value, optional = false) {
+ return {
+ type: 'ObjectTypeProperty',
+ key,
+ static: false,
+ proto: false,
+ kind: 'init',
+ method: false,
+ value,
+ variance: null,
+ optional,
+ }
+}
+
+function addTypeAnotation(obj, typeAnnotation, opts) {
+ if (!opts.typescript) return obj
+ return { ...obj, typeAnnotation }
+}
+
+function getSvgPropsTypeAnnotation(t) {
+ return genericTypeAnnotation(
+ qualifiedTypeIdentifier(t.identifier('React'), t.identifier('SVGProps')),
+ typeParameters([genericTypeAnnotation(t.identifier('SVGSVGElement'))]),
+ )
+}
+
export const getProps = ({ types: t }, opts) => {
const props = []
@@ -41,10 +112,57 @@ export const getProps = ({ types: t }, opts) => {
}
if (props.length === 1 && opts.expandProps) {
- return t.identifier('props')
+ return addTypeAnotation(
+ t.identifier('props'),
+ typeAnnotation(getSvgPropsTypeAnnotation(t)),
+ opts,
+ )
}
- return t.objectPattern(props)
+ return addTypeAnotation(
+ t.objectPattern(props),
+ typeAnnotation(
+ opts.expandProps
+ ? intersectionTypeAnnotation([
+ getSvgPropsTypeAnnotation(t),
+ genericTypeAnnotation(t.identifier('SVGRProps')),
+ ])
+ : genericTypeAnnotation(t.identifier('SVGRProps')),
+ ),
+ opts,
+ )
+}
+
+export const getInterface = ({ types: t }, opts) => {
+ if (!opts.typescript) return null
+ const properties = []
+ if (opts.ref) {
+ properties.push(
+ objectTypeProperty(
+ t.identifier('svgRef'),
+ genericTypeAnnotation(
+ qualifiedTypeIdentifier(t.identifier('React'), t.identifier('Ref')),
+ typeParameters([
+ genericTypeAnnotation(t.identifier('SVGSVGElement')),
+ ]),
+ ),
+ true,
+ ),
+ )
+ }
+ if (opts.titleProp) {
+ properties.push(
+ objectTypeProperty(t.identifier('title'), t.identifier('String'), true),
+ )
+ properties.push(
+ objectTypeProperty(t.identifier('titleId'), t.identifier('String'), true),
+ )
+ }
+ if (properties.length === 0) return null
+ return interfaceDeclaration(
+ t.identifier('SVGRProps'),
+ objectTypeAnnotation(properties),
+ )
}
export const getImport = ({ types: t }, opts) => {
diff --git a/packages/cli/src/__snapshots__/index.test.js.snap b/packages/cli/src/__snapshots__/index.test.js.snap
index 12e44574..ef5408c7 100644
--- a/packages/cli/src/__snapshots__/index.test.js.snap
+++ b/packages/cli/src/__snapshots__/index.test.js.snap
@@ -404,25 +404,85 @@ export default SvgFile
"
`;
+exports[`cli should support various args: --typescript --ref 1`] = `
+"import * as React from 'react'
+interface SVGRProps {
+ svgRef?: React.Ref;
+}
+
+function SvgFile({
+ svgRef,
+ ...props
+}: React.SVGProps & SVGRProps) {
+ return (
+
+ )
+}
+
+const ForwardRef = React.forwardRef((props, ref) => (
+
+))
+export default ForwardRef
+
+"
+`;
+
+exports[`cli should support various args: --typescript 1`] = `
+"import * as React from 'react'
+
+function SvgFile(props: React.SVGProps) {
+ return (
+
+ )
+}
+
+export default SvgFile
+
+"
+`;
+
exports[`cli should suppress output when transforming a directory with a --silent option 1`] = `""`;
exports[`cli should transform a whole directory and output relative destination paths 1`] = `
-"
-__fixtures__/cased/PascalCase.svg -> __fixtures_build__/whole/cased/PascalCase.js
-__fixtures__/cased/camelCase.svg -> __fixtures_build__/whole/cased/CamelCase.js
-__fixtures__/cased/kebab-case.svg -> __fixtures_build__/whole/cased/KebabCase.js
-__fixtures__/cased/multiple---dashes.svg -> __fixtures_build__/whole/cased/MultipleDashes.js
-__fixtures__/complex/skype.svg -> __fixtures_build__/whole/complex/Skype.js
-__fixtures__/complex/telegram.svg -> __fixtures_build__/whole/complex/Telegram.js
-__fixtures__/nesting/a/c/three.svg -> __fixtures_build__/whole/nesting/a/c/Three.js
-__fixtures__/nesting/a/two.svg -> __fixtures_build__/whole/nesting/a/Two.js
-__fixtures__/nesting/one.svg -> __fixtures_build__/whole/nesting/One.js
-__fixtures__/numeric/2.file.svg -> __fixtures_build__/whole/numeric/2File.js
-__fixtures__/numeric/file.svg -> __fixtures_build__/whole/numeric/File.js
-__fixtures__/simple/file.svg -> __fixtures_build__/whole/simple/File.js
-__fixtures__/withPrettierRc/file.svg -> __fixtures_build__/whole/withPrettierRc/File.js
-__fixtures__/withSvgoYml/file.svg -> __fixtures_build__/whole/withSvgoYml/File.js
-__fixtures__/withSvgrRc/file.svg -> __fixtures_build__/whole/withSvgrRc/File.js"
+"[37m[39m
+[37m[39m[37m__fixtures__/cased/camelCase.svg -> __fixtures_build__/whole/cased/CamelCase.js[39m
+[37m[39m[37m__fixtures__/cased/kebab-case.svg -> __fixtures_build__/whole/cased/KebabCase.js[39m
+[37m[39m[37m__fixtures__/cased/multiple---dashes.svg -> __fixtures_build__/whole/cased/MultipleDashes.js[39m
+[37m[39m[37m__fixtures__/complex/skype.svg -> __fixtures_build__/whole/complex/Skype.js[39m
+[37m[39m[37m__fixtures__/complex/telegram.svg -> __fixtures_build__/whole/complex/Telegram.js[39m
+[37m[39m[37m__fixtures__/nesting/a/c/three.svg -> __fixtures_build__/whole/nesting/a/c/Three.js[39m
+[37m[39m[37m__fixtures__/nesting/a/two.svg -> __fixtures_build__/whole/nesting/a/Two.js[39m
+[37m[39m[37m__fixtures__/nesting/one.svg -> __fixtures_build__/whole/nesting/One.js[39m
+[37m[39m[37m__fixtures__/numeric/2.file.svg -> __fixtures_build__/whole/numeric/2File.js[39m
+[37m[39m[37m__fixtures__/numeric/file.svg -> __fixtures_build__/whole/numeric/File.js[39m
+[37m[39m[37m__fixtures__/simple/file.svg -> __fixtures_build__/whole/simple/File.js[39m
+[37m[39m[37m__fixtures__/withPrettierRc/file.svg -> __fixtures_build__/whole/withPrettierRc/File.js[39m
+[37m[39m[37m__fixtures__/withSvgoYml/file.svg -> __fixtures_build__/whole/withSvgoYml/File.js[39m
+[37m[39m[37m__fixtures__/withSvgrRc/file.svg -> __fixtures_build__/whole/withSvgrRc/File.js[39m
+[37m__fixtures__/cased/PascalCase.svg -> __fixtures_build__/whole/cased/PascalCase.js[39m"
+`;
+
+exports[`cli should transform a whole directory with --typescript 1`] = `
+"[37m[39m
+[37m[39m[37m__fixtures__/cased/camelCase.svg -> __fixtures_build__/whole/cased/CamelCase.tsx[39m
+[37m[39m[37m__fixtures__/cased/kebab-case.svg -> __fixtures_build__/whole/cased/KebabCase.tsx[39m
+[37m[39m[37m__fixtures__/cased/multiple---dashes.svg -> __fixtures_build__/whole/cased/MultipleDashes.tsx[39m
+[37m[39m[37m__fixtures__/complex/skype.svg -> __fixtures_build__/whole/complex/Skype.tsx[39m
+[37m[39m[37m__fixtures__/complex/telegram.svg -> __fixtures_build__/whole/complex/Telegram.tsx[39m
+[37m[39m[37m__fixtures__/nesting/a/c/three.svg -> __fixtures_build__/whole/nesting/a/c/Three.tsx[39m
+[37m[39m[37m__fixtures__/nesting/a/two.svg -> __fixtures_build__/whole/nesting/a/Two.tsx[39m
+[37m[39m[37m__fixtures__/nesting/one.svg -> __fixtures_build__/whole/nesting/One.tsx[39m
+[37m[39m[37m__fixtures__/numeric/2.file.svg -> __fixtures_build__/whole/numeric/2File.tsx[39m
+[37m[39m[37m__fixtures__/numeric/file.svg -> __fixtures_build__/whole/numeric/File.tsx[39m
+[37m[39m[37m__fixtures__/simple/file.svg -> __fixtures_build__/whole/simple/File.tsx[39m
+[37m[39m[37m__fixtures__/withPrettierRc/file.svg -> __fixtures_build__/whole/withPrettierRc/File.tsx[39m
+[37m[39m[37m__fixtures__/withSvgoYml/file.svg -> __fixtures_build__/whole/withSvgoYml/File.tsx[39m
+[37m[39m[37m__fixtures__/withSvgrRc/file.svg -> __fixtures_build__/whole/withSvgrRc/File.tsx[39m
+[37m__fixtures__/cased/PascalCase.svg -> __fixtures_build__/whole/cased/PascalCase.tsx[39m"
`;
exports[`cli should work with a simple file 1`] = `
diff --git a/packages/cli/src/dirCommand.js b/packages/cli/src/dirCommand.js
index 2219ec4f..de2d631e 100644
--- a/packages/cli/src/dirCommand.js
+++ b/packages/cli/src/dirCommand.js
@@ -44,14 +44,19 @@ function defaultIndexTemplate(files) {
return exportEntries.join('\n')
}
+function getDefaultExtension(options) {
+ return options.typescript ? 'tsx' : 'js'
+}
+
export default async function dirCommand(
program,
filenames,
- { ext = 'js', filenameCase = CASE.PASCAL, ...options },
+ { ext, filenameCase = CASE.PASCAL, ...options },
) {
async function write(src, dest) {
if (!isCompilable(src)) return null
+ ext = ext || getDefaultExtension(options)
dest = rename(dest, ext, filenameCase)
const code = await convertFile(src, options)
const cwdRelative = path.relative(process.cwd(), dest)
diff --git a/packages/cli/src/index.js b/packages/cli/src/index.js
index ffda4b9c..9d8b53b0 100644
--- a/packages/cli/src/index.js
+++ b/packages/cli/src/index.js
@@ -55,6 +55,7 @@ program
'specify filename case ("pascal", "kebab", "camel") (default: "pascal")',
)
.option('--icon', 'use "1em" as width and height')
+ .option('--typescript', 'transform svg into typescript')
.option('--native', 'add react-native support with react-native-svg')
.option('--memo', 'add React.memo into the result component')
.option('--ref', 'forward ref to SVG root element')
diff --git a/packages/cli/src/index.test.js b/packages/cli/src/index.test.js
index d75fe367..466c6a3a 100644
--- a/packages/cli/src/index.test.js
+++ b/packages/cli/src/index.test.js
@@ -64,6 +64,17 @@ describe('cli', () => {
expect(sorted).toMatchSnapshot()
}, 10000)
+ it('should transform a whole directory with --typescript', async () => {
+ const result = await cli(
+ '--typescript --out-dir __fixtures_build__/whole __fixtures__',
+ )
+ const sorted = result
+ .split(/\n/)
+ .sort()
+ .join('\n')
+ expect(sorted).toMatchSnapshot()
+ }, 10000)
+
it('should suppress output when transforming a directory with a --silent option', async () => {
const result = await cli(
'--silent --out-dir __fixtures_build__/whole __fixtures__',
@@ -118,6 +129,8 @@ describe('cli', () => {
['--no-svgo'],
['--no-prettier'],
['--title-prop'],
+ ['--typescript'],
+ ['--typescript --ref'],
])(
'should support various args',
async args => {
diff --git a/packages/core/src/config.js b/packages/core/src/config.js
index 0c4b45ed..58429389 100644
--- a/packages/core/src/config.js
+++ b/packages/core/src/config.js
@@ -6,6 +6,7 @@ export const DEFAULT_CONFIG = {
expandProps: 'end',
icon: false,
native: false,
+ typescript: false,
prettier: true,
prettierConfig: null,
memo: false,
diff --git a/website/src/pages/docs/custom-templates.mdx b/website/src/pages/docs/custom-templates.mdx
new file mode 100644
index 00000000..20d43098
--- /dev/null
+++ b/website/src/pages/docs/custom-templates.mdx
@@ -0,0 +1,93 @@
+---
+menu: Advanced
+title: Custom Templates
+order: 6
+---
+
+# Custom Templates
+
+Custom templates give you the opportunity to personalize the final generated component by SVGR. In most of case you don't need it, only advanced use-cases require templates.
+
+## Create a custom template
+
+A custom template takes place in a file that exports a "template function".
+
+This function is called in a babel plugin: `babel-plugin-transform-svg-component` and must returns a Babel AST. If you are not familiar with all Babel stuff, you should read [this guide](https://github.com/jamiebuilds/babel-handbook).
+
+- `api`: The API object returned by Babel
+- `opts`: Options passed to `babel-plugin-transform-svg-component`
+- `astParts`: All pre-compiled parts by SVGR
+ - `componentName`: The component name
+ - `props`: The properties
+ - `interfaces`: All necessary interfaces (typescript)
+ - `imports`: All necessary imports
+ - `exports`: The export of the component
+ - `jsx`: The JSX part of the component
+
+The following template is the default template used by SVGR. It is a good idea to start with it:
+
+```js
+function defaultTemplate(
+ { template },
+ opts,
+ { imports, componentName, props, jsx, exports },
+) {
+ return template.ast`${imports}
+function ${componentName}(${props}) {
+ return ${jsx};
+}
+${exports}
+`
+}
+
+module.exports = defaultTemplate
+```
+
+As you can see, we use [the `template.ast` helper of Babel](https://babeljs.io/docs/en/babel-template#template). This function have to return an AST.
+
+Let's try something very simple. You want to add some PropTypes to your component:
+
+```js
+function propTypesTemplate(
+ { template },
+ opts,
+ { imports, componentName, props, jsx, exports },
+) {
+ return template.ast`${imports}
+import PropTypes from 'prop-types';
+
+function ${componentName}(${props}) {
+ return ${jsx};
+}
+
+${componentName}.propTypes = {
+ title: PropTypes.string,
+};
+
+${exports}
+`
+}
+
+module.exports = propTypesTemplate
+```
+
+As you can see it is very natural, we just add code and use AST parts in the template.
+
+## Use template with CLI
+
+You can use this template in the CLI, along the `--ext` argument:
+
+```sh
+$ npx @svgr/cli --template my/template.js --ext .ts my-icon.svg
+```
+
+## Use template in config
+
+Specify `.svgrrc.js`:
+
+```js
+// .svgrrc.js
+module.exports = {
+ template: require('./my-template'),
+}
+```
diff --git a/website/src/pages/docs/options.mdx b/website/src/pages/docs/options.mdx
index 1b7755c6..1f76dff2 100644
--- a/website/src/pages/docs/options.mdx
+++ b/website/src/pages/docs/options.mdx
@@ -53,6 +53,14 @@ Override using the API with `native: { expo: true }` to template SVG nodes with
| ------- | ------------ | -------------------------------------------- |
| `false` | `--native` | `native: ` or `native: { expo: true }` |
+## TypeScript
+
+Generates `.tsx` files with [TypeScript](https://www.typescriptlang.org/) typings.
+
+| Default | CLI Override | API Override |
+| ------- | -------------- | -------------------- |
+| `false` | `--typescript` | `typescript: ` |
+
## Dimensions
Remove width and height from root SVG tag.
diff --git a/website/src/pages/docs/typescript.mdx b/website/src/pages/docs/typescript.mdx
deleted file mode 100644
index c8ba4e93..00000000
--- a/website/src/pages/docs/typescript.mdx
+++ /dev/null
@@ -1,57 +0,0 @@
----
-menu: Advanced
-title: TypeScript
-order: 5
----
-
-# TypeScript
-
-Every languages supported by Babel are also supported by SVGR including TypeScript and Flow.
-
-## Create a custom template
-
-To target TypeScript, you need to create a custom template:
-
-```js
-function template(
- { template },
- opts,
- { imports, componentName, props, jsx, exports }
-) {
- const typeScriptTpl = template.smart({ plugins: ['typescript'] })
- return typeScriptTpl.ast`
- import * as React from 'react';
- const ${componentName} = (props: React.SVGProps) => ${jsx};
- export default ${componentName};
- `
-}
-
-module.exports = template
-```
-
-You can use this template in the CLI, along the `--ext` argument:
-
-```sh
-$ npx @svgr/cli --template path/to/template.js --ext .ts my-icon.svg
-```
-
-Or specify it in your `.svgrrc.js`:
-
-```js
-module.exports = {
- template(
- { template },
- opts,
- { imports, componentName, props, jsx, exports }
- ) {
- const typeScriptTpl = template.smart({ plugins: ['typescript'] })
- return typeScriptTpl.ast`
- import * as React from 'react';
- const ${componentName} = (props: React.SVGProps) => ${jsx};
- export default ${componentName};
- `
- },
-}
-```
-
-> To target flow, follow the same steps and replace `plugins: ['typescript']` by `plugins: ['flow']`