Repository: smooth-code/svgr
Pull request #1 (2017-09-03 08:21:06)

Add template option


Patch:
diff --git a/README.md b/README.md
index ff4f932..e03b66b 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,7 @@ configurable HTML transpiler. It uses AST (like [Babel](https://github.com/babel
     -d, --out-dir <dirname>          output files into a directory
     --no-svgo                        disable SVGO
     --no-prettier                    disable Prettier
+    --template <file>                specify a custom template to use
     --no-expand-props                disable props expanding
     --icon                           use "1em" as width and height
     --replace-attr-value [old=new]   replace an attribute value
@@ -142,6 +143,14 @@ To create icons, two options are important:
 $ svgr --icon --replace-attr-value "#000000=currentColor" my-icon.svg
 ```
 
+#### Use a specific template
+
+You can use a specific template, for an example of template, see [the default one](src/transforms/wrapIntoComponent.js).
+
+```
+$ svgr --template path/to/template.js my-icon.svg
+```
+
 ## Node API usage
 
 SVGR can also be used programmatically:
@@ -189,6 +198,13 @@ Default | CLI Override | API Override
 --------|--------------|-------------
 `true`  | `--no-prettier` | `prettier: <bool>`
 
+### Template
+Specify a template file (CLI) or a template function (API) to use. For an example of template, see [the default one](src/transforms/wrapIntoComponent.js).
+
+Default | CLI Override | API Override
+--------|--------------|-------------
+[`wrapIntoComponent`](src/transforms/wrapIntoComponent.js)  | `--template` | `template: <func>`
+
 ### Expand props
 All properties given to component will be forwarded on SVG tag.
 
diff --git a/__fixtures__/template.js b/__fixtures__/template.js
new file mode 100644
index 0000000..cea9520
--- /dev/null
+++ b/__fixtures__/template.js
@@ -0,0 +1,7 @@
+export default () => (code, state) => `
+import React from 'react'
+
+export default function ${state.componentName}() {
+  return ${code}
+}
+`
diff --git a/package.json b/package.json
index f692cdc..55a7e61 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,9 @@
     "recursive-readdir": "^2.2.1",
     "svgo": "^0.7.2"
   },
+  "bin": {
+    "svgr": "./bin/svgr"
+  },
   "jest": {
     "rootDir": "src",
     "coverageDirectory": "./coverage/"
diff --git a/src/cli/index.js b/src/cli/index.js
index a7ed7c0..8b22f2e 100644
--- a/src/cli/index.js
+++ b/src/cli/index.js
@@ -1,6 +1,7 @@
 /* eslint-disable no-console */
 import program from 'commander'
-import fs from 'fs'
+import path from 'path'
+import fs from 'mz/fs'
 import glob from 'glob'
 import uniq from 'lodash/uniq'
 import pkg from '../../package.json'
@@ -20,6 +21,7 @@ program
   .option('-d, --out-dir <dirname>', 'output files into a directory')
   .option('--no-svgo', 'disable SVGO')
   .option('--no-prettier', 'disable Prettier')
+  .option('--template <file>', 'specify a custom template to use')
   .option('--no-expand-props', 'disable props expanding')
   .option('--icon', 'use "1em" as width and height')
   .option(
@@ -74,18 +76,36 @@ async function run() {
 
   filenames = uniq(filenames)
 
-  filenames.forEach(filename => {
-    if (!fs.existsSync(filename)) {
-      errors.push(`${filename} does not exist`)
-    }
-  })
+  await Promise.all(
+    filenames.map(async filename => {
+      if (await !fs.exists(filename)) {
+        errors.push(`${filename} does not exist`)
+      }
+    }),
+  )
 
   if (errors.length) {
     console.error(errors.join('. '))
     process.exit(2)
   }
 
-  const opts = configToOptions(program)
+  const config = { ...program }
+
+  if (config.template) {
+    try {
+      const template = require(path.join(process.cwd(), program.template)) // eslint-disable-line global-require, import/no-dynamic-require
+      if (template.default) config.template = template.default
+      else config.template = template
+
+      if (typeof config.template !== 'function')
+        throw new Error('Template must be a function')
+    } catch (error) {
+      console.error(`Error when loading template: ${program.template}`)
+      process.exit(2)
+    }
+  }
+
+  const opts = configToOptions(config)
 
   const command = program.outDir ? dirCommand : fileCommand
   await command(program, filenames, opts)
diff --git a/src/configToOptions.js b/src/configToOptions.js
index a34aee7..6dc0ca5 100644
--- a/src/configToOptions.js
+++ b/src/configToOptions.js
@@ -21,6 +21,7 @@ const defaultConfig = {
   trailingComma: undefined, // default to prettier
   bracketSpacing: undefined, // default to prettier
   jsxBracketSameLine: undefined, // default to prettier
+  template: wrapIntoComponent,
 }
 
 function configToOptions(config = {}) {
@@ -64,7 +65,7 @@ function configToOptions(config = {}) {
       plugins: getH2xPlugins(),
     },
     prettier: config.prettier ? getPrettierConfig() : null,
-    template: wrapIntoComponent({ expandProps: config.expandProps }),
+    template: config.template(config),
   }
 }
 
diff --git a/src/index.js b/src/index.js
index 1c01e3a..ce4b2e4 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,5 @@
 import jsx from 'h2x-plugin-jsx'
+import path from 'path'
 import svgo from './plugins/svgo'
 import h2x from './plugins/h2x'
 import prettier from './plugins/prettier'
@@ -21,7 +22,20 @@ export {
   removeComments,
 }
 
+const firstUpperCase = str => `${str.charAt(0).toUpperCase()}${str.slice(1)}`
+const hyphenToCamelCase = str =>
+  str.replace(/-(.)/g, (match, chr) => chr.toUpperCase())
+
+function expandState(state) {
+  const componentName = firstUpperCase(
+    hyphenToCamelCase(path.parse(state.filePath).name),
+  )
+
+  return { ...state, componentName }
+}
+
 export async function rawConvert(code, options, state) {
+  state = expandState(state)
   let result = code
   result = options.svgo ? await svgo(result, options.svgo, state) : result
   result = await h2x(result, options.h2x, state)
diff --git a/src/transforms/wrapIntoComponent.js b/src/transforms/wrapIntoComponent.js
index 67a79fc..02fe9f3 100644
--- a/src/transforms/wrapIntoComponent.js
+++ b/src/transforms/wrapIntoComponent.js
@@ -1,18 +1,5 @@
-import path from 'path'
+export default (opts = {}) => (code, state) => `import React from 'react'
 
-const wrapIntoComponent = (opts = {}) => (code, state) => {
-  const componentName = firstUpperCase(
-    hyphenToCamelCase(path.parse(state.filePath).name),
-  )
-  return `import React from 'react'
+const ${state.componentName} = (${opts.expandProps ? 'props' : ''}) => ${code}
 
-const ${componentName} = (${opts.expandProps ? 'props' : ''}) => ${code}
-
-export default ${componentName}`
-}
-
-const firstUpperCase = str => `${str.charAt(0).toUpperCase()}${str.slice(1)}`
-const hyphenToCamelCase = str =>
-  str.replace(/-(.)/g, (match, chr) => chr.toUpperCase())
-
-export default wrapIntoComponent
+export default ${state.componentName}`


Test patch:
diff --git a/src/cli/__snapshots__/index.test.js.snap b/src/cli/__snapshots__/index.test.js.snap
index 357535b..e5a33f4 100644
--- a/src/cli/__snapshots__/index.test.js.snap
+++ b/src/cli/__snapshots__/index.test.js.snap
@@ -143,6 +143,21 @@ export default One;
 "
 `;
 
+exports[`cli --template 1`] = `
+"import React from \\"react\\";
+
+export default function One() {
+  return (
+    <svg width={48} height={1} viewBox=\\"0 0 48 1\\" {...props}>
+      <title>Rectangle 5</title>
+      <path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
+    </svg>
+  );
+}
+
+"
+`;
+
 exports[`cli should work with a simple file 1`] = `
 "import React from \\"react\\";
 
diff --git a/src/cli/index.test.js b/src/cli/index.test.js
index 0bcbcc2..bb6291e 100644
--- a/src/cli/index.test.js
+++ b/src/cli/index.test.js
@@ -68,6 +68,13 @@ describe('cli', () => {
     expect(stdout).toMatchSnapshot()
   })
 
+  it('--template', async () => {
+    const [stdout] = await exec(
+      'babel-node src/cli --template __fixtures__/template.js __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()