Prepare for publishing

This commit is contained in:
Greg Bergé 2017-09-02 10:11:13 +00:00
parent b4b3987579
commit dadd49fcb8
15 changed files with 1382 additions and 185 deletions

7
.mversionrc Normal file
View File

@ -0,0 +1,7 @@
{
"scripts": {
"precommit": "yarn build && git add -A",
"postcommit": "git push && git push --tags",
"postupdate": "npm publish lib && conventional-github-releaser --preset angular"
}
}

3
.npmignore Normal file
View File

@ -0,0 +1,3 @@
/*
!/lib/*.js
*.test.js

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"singleQuote": true,
"trailingComma": "all",
"semi": false
}

21
.travis.yml Normal file
View File

@ -0,0 +1,21 @@
language: node_js
node_js:
- 6
- 8
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 0.28.4
- export PATH="$HOME/.yarn/bin:$PATH"
script:
- yarn ci
notifications:
email: false
cache:
yarn: true
directories:
- ".eslintcache"
- "node_modules"

218
README.md
View File

@ -1,14 +1,228 @@
# SVGR # SVGR
[![Build Status](https://travis-ci.org/smooth-code/svgr.svg?branch=master)](https://travis-ci.org/smooth-code/svgr)
[![codecov](https://codecov.io/gh/smooth-code/svgr/branch/master/graph/badge.svg)](https://codecov.io/gh/smooth-code/svgr)
**SVGR** is a tool to convert raw SVG into React components. **SVGR** is a tool to convert raw SVG into React components.
## Why? ```sh
npm install svgr
```
## Motivation
React supports SVG out of the box, it's simpler, easier and much more powerful to React supports SVG out of the box, it's simpler, easier and much more powerful to
have a component instead of an SVG file. Wrapped in React components, your SVG are have a component instead of an SVG file. Wrapped in React components, your SVG are
inlined in the page and you can style it using CSS. inlined in the page and you can style it using CSS.
A lot of projects are trying to solve this problem. There is a lot of projects doing the same but I wanted something solid and configurable.
This is project is based on [h2x](https://github.com/smooth-code/h2x), a powerful and
configurable HTML transpiler. It permits to modify SVG code using AST, that give a lot
of power.
## Command line usage
```
Usage: svgr [options] <file>
Options:
-V, --version output the version number
-d, --out-dir <dirname> output files into a directory
--no-svgo disable SVGO
--no-prettier disable Prettier
--no-expand-props disable props expanding
--icon use "1em" as width and height
--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)
--tab-width specify the number of spaces by indentation-level (prettier)
--use-tabs indent lines with tabs instead of spaces (prettier)
--no-semi remove semi-colons (prettier)
--single-quote use single-quotes instead of double-quotes (prettier)
--trailing-comma <none|es5|all> print trailing commas wherever possible when multi-line (prettier)
--no-bracket-spacing print spaces between brackets in object literals (prettier)
--jsx-bracket-same-line put the > of a multi-line JSX element at the end of the last line instead of being alone on the next line (prettier)
-h, --help output usage information
Examples:
svgr --replace-attr-value "#fff=currentColor" icon.svg
```
### Recipes
#### Processing a directory
A whole directory can be processed, all svg (matching `.svg` or `.SVG`) will be transformed into React
components.
```
$ svgr -d icons icons
icons/web/clock-icon.svg -> icons/web/ClockIcon.js
icons/web/wifi-icon.svg -> icons/web/WifiIcon.js
icons/spinner/cog-icon.svg -> icons/spinner/CogIcon.js
icons/spinner/spinner-icon.svg -> icons/spinner/SpinnerIcon.js
```
#### Using stdin
```
$ svgr < icons/web/wifi-icon.svg
```
#### Using stdin / stdout
```
$ svgr < icons/web/wifi-icon.svg > icons/web/WifiIcon.js
```
#### Creating icons
To create icons, two options are important:
- `--icon`: title will be removed and svg will automatically inherits text size
- `--replace-attr-value`: replace color value to "currentColor" to inherits text color
```
$ svgr --icon --replace-attr-value="#000000=currentColor" my-icon.svg
```
## Node API usage
SVGR can also be used programatically:
```js
import svgr from 'svgr'
const svgCode = `
<?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>
<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>
</g>
</svg>
`
svgr(svgCode, { prettier: false }).then(jsCode => {
console.log(jsCode)
})
```
## Options
SVGR ships with a handful of customizable options, usable in both the CLI and API.
### SVGO
Use [SVGO](https://github.com/svg/svgo/) to optimize svg code before transforming it into component.
Default | CLI Override | API Override
--------|--------------|-------------
`true` | `--no-svgo` | `svgo: <bool>`
### Prettier
Use [Prettier](https://github.com/prettier/prettier) to format JavaScript code output.
Default | CLI Override | API Override
--------|--------------|-------------
`true` | `--no-prettier` | `prettier: <bool>`
### Expand props
Expand props, so all properties given to component will be forwarded on SVG tag.
Default | CLI Override | API Override
--------|--------------|-------------
`true` | `--no-expand-props` | `expandProps: <bool>`
### Icon
Remove title and replace SVG "width" and "height" value by "1em" (SVG size inherits
from text size).
Default | CLI Override | API Override
--------|--------------|-------------
`false` | `--icon` | `icon: <bool>`
### Replace attribute value
Replace an attribute value by an other. The main usage of this option is to change
an icon color to "currentColor" in order to inherit from text color.
Default | CLI Override | API Override
--------|--------------|-------------
`[]` | `--replace-attr-value <old=new>` | `replaceAttrValues: <string[]>`
### Precision
Set number of digits in the fractional part. For more information, see [SVGO](https://github.com/svg/svgo).
Default | CLI Override | API Override
--------|--------------|-------------
`3` | `--precision <int>` | `precision: <int>`
### Title
Remove the title from SVG. For more information, see [SVGO `removeTitle` plugin](https://github.com/svg/svgo).
Default | CLI Override | API Override
--------|--------------|-------------
`true` | `--no-title` | `title: <bool>`
### Tab Width
Specify the number of spaces per indentation-level. For more information, see [Prettier](https://github.com/prettier/prettier/blob/master/README.md#tab-width).
Default | CLI Override | API Override
--------|--------------|-------------
`2` | `--tab-width <int>` | `tabWidth: <int>`
### Tabs
Indent lines with tabs instead of spaces For more information, see [Prettier](https://github.com/prettier/prettier/blob/master/README.md#tabs).
Default | CLI Override | API Override
--------|--------------|-------------
`false` | `--use-tabs` | `useTabs: <bool>`
### Semicolons
Print semicolons at the ends of statements. For more information, see [Prettier](https://github.com/prettier/prettier/blob/master/README.md#semicolons).
Default | CLI Override | API Override
--------|--------------|-------------
`true` | `--no-semi` | `semi: <bool>`
### Quotes
Use single quotes instead of double quotes. For more information, see [Prettier](https://github.com/prettier/prettier/blob/master/README.md#quotes).
Default | CLI Override | API Override
--------|--------------|-------------
`false` | `--single-quote` | `singleQuote: <bool>`
### Trailing Commas
Print trailing commas wherever possible when multi-line. For more information, see [Prettier](https://github.com/prettier/prettier/blob/master/README.md#trailing-commas).
Default | CLI Override | API Override
--------|--------------|-------------
`"none"` | <code>--trailing-comma <none&#124;es5&#124;all></code> | <code>trailingComma: "<none&#124;es5&#124;all>"</code>
### Bracket Spacing
Print spaces between brackets in object literals. For more information, see [Prettier](https://github.com/prettier/prettier/blob/master/README.md#bracket-spacing).
Default | CLI Override | API Override
--------|--------------|-------------
`true` | `--no-bracket-spacing` | `bracketSpacing: <bool>`
### JSX Brackets
Put the `>` of a multi-line JSX element at the end of the last line instead of being alone on the next line (does not apply to self closing elements). For more information, see [Prettier](https://github.com/prettier/prettier/blob/master/README.md#jsx-brackets).
Default | CLI Override | API Override
--------|--------------|-------------
`false` | `--jsx-bracket-same-line` | `jsxBracketSameLine: <bool>`
## Other projects
A lot of projects are tried to solve this problem.
Using raw node: Using raw node:

3
bin/svgr Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env node
require('../lib/cli')

View File

@ -3,8 +3,18 @@
"description": "Convert raw SVG into React components.", "description": "Convert raw SVG into React components.",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.js",
"repository": "git@github.com:smooth-code/svgr.git",
"author": "Greg Bergé <berge.greg@gmail.com>", "author": "Greg Bergé <berge.greg@gmail.com>",
"keywords": [
"svg",
"h2x",
"react",
"component",
"svg2react",
"svg-to-react"
],
"license": "MIT", "license": "MIT",
"main": "lib/index.js",
"devDependencies": { "devDependencies": {
"babel-cli": "^6.26.0", "babel-cli": "^6.26.0",
"babel-eslint": "^7.2.3", "babel-eslint": "^7.2.3",
@ -13,16 +23,18 @@
"babel-preset-env": "^1.6.0", "babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.24.1",
"codecov": "^2.3.0", "codecov": "^2.3.0",
"eslint": "^4.5.0", "conventional-github-releaser": "^1.1.12",
"conventional-recommended-bump": "^1.0.1",
"eslint": "^4.6.0",
"eslint-config-airbnb-base": "^11.3.2", "eslint-config-airbnb-base": "^11.3.2",
"eslint-config-prettier": "^2.3.0", "eslint-config-prettier": "^2.4.0",
"eslint-plugin-import": "^2.7.0", "eslint-plugin-import": "^2.7.0",
"jest": "^20.0.4" "jest": "^20.0.4",
"mversion": "^1.10.1"
}, },
"dependencies": { "dependencies": {
"chalk": "^2.1.0", "chalk": "^2.1.0",
"commander": "^2.11.0", "commander": "^2.11.0",
"fs-readdir-recursive": "^1.0.0",
"glob": "^7.1.2", "glob": "^7.1.2",
"h2x-core": "^0.1.6", "h2x-core": "^0.1.6",
"h2x-plugin-jsx": "^0.1.6", "h2x-plugin-jsx": "^0.1.6",
@ -31,7 +43,17 @@
"output-file-sync": "^2.0.0", "output-file-sync": "^2.0.0",
"prettier": "^1.6.1", "prettier": "^1.6.1",
"recursive-readdir": "^2.2.1", "recursive-readdir": "^2.2.1",
"svgo": "^0.7.2", "svgo": "^0.7.2"
"util.promisify": "^1.0.0" },
"jest": {
"rootDir": "src",
"coverageDirectory": "./coverage/"
},
"scripts": {
"prepublish": "yarn run build",
"build": "rm -rf lib/ && NODE_ENV=production babel src -d lib",
"format": "prettier \"src/**/*.js\"",
"release": "mversion `conventional-recommended-bump -p angular` -m",
"test": "eslint . && jest --runInBand --coverage && codecov"
} }
} }

View File

@ -1,32 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`convert should be possible to specify options 1`] = ` exports[`convert should convert using config 1`] = `
"import React from \\"react\\"; "import React from \\"react\\";
const MyComponent = props => ( const SvgComponent = props => (
<svg width={88} height={88} viewBox=\\"0 0 88 88\\" {...props}> <svg width={88} height={88} viewBox=\\"0 0 88 88\\" {...props}>
<title>Dismiss</title>
<g
stroke=\\"currentColor\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37L37 51M51 51L37 37\\" />
</g>
</svg>
);
export default MyComponent;
"
`;
exports[`convert should convert 1`] = `
"import React from \\"react\\";
const MyComponent = props => (
<svg width=\\"1em\\" height=\\"1em\\" viewBox=\\"0 0 88 88\\" {...props}>
<title>Dismiss</title> <title>Dismiss</title>
<g <g
stroke=\\"#063855\\" stroke=\\"#063855\\"
@ -40,6 +18,31 @@ const MyComponent = props => (
</svg> </svg>
); );
export default MyComponent; export default SvgComponent;
" "
`; `;
exports[`rawConvert should convert using specific options 1`] = `
"import React from 'react'
const MyComponent = (props) => {/*?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?*/}
<svg width=\\"88px\\" height=\\"88px\\" viewBox=\\"0 0 88 88\\" version=\\"1.1\\" xmlnsXlink=\\"http://www.w3.org/1999/xlink\\" {...props}>
{/*Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch*/}
<title>
Dismiss
</title>
<desc>
Created with Sketch.
</desc>
<defs />
<g id=\\"Blocks\\" stroke=\\"none\\" strokeWidth={1} fill=\\"none\\" fillRule=\\"evenodd\\" strokeLinecap=\\"square\\">
<g id=\\"Dismiss\\" stroke=\\"currentColor\\" strokeWidth={2}>
<path d=\\"M51,37 L37,51\\" id=\\"Shape\\" />
<path d=\\"M51,51 L37,37\\" id=\\"Shape\\" />
</g>
</g>
</svg>
export default MyComponent"
`;

View File

@ -22,8 +22,11 @@ function getOpts(program) {
function getSvgoConfig() { function getSvgoConfig() {
const plugins = [] const plugins = []
if (!program.title) plugins.push({ removeTitle: {} }) const config = { plugins }
return { plugins } if (!program.title || program.icon) plugins.push({ removeTitle: {} })
if (program.precision !== undefined)
config.floatPrecision = Number(program.precision)
return config
} }
function getPrettierConfig() { function getPrettierConfig() {

View File

@ -27,6 +27,10 @@ program
'replace an attribute value', 'replace an attribute value',
values, values,
) )
.option(
'-p, --precision <value>',
'set the number of digits in the fractional part (svgo)',
)
.option('--no-title', 'remove title tag (svgo)') .option('--no-title', 'remove title tag (svgo)')
.option( .option(
'--tab-width', '--tab-width',

View File

@ -28,6 +28,27 @@ describe('cli', () => {
expect(stdout).toMatchSnapshot() expect(stdout).toMatchSnapshot()
}) })
it('--icon', async () => {
const [stdout] = await exec(
'babel-node src/cli --icon __fixtures__/one.svg',
)
expect(stdout).toMatchSnapshot()
})
it('--replace-attr-value', async () => {
const [stdout] = await exec(
'babel-node src/cli --replace-attr-value "#063855=currentColor" __fixtures__/one.svg',
)
expect(stdout).toMatchSnapshot()
})
it('--precision', async () => {
const [stdout] = await exec(
'babel-node src/cli --precision 1 __fixtures__/one.svg',
)
expect(stdout).toMatchSnapshot()
})
it('--no-title', async () => { it('--no-title', async () => {
const [stdout] = await exec( const [stdout] = await exec(
'babel-node src/cli --no-title __fixtures__/one.svg', 'babel-node src/cli --no-title __fixtures__/one.svg',
@ -42,10 +63,8 @@ describe('cli', () => {
expect(stdout).toMatchSnapshot() expect(stdout).toMatchSnapshot()
}) })
it('--replace-attr-value', async () => { it('should work with stdin', async () => {
const [stdout] = await exec( const [stdout] = await exec('babel-node < src/cli __fixtures__/one.svg')
'babel-node src/cli --replace-attr-value "#063855=currentColor" __fixtures__/one.svg',
)
expect(stdout).toMatchSnapshot() expect(stdout).toMatchSnapshot()
}) })

71
src/configToOptions.js Normal file
View File

@ -0,0 +1,71 @@
import jsx from 'h2x-plugin-jsx'
import wrapIntoComponent from './transforms/wrapIntoComponent'
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'
const defaultConfig = {
svgo: true,
prettier: true,
icon: false,
replaceAttrValues: [],
expandProps: true,
title: true,
precision: 3, // default to svgo
semi: undefined, // default to prettier
singleQuote: undefined, // default to prettier
tabWidth: undefined, // default to prettier
useTabs: undefined, // default to prettier
trailingComma: undefined, // default to prettier
bracketSpacing: undefined, // default to prettier
jsxBracketSameLine: undefined, // default to prettier
}
function configToOptions(config = {}) {
config = { ...defaultConfig, ...config }
function getH2xPlugins() {
const plugins = [jsx, stripAttribute('xmlns'), removeComments]
if (config.icon) plugins.push(emSize)
config.replaceAttrValues.forEach(([oldValue, newValue]) => {
plugins.push(replaceAttrValue(oldValue, newValue))
})
if (config.expandProps) plugins.push(expandProps)
return plugins
}
function getSvgoConfig() {
const plugins = []
const svgoConfig = { plugins }
if (!config.title || config.icon) plugins.push({ removeTitle: {} })
if (config.precision !== undefined)
svgoConfig.floatPrecision = Number(svgoConfig.precision)
return svgoConfig
}
function getPrettierConfig() {
return {
semi: config.semi,
singleQuote: config.singleQuote,
tabWidth: config.tabWidth,
useTabs: config.useTabs,
trailingComma: config.trailingComma,
bracketSpacing: config.bracketSpacing,
jsxBracketSameLine: config.jsxBracketSameLine,
}
}
return {
svgo: config.svgo ? getSvgoConfig() : null,
h2x: {
plugins: getH2xPlugins(),
},
prettier: config.prettier ? getPrettierConfig() : null,
template: wrapIntoComponent({ expandProps: config.expandProps }),
}
}
export default configToOptions

View File

@ -9,8 +9,10 @@ import emSize from './h2x/emSize'
import expandProps from './h2x/expandProps' import expandProps from './h2x/expandProps'
import replaceAttrValue from './h2x/replaceAttrValue' import replaceAttrValue from './h2x/replaceAttrValue'
import removeComments from './h2x/removeComments' import removeComments from './h2x/removeComments'
import configToOptions from './configToOptions'
export { export {
jsx,
stripAttribute, stripAttribute,
emSize, emSize,
expandProps, expandProps,
@ -19,33 +21,22 @@ export {
removeComments, removeComments,
} }
const defaultOptions = { export async function rawConvert(code, options, state) {
svgo: {},
h2x: {
plugins: [
jsx,
removeComments,
emSize,
stripAttribute('xmlns'),
expandProps,
],
},
template: wrapIntoComponent({ expandProps: true }),
prettier: {},
}
async function convert(code, options, state) {
let result = code let result = code
const finalOptions = { ...defaultOptions, ...options } result = options.svgo ? await svgo(result, options.svgo, state) : result
result = finalOptions.svgo result = await h2x(result, options.h2x, state)
? await svgo(result, finalOptions.svgo, state) result = await transform(result, { transform: options.template }, state)
: result result = options.prettier
result = await h2x(result, finalOptions.h2x, state) ? await prettier(result, options.prettier, state)
result = await transform(result, { transform: finalOptions.template }, state)
result = finalOptions.prettier
? await prettier(result, finalOptions.prettier, state)
: result : result
return result return result
} }
export default convert export default async function convert(
code,
{ componentName = 'SvgComponent', ...config } = {},
) {
return rawConvert(code, configToOptions(config), {
filePath: componentName,
})
}

View File

@ -1,36 +1,15 @@
import jsx from 'h2x-plugin-jsx' import convert, {
import stripAttribute from './h2x/stripAttribute' rawConvert,
import expandProps from './h2x/expandProps' jsx,
import replaceAttrValue from './h2x/replaceAttrValue' stripAttribute,
import convert from './' expandProps,
replaceAttrValue,
wrapIntoComponent,
} from './'
describe('convert', () => { describe('rawConvert', () => {
it('should convert', async () => { it('should convert using specific options', async () => {
const result = await convert( const result = await rawConvert(
`
<?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>
<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>
</g>
</svg>
`,
null,
{ filePath: 'MyComponent.js' },
)
expect(result).toMatchSnapshot()
})
it('should be possible to specify options', async () => {
const result = await convert(
` `
<?xml version="1.0" encoding="UTF-8"?> <?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"> <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">
@ -55,6 +34,7 @@ describe('convert', () => {
replaceAttrValue('#063855', 'currentColor'), replaceAttrValue('#063855', 'currentColor'),
], ],
}, },
template: wrapIntoComponent({ expandProps: true }),
}, },
{ filePath: 'MyComponent.js' }, { filePath: 'MyComponent.js' },
) )
@ -62,3 +42,27 @@ describe('convert', () => {
expect(result).toMatchSnapshot() expect(result).toMatchSnapshot()
}) })
}) })
describe('convert', () => {
it('should convert using config', 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>
<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>
</g>
</svg>
`,
)
expect(result).toMatchSnapshot()
})
})

1005
yarn.lock

File diff suppressed because it is too large Load Diff