Skip to content

Commit 3700aba

Browse files
authored
feat: support jsxRuntime (#613)
It default to "classic", the old behaviour. But it can be "automatic" (the recommended) or "classic-preact".
1 parent bbf0430 commit 3700aba

File tree

11 files changed

+313
-21
lines changed

11 files changed

+313
-21
lines changed

packages/babel-plugin-transform-svg-component/src/__snapshots__/index.test.ts.snap

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,46 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`plugin javascript #jsxRuntime allows to specify a custom "classic" jsxRuntime using "namespace" 1`] = `
4+
"import * as Preact from \\"preact\\";
5+
6+
const SvgComponent = () => <svg><g /></svg>;
7+
8+
export default SvgComponent;"
9+
`;
10+
11+
exports[`plugin javascript #jsxRuntime allows to specify a custom "classic" jsxRuntime using "specifiers" 1`] = `
12+
"import { h } from \\"preact\\";
13+
14+
const SvgComponent = () => <svg><g /></svg>;
15+
16+
export default SvgComponent;"
17+
`;
18+
19+
exports[`plugin javascript #jsxRuntime supports "automatic" jsxRuntime 1`] = `
20+
"const SvgComponent = () => <svg><g /></svg>;
21+
22+
export default SvgComponent;"
23+
`;
24+
25+
exports[`plugin javascript #jsxRuntime supports "classic" jsxRuntime 1`] = `
26+
"import * as React from \\"react\\";
27+
28+
const SvgComponent = () => <svg><g /></svg>;
29+
30+
export default SvgComponent;"
31+
`;
32+
33+
exports[`plugin javascript allows to specify a different import source 1`] = `
34+
"import { h } from \\"preact\\";
35+
import { forwardRef, memo } from \\"preact/compat\\";
36+
37+
const SvgComponent = (_, ref) => <svg><g /></svg>;
38+
39+
const ForwardRef = forwardRef(SvgComponent);
40+
const Memo = memo(ForwardRef);
41+
export default Memo;"
42+
`;
43+
344
exports[`plugin javascript custom templates support basic template 1`] = `
445
"import * as React from 'react';
546
@@ -162,6 +203,47 @@ const Memo = memo(ForwardRef);
162203
export default Memo;"
163204
`;
164205
206+
exports[`plugin typescript #jsxRuntime allows to specify a custom "classic" jsxRuntime using "namespace" 1`] = `
207+
"import * as Preact from \\"preact\\";
208+
209+
const SvgComponent = () => <svg><g /></svg>;
210+
211+
export default SvgComponent;"
212+
`;
213+
214+
exports[`plugin typescript #jsxRuntime allows to specify a custom "classic" jsxRuntime using "specifiers" 1`] = `
215+
"import { h } from \\"preact\\";
216+
217+
const SvgComponent = () => <svg><g /></svg>;
218+
219+
export default SvgComponent;"
220+
`;
221+
222+
exports[`plugin typescript #jsxRuntime supports "automatic" jsxRuntime 1`] = `
223+
"const SvgComponent = () => <svg><g /></svg>;
224+
225+
export default SvgComponent;"
226+
`;
227+
228+
exports[`plugin typescript #jsxRuntime supports "classic" jsxRuntime 1`] = `
229+
"import * as React from \\"react\\";
230+
231+
const SvgComponent = () => <svg><g /></svg>;
232+
233+
export default SvgComponent;"
234+
`;
235+
236+
exports[`plugin typescript allows to specify a different import source 1`] = `
237+
"import { h } from \\"preact\\";
238+
import { Ref, forwardRef, memo } from \\"preact/compat\\";
239+
240+
const SvgComponent = (_, ref: Ref<SVGSVGElement>) => <svg><g /></svg>;
241+
242+
const ForwardRef = forwardRef(SvgComponent);
243+
const Memo = memo(ForwardRef);
244+
export default Memo;"
245+
`;
246+
165247
exports[`plugin typescript custom templates support basic template 1`] = `
166248
"import * as React from 'react';
167249

packages/babel-plugin-transform-svg-component/src/index.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,5 +228,58 @@ describe('plugin', () => {
228228
expect(code).toMatchSnapshot()
229229
})
230230
})
231+
232+
describe('#jsxRuntime', () => {
233+
it('supports "automatic" jsxRuntime', () => {
234+
const { code } = testPlugin(language)('<svg><g /></svg>', {
235+
jsxRuntime: 'automatic',
236+
})
237+
expect(code).toMatchSnapshot()
238+
})
239+
240+
it('supports "classic" jsxRuntime', () => {
241+
const { code } = testPlugin(language)('<svg><g /></svg>', {
242+
jsxRuntime: 'classic',
243+
})
244+
expect(code).toMatchSnapshot()
245+
})
246+
247+
it('allows to specify a custom "classic" jsxRuntime using "specifiers"', () => {
248+
const { code } = testPlugin(language)('<svg><g /></svg>', {
249+
jsxRuntime: 'classic',
250+
jsxRuntimeImport: { specifiers: ['h'], source: 'preact' },
251+
})
252+
expect(code).toMatchSnapshot()
253+
})
254+
255+
it('allows to specify a custom "classic" jsxRuntime using "namespace"', () => {
256+
const { code } = testPlugin(language)('<svg><g /></svg>', {
257+
jsxRuntime: 'classic',
258+
jsxRuntimeImport: { namespace: 'Preact', source: 'preact' },
259+
})
260+
expect(code).toMatchSnapshot()
261+
})
262+
263+
it('throws with invalid configuration', () => {
264+
expect(() => {
265+
testPlugin(language)('<svg><g /></svg>', {
266+
jsxRuntime: 'classic',
267+
jsxRuntimeImport: { source: 'preact' },
268+
})
269+
}).toThrow(
270+
'Specify either "namespace" or "specifiers" in "jsxRuntimeImport" option',
271+
)
272+
})
273+
})
274+
275+
it('allows to specify a different import source', () => {
276+
const { code } = testPlugin(language)('<svg><g /></svg>', {
277+
memo: true,
278+
ref: true,
279+
importSource: 'preact/compat',
280+
jsxRuntimeImport: { specifiers: ['h'], source: 'preact' },
281+
})
282+
expect(code).toMatchSnapshot()
283+
})
231284
})
232285
})

packages/babel-plugin-transform-svg-component/src/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ interface State {
2626
caller?: { previousExport?: string | null }
2727
}
2828

29+
export interface JSXRuntimeImport {
30+
source: string
31+
namespace?: string
32+
specifiers?: string[]
33+
}
34+
2935
export interface Options {
3036
typescript?: boolean
3137
titleProp?: boolean
@@ -36,5 +42,8 @@ export interface Options {
3642
native?: boolean
3743
memo?: boolean
3844
exportType?: 'named' | 'default'
39-
namedExport: string
45+
namedExport?: string
46+
jsxRuntime?: 'automatic' | 'classic'
47+
jsxRuntimeImport?: JSXRuntimeImport
48+
importSource?: string
4049
}

packages/babel-plugin-transform-svg-component/src/variables.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { types as t } from '@babel/core'
2-
import type { Options, TemplateVariables } from './types'
2+
import type { Options, TemplateVariables, JSXRuntimeImport } from './types'
33

44
const tsOptionalPropertySignature = (
55
...args: Parameters<typeof t.tsPropertySignature>
@@ -15,6 +15,7 @@ interface Context {
1515
interfaces: t.TSInterfaceDeclaration[]
1616
props: (t.Identifier | t.ObjectPattern)[]
1717
imports: t.ImportDeclaration[]
18+
importSource: string
1819
}
1920

2021
const getOrCreateImport = ({ imports }: Context, sourceValue: string) => {
@@ -40,7 +41,7 @@ const tsTypeReferenceSVGProps = (ctx: Context) => {
4041
return t.tsTypeReference(identifier)
4142
}
4243
const identifier = t.identifier('SVGProps')
43-
getOrCreateImport(ctx, 'react').specifiers.push(
44+
getOrCreateImport(ctx, ctx.importSource).specifiers.push(
4445
t.importSpecifier(identifier, identifier),
4546
)
4647
return t.tsTypeReference(
@@ -53,7 +54,7 @@ const tsTypeReferenceSVGProps = (ctx: Context) => {
5354

5455
const tsTypeReferenceSVGRef = (ctx: Context) => {
5556
const identifier = t.identifier('Ref')
56-
getOrCreateImport(ctx, 'react').specifiers.push(
57+
getOrCreateImport(ctx, ctx.importSource).specifiers.push(
5758
t.importSpecifier(identifier, identifier),
5859
)
5960
return t.tsTypeReference(
@@ -64,6 +65,29 @@ const tsTypeReferenceSVGRef = (ctx: Context) => {
6465
)
6566
}
6667

68+
const getJsxRuntimeImport = (cfg: JSXRuntimeImport) => {
69+
const specifiers = (() => {
70+
if (cfg.namespace)
71+
return [t.importNamespaceSpecifier(t.identifier(cfg.namespace))]
72+
if (cfg.specifiers)
73+
return cfg.specifiers.map((specifier) => {
74+
const identifier = t.identifier(specifier)
75+
return t.importSpecifier(identifier, identifier)
76+
})
77+
throw new Error(
78+
`Specify either "namespace" or "specifiers" in "jsxRuntimeImport" option`,
79+
)
80+
})()
81+
return t.importDeclaration(specifiers, t.stringLiteral(cfg.source))
82+
}
83+
84+
const defaultJsxRuntimeImport: JSXRuntimeImport = {
85+
source: 'react',
86+
namespace: 'React',
87+
}
88+
89+
const defaultImportSource = 'react'
90+
6791
export const getVariables = ({
6892
opts,
6993
jsx,
@@ -77,6 +101,7 @@ export const getVariables = ({
77101
const imports: t.ImportDeclaration[] = []
78102
const exports: (t.VariableDeclaration | t.ExportDeclaration)[] = []
79103
const ctx = {
104+
importSource: opts.importSource ?? defaultImportSource,
80105
exportIdentifier: componentName,
81106
opts,
82107
interfaces,
@@ -85,12 +110,11 @@ export const getVariables = ({
85110
exports,
86111
}
87112

88-
imports.push(
89-
t.importDeclaration(
90-
[t.importNamespaceSpecifier(t.identifier('React'))],
91-
t.stringLiteral('react'),
92-
),
93-
)
113+
if (opts.jsxRuntime !== 'automatic') {
114+
imports.push(
115+
getJsxRuntimeImport(opts.jsxRuntimeImport ?? defaultJsxRuntimeImport),
116+
)
117+
}
94118

95119
if (opts.native) {
96120
getOrCreateImport(ctx, 'react-native-svg').specifiers.push(
@@ -171,7 +195,7 @@ export const getVariables = ({
171195
}
172196
const forwardRef = t.identifier('forwardRef')
173197
const ForwardRef = t.identifier('ForwardRef')
174-
getOrCreateImport(ctx, 'react').specifiers.push(
198+
getOrCreateImport(ctx, ctx.importSource).specifiers.push(
175199
t.importSpecifier(forwardRef, forwardRef),
176200
)
177201
exports.push(
@@ -188,7 +212,7 @@ export const getVariables = ({
188212
if (opts.memo) {
189213
const memo = t.identifier('memo')
190214
const Memo = t.identifier('Memo')
191-
getOrCreateImport(ctx, 'react').specifiers.push(
215+
getOrCreateImport(ctx, ctx.importSource).specifiers.push(
192216
t.importSpecifier(memo, memo),
193217
)
194218
exports.push(
@@ -203,6 +227,9 @@ export const getVariables = ({
203227
}
204228

205229
if (opts.state.caller?.previousExport || opts.exportType === 'named') {
230+
if (!opts.namedExport) {
231+
throw new Error(`"namedExport" not specified`)
232+
}
206233
exports.push(
207234
t.exportNamedDeclaration(null, [
208235
t.exportSpecifier(ctx.exportIdentifier, t.identifier(opts.namedExport)),

packages/cli/src/__snapshots__/index.test.ts.snap

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,32 @@ export default SvgFile
196196
"
197197
`;
198198

199+
exports[`cli should support various args: --jsx-runtime automatic 1`] = `
200+
"const SvgFile = (props) => (
201+
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
202+
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
203+
</svg>
204+
)
205+
206+
export default SvgFile
207+
208+
"
209+
`;
210+
211+
exports[`cli should support various args: --jsx-runtime classic-preact 1`] = `
212+
"import { h } from 'preact'
213+
214+
const SvgFile = (props) => (
215+
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
216+
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
217+
</svg>
218+
)
219+
220+
export default SvgFile
221+
222+
"
223+
`;
224+
199225
exports[`cli should support various args: --native --expand-props none 1`] = `
200226
"import * as React from 'react'
201227
import Svg, { Path } from 'react-native-svg'

packages/cli/src/index.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ describe('cli', () => {
114114

115115
it.each([
116116
['--no-dimensions'],
117+
['--jsx-runtime classic-preact'],
118+
['--jsx-runtime automatic'],
117119
['--expand-props none'],
118120
['--expand-props start'],
119121
['--icon'],

packages/cli/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ program
106106
'specify filename case ("pascal", "kebab", "camel") (default: "pascal")',
107107
)
108108
.option('--icon', 'use "1em" as width and height')
109+
.option(
110+
'--jsx-runtime <runtime>',
111+
'specify JSX runtime ("automatic", "classic", "classic-preact") (default: "classic")',
112+
)
109113
.option('--typescript', 'transform svg into typescript')
110114
.option('--native', 'add react-native support with react-native-svg')
111115
.option('--memo', 'add React.memo into the result component')

packages/core/src/config.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,40 @@ import type { TransformOptions as BabelTransformOptions } from '@babel/core'
66
import type { ConfigPlugin } from './plugins'
77
import type { State } from './state'
88

9-
export interface Config extends Partial<Omit<TransformOptions, 'state'>> {
9+
export interface Config {
10+
ref?: boolean
11+
titleProp?: boolean
12+
expandProps?: boolean | 'start' | 'end'
1013
dimensions?: boolean
11-
runtimeConfig?: boolean
14+
icon?: boolean
1215
native?: boolean
16+
svgProps?: {
17+
[key: string]: string
18+
}
19+
replaceAttrValues?: {
20+
[key: string]: string
21+
}
22+
runtimeConfig?: boolean
1323
typescript?: boolean
1424
prettier?: boolean
1525
prettierConfig?: PrettierOptions
1626
svgo?: boolean
1727
svgoConfig?: SvgoOptions
1828
configFile?: string
29+
template?: TransformOptions['template']
30+
memo?: boolean
31+
exportType?: 'named' | 'default'
32+
namedExport?: string
33+
jsxRuntime?: 'classic' | 'classic-preact' | 'automatic'
1934

2035
// CLI only
2136
index?: boolean
2237
plugins?: ConfigPlugin[]
2338

2439
// JSX
25-
jsx?: { babelConfig?: BabelTransformOptions }
40+
jsx?: {
41+
babelConfig?: BabelTransformOptions
42+
}
2643
}
2744

2845
export const DEFAULT_CONFIG: Config = {

0 commit comments

Comments
 (0)