Repository: smooth-code/svgr
Pull request #26 (2017-12-07 14:45:12)

feat: add -native option for react-native-svg

codecov[bot]: # [Codecov](https://codecov.io/gh/smooth-code/svgr/pull/26?src=pr&el=h1) Report
> Merging [#26](https://codecov.io/gh/smooth-code/svgr/pull/26?src=pr&el=desc) into [master](https://codecov.io/gh/smooth-code/svgr/commit/e8988866c8a7ce99dade65dc72fb0b163b498e4c?src=pr&el=desc) will **increase** coverage by `2.56%`.
> The diff coverage is `75%`.

[![Impacted file tree graph](https://codecov.io/gh/smooth-code/svgr/pull/26/graphs/tree.svg?height=150&token=ESkRTvXFW3&src=pr&width=650)](https://codecov.io/gh/smooth-code/svgr/pull/26?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #26      +/-   ##
==========================================
+ Coverage   38.85%   41.42%   +2.56%     
==========================================
  Files          19       21       +2     
  Lines         157      169      +12     
  Branches       34       39       +5     
==========================================
+ Hits           61       70       +9     
- Misses         77       79       +2     
- Partials       19       20       +1
```


| [Impacted Files](https://codecov.io/gh/smooth-code/svgr/pull/26?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/cli/index.js](https://codecov.io/gh/smooth-code/svgr/pull/26/diff?src=pr&el=tree#diff-c3JjL2NsaS9pbmRleC5qcw==) | `0% <ø> (ø)` | :arrow_up: |
| [src/transforms/wrapIntoNativeComponent.js](https://codecov.io/gh/smooth-code/svgr/pull/26/diff?src=pr&el=tree#diff-c3JjL3RyYW5zZm9ybXMvd3JhcEludG9OYXRpdmVDb21wb25lbnQuanM=) | `0% <0%> (ø)` | |
| [src/configToOptions.js](https://codecov.io/gh/smooth-code/svgr/pull/26/diff?src=pr&el=tree#diff-c3JjL2NvbmZpZ1RvT3B0aW9ucy5qcw==) | `86.36% <66.66%> (-3.12%)` | :arrow_down: |
| [src/h2x/toReactNative.js](https://codecov.io/gh/smooth-code/svgr/pull/26/diff?src=pr&el=tree#diff-c3JjL2gyeC90b1JlYWN0TmF0aXZlLmpz) | `87.5% <87.5%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/smooth-code/svgr/pull/26?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/smooth-code/svgr/pull/26?src=pr&el=footer). Last update [e898886...391d23c](https://codecov.io/gh/smooth-code/svgr/pull/26?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 3e93805..7421612 100644
--- a/README.md
+++ b/README.md
@@ -91,6 +91,7 @@ powerful and configurable HTML transpiler. It uses AST (like
     --no-expand-props                disable props expanding
     --ids                            keep ids within the svg
     --icon                           use "1em" as width and height
+    --native                         add react-native support with react-native-svg 
     --replace-attr-value [old=new]   replace an attribute value
     -p, --precision <value>          set the number of digits in the fractional part (svgo)
     --no-title                       remove title tag (svgo)
@@ -282,6 +283,15 @@ inherits from text size. Also remove title.
 | ------- | ------------ | -------------- |
 | `false` | `--icon`     | `icon: <bool>` |
 
+### Native
+
+Modify all SVG nodes with uppercase and use a specific template with
+react-native-svg imports. **All unsupported nodes will be removed.**
+
+| Default | CLI Override | API Override   |
+| ------- | ------------ | -------------- |
+| `false` | `--native`     | `native: <bool>` |
+
 ### ViewBox
 
 Setting this to `false` will remove the viewBox property.
diff --git a/src/cli/index.js b/src/cli/index.js
index bae447c..a2a91a9 100644
--- a/src/cli/index.js
+++ b/src/cli/index.js
@@ -26,6 +26,7 @@ program
   .option('--ids', 'keep ids within the svg')
   .option('--icon', 'use "1em" as width and height')
   .option('--no-view-box', 'remove viewBox')
+  .option('--native', 'add react-native support with react-native-svg')
   .option(
     '--replace-attr-value [old=new]',
     'replace an attribute value',
diff --git a/src/configToOptions.js b/src/configToOptions.js
index 2c8caac..d021b92 100644
--- a/src/configToOptions.js
+++ b/src/configToOptions.js
@@ -1,15 +1,18 @@
 import jsx from 'h2x-plugin-jsx'
 import wrapIntoComponent from './transforms/wrapIntoComponent'
+import wrapIntoNativeComponent from './transforms/wrapIntoNativeComponent'
 import stripAttribute from './h2x/stripAttribute'
 import emSize from './h2x/emSize'
 import expandProps from './h2x/expandProps'
 import replaceAttrValue from './h2x/replaceAttrValue'
 import removeComments from './h2x/removeComments'
 import removeStyle from './h2x/removeStyle'
+import toReactNative from './h2x/toReactNative'
 
 const defaultConfig = {
   svgo: true,
   prettier: true,
+  native: false,
   icon: false,
   viewBox: true,
   replaceAttrValues: [],
@@ -28,6 +31,8 @@ const defaultConfig = {
 }
 
 function configToOptions(config = {}) {
+  if (!config.template && config.native)
+    config.template = wrapIntoNativeComponent
   config = { ...defaultConfig, ...config }
 
   function getH2xPlugins() {
@@ -37,6 +42,7 @@ function configToOptions(config = {}) {
       plugins.push(replaceAttrValue(oldValue, newValue))
     })
     if (config.expandProps) plugins.push(expandProps)
+    if (config.native) plugins.push(toReactNative)
 
     return plugins
   }
diff --git a/src/h2x/toReactNative.js b/src/h2x/toReactNative.js
new file mode 100644
index 0000000..aaaff4f
--- /dev/null
+++ b/src/h2x/toReactNative.js
@@ -0,0 +1,39 @@
+const supportedElements = {
+  svg: 'Svg',
+  circle: 'Circle',
+  ellipse: 'Ellipse',
+  g: 'G',
+  linearGradient: 'LinearGradient',
+  radialGradient: 'RadialGradient',
+  line: 'Line',
+  path: 'Path',
+  polygon: 'Polygon',
+  polyline: 'Polyline',
+  rect: 'Rect',
+  symbol: 'Symbol',
+  text: 'Text',
+  use: 'Use',
+  defs: 'Defs',
+  stop: 'Stop',
+}
+
+const reverse = Object.keys(supportedElements).reduce(
+  (map, key) => ({ ...map, [supportedElements[key]]: key }),
+  {},
+)
+
+const toReactNative = () => ({
+  visitor: {
+    JSXElement: {
+      enter(path) {
+        if (supportedElements[path.node.name]) {
+          path.node.name = supportedElements[path.node.name]
+        } else if (!reverse[path.node.name]) {
+          path.remove()
+        }
+      },
+    },
+  },
+})
+
+export default toReactNative
diff --git a/src/transforms/wrapIntoNativeComponent.js b/src/transforms/wrapIntoNativeComponent.js
new file mode 100644
index 0000000..cb3c2f1
--- /dev/null
+++ b/src/transforms/wrapIntoNativeComponent.js
@@ -0,0 +1,22 @@
+export default (opts = {}) => (code, state) => `import React from 'react'
+import Svg, {
+    Circle,
+    Ellipse,
+    G,
+    LinearGradient,
+    RadialGradient,
+    Line,
+    Path,
+    Polygon,
+    Polyline,
+    Rect,
+    Symbol,
+    Text,
+    Use,
+    Defs,
+    Stop
+} from 'react-native-svg';
+
+const ${state.componentName} = (${opts.expandProps ? 'props' : ''}) => ${code}
+
+export default ${state.componentName}`


Test patch:
diff --git a/src/__snapshots__/index.test.js.snap b/src/__snapshots__/index.test.js.snap
index 7afd709..8cc9933 100644
--- a/src/__snapshots__/index.test.js.snap
+++ b/src/__snapshots__/index.test.js.snap
@@ -87,6 +87,25 @@ export default SvgComponent;
 "
 `;
 
+exports[`convert should support react-native 1`] = `
+"import React from \\"react\\";
+
+const SvgComponent = props => (
+  <svg width={88} height={88} viewBox=\\"0 0 88 88\\" {...props}>
+    <title>Dismiss</title>
+    <g fill=\\"red\\" fillRule=\\"evenodd\\" strokeLinecap=\\"square\\">
+      <g id=\\"Dismiss\\" stroke=\\"#063855\\" strokeWidth={2}>
+        <path d=\\"M51 37L37 51\\" id=\\"Shape\\" />
+        <path d=\\"M51 51L37 37\\" />
+      </g>
+    </g>
+  </svg>
+);
+
+export default SvgComponent;
+"
+`;
+
 exports[`rawConvert should convert using specific options 1`] = `
 "import React from 'react'
 
diff --git a/src/cli/__snapshots__/index.test.js.snap b/src/cli/__snapshots__/index.test.js.snap
index 25df2c6..bbd1fbe 100644
--- a/src/cli/__snapshots__/index.test.js.snap
+++ b/src/cli/__snapshots__/index.test.js.snap
@@ -41,6 +41,37 @@ export default One;
 "
 `;
 
+exports[`cli --native 1`] = `
+"import React from \\"react\\";
+import Svg, {
+  Circle,
+  Ellipse,
+  G,
+  LinearGradient,
+  RadialGradient,
+  Line,
+  Path,
+  Polygon,
+  Polyline,
+  Rect,
+  Symbol,
+  Text,
+  Use,
+  Defs,
+  Stop
+} from \\"react-native-svg\\";
+
+const One = props => (
+  <Svg width={48} height={1} viewBox=\\"0 0 48 1\\" {...props}>
+    <Path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
+  </Svg>
+);
+
+export default One;
+
+"
+`;
+
 exports[`cli --no-expand-props 1`] = `
 "import React from \\"react\\";
 
diff --git a/src/cli/index.test.js b/src/cli/index.test.js
index 49b8af2..783430e 100644
--- a/src/cli/index.test.js
+++ b/src/cli/index.test.js
@@ -87,6 +87,13 @@ describe('cli', () => {
     expect(stdout).toMatchSnapshot()
   })
 
+  it('--native', async () => {
+    const [stdout] = await exec(
+      'babel-node src/cli --native __fixtures__/one.svg',
+    )
+    expect(stdout).toMatchSnapshot()
+  })
+
   it('should work with stdin', async () => {
     const [stdout] = await exec('babel-node src/cli < __fixtures__/one.svg')
     expect(stdout).toMatchSnapshot()
diff --git a/src/h2x/removeStyle.test.js b/src/h2x/removeStyle.test.js
new file mode 100644
index 0000000..109c24c
--- /dev/null
+++ b/src/h2x/removeStyle.test.js
@@ -0,0 +1,26 @@
+import jsx from 'h2x-plugin-jsx'
+import h2x from '../plugins/h2x'
+import removeStyle from './removeStyle'
+
+describe('removeStyle', () => {
+  it('should remove style elements', () => {
+    const result = h2x(
+      `
+      <svg>
+        <style></style>
+        <style></style>
+        <g stroke="#063855" stroke-width="2">
+          <path d="M51,37 L37,51" id="Shape"></path>
+        </g>
+      </svg>
+    `,
+      { plugins: [jsx, removeStyle] },
+    )
+
+    expect(result.trim()).toBe(`<svg>
+  <g stroke="#063855" strokeWidth={2}>
+    <path d="M51,37 L37,51" id="Shape" />
+  </g>
+</svg>`)
+  })
+})
diff --git a/src/h2x/toReactNative.test.js b/src/h2x/toReactNative.test.js
new file mode 100644
index 0000000..b312260
--- /dev/null
+++ b/src/h2x/toReactNative.test.js
@@ -0,0 +1,24 @@
+import jsx from 'h2x-plugin-jsx'
+import h2x from '../plugins/h2x'
+import toReactNative from './toReactNative'
+
+describe('toReactNative', () => {
+  it('should take svg and convert it to react-native', () => {
+    const result = h2x(
+      `
+      <svg>
+        <g stroke="#063855" stroke-width="2">
+          <path d="M51,37 L37,51" id="Shape"></path>
+        </g>
+      </svg>
+    `,
+      { plugins: [jsx, toReactNative] },
+    )
+
+    expect(result.trim()).toBe(`<Svg>
+  <G stroke="#063855" strokeWidth={2}>
+    <Path d="M51,37 L37,51" id="Shape" />
+  </G>
+</Svg>`)
+  })
+})
diff --git a/src/index.test.js b/src/index.test.js
index 6a0ebd6..3659232 100644
--- a/src/index.test.js
+++ b/src/index.test.js
@@ -96,6 +96,38 @@ describe('convert', () => {
     expect(result).toMatchSnapshot()
   })
 
+
+  it('should support react-native', async () => {
+    const result = await convert(
+      `
+      <?xml version="1.0" encoding="UTF-8"?>
+      <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">
+          <!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
+          <title>Dismiss</title>
+          <desc>Created with Sketch.</desc>
+          <defs></defs>
+          <style>
+            #Blocks {
+              fill: red;
+            }
+          </style>
+          <g id="Blocks" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square">
+              <g id="Dismiss" stroke="#063855" stroke-width="2">
+                  <path d="M51,37 L37,51" id="Shape"></path>
+                  <path d="M51,51 L37,37" id="Shape"></path>
+              </g>
+              <style>
+                #Shape {}
+              </style>
+          </g>
+      </svg>
+    `,
+    {}
+    )
+
+    expect(result).toMatchSnapshot()
+  })
+
   it('should convert style attribute', async () => {
     const result = await convert(
       `<?xml version="1.0" encoding="UTF-8" standalone="no"?>