Skip to content

Commit 76fd6f5

Browse files
kimakgregberge
authored andcommitted
feat: add "-native" option to target React Native
1 parent e898886 commit 76fd6f5

11 files changed

Lines changed: 217 additions & 0 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ powerful and configurable HTML transpiler. It uses AST (like
9191
--no-expand-props disable props expanding
9292
--ids keep ids within the svg
9393
--icon use "1em" as width and height
94+
--native add react-native support with react-native-svg
9495
--replace-attr-value [old=new] replace an attribute value
9596
-p, --precision <value> set the number of digits in the fractional part (svgo)
9697
--no-title remove title tag (svgo)
@@ -282,6 +283,15 @@ inherits from text size. Also remove title.
282283
| ------- | ------------ | -------------- |
283284
| `false` | `--icon` | `icon: <bool>` |
284285

286+
### Native
287+
288+
Modify all SVG nodes with uppercase and use a specific template with
289+
react-native-svg imports. **All unsupported nodes will be removed.**
290+
291+
| Default | CLI Override | API Override |
292+
| ------- | ------------ | -------------- |
293+
| `false` | `--native` | `native: <bool>` |
294+
285295
### ViewBox
286296

287297
Setting this to `false` will remove the viewBox property.

src/__snapshots__/index.test.js.snap

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,25 @@ export default SvgComponent;
8787
"
8888
`;
8989

90+
exports[`convert should support react-native 1`] = `
91+
"import React from \\"react\\";
92+
93+
const SvgComponent = props => (
94+
<svg width={88} height={88} viewBox=\\"0 0 88 88\\" {...props}>
95+
<title>Dismiss</title>
96+
<g fill=\\"red\\" fillRule=\\"evenodd\\" strokeLinecap=\\"square\\">
97+
<g id=\\"Dismiss\\" stroke=\\"#063855\\" strokeWidth={2}>
98+
<path d=\\"M51 37L37 51\\" id=\\"Shape\\" />
99+
<path d=\\"M51 51L37 37\\" />
100+
</g>
101+
</g>
102+
</svg>
103+
);
104+
105+
export default SvgComponent;
106+
"
107+
`;
108+
90109
exports[`rawConvert should convert using specific options 1`] = `
91110
"import React from 'react'
92111

src/cli/__snapshots__/index.test.js.snap

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,37 @@ export default One;
4141
"
4242
`;
4343

44+
exports[`cli --native 1`] = `
45+
"import React from \\"react\\";
46+
import Svg, {
47+
Circle,
48+
Ellipse,
49+
G,
50+
LinearGradient,
51+
RadialGradient,
52+
Line,
53+
Path,
54+
Polygon,
55+
Polyline,
56+
Rect,
57+
Symbol,
58+
Text,
59+
Use,
60+
Defs,
61+
Stop
62+
} from \\"react-native-svg\\";
63+
64+
const One = props => (
65+
<Svg width={48} height={1} viewBox=\\"0 0 48 1\\" {...props}>
66+
<Path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
67+
</Svg>
68+
);
69+
70+
export default One;
71+
72+
"
73+
`;
74+
4475
exports[`cli --no-expand-props 1`] = `
4576
"import React from \\"react\\";
4677

src/cli/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ program
2626
.option('--ids', 'keep ids within the svg')
2727
.option('--icon', 'use "1em" as width and height')
2828
.option('--no-view-box', 'remove viewBox')
29+
.option('--native', 'add react-native support with react-native-svg')
2930
.option(
3031
'--replace-attr-value [old=new]',
3132
'replace an attribute value',

src/cli/index.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ describe('cli', () => {
8787
expect(stdout).toMatchSnapshot()
8888
})
8989

90+
it('--native', async () => {
91+
const [stdout] = await exec(
92+
'babel-node src/cli --native __fixtures__/one.svg',
93+
)
94+
expect(stdout).toMatchSnapshot()
95+
})
96+
9097
it('should work with stdin', async () => {
9198
const [stdout] = await exec('babel-node src/cli < __fixtures__/one.svg')
9299
expect(stdout).toMatchSnapshot()

src/configToOptions.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import jsx from 'h2x-plugin-jsx'
22
import wrapIntoComponent from './transforms/wrapIntoComponent'
3+
import wrapIntoNativeComponent from './transforms/wrapIntoNativeComponent'
34
import stripAttribute from './h2x/stripAttribute'
45
import emSize from './h2x/emSize'
56
import expandProps from './h2x/expandProps'
67
import replaceAttrValue from './h2x/replaceAttrValue'
78
import removeComments from './h2x/removeComments'
89
import removeStyle from './h2x/removeStyle'
10+
import toReactNative from './h2x/toReactNative'
911

1012
const defaultConfig = {
1113
svgo: true,
1214
prettier: true,
15+
native: false,
1316
icon: false,
1417
viewBox: true,
1518
replaceAttrValues: [],
@@ -28,6 +31,8 @@ const defaultConfig = {
2831
}
2932

3033
function configToOptions(config = {}) {
34+
if (!config.template && config.native)
35+
config.template = wrapIntoNativeComponent
3136
config = { ...defaultConfig, ...config }
3237

3338
function getH2xPlugins() {
@@ -37,6 +42,7 @@ function configToOptions(config = {}) {
3742
plugins.push(replaceAttrValue(oldValue, newValue))
3843
})
3944
if (config.expandProps) plugins.push(expandProps)
45+
if (config.native) plugins.push(toReactNative)
4046

4147
return plugins
4248
}

src/h2x/removeStyle.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import jsx from 'h2x-plugin-jsx'
2+
import h2x from '../plugins/h2x'
3+
import removeStyle from './removeStyle'
4+
5+
describe('removeStyle', () => {
6+
it('should remove style elements', () => {
7+
const result = h2x(
8+
`
9+
<svg>
10+
<style></style>
11+
<style></style>
12+
<g stroke="#063855" stroke-width="2">
13+
<path d="M51,37 L37,51" id="Shape"></path>
14+
</g>
15+
</svg>
16+
`,
17+
{ plugins: [jsx, removeStyle] },
18+
)
19+
20+
expect(result.trim()).toBe(`<svg>
21+
<g stroke="#063855" strokeWidth={2}>
22+
<path d="M51,37 L37,51" id="Shape" />
23+
</g>
24+
</svg>`)
25+
})
26+
})

src/h2x/toReactNative.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const supportedElements = {
2+
svg: 'Svg',
3+
circle: 'Circle',
4+
ellipse: 'Ellipse',
5+
g: 'G',
6+
linearGradient: 'LinearGradient',
7+
radialGradient: 'RadialGradient',
8+
line: 'Line',
9+
path: 'Path',
10+
polygon: 'Polygon',
11+
polyline: 'Polyline',
12+
rect: 'Rect',
13+
symbol: 'Symbol',
14+
text: 'Text',
15+
use: 'Use',
16+
defs: 'Defs',
17+
stop: 'Stop',
18+
}
19+
20+
const reverse = Object.keys(supportedElements).reduce(
21+
(map, key) => ({ ...map, [supportedElements[key]]: key }),
22+
{},
23+
)
24+
25+
const toReactNative = () => ({
26+
visitor: {
27+
JSXElement: {
28+
enter(path) {
29+
if (supportedElements[path.node.name]) {
30+
path.node.name = supportedElements[path.node.name]
31+
} else if (!reverse[path.node.name]) {
32+
path.remove()
33+
}
34+
},
35+
},
36+
},
37+
})
38+
39+
export default toReactNative

src/h2x/toReactNative.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import jsx from 'h2x-plugin-jsx'
2+
import h2x from '../plugins/h2x'
3+
import toReactNative from './toReactNative'
4+
5+
describe('toReactNative', () => {
6+
it('should take svg and convert it to react-native', () => {
7+
const result = h2x(
8+
`
9+
<svg>
10+
<g stroke="#063855" stroke-width="2">
11+
<path d="M51,37 L37,51" id="Shape"></path>
12+
</g>
13+
</svg>
14+
`,
15+
{ plugins: [jsx, toReactNative] },
16+
)
17+
18+
expect(result.trim()).toBe(`<Svg>
19+
<G stroke="#063855" strokeWidth={2}>
20+
<Path d="M51,37 L37,51" id="Shape" />
21+
</G>
22+
</Svg>`)
23+
})
24+
})

src/index.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,38 @@ describe('convert', () => {
9696
expect(result).toMatchSnapshot()
9797
})
9898

99+
100+
it('should support react-native', async () => {
101+
const result = await convert(
102+
`
103+
<?xml version="1.0" encoding="UTF-8"?>
104+
<svg width="88px" height="88px" viewBox="0 0 88 88" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
105+
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
106+
<title>Dismiss</title>
107+
<desc>Created with Sketch.</desc>
108+
<defs></defs>
109+
<style>
110+
#Blocks {
111+
fill: red;
112+
}
113+
</style>
114+
<g id="Blocks" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square">
115+
<g id="Dismiss" stroke="#063855" stroke-width="2">
116+
<path d="M51,37 L37,51" id="Shape"></path>
117+
<path d="M51,51 L37,37" id="Shape"></path>
118+
</g>
119+
<style>
120+
#Shape {}
121+
</style>
122+
</g>
123+
</svg>
124+
`,
125+
{}
126+
)
127+
128+
expect(result).toMatchSnapshot()
129+
})
130+
99131
it('should convert style attribute', async () => {
100132
const result = await convert(
101133
`<?xml version="1.0" encoding="UTF-8" standalone="no"?>

0 commit comments

Comments
 (0)