feat(typescript): add types everywhere (#611)

The whole codebase has been rewritten to TypeScript.

BREAKING CHANGE: template signature has changed
This commit is contained in:
Greg Bergé 2021-10-31 09:49:38 +01:00 committed by GitHub
parent 5decc9448e
commit 16664327ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
169 changed files with 7252 additions and 19564 deletions

View File

@ -1,21 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "10"
},
"loose": true
},
"preset-env"
]
],
"plugins": [
[
"@babel/plugin-proposal-class-properties",
{ "loose": true },
"class-properties"
]
]
}

View File

@ -1,8 +1,8 @@
node_modules/
lib/
__fixtures__/
__fixtures_build__/
coverage/
examples/
svgr.now.sh/
/website/
/website/
dist/

View File

@ -1,26 +1,26 @@
{
"root": true,
"parser": "babel-eslint",
"extends": ["airbnb", "prettier"],
"env": {
"jest": true
"node": true
},
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"plugin:react/recommended"
],
"rules": {
"no-plusplus": "off",
"no-shadow": "off",
"class-methods-use-this": "off",
"no-param-reassign": "off",
"no-use-before-define": "off",
"import/prefer-default-export": "off",
"import/no-extraneous-dependencies": "off",
"import/extensions": "off",
"react/jsx-filename-extension": ["error", { "extensions": [".js"] }],
"react/jsx-wrap-multilines": "off",
"react/no-unused-state": "off",
"react/destructuring-assignment": "off",
"react/prop-types": "off",
"react/sort-comp": "off",
"react/jsx-props-no-spreading": "off",
"react/state-in-constructor": "off"
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "warn",
"@typescript-eslint/ban-types": "warn",
"react/prop-types": "off"
},
"settings": {
"react": {
"version": "detect"
}
}
}

View File

@ -25,8 +25,8 @@ jobs:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Use npm v7
run: npm i -g npm@7 --registry=https://registry.npmjs.org
- name: Use latest npm
run: npm i -g npm@latest --registry=https://registry.npmjs.org
if: ${{ matrix.node-version == '12.x' || matrix.node-version == '14.x' }}
- name: Install dependencies
@ -38,9 +38,6 @@ jobs:
- name: Lint
run: npm run lint
- name: Check DTS
run: npx check-dts
- name: Test
run: npm run test -- --ci --coverage

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
node_modules/
lib/
dist/
!svgr.now.sh/lib/
__fixtures_build__/
src/__fixtures__/dist/

View File

@ -1,23 +0,0 @@
tasks:
- command: gp await-port 8000 && sleep 3 && gp preview $(gp url 8000)
- name: Docs
before: cd website
init: |
npm install
gp sync-done boot
command: npm run dev
- name: Dev
init: |
gp sync-await boot
npm install
command: npm run dev
openMode: split-right
ports:
- port: 8000
onOpen: ignore
vscode:
extensions:
- esbenp.prettier-vscode@5.7.1:4Zx39KyQMoIz7x94PSmDmQ==

View File

@ -3,7 +3,7 @@ __fixtures__/
CHANGELOG.md
package.json
lerna.json
lib/
dist/
.next/
/website/.cache/
/website/public/

View File

@ -37,7 +37,7 @@ _Before_ submitting a pull request, please make sure the following is done…
Note: Replace `<your_username>` with your GitHub username
2. Run `npm install`.
2. Run `npm install` and `npm run build`.
3. If you've added code that should be tested, add tests. You can use watch mode that continuously transforms changed files to make your life easier.

View File

@ -1,11 +1,10 @@
const indexTemplate = require('./custom-index-template.js')
function template(
{ template },
opts,
{ imports, componentName, props, jsx, exports },
{ tpl }
) {
return template.ast`${imports}
return tpl`${imports}
export function ${componentName}(${props}) {
return ${jsx};
}

View File

@ -1,5 +1,4 @@
/* eslint-disable import/no-unresolved */
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @typescript-eslint/no-var-requires */
const { default: svgr } = require('@svgr/core')
const { default: jsx } = require('@svgr/plugin-jsx')
const { default: svgo } = require('@svgr/plugin-svgo')

View File

@ -1,6 +1,10 @@
const path = require('path')
const fs = require('fs')
module.exports = (api) => {
api.cache(true)
const config = fs.readFileSync(path.join(__dirname, '.babelrc'))
module.exports = JSON.parse(config)
return {
presets: [
['@babel/preset-env', { targets: { node: '12' }, loose: true }],
'@babel/preset-typescript',
],
}
}

20
build/build.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
set -e
npm run build --workspace @svgr/babel-plugin-add-jsx-attribute
npm run build --workspace @svgr/babel-plugin-remove-jsx-attribute
npm run build --workspace @svgr/babel-plugin-remove-jsx-empty-expression
npm run build --workspace @svgr/babel-plugin-replace-jsx-attribute-value
npm run build --workspace @svgr/babel-plugin-svg-dynamic-title
npm run build --workspace @svgr/babel-plugin-svg-em-dimensions
npm run build --workspace @svgr/babel-plugin-transform-react-native-svg
npm run build --workspace @svgr/babel-plugin-transform-svg-component
npm run build --workspace @svgr/babel-preset
npm run build --workspace @svgr/core
npm run build --workspace @svgr/hast-util-to-babel-ast
npm run build --workspace @svgr/plugin-jsx
npm run build --workspace @svgr/plugin-prettier
npm run build --workspace @svgr/plugin-svgo
npm run build --workspace @svgr/cli
npm run build --workspace @svgr/rollup
npm run build --workspace @svgr/webpack

39
build/rollup.config.js Normal file
View File

@ -0,0 +1,39 @@
import path from 'path'
import json from '@rollup/plugin-json'
import dts from 'rollup-plugin-dts'
import esbuild from 'rollup-plugin-esbuild'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require(path.resolve(process.cwd(), './package.json'))
const name = pkg.main ? pkg.main.replace(/\.js$/, '') : './dist/index'
const bundle = (config) => ({
...config,
input: 'src/index.ts',
external: (id) => !/^[./]/.test(id),
})
export default [
bundle({
plugins: [json(), esbuild()],
output: [
{
file: `${name}.js`,
format: 'cjs',
sourcemap: Boolean(pkg.main),
exports: 'auto',
},
],
}),
...(pkg.main
? [
bundle({
plugins: [dts()],
output: {
file: `${name}.d.ts`,
format: 'es',
},
}),
]
: []),
]

View File

@ -1,4 +1,4 @@
/* eslint-disable no-console, import/no-unresolved */
/* eslint-disable @typescript-eslint/no-var-requires */
const githubCurrentUser = require('github-current-user')
githubCurrentUser.verify((err, verified, username) => {

View File

@ -1,4 +1,7 @@
module.exports = {
watchPathIgnorePatterns: ['__fixtures__', '__fixtures__build__'],
rootDir: 'packages',
transform: {
'^.+\\.(j|t)sx?$': 'babel-jest',
},
}

19585
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
"packages/*"
],
"scripts": {
"build": "lerna run build",
"build": "build/build.sh",
"dev": "lerna run build --parallel -- --watch",
"format": "prettier --write \"**/*.{js,json,md}\"",
"lint": "eslint .",
@ -12,28 +12,26 @@
"test": "jest --runInBand"
},
"devDependencies": {
"@babel/cli": "^7.15.4",
"@babel/core": "^7.15.5",
"@babel/generator": "^7.15.4",
"@babel/node": "^7.15.4",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-syntax-typescript": "^7.14.5",
"@babel/preset-env": "^7.15.6",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^27.1.1",
"babel-loader": "^8.2.2",
"check-dts": "^0.4.4",
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"@babel/preset-typescript": "^7.16.0",
"@rollup/plugin-json": "^4.1.0",
"@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"babel-jest": "^27.3.1",
"codecov": "^3.8.3",
"conventional-github-releaser": "^3.1.5",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.25.1",
"jest": "^27.1.1",
"esbuild": "^0.13.12",
"eslint": "^8.1.0",
"eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^27.3.1",
"lerna": "^4.0.0",
"react": "^17.0.2"
"react": "^17.0.2",
"rollup": "^2.58.3",
"rollup-plugin-dts": "^4.0.0",
"rollup-plugin-esbuild": "^4.6.0",
"typescript": "^4.4.4"
}
}

View File

@ -2,7 +2,9 @@
"name": "@svgr/babel-plugin-add-jsx-attribute",
"description": "Add JSX attribute",
"version": "5.4.0",
"main": "lib/index.js",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"typings": "./dist/index.d.ts",
"repository": "https://github.com/gregberge/svgr/tree/master/packages/babel-plugin-add-jsx-attribute",
"author": "Greg Bergé <berge.greg@gmail.com>",
"publishConfig": {
@ -20,9 +22,12 @@
"url": "https://github.com/sponsors/gregberge"
},
"license": "MIT",
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "npm run build"
"reset": "rm -rf dist",
"build": "rollup -c ../../build/rollup.config.js",
"prepublishOnly": "npm run reset && npm run build"
}
}

View File

@ -1,13 +1,13 @@
import { transform } from '@babel/core'
import plugin from '.'
import plugin, { Options } from '.'
const testPlugin = (code, options) => {
const testPlugin = (code: string, options: Options) => {
const result = transform(code, {
plugins: ['@babel/plugin-syntax-jsx', [plugin, options]],
configFile: false,
})
return result.code
return result?.code
}
describe('plugin', () => {

View File

@ -1,10 +1,32 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ConfigAPI, types as t, NodePath, template } from '@babel/core'
export interface Attribute {
name: string
value?: boolean | number | string | null
spread?: boolean
literal?: boolean
position?: 'start' | 'end'
}
export interface Options {
elements: string[]
attributes: Attribute[]
}
const positionMethod = {
start: 'unshiftContainer',
end: 'pushContainer',
}
} as const
const addJSXAttribute = ({ types: t, template }, opts) => {
function getAttributeValue({ literal, value }) {
const addJSXAttribute = (_: ConfigAPI, opts: Options) => {
function getAttributeValue({
literal,
value,
}: {
literal?: Attribute['literal']
value: Attribute['value']
}) {
if (typeof value === 'boolean') {
return t.jsxExpressionContainer(t.booleanLiteral(value))
}
@ -14,7 +36,9 @@ const addJSXAttribute = ({ types: t, template }, opts) => {
}
if (typeof value === 'string' && literal) {
return t.jsxExpressionContainer(template.ast(value).expression)
return t.jsxExpressionContainer(
(template.ast(value) as t.ExpressionStatement).expression,
)
}
if (typeof value === 'string') {
@ -24,7 +48,7 @@ const addJSXAttribute = ({ types: t, template }, opts) => {
return null
}
function getAttribute({ spread, name, value, literal }) {
function getAttribute({ spread, name, value, literal }: Attribute) {
if (spread) {
return t.jsxSpreadAttribute(t.identifier(name))
}
@ -37,7 +61,8 @@ const addJSXAttribute = ({ types: t, template }, opts) => {
return {
visitor: {
JSXOpeningElement(path) {
JSXOpeningElement(path: NodePath<t.JSXOpeningElement>) {
if (!t.isJSXIdentifier(path.node.name)) return
if (!opts.elements.includes(path.node.name.name)) return
opts.attributes.forEach(
@ -52,12 +77,18 @@ const addJSXAttribute = ({ types: t, template }, opts) => {
const newAttribute = getAttribute({ spread, name, value, literal })
const attributes = path.get('attributes')
const isEqualAttribute = (attribute) => {
if (spread) {
return attribute.get('argument').isIdentifier({ name })
}
return attribute.get('name').isJSXIdentifier({ name })
const isEqualAttribute = (
attribute: NodePath<t.JSXSpreadAttribute | t.JSXAttribute>,
) => {
if (spread)
return (
attribute.isJSXSpreadAttribute() &&
attribute.get('argument').isIdentifier({ name })
)
return (
attribute.isJSXAttribute() &&
attribute.get('name').isJSXIdentifier({ name })
)
}
const replaced = attributes.some((attribute) => {

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig",
"include": ["src"]
}

View File

@ -1,2 +1,4 @@
src/
.*
/*
/dist/*
!/dist/index.{d.ts,js}
!/dist/index.js.map

View File

@ -2,7 +2,9 @@
"name": "@svgr/babel-plugin-remove-jsx-attribute",
"description": "Remove JSX attribute",
"version": "5.4.0",
"main": "lib/index.js",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"typings": "./dist/index.d.ts",
"repository": "https://github.com/gregberge/svgr/tree/master/packages/babel-plugin-remove-jsx-attribute",
"author": "Greg Bergé <berge.greg@gmail.com>",
"publishConfig": {
@ -20,9 +22,12 @@
"url": "https://github.com/sponsors/gregberge"
},
"license": "MIT",
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "npm run build"
"reset": "rm -rf dist",
"build": "rollup -c ../../build/rollup.config.js",
"prepublishOnly": "npm run reset && npm run build"
}
}

View File

@ -1,16 +0,0 @@
const removeJSXAttribute = (api, opts) => ({
visitor: {
JSXOpeningElement(path) {
if (!opts.elements.includes(path.node.name.name)) return
path.get('attributes').forEach((attribute) => {
const nodeName = attribute.node.name
if (nodeName && opts.attributes.includes(nodeName.name)) {
attribute.remove()
}
})
},
},
})
export default removeJSXAttribute

View File

@ -1,13 +1,13 @@
import { transform } from '@babel/core'
import plugin from '.'
import plugin, { Options } from '.'
const testPlugin = (code, options) => {
const testPlugin = (code: string, options: Options) => {
const result = transform(code, {
plugins: ['@babel/plugin-syntax-jsx', [plugin, options]],
configFile: false,
})
return result.code
return result?.code
}
describe('plugin', () => {

View File

@ -0,0 +1,29 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ConfigAPI, types as t, NodePath } from '@babel/core'
export interface Options {
elements: string[]
attributes: string[]
}
const removeJSXAttribute = (_: ConfigAPI, opts: Options) => ({
visitor: {
JSXOpeningElement(path: NodePath<t.JSXOpeningElement>) {
if (!t.isJSXIdentifier(path.node.name)) return
if (!opts.elements.includes(path.node.name.name)) return
// @ts-ignore
path.get('attributes').forEach((attribute) => {
if (
t.isJSXAttribute(attribute.node) &&
t.isJSXIdentifier(attribute.node.name) &&
opts.attributes.includes(attribute.node.name.name)
) {
attribute.remove()
}
})
},
},
})
export default removeJSXAttribute

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig",
"include": ["src"]
}

View File

@ -1,2 +1,4 @@
src/
.*
/*
/dist/*
!/dist/index.{d.ts,js}
!/dist/index.js.map

View File

@ -2,7 +2,9 @@
"name": "@svgr/babel-plugin-remove-jsx-empty-expression",
"description": "Remove JSX empty expression",
"version": "5.0.1",
"main": "lib/index.js",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"typings": "./dist/index.d.ts",
"repository": "https://github.com/gregberge/svgr/tree/master/packages/babel-plugin-remove-jsx-empty-expression",
"author": "Greg Bergé <berge.greg@gmail.com>",
"publishConfig": {
@ -20,9 +22,12 @@
"url": "https://github.com/sponsors/gregberge"
},
"license": "MIT",
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "npm run build"
"reset": "rm -rf dist",
"build": "rollup -c ../../build/rollup.config.js",
"prepublishOnly": "npm run reset && npm run build"
}
}

View File

@ -1,10 +0,0 @@
const removeJSXEmptyExpression = () => ({
visitor: {
JSXExpressionContainer(path) {
if (!path.get('expression').isJSXEmptyExpression()) return
path.remove()
},
},
})
export default removeJSXEmptyExpression

View File

@ -1,19 +0,0 @@
import { transform } from '@babel/core'
import plugin from '.'
const testPlugin = (code, options) => {
const result = transform(code, {
plugins: ['@babel/plugin-syntax-jsx', [plugin, options]],
configFile: false,
})
return result.code
}
describe('plugin', () => {
it('should remove empty expression', () => {
expect(
testPlugin('<div>{/* Hello */}<a /></div>', { attribute: 'foo' }),
).toMatchInlineSnapshot(`"<div><a /></div>;"`)
})
})

View File

@ -0,0 +1,19 @@
import { transform } from '@babel/core'
import plugin from '.'
const testPlugin = (code: string) => {
const result = transform(code, {
plugins: ['@babel/plugin-syntax-jsx', plugin],
configFile: false,
})
return result?.code
}
describe('plugin', () => {
it('should remove empty expression', () => {
expect(testPlugin('<div>{/* Hello */}<a /></div>')).toMatchInlineSnapshot(
`"<div><a /></div>;"`,
)
})
})

View File

@ -0,0 +1,14 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { types as t, NodePath } from '@babel/core'
const removeJSXEmptyExpression = () => ({
visitor: {
JSXExpressionContainer(path: NodePath<t.JSXExpressionContainer>) {
if (t.isJSXEmptyExpression(path.get('expression'))) {
path.remove()
}
},
},
})
export default removeJSXEmptyExpression

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig",
"include": ["src"]
}

View File

@ -1,2 +1,6 @@
src/
.*
/*
/dist/*
!/dist/index.{d.ts,js}
!/dist/index.js.map

View File

@ -2,7 +2,9 @@
"name": "@svgr/babel-plugin-replace-jsx-attribute-value",
"description": "Replace JSX attribute value",
"version": "5.0.1",
"main": "lib/index.js",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"typings": "./dist/index.d.ts",
"repository": "https://github.com/gregberge/svgr/tree/master/packages/babel-plugin-replace-jsx-attribute-value",
"author": "Greg Bergé <berge.greg@gmail.com>",
"publishConfig": {
@ -20,9 +22,12 @@
"url": "https://github.com/sponsors/gregberge"
},
"license": "MIT",
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "npm run build"
"reset": "rm -rf dist",
"build": "rollup -c ../../build/rollup.config.js",
"prepublishOnly": "npm run reset && npm run build"
}
}

View File

@ -1,37 +0,0 @@
const addJSXAttribute = ({ types: t, template }, opts) => {
function getAttributeValue(value, literal) {
if (typeof value === 'string' && literal) {
return t.jsxExpressionContainer(template.ast(value).expression)
}
if (typeof value === 'string') {
return t.stringLiteral(value)
}
if (typeof value === 'boolean') {
return t.jsxExpressionContainer(t.booleanLiteral(value))
}
if (typeof value === 'number') {
return t.jsxExpressionContainer(t.numericLiteral(value))
}
return null
}
return {
visitor: {
JSXAttribute(path) {
const valuePath = path.get('value')
if (!valuePath.isStringLiteral()) return
opts.values.forEach(({ value, newValue, literal }) => {
if (!valuePath.isStringLiteral({ value })) return
valuePath.replaceWith(getAttributeValue(newValue, literal))
})
},
},
}
}
export default addJSXAttribute

View File

@ -1,13 +1,13 @@
import { transform } from '@babel/core'
import plugin from '.'
import plugin, { Options } from '.'
const testPlugin = (code, options) => {
const testPlugin = (code: string, options: Options) => {
const result = transform(code, {
plugins: ['@babel/plugin-syntax-jsx', [plugin, options]],
configFile: false,
})
return result.code
return result?.code
}
describe('plugin', () => {

View File

@ -0,0 +1,58 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ConfigAPI, types as t, NodePath, template } from '@babel/core'
export interface Value {
value: string
newValue: string | boolean | number
literal?: boolean
}
export interface Options {
values: Value[]
}
const addJSXAttribute = (api: ConfigAPI, opts: Options) => {
const getAttributeValue = (
value: string | boolean | number,
literal?: boolean,
) => {
if (typeof value === 'string' && literal) {
return t.jsxExpressionContainer(
(template.ast(value) as t.ExpressionStatement).expression,
)
}
if (typeof value === 'string') {
return t.stringLiteral(value)
}
if (typeof value === 'boolean') {
return t.jsxExpressionContainer(t.booleanLiteral(value))
}
if (typeof value === 'number') {
return t.jsxExpressionContainer(t.numericLiteral(value))
}
return null
}
return {
visitor: {
JSXAttribute(path: NodePath<t.JSXAttribute>) {
const valuePath = path.get('value')
if (!valuePath.isStringLiteral()) return
opts.values.forEach(({ value, newValue, literal }) => {
if (!valuePath.isStringLiteral({ value })) return
const attributeValue = getAttributeValue(newValue, literal)
if (attributeValue) {
valuePath.replaceWith(attributeValue)
}
})
},
},
}
}
export default addJSXAttribute

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig",
"include": ["src"]
}

View File

@ -1,2 +1,4 @@
src/
.*
/*
/dist/*
!/dist/index.{d.ts,js}
!/dist/index.js.map

View File

@ -2,7 +2,9 @@
"name": "@svgr/babel-plugin-svg-dynamic-title",
"description": "Transform SVG by adding a dynamic title element",
"version": "5.4.0",
"main": "lib/index.js",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"typings": "./dist/index.d.ts",
"repository": "https://github.com/gregberge/svgr/tree/master/packages/babel-plugin-svg-dynamic-title",
"author": "Greg Bergé <berge.greg@gmail.com>",
"publishConfig": {
@ -20,9 +22,12 @@
"url": "https://github.com/sponsors/gregberge"
},
"license": "MIT",
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "npm run build"
"reset": "rm -rf dist",
"build": "rollup -c ../../build/rollup.config.js",
"prepublishOnly": "npm run reset && npm run build"
}
}

View File

@ -1,113 +0,0 @@
const elements = ['svg', 'Svg']
const plugin = ({ types: t }) => ({
visitor: {
JSXElement(path) {
if (
!elements.some((element) =>
path.get('openingElement.name').isJSXIdentifier({ name: element }),
)
) {
return
}
function createTitle(children = [], attributes = []) {
return t.jsxElement(
t.jsxOpeningElement(t.jsxIdentifier('title'), attributes),
t.jsxClosingElement(t.jsxIdentifier('title')),
children,
)
}
function createTitleIdAttribute() {
return t.jsxAttribute(
t.jsxIdentifier('id'),
t.jsxExpressionContainer(t.identifier('titleId')),
)
}
function enhanceAttributes(attributes) {
const existingId = attributes.find(
(attribute) => attribute.name.name === 'id',
)
if (!existingId) {
return [...attributes, createTitleIdAttribute()]
}
existingId.value = t.jsxExpressionContainer(
t.logicalExpression('||', t.identifier('titleId'), existingId.value),
)
return attributes
}
function getTitleElement(existingTitle) {
const titleExpression = t.identifier('title')
if (existingTitle) {
existingTitle.openingElement.attributes = enhanceAttributes(
existingTitle.openingElement.attributes,
)
}
let titleElement = t.conditionalExpression(
titleExpression,
createTitle(
[t.jsxExpressionContainer(titleExpression)],
existingTitle
? existingTitle.openingElement.attributes
: [
t.jsxAttribute(
t.jsxIdentifier('id'),
t.jsxExpressionContainer(t.identifier('titleId')),
),
],
),
t.nullLiteral(),
)
if (
existingTitle &&
existingTitle.children &&
existingTitle.children.length
) {
// if title already exists
// render as follows
const fallbackTitleElement = existingTitle
// {title === undefined ? fallbackTitleElement : titleElement}
const conditionalExpressionForTitle = t.conditionalExpression(
t.binaryExpression(
'===',
titleExpression,
t.identifier('undefined'),
),
fallbackTitleElement,
titleElement,
)
titleElement = t.jsxExpressionContainer(conditionalExpressionForTitle)
} else {
titleElement = t.jsxExpressionContainer(titleElement)
}
return titleElement
}
// store the title element
let titleElement
const hasTitle = path.get('children').some((childPath) => {
if (!childPath.isJSXElement()) return false
if (childPath.node === titleElement) return false
if (childPath.node.openingElement.name.name !== 'title') return false
titleElement = getTitleElement(childPath.node)
childPath.replaceWith(titleElement)
return true
})
// create a title element if not already create
titleElement = titleElement || getTitleElement()
if (!hasTitle) {
// path.unshiftContainer is not working well :(
// path.unshiftContainer('children', titleElement)
path.node.children.unshift(titleElement)
path.replaceWith(path.node)
}
},
},
})
export default plugin

View File

@ -1,13 +1,13 @@
import { transform } from '@babel/core'
import plugin from '.'
const testPlugin = (code, options) => {
const testPlugin = (code: string) => {
const result = transform(code, {
plugins: ['@babel/plugin-syntax-jsx', [plugin, options]],
plugins: ['@babel/plugin-syntax-jsx', plugin],
configFile: false,
})
return result.code
return result?.code
}
describe('plugin', () => {

View File

@ -0,0 +1,120 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { NodePath, types as t } from '@babel/core'
const elements = ['svg', 'Svg']
const createTitleElement = (
children: t.JSXExpressionContainer[] = [],
attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[] = [],
) => {
const title = t.jsxIdentifier('title')
return t.jsxElement(
t.jsxOpeningElement(title, attributes),
t.jsxClosingElement(title),
children,
)
}
const createTitleIdAttribute = () =>
t.jsxAttribute(
t.jsxIdentifier('id'),
t.jsxExpressionContainer(t.identifier('titleId')),
)
const addTitleIdAttribute = (
attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[],
) => {
const existingId = attributes.find(
(attribute) => t.isJSXAttribute(attribute) && attribute.name.name === 'id',
) as t.JSXAttribute | undefined
if (!existingId) {
return [...attributes, createTitleIdAttribute()]
}
existingId.value = t.jsxExpressionContainer(
t.isStringLiteral(existingId.value)
? t.logicalExpression('||', t.identifier('titleId'), existingId.value)
: t.identifier('titleId'),
)
return attributes
}
const plugin = () => ({
visitor: {
JSXElement(path: NodePath<t.JSXElement>) {
if (!elements.length) return
const openingElement = path.get('openingElement')
const openingElementName = openingElement.get('name')
if (
!elements.some((element) =>
openingElementName.isJSXIdentifier({ name: element }),
)
) {
return
}
const getTitleElement = (
existingTitle?: t.JSXElement,
): t.JSXExpressionContainer => {
const titleExpression = t.identifier('title')
if (existingTitle) {
existingTitle.openingElement.attributes = addTitleIdAttribute(
existingTitle.openingElement.attributes,
)
}
const conditionalTitle = t.conditionalExpression(
titleExpression,
createTitleElement(
[t.jsxExpressionContainer(titleExpression)],
existingTitle
? existingTitle.openingElement.attributes
: [createTitleIdAttribute()],
),
t.nullLiteral(),
)
if (existingTitle?.children?.length) {
// If title already exists render as follows
// `{title === undefined ? fallbackTitleElement : titleElement}`
return t.jsxExpressionContainer(
t.conditionalExpression(
t.binaryExpression(
'===',
titleExpression,
t.identifier('undefined'),
),
existingTitle,
conditionalTitle,
),
)
}
return t.jsxExpressionContainer(conditionalTitle)
}
// store the title element
let titleElement: t.JSXExpressionContainer | null = null
const hasTitle = path.get('children').some((childPath) => {
if (childPath.node === titleElement) return false
if (!childPath.isJSXElement()) return false
const name = childPath.get('openingElement').get('name')
if (!name.isJSXIdentifier()) return false
if (name.node.name !== 'title') return false
titleElement = getTitleElement(childPath.node)
childPath.replaceWith(titleElement)
return true
})
// create a title element if not already create
titleElement = titleElement || getTitleElement()
if (!hasTitle) {
// path.unshiftContainer is not working well :(
// path.unshiftContainer('children', titleElement)
path.node.children.unshift(titleElement)
path.replaceWith(path.node)
}
},
},
})
export default plugin

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig",
"include": ["src"]
}

View File

@ -1,2 +1,6 @@
src/
.*
/*
/dist/*
!/dist/index.{d.ts,js}
!/dist/index.js.map

View File

@ -2,7 +2,9 @@
"name": "@svgr/babel-plugin-svg-em-dimensions",
"description": "Transform SVG to use em-based dimensions",
"version": "5.4.0",
"main": "lib/index.js",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"typings": "./dist/index.d.ts",
"repository": "https://github.com/gregberge/svgr/tree/master/packages/babel-plugin-svg-em-dimensions",
"author": "Greg Bergé <berge.greg@gmail.com>",
"publishConfig": {
@ -20,9 +22,12 @@
"url": "https://github.com/sponsors/gregberge"
},
"license": "MIT",
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "npm run build"
"reset": "rm -rf dist",
"build": "rollup -c ../../build/rollup.config.js",
"prepublishOnly": "npm run reset && npm run build"
}
}

View File

@ -1,42 +0,0 @@
const elements = ['svg', 'Svg']
const plugin = ({ types: t }) => ({
visitor: {
JSXOpeningElement: {
enter(path) {
if (
!elements.some((element) =>
path.get('name').isJSXIdentifier({ name: element }),
)
)
return
const requiredAttributes = ['width', 'height']
const attributeValue = '1em'
path.get('attributes').forEach((attributePath) => {
if (!attributePath.isJSXAttribute()) return
const index = requiredAttributes.indexOf(attributePath.node.name.name)
if (index === -1) return
const value = attributePath.get('value')
value.replaceWith(t.stringLiteral(attributeValue))
requiredAttributes.splice(index, 1)
})
requiredAttributes.forEach((attribute) => {
path.pushContainer(
'attributes',
t.jsxAttribute(
t.jsxIdentifier(attribute),
t.stringLiteral(attributeValue),
),
)
})
},
},
},
})
export default plugin

View File

@ -1,13 +1,13 @@
import { transform } from '@babel/core'
import plugin from '.'
const testPlugin = (code, options) => {
const testPlugin = (code: string) => {
const result = transform(code, {
plugins: ['@babel/plugin-syntax-jsx', [plugin, options]],
plugins: ['@babel/plugin-syntax-jsx', plugin],
configFile: false,
})
return result.code
return result?.code
}
describe('plugin', () => {

View File

@ -0,0 +1,42 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { types as t, NodePath } from '@babel/core'
const elements = ['svg', 'Svg']
const value = t.stringLiteral('1em')
const plugin = () => ({
visitor: {
JSXOpeningElement(path: NodePath<t.JSXOpeningElement>) {
if (
!elements.some((element) =>
path.get('name').isJSXIdentifier({ name: element }),
)
)
return
const requiredAttributes = ['width', 'height']
path.get('attributes').forEach((attributePath) => {
if (!attributePath.isJSXAttribute()) return
const namePath = attributePath.get('name')
if (!namePath.isJSXIdentifier()) return
const index = requiredAttributes.indexOf(namePath.node.name)
if (index === -1) return
const valuePath = attributePath.get('value')
valuePath.replaceWith(value)
requiredAttributes.splice(index, 1)
})
path.pushContainer(
'attributes',
requiredAttributes.map((attr) =>
t.jsxAttribute(t.jsxIdentifier(attr), value),
),
)
},
},
})
export default plugin

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig",
"include": ["src"]
}

View File

@ -1,2 +1,6 @@
src/
.*
/*
/dist/*
!/dist/index.{d.ts,js}
!/dist/index.js.map

View File

@ -2,7 +2,9 @@
"name": "@svgr/babel-plugin-transform-react-native-svg",
"description": "Transform DOM elements into react-native-svg components",
"version": "5.4.0",
"main": "lib/index.js",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"typings": "./dist/index.d.ts",
"repository": "https://github.com/gregberge/svgr/tree/master/packages/babel-plugin-transform-react-native-svg",
"author": "Greg Bergé <berge.greg@gmail.com>",
"publishConfig": {
@ -20,9 +22,12 @@
"url": "https://github.com/sponsors/gregberge"
},
"license": "MIT",
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "npm run build"
"reset": "rm -rf dist",
"build": "rollup -c ../../build/rollup.config.js",
"prepublishOnly": "npm run reset && npm run build"
}
}

View File

@ -1,23 +1,23 @@
import { transform } from '@babel/core'
import plugin from '.'
const testPlugin = (code, options) => {
const testPlugin = (code: string) => {
const result = transform(code, {
plugins: ['@babel/plugin-syntax-jsx', [plugin, options]],
plugins: ['@babel/plugin-syntax-jsx', plugin],
configFile: false,
})
return result
return result?.code
}
describe('plugin', () => {
it('should transform elements', () => {
const { code } = testPlugin('<svg><div /></svg>')
const code = testPlugin('<svg><div /></svg>')
expect(code).toMatchInlineSnapshot(`"<Svg></Svg>;"`)
})
it('should add import', () => {
const { code } = testPlugin(
const code = testPlugin(
`import Svg from 'react-native-svg'; <svg><g /><div /></svg>;`,
)
expect(code).toMatchInlineSnapshot(`
@ -25,19 +25,6 @@ describe('plugin', () => {
/* SVGR has dropped some elements not supported by react-native-svg: div */
<Svg><G /></Svg>;"
`)
})
it('should transform for expo import', () => {
const { code } = testPlugin(`import 'expo'; <svg><g /><div /></svg>;`, {
expo: true,
})
expect(code).toMatchInlineSnapshot(`
"import { Svg } from 'expo';
/* SVGR has dropped some elements not supported by react-native-svg: div */
<Svg><Svg.G /></Svg>;"
`)
})
})

View File

@ -1,4 +1,12 @@
const elementToComponent = {
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { NodePath, types as t } from '@babel/core'
interface State {
replacedComponents: Set<string>
unsupportedComponents: Set<string>
}
const elementToComponent: { [key: string]: string } = {
svg: 'Svg',
circle: 'Circle',
clipPath: 'ClipPath',
@ -24,30 +32,24 @@ const elementToComponent = {
foreignObject: 'ForeignObject',
}
const expoPrefix = (component, expo) => {
// Prefix with 'Svg.' in the case we're transforming for Expo
if (!expo) {
return component
}
return (component !== 'Svg' ? 'Svg.' : '') + component
}
const plugin = ({ types: t }, { expo }) => {
function replaceElement(path, state) {
const { name } = path.node.openingElement.name
const plugin = () => {
function replaceElement(path: NodePath<t.JSXElement>, state: State) {
const namePath = path.get('openingElement').get('name')
if (!namePath.isJSXIdentifier()) return
const { name } = namePath.node
// Replace element by react-native-svg components
const component = elementToComponent[name]
if (component) {
const prefixedComponent = expoPrefix(component, expo)
const openingElementName = path.get('openingElement.name')
openingElementName.replaceWith(t.jsxIdentifier(prefixedComponent))
namePath.replaceWith(t.jsxIdentifier(component))
if (path.has('closingElement')) {
const closingElementName = path.get('closingElement.name')
closingElementName.replaceWith(t.jsxIdentifier(prefixedComponent))
const closingNamePath = path
.get('closingElement')
.get('name') as NodePath<t.JSXIdentifier>
closingNamePath.replaceWith(t.jsxIdentifier(component))
}
state.replacedComponents.add(prefixedComponent)
state.replacedComponents.add(component)
return
}
@ -57,8 +59,10 @@ const plugin = ({ types: t }, { expo }) => {
}
const svgElementVisitor = {
JSXElement(path, state) {
if (!path.get('openingElement.name').isJSXIdentifier({ name: 'svg' })) {
JSXElement(path: NodePath<t.JSXElement>, state: State) {
if (
!path.get('openingElement').get('name').isJSXIdentifier({ name: 'svg' })
) {
return
}
@ -68,13 +72,13 @@ const plugin = ({ types: t }, { expo }) => {
}
const jsxElementVisitor = {
JSXElement(path, state) {
JSXElement(path: NodePath<t.JSXElement>, state: State) {
replaceElement(path, state)
},
}
const importDeclarationVisitor = {
ImportDeclaration(path, state) {
ImportDeclaration(path: NodePath<t.ImportDeclaration>, state: State) {
if (path.get('source').isStringLiteral({ value: 'react-native-svg' })) {
state.replacedComponents.forEach((component) => {
if (
@ -113,12 +117,12 @@ const plugin = ({ types: t }, { expo }) => {
return {
visitor: {
Program(path, state) {
Program(path: NodePath<t.Program>, state: Partial<State>) {
state.replacedComponents = new Set()
state.unsupportedComponents = new Set()
path.traverse(svgElementVisitor, state)
path.traverse(importDeclarationVisitor, state)
path.traverse(svgElementVisitor, state as State)
path.traverse(importDeclarationVisitor, state as State)
},
},
}

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig",
"include": ["src"]
}

View File

@ -1,2 +1,4 @@
src/
.*
/*
/dist/*
!/dist/index.{d.ts,js}
!/dist/index.js.map

View File

@ -2,7 +2,9 @@
"name": "@svgr/babel-plugin-transform-svg-component",
"description": "Transform SVG into component",
"version": "6.0.0-alpha.0",
"main": "lib/index.js",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"typings": "./dist/index.d.ts",
"repository": "https://github.com/gregberge/svgr/tree/master/packages/babel-plugin-transform-svg-component",
"author": "Greg Bergé <berge.greg@gmail.com>",
"publishConfig": {
@ -12,7 +14,7 @@
"babel-plugin"
],
"engines": {
"node": ">=10"
"node": ">=12"
},
"homepage": "https://react-svgr.com",
"funding": {
@ -20,9 +22,12 @@
"url": "https://github.com/sponsors/gregberge"
},
"license": "MIT",
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "npm run build"
"reset": "rm -rf dist",
"build": "rollup -c ../../build/rollup.config.js",
"prepublishOnly": "npm run reset && npm run build"
}
}

View File

@ -1,21 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[` 1`] = `
"import * as React from 'react';
const MyComponent = () => <main>{<svg><g /></svg>}</main>;
export default MyComponent;"
`;
exports[` 2`] = `
"import * as React from 'react';
const MyComponent = () => <main>{<svg><g /></svg>}</main>;
export default MyComponent;"
`;
exports[`plugin javascript custom templates support basic template 1`] = `
"import * as React from 'react';
@ -24,6 +8,14 @@ const MyComponent = () => <svg><g /></svg>;
export default MyComponent;"
`;
exports[`plugin javascript custom templates supports JSX template 1`] = `
"import * as React from 'react';
const MyComponent = () => <main>{<svg><g /></svg>}</main>;
export default MyComponent;"
`;
exports[`plugin javascript custom templates supports TypeScript template 1`] = `
"import * as React from 'react';
@ -37,9 +29,7 @@ exports[`plugin javascript custom templates supports template that does not retu
exports[`plugin javascript transforms whole program 1`] = `
"import * as React from \\"react\\";
function SvgComponent() {
return <svg><g /></svg>;
}
const SvgComponent = () => <svg><g /></svg>;
export default SvgComponent;"
`;
@ -47,30 +37,25 @@ export default SvgComponent;"
exports[`plugin javascript with "expandProps" add props 1`] = `
"import * as React from \\"react\\";
function SvgComponent(props) {
return <svg><g /></svg>;
}
const SvgComponent = props => <svg><g /></svg>;
export default SvgComponent;"
`;
exports[`plugin javascript with "memo" option wrap component in "React.memo" 1`] = `
"import * as React from \\"react\\";
import { memo } from \\"react\\";
function SvgComponent() {
return <svg><g /></svg>;
}
const SvgComponent = () => <svg><g /></svg>;
const MemoSvgComponent = React.memo(SvgComponent);
export default MemoSvgComponent;"
const Memo = memo(SvgComponent);
export default Memo;"
`;
exports[`plugin javascript with "namedExport" and "exportType" option and without "previousExport" state exports via named export 1`] = `
"import * as React from \\"react\\";
function SvgComponent() {
return <svg><g /></svg>;
}
const SvgComponent = () => <svg><g /></svg>;
export { SvgComponent as ReactComponent };"
`;
@ -78,11 +63,8 @@ export { SvgComponent as ReactComponent };"
exports[`plugin javascript with "namedExport" option and "previousExport" state has custom named export 1`] = `
"import * as React from \\"react\\";
function SvgComponent() {
return <svg><g /></svg>;
}
const SvgComponent = () => <svg><g /></svg>;
export default \\"logo.svg\\";
export { SvgComponent as Component };"
`;
@ -90,9 +72,7 @@ exports[`plugin javascript with "native" and "expandProps" option adds import fr
"import * as React from \\"react\\";
import Svg from \\"react-native-svg\\";
function SvgComponent(props) {
return <Svg><g /></Svg>;
}
const SvgComponent = props => <Svg><g /></Svg>;
export default SvgComponent;"
`;
@ -101,9 +81,7 @@ exports[`plugin javascript with "native" option adds import from "react-native-s
"import * as React from \\"react\\";
import Svg from \\"react-native-svg\\";
function SvgComponent() {
return <Svg><g /></Svg>;
}
const SvgComponent = () => <Svg><g /></Svg>;
export default SvgComponent;"
`;
@ -111,69 +89,52 @@ export default SvgComponent;"
exports[`plugin javascript with "native", "ref" and "expandProps" option adds import from "react-native-svg" and adds props and adds ForwardRef component 1`] = `
"import * as React from \\"react\\";
import Svg from \\"react-native-svg\\";
import { forwardRef } from \\"react\\";
function SvgComponent(props, svgRef) {
return <Svg><g /></Svg>;
}
const SvgComponent = (props, ref) => <Svg><g /></Svg>;
const ForwardRef = React.forwardRef(SvgComponent);
const ForwardRef = forwardRef(SvgComponent);
export default ForwardRef;"
`;
exports[`plugin javascript with "native", "ref" option adds import from "react-native-svg" and adds ForwardRef component 1`] = `
"import * as React from \\"react\\";
import Svg from \\"react-native-svg\\";
import { forwardRef } from \\"react\\";
function SvgComponent(props, svgRef) {
return <Svg><g /></Svg>;
}
const SvgComponent = (_, ref) => <Svg><g /></Svg>;
const ForwardRef = React.forwardRef(SvgComponent);
const ForwardRef = forwardRef(SvgComponent);
export default ForwardRef;"
`;
exports[`plugin javascript with "native.expo" option adds import from "react-native-svg" & from "expo" 1`] = `
"import * as React from \\"react\\";
import \\"expo\\";
function SvgComponent() {
return <Svg><g /></Svg>;
}
export default SvgComponent;"
`;
exports[`plugin javascript with "ref" and "expandProps" option expands props 1`] = `
"import * as React from \\"react\\";
import { forwardRef } from \\"react\\";
function SvgComponent(props, svgRef) {
return <svg><g /></svg>;
}
const SvgComponent = (props, ref) => <svg><g /></svg>;
const ForwardRef = React.forwardRef(SvgComponent);
const ForwardRef = forwardRef(SvgComponent);
export default ForwardRef;"
`;
exports[`plugin javascript with "ref" option adds ForwardRef component 1`] = `
"import * as React from \\"react\\";
import { forwardRef } from \\"react\\";
function SvgComponent(props, svgRef) {
return <svg><g /></svg>;
}
const SvgComponent = (_, ref) => <svg><g /></svg>;
const ForwardRef = React.forwardRef(SvgComponent);
const ForwardRef = forwardRef(SvgComponent);
export default ForwardRef;"
`;
exports[`plugin javascript with "titleProp" adds "titleProp" and "titleId" prop 1`] = `
"import * as React from \\"react\\";
function SvgComponent({
const SvgComponent = ({
title,
titleId
}) {
return <svg><g /></svg>;
}
}) => <svg><g /></svg>;
export default SvgComponent;"
`;
@ -181,27 +142,24 @@ export default SvgComponent;"
exports[`plugin javascript with "titleProp" and "expandProps" adds "titleProp", "titleId" props and expands props 1`] = `
"import * as React from \\"react\\";
function SvgComponent({
const SvgComponent = ({
title,
titleId,
...props
}) {
return <svg><g /></svg>;
}
}) => <svg><g /></svg>;
export default SvgComponent;"
`;
exports[`plugin javascript with both "memo" and "ref" option wrap component in "React.memo" and "React.forwardRef" 1`] = `
"import * as React from \\"react\\";
import { forwardRef, memo } from \\"react\\";
function SvgComponent(props, svgRef) {
return <svg><g /></svg>;
}
const SvgComponent = (_, ref) => <svg><g /></svg>;
const ForwardRef = React.forwardRef(SvgComponent);
const MemoForwardRef = React.memo(ForwardRef);
export default MemoForwardRef;"
const ForwardRef = forwardRef(SvgComponent);
const Memo = memo(ForwardRef);
export default Memo;"
`;
exports[`plugin typescript custom templates support basic template 1`] = `
@ -212,6 +170,14 @@ const MyComponent = () => <svg><g /></svg>;
export default MyComponent;"
`;
exports[`plugin typescript custom templates supports JSX template 1`] = `
"import * as React from 'react';
const MyComponent = () => <main>{<svg><g /></svg>}</main>;
export default MyComponent;"
`;
exports[`plugin typescript custom templates supports TypeScript template 1`] = `
"import * as React from 'react';
@ -225,40 +191,34 @@ exports[`plugin typescript custom templates supports template that does not retu
exports[`plugin typescript transforms whole program 1`] = `
"import * as React from \\"react\\";
function SvgComponent() {
return <svg><g /></svg>;
}
const SvgComponent = () => <svg><g /></svg>;
export default SvgComponent;"
`;
exports[`plugin typescript with "expandProps" add props 1`] = `
"import * as React from \\"react\\";
import { SVGProps } from \\"react\\";
function SvgComponent(props: React.SVGProps<SVGSVGElement>) {
return <svg><g /></svg>;
}
const SvgComponent = (props: SVGProps<SVGSVGElement>) => <svg><g /></svg>;
export default SvgComponent;"
`;
exports[`plugin typescript with "memo" option wrap component in "React.memo" 1`] = `
"import * as React from \\"react\\";
import { memo } from \\"react\\";
function SvgComponent() {
return <svg><g /></svg>;
}
const SvgComponent = () => <svg><g /></svg>;
const MemoSvgComponent = React.memo(SvgComponent);
export default MemoSvgComponent;"
const Memo = memo(SvgComponent);
export default Memo;"
`;
exports[`plugin typescript with "namedExport" and "exportType" option and without "previousExport" state exports via named export 1`] = `
"import * as React from \\"react\\";
function SvgComponent() {
return <svg><g /></svg>;
}
const SvgComponent = () => <svg><g /></svg>;
export { SvgComponent as ReactComponent };"
`;
@ -266,11 +226,8 @@ export { SvgComponent as ReactComponent };"
exports[`plugin typescript with "namedExport" option and "previousExport" state has custom named export 1`] = `
"import * as React from \\"react\\";
function SvgComponent() {
return <svg><g /></svg>;
}
const SvgComponent = () => <svg><g /></svg>;
export default \\"logo.svg\\";
export { SvgComponent as Component };"
`;
@ -278,9 +235,7 @@ exports[`plugin typescript with "native" and "expandProps" option adds import fr
"import * as React from \\"react\\";
import Svg, { SvgProps } from \\"react-native-svg\\";
function SvgComponent(props: SvgProps) {
return <Svg><g /></Svg>;
}
const SvgComponent = (props: SvgProps) => <Svg><g /></Svg>;
export default SvgComponent;"
`;
@ -289,9 +244,7 @@ exports[`plugin typescript with "native" option adds import from "react-native-s
"import * as React from \\"react\\";
import Svg from \\"react-native-svg\\";
function SvgComponent() {
return <Svg><g /></Svg>;
}
const SvgComponent = () => <Svg><g /></Svg>;
export default SvgComponent;"
`;
@ -299,103 +252,84 @@ export default SvgComponent;"
exports[`plugin typescript with "native", "ref" and "expandProps" option adds import from "react-native-svg" and adds props and adds ForwardRef component 1`] = `
"import * as React from \\"react\\";
import Svg, { SvgProps } from \\"react-native-svg\\";
import { Ref, forwardRef } from \\"react\\";
function SvgComponent(props: SvgProps, svgRef?: React.Ref<React.Component<SvgProps>>) {
return <Svg><g /></Svg>;
}
const SvgComponent = (props: SvgProps, ref: Ref<SVGSVGElement>) => <Svg><g /></Svg>;
const ForwardRef = React.forwardRef(SvgComponent);
const ForwardRef = forwardRef(SvgComponent);
export default ForwardRef;"
`;
exports[`plugin typescript with "native", "ref" option adds import from "react-native-svg" and adds ForwardRef component 1`] = `
"import * as React from \\"react\\";
import Svg from \\"react-native-svg\\";
import { Ref, forwardRef } from \\"react\\";
function SvgComponent(props: {}, svgRef?: React.Ref<React.Component<SvgProps>>) {
return <Svg><g /></Svg>;
}
const SvgComponent = (_, ref: Ref<SVGSVGElement>) => <Svg><g /></Svg>;
const ForwardRef = React.forwardRef(SvgComponent);
const ForwardRef = forwardRef(SvgComponent);
export default ForwardRef;"
`;
exports[`plugin typescript with "native.expo" option adds import from "react-native-svg" & from "expo" 1`] = `
"import * as React from \\"react\\";
import \\"expo\\";
function SvgComponent() {
return <Svg><g /></Svg>;
}
export default SvgComponent;"
`;
exports[`plugin typescript with "ref" and "expandProps" option expands props 1`] = `
"import * as React from \\"react\\";
import { SVGProps, Ref, forwardRef } from \\"react\\";
function SvgComponent(props: React.SVGProps<SVGSVGElement>, svgRef?: React.Ref<SVGSVGElement>) {
return <svg><g /></svg>;
}
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => <svg><g /></svg>;
const ForwardRef = React.forwardRef(SvgComponent);
const ForwardRef = forwardRef(SvgComponent);
export default ForwardRef;"
`;
exports[`plugin typescript with "ref" option adds ForwardRef component 1`] = `
"import * as React from \\"react\\";
import { Ref, forwardRef } from \\"react\\";
function SvgComponent(props: {}, svgRef?: React.Ref<SVGSVGElement>) {
return <svg><g /></svg>;
}
const SvgComponent = (_, ref: Ref<SVGSVGElement>) => <svg><g /></svg>;
const ForwardRef = React.forwardRef(SvgComponent);
const ForwardRef = forwardRef(SvgComponent);
export default ForwardRef;"
`;
exports[`plugin typescript with "titleProp" adds "titleProp" and "titleId" prop 1`] = `
"import * as React from \\"react\\";
interface SVGRProps {
title?: string,
titleId?: string,
title?: string;
titleId?: string;
}
function SvgComponent({
const SvgComponent = ({
title,
titleId
}: SVGRProps) {
return <svg><g /></svg>;
}
}: SVGRProps) => <svg><g /></svg>;
export default SvgComponent;"
`;
exports[`plugin typescript with "titleProp" and "expandProps" adds "titleProp", "titleId" props and expands props 1`] = `
"import * as React from \\"react\\";
import { SVGProps } from \\"react\\";
interface SVGRProps {
title?: string,
titleId?: string,
title?: string;
titleId?: string;
}
function SvgComponent({
const SvgComponent = ({
title,
titleId,
...props
}: React.SVGProps<SVGSVGElement> & SVGRProps) {
return <svg><g /></svg>;
}
}: SVGProps<SVGSVGElement> & SVGRProps) => <svg><g /></svg>;
export default SvgComponent;"
`;
exports[`plugin typescript with both "memo" and "ref" option wrap component in "React.memo" and "React.forwardRef" 1`] = `
"import * as React from \\"react\\";
import { Ref, forwardRef, memo } from \\"react\\";
function SvgComponent(props: {}, svgRef?: React.Ref<SVGSVGElement>) {
return <svg><g /></svg>;
}
const SvgComponent = (_, ref: Ref<SVGSVGElement>) => <svg><g /></svg>;
const ForwardRef = React.forwardRef(SvgComponent);
const MemoForwardRef = React.memo(ForwardRef);
export default MemoForwardRef;"
const ForwardRef = forwardRef(SvgComponent);
const Memo = memo(ForwardRef);
export default Memo;"
`;

View File

@ -0,0 +1,15 @@
import type { Template } from './types'
export const defaultTemplate: Template = (variables, { tpl }) => {
return tpl`
${variables.imports};
${variables.interfaces};
const ${variables.componentName} = (${variables.props}) => (
${variables.jsx}
);
${variables.exports};
`
}

View File

@ -1,47 +0,0 @@
import { getProps, getImport, getExport, getInterface } from './util'
function defaultTemplate(
{ template },
opts,
{ imports, interfaces, componentName, props, jsx, exports },
) {
const plugins = ['jsx']
if (opts.typescript) {
plugins.push('typescript')
}
const typeScriptTpl = template.smart({ plugins })
return typeScriptTpl.ast`${imports}
${interfaces}
function ${componentName}(${props}) {
return ${jsx};
}
${exports}
`
}
const plugin = (api, opts) => ({
visitor: {
Program(path) {
const { types: t } = api
const template = opts.template || defaultTemplate
const body = template(api, opts, {
componentName: t.identifier(opts.state.componentName),
interfaces: getInterface(api, opts),
props: getProps(api, opts),
imports: getImport(api, opts),
exports: getExport(api, opts),
jsx: path.node.body[0].expression,
})
if (Array.isArray(body)) {
path.node.body = body
} else {
path.node.body = [body]
}
path.replaceWith(path.node)
},
},
})
export default plugin

View File

@ -1,51 +1,55 @@
import { transform } from '@babel/core'
import plugin from '.'
import plugin, { Options } from '.'
const testPlugin = (language) => (code, options) => {
const result = transform(code, {
plugins: [
'@babel/plugin-syntax-jsx',
[plugin, { ...options, typescript: language === 'typescript' }],
],
configFile: false,
})
return result
const defaultOptions = {
namedExport: 'ReactComponent',
state: { componentName: 'SvgComponent' },
}
const testPlugin =
(language: string) =>
(code: string, options: Partial<Options> = {}) => {
const result = transform(code, {
plugins: [
'@babel/plugin-syntax-jsx',
[
plugin,
{
typescript: language === 'typescript',
...defaultOptions,
...options,
},
],
],
configFile: false,
})
if (!result) {
throw new Error(`No result`)
}
return result
}
describe('plugin', () => {
describe.each(['javascript', 'typescript'])('%s', (language) => {
it('transforms whole program', () => {
const { code } = testPlugin(language)('<svg><g /></svg>', {
state: { componentName: 'SvgComponent' },
})
const { code } = testPlugin(language)('<svg><g /></svg>')
expect(code).toMatchSnapshot()
})
describe('with "native" option', () => {
it('adds import from "react-native-svg"', () => {
const { code } = testPlugin(language)('<Svg><g /></Svg>', {
state: { componentName: 'SvgComponent' },
native: true,
})
expect(code).toMatchSnapshot()
})
})
describe('with "native.expo" option', () => {
it('adds import from "react-native-svg" & from "expo"', () => {
const { code } = testPlugin(language)('<Svg><g /></Svg>', {
state: { componentName: 'SvgComponent' },
native: { expo: true },
})
expect(code).toMatchSnapshot()
})
})
describe('with "ref" option', () => {
it('adds ForwardRef component', () => {
const { code } = testPlugin(language)('<svg><g /></svg>', {
state: { componentName: 'SvgComponent' },
ref: true,
})
expect(code).toMatchSnapshot()
@ -55,7 +59,6 @@ describe('plugin', () => {
describe('with "titleProp"', () => {
it('adds "titleProp" and "titleId" prop', () => {
const { code } = testPlugin(language)('<svg><g /></svg>', {
state: { componentName: 'SvgComponent' },
titleProp: true,
})
expect(code).toMatchSnapshot()
@ -65,7 +68,7 @@ describe('plugin', () => {
describe('with "titleProp" and "expandProps"', () => {
it('adds "titleProp", "titleId" props and expands props', () => {
const { code } = testPlugin(language)('<svg><g /></svg>', {
state: { componentName: 'SvgComponent' },
...defaultOptions,
expandProps: true,
titleProp: true,
})
@ -76,6 +79,7 @@ describe('plugin', () => {
describe('with "expandProps"', () => {
it('add props', () => {
const { code } = testPlugin(language)('<svg><g /></svg>', {
...defaultOptions,
state: { componentName: 'SvgComponent' },
expandProps: true,
})
@ -179,24 +183,19 @@ describe('plugin', () => {
describe('custom templates', () => {
it('support basic template', () => {
const { code } = testPlugin(language)('<svg><g /></svg>', {
template: (
{ template },
opts,
{ jsx },
) => template.ast`import * as React from 'react';
const MyComponent = () => ${jsx}
export default MyComponent
`,
template: ({ jsx }, { tpl }) => tpl`import * as React from 'react';
const MyComponent = () => ${jsx}
export default MyComponent
`,
state: { componentName: 'SvgComponent' },
})
expect(code).toMatchSnapshot()
})
describe('it supports JSX template', () => {
it('supports JSX template', () => {
const { code } = testPlugin(language)('<svg><g /></svg>', {
template: ({ template }, opts, { jsx }) => {
const jsxTemplate = template.smart({ plugins: ['jsx'] })
return jsxTemplate.ast`import * as React from 'react';
template: ({ jsx }, { tpl }) => {
return tpl`import * as React from 'react';
const MyComponent = () => <main>{${jsx}}</main>
export default MyComponent
`
@ -208,16 +207,14 @@ describe('plugin', () => {
it('supports TypeScript template', () => {
const { code } = testPlugin(language)('<svg><g /></svg>', {
template: ({ template }, opts, { jsx }) => {
const typescriptTemplate = template.smart({
plugins: ['typescript'],
})
return typescriptTemplate.ast`
template: ({ jsx }, { tpl }) => {
return tpl`
import * as React from 'react';
const MyComponent = (props: React.SVGProps<SVGSVGElement>) => ${jsx};
export default MyComponent;
`
},
typescript: true,
state: { componentName: 'SvgComponent' },
})
expect(code).toMatchSnapshot()
@ -225,7 +222,7 @@ describe('plugin', () => {
it('supports template that does not return an array', () => {
const { code } = testPlugin(language)('<svg><g /></svg>', {
template: ({ template }, opts, { jsx }) => template.ast`${jsx}`,
template: ({ jsx }, { tpl }) => tpl`${jsx}`,
state: { componentName: 'SvgComponent' },
})
expect(code).toMatchSnapshot()

View File

@ -0,0 +1,38 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import {
ConfigAPI,
NodePath,
types as t,
template as babelTemplate,
ParserOptions,
} from '@babel/core'
import type { Options } from './types'
import { defaultTemplate } from './defaultTemplate'
import { getVariables } from './variables'
export type { Options } from './types'
const plugin = (_: ConfigAPI, opts: Options) => {
const template = opts.template || defaultTemplate
const plugins: ParserOptions['plugins'] = opts.typescript
? ['jsx', 'typescript']
: ['jsx']
const tpl = babelTemplate.smart({ plugins }).ast
return {
visitor: {
Program(path: NodePath<t.Program>) {
const jsx = (path.node.body[0] as t.ExpressionStatement)
.expression as t.JSXElement
const variables = getVariables({
opts,
jsx,
})
const body = template(variables, { options: opts, tpl })
path.node.body = Array.isArray(body) ? body : [body]
path.replaceWith(path.node)
},
},
}
}
export default plugin

View File

@ -0,0 +1,40 @@
import type { types as t } from '@babel/core'
import type { TemplateBuilder } from '@babel/template'
export interface TemplateVariables {
componentName: t.Identifier
interfaces: t.TSInterfaceDeclaration[]
props: (t.ObjectPattern | t.Identifier)[]
imports: t.ImportDeclaration[]
exports: (t.VariableDeclaration | t.ExportDeclaration)[]
jsx: t.JSXElement
}
interface TemplateContext {
options: Options
tpl: TemplateBuilder<t.Statement | t.Statement[]>['ast']
}
export interface Template {
(variables: TemplateVariables, context: TemplateContext):
| t.Statement
| t.Statement[]
}
interface State {
componentName: string
caller?: { previousExport?: string | null }
}
export interface Options {
typescript?: boolean
titleProp?: boolean
expandProps?: boolean | 'start' | 'end'
ref?: boolean
template?: Template
state: State
native?: boolean
memo?: boolean
exportType?: 'named' | 'default'
namedExport: string
}

View File

@ -1,242 +0,0 @@
function typeAnnotation(typeAnnotation) {
return {
type: 'TypeAnnotation',
typeAnnotation,
}
}
function genericTypeAnnotation(id, typeParameters = null) {
return { type: 'GenericTypeAnnotation', id, typeParameters }
}
function typeParameters(params) {
return {
type: 'TypeParameterInstantiation',
params,
}
}
function qualifiedTypeIdentifier(qualification, id) {
return { type: 'QualifiedTypeIdentifier', qualification, id }
}
function intersectionTypeAnnotation(types) {
return { type: 'IntersectionTypeAnnotation', types }
}
function interfaceDeclaration(id, body) {
return {
type: 'InterfaceDeclaration',
id,
typeParameters: null,
extends: [],
implements: [],
mixins: [],
body,
}
}
function objectTypeAnnotation(properties) {
return {
type: 'ObjectTypeAnnotation',
properties,
}
}
function objectTypeProperty(key, value, optional = false) {
return {
type: 'ObjectTypeProperty',
key,
static: false,
proto: false,
kind: 'init',
method: false,
value,
variance: null,
optional,
}
}
function addTypeAnotation(obj, typeAnnotation, opts) {
if (!opts.typescript) return obj
return { ...obj, typeAnnotation }
}
function getSvgPropsTypeAnnotation(t, opts) {
if (opts.native) {
return t.genericTypeAnnotation(t.identifier('SvgProps'))
}
return genericTypeAnnotation(
qualifiedTypeIdentifier(t.identifier('React'), t.identifier('SVGProps')),
typeParameters([genericTypeAnnotation(t.identifier('SVGSVGElement'))]),
)
}
export const getProps = ({ types: t }, opts) => {
const props = []
if (opts.titleProp) {
props.push(
t.objectProperty(
t.identifier('title'),
t.identifier('title'),
false,
true,
),
)
props.push(
t.objectProperty(
t.identifier('titleId'),
t.identifier('titleId'),
false,
true,
),
)
}
if (opts.expandProps && props.length > 0) {
props.push(t.restElement(t.identifier('props')))
}
const propsArgument =
props.length > 0 ? t.objectPattern(props) : t.identifier('props')
let propsTypeAnnotation
if (props.length > 0) {
propsTypeAnnotation = genericTypeAnnotation(t.identifier('SVGRProps'))
if (opts.expandProps) {
propsTypeAnnotation = intersectionTypeAnnotation([
getSvgPropsTypeAnnotation(t, opts),
propsTypeAnnotation,
])
}
} else {
propsTypeAnnotation = opts.expandProps
? getSvgPropsTypeAnnotation(t, opts)
: t.objectPattern([])
}
const typedPropsArgument = addTypeAnotation(
propsArgument,
typeAnnotation(propsTypeAnnotation),
opts,
)
const args = []
if (opts.expandProps || props.length > 0 || opts.ref)
args.push(typedPropsArgument)
if (opts.ref) {
const refArgument = t.identifier(opts.typescript ? 'svgRef?' : 'svgRef')
const typedRefArgument = addTypeAnotation(
refArgument,
typeAnnotation(
genericTypeAnnotation(
qualifiedTypeIdentifier(t.identifier('React'), t.identifier('Ref')),
typeParameters([
opts.native
? genericTypeAnnotation(
qualifiedTypeIdentifier(
t.identifier('React'),
t.identifier('Component'),
),
typeParameters([
genericTypeAnnotation(t.identifier('SvgProps')),
]),
)
: genericTypeAnnotation(t.identifier('SVGSVGElement')),
]),
),
),
opts,
)
args.push(typedRefArgument)
}
return args
}
export const getInterface = ({ types: t }, opts) => {
if (!opts.typescript) return null
const properties = []
if (opts.titleProp) {
properties.push(
objectTypeProperty(t.identifier('title'), t.identifier('string'), true),
)
properties.push(
objectTypeProperty(t.identifier('titleId'), t.identifier('string'), true),
)
}
if (properties.length === 0) return null
return interfaceDeclaration(
t.identifier('SVGRProps'),
objectTypeAnnotation(properties),
)
}
export const getImport = ({ types: t }, opts) => {
const importDeclarations = [
t.importDeclaration(
[t.importNamespaceSpecifier(t.identifier('React'))],
t.stringLiteral('react'),
),
]
if (opts.native) {
if (opts.native.expo) {
importDeclarations.push(t.importDeclaration([], t.stringLiteral('expo')))
} else {
const imports = [t.importDefaultSpecifier(t.identifier('Svg'))]
if (opts.typescript && opts.expandProps) {
imports.push(
t.importSpecifier(t.identifier('SvgProps'), t.identifier('SvgProps')),
)
}
importDeclarations.push(
t.importDeclaration(imports, t.stringLiteral('react-native-svg')),
)
}
}
return importDeclarations
}
export const getExport = ({ template }, opts) => {
let result = ''
let exportName = opts.state.componentName
const plugins = ['jsx']
if (opts.typescript) {
plugins.push('typescript')
}
if (opts.ref) {
const nextExportName = `ForwardRef`
result += `const ${nextExportName} = React.forwardRef(${exportName})\n\n`
exportName = nextExportName
}
if (opts.memo) {
const nextExportName = `Memo${exportName}`
result += `const ${nextExportName} = React.memo(${exportName})\n\n`
exportName = nextExportName
}
if (opts.state.caller && opts.state.caller.previousExport) {
result += `${opts.state.caller.previousExport}\n`
result += `export { ${exportName} as ${opts.namedExport} }`
return template.ast(result, {
plugins,
})
}
result +=
opts.exportType === 'named'
? `export { ${exportName} as ${opts.namedExport} }`
: `export default ${exportName}`
return template.ast(result, {
plugins,
})
}

View File

@ -0,0 +1,215 @@
import { types as t } from '@babel/core'
import type { Options, TemplateVariables } from './types'
const tsOptionalPropertySignature = (
...args: Parameters<typeof t.tsPropertySignature>
) => {
return {
...t.tsPropertySignature(...args),
optional: true,
} as t.TSPropertySignature
}
interface Context {
opts: Options
interfaces: t.TSInterfaceDeclaration[]
props: (t.Identifier | t.ObjectPattern)[]
imports: t.ImportDeclaration[]
}
const getOrCreateImport = ({ imports }: Context, sourceValue: string) => {
const existing = imports.find(
(imp) =>
imp.source.value === sourceValue &&
!imp.specifiers.some(
(specifier) => specifier.type === 'ImportNamespaceSpecifier',
),
)
if (existing) return existing
const imp = t.importDeclaration([], t.stringLiteral(sourceValue))
imports.push(imp)
return imp
}
const tsTypeReferenceSVGProps = (ctx: Context) => {
if (ctx.opts.native) {
const identifier = t.identifier('SvgProps')
getOrCreateImport(ctx, 'react-native-svg').specifiers.push(
t.importSpecifier(identifier, identifier),
)
return t.tsTypeReference(identifier)
}
const identifier = t.identifier('SVGProps')
getOrCreateImport(ctx, 'react').specifiers.push(
t.importSpecifier(identifier, identifier),
)
return t.tsTypeReference(
identifier,
t.tsTypeParameterInstantiation([
t.tsTypeReference(t.identifier('SVGSVGElement')),
]),
)
}
const tsTypeReferenceSVGRef = (ctx: Context) => {
const identifier = t.identifier('Ref')
getOrCreateImport(ctx, 'react').specifiers.push(
t.importSpecifier(identifier, identifier),
)
return t.tsTypeReference(
identifier,
t.tsTypeParameterInstantiation([
t.tsTypeReference(t.identifier('SVGSVGElement')),
]),
)
}
export const getVariables = ({
opts,
jsx,
}: {
opts: Options
jsx: t.JSXElement
}): TemplateVariables => {
const componentName = t.identifier(opts.state.componentName)
const interfaces: t.TSInterfaceDeclaration[] = []
const props: (t.Identifier | t.ObjectPattern)[] = []
const imports: t.ImportDeclaration[] = []
const exports: (t.VariableDeclaration | t.ExportDeclaration)[] = []
const ctx = {
exportIdentifier: componentName,
opts,
interfaces,
props,
imports,
exports,
}
imports.push(
t.importDeclaration(
[t.importNamespaceSpecifier(t.identifier('React'))],
t.stringLiteral('react'),
),
)
if (opts.native) {
getOrCreateImport(ctx, 'react-native-svg').specifiers.push(
t.importDefaultSpecifier(t.identifier('Svg')),
)
}
if (opts.titleProp) {
const prop = t.objectPattern([
t.objectProperty(
t.identifier('title'),
t.identifier('title'),
false,
true,
),
t.objectProperty(
t.identifier('titleId'),
t.identifier('titleId'),
false,
true,
),
])
props.push(prop)
if (opts.typescript) {
interfaces.push(
t.tsInterfaceDeclaration(
t.identifier('SVGRProps'),
null,
null,
t.tSInterfaceBody([
tsOptionalPropertySignature(
t.identifier('title'),
t.tsTypeAnnotation(t.tsStringKeyword()),
),
tsOptionalPropertySignature(
t.identifier('titleId'),
t.tsTypeAnnotation(t.tsStringKeyword()),
),
]),
),
)
prop.typeAnnotation = t.tsTypeAnnotation(
t.tsTypeReference(t.identifier('SVGRProps')),
)
}
}
if (opts.expandProps) {
const identifier = t.identifier('props')
if (t.isObjectPattern(props[0])) {
props[0].properties.push(t.restElement(identifier))
if (opts.typescript) {
props[0].typeAnnotation = t.tsTypeAnnotation(
t.tsIntersectionType([
tsTypeReferenceSVGProps(ctx),
(props[0].typeAnnotation as t.TSTypeAnnotation).typeAnnotation,
]),
)
}
} else {
props.push(identifier)
if (opts.typescript) {
identifier.typeAnnotation = t.tsTypeAnnotation(
tsTypeReferenceSVGProps(ctx),
)
}
}
}
if (opts.ref) {
if (props.length === 0) {
props.push(t.identifier('_'))
}
const prop = t.identifier('ref')
props.push(prop)
if (opts.typescript) {
prop.typeAnnotation = t.tsTypeAnnotation(tsTypeReferenceSVGRef(ctx))
}
const forwardRef = t.identifier('forwardRef')
const ForwardRef = t.identifier('ForwardRef')
getOrCreateImport(ctx, 'react').specifiers.push(
t.importSpecifier(forwardRef, forwardRef),
)
exports.push(
t.variableDeclaration('const', [
t.variableDeclarator(
ForwardRef,
t.callExpression(forwardRef, [ctx.exportIdentifier]),
),
]),
)
ctx.exportIdentifier = ForwardRef
}
if (opts.memo) {
const memo = t.identifier('memo')
const Memo = t.identifier('Memo')
getOrCreateImport(ctx, 'react').specifiers.push(
t.importSpecifier(memo, memo),
)
exports.push(
t.variableDeclaration('const', [
t.variableDeclarator(
Memo,
t.callExpression(memo, [ctx.exportIdentifier]),
),
]),
)
ctx.exportIdentifier = Memo
}
if (opts.state.caller?.previousExport || opts.exportType === 'named') {
exports.push(
t.exportNamedDeclaration(null, [
t.exportSpecifier(ctx.exportIdentifier, t.identifier(opts.namedExport)),
]),
)
} else {
exports.push(t.exportDefaultDeclaration(ctx.exportIdentifier))
}
return { componentName, props, interfaces, imports, exports, jsx }
}

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig",
"include": ["src"]
}

View File

@ -1,2 +1,4 @@
src/
.*
/*
/dist/*
!/dist/index.{d.ts,js}
!/dist/index.js.map

View File

@ -2,7 +2,9 @@
"name": "@svgr/babel-preset",
"description": "SVGR preset that apply transformations from config",
"version": "6.0.0-alpha.0",
"main": "lib/index.js",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"typings": "./dist/index.d.ts",
"repository": "https://github.com/gregberge/svgr/tree/master/packages/babel-preset",
"author": "Greg Bergé <berge.greg@gmail.com>",
"publishConfig": {
@ -21,11 +23,6 @@
"url": "https://github.com/sponsors/gregberge"
},
"license": "MIT",
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@svgr/babel-plugin-add-jsx-attribute": "^5.4.0",
"@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0",
@ -35,5 +32,13 @@
"@svgr/babel-plugin-svg-em-dimensions": "^5.4.0",
"@svgr/babel-plugin-transform-react-native-svg": "^5.4.0",
"@svgr/babel-plugin-transform-svg-component": "^6.0.0-alpha.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"scripts": {
"reset": "rm -rf dist",
"build": "rollup -c ../../build/rollup.config.js",
"prepublishOnly": "npm run reset && npm run build"
}
}

View File

@ -1,14 +1,19 @@
import { transform } from '@babel/core'
import preset from '.'
import preset, { Options } from '.'
const testPreset = (code, options) => {
const defaultOptions = {
namedExport: 'ReactComponent',
state: { componentName: 'SvgComponent' },
}
const testPreset = (code: string, options: Partial<Options>) => {
const result = transform(code, {
plugins: ['@babel/plugin-syntax-jsx'],
presets: [[preset, options]],
presets: [[preset, { ...defaultOptions, ...options }]],
configFile: false,
})
return result.code
return result?.code
}
describe('preset', () => {
@ -19,36 +24,11 @@ describe('preset', () => {
foo: 'bar',
x: '{y}',
},
state: {
componentName: 'SvgComponent',
},
}),
).toMatchInlineSnapshot(`
"import * as React from \\"react\\";
function SvgComponent() {
return <svg foo=\\"bar\\" x={y} />;
}
export default SvgComponent;"
`)
})
it('should handle native expo option', () => {
expect(
testPreset('<svg><g><path d="M0 0h48v1H0z" /></g></svg>', {
native: { expo: true },
state: {
componentName: 'SvgComponent',
},
}),
).toMatchInlineSnapshot(`
"import * as React from \\"react\\";
import { Svg } from \\"expo\\";
function SvgComponent() {
return <Svg><Svg.G><Svg.Path d=\\"M0 0h48v1H0z\\" /></Svg.G></Svg>;
}
const SvgComponent = () => <svg foo=\\"bar\\" x={y} />;
export default SvgComponent;"
`)
@ -58,19 +38,14 @@ describe('preset', () => {
expect(
testPreset('<svg></svg>', {
titleProp: true,
state: {
componentName: 'SvgComponent',
},
}),
).toMatchInlineSnapshot(`
"import * as React from \\"react\\";
function SvgComponent({
const SvgComponent = ({
title,
titleId
}) {
return <svg aria-labelledby={titleId}>{title ? <title id={titleId}>{title}</title> : null}</svg>;
}
}) => <svg aria-labelledby={titleId}>{title ? <title id={titleId}>{title}</title> : null}</svg>;
export default SvgComponent;"
`)
@ -80,19 +55,14 @@ describe('preset', () => {
expect(
testPreset(`<svg><title>Hello</title></svg>`, {
titleProp: true,
state: {
componentName: 'SvgComponent',
},
}),
).toMatchInlineSnapshot(`
"import * as React from \\"react\\";
function SvgComponent({
const SvgComponent = ({
title,
titleId
}) {
return <svg aria-labelledby={titleId}>{title === undefined ? <title id={titleId}>Hello</title> : title ? <title id={titleId}>{title}</title> : null}</svg>;
}
}) => <svg aria-labelledby={titleId}>{title === undefined ? <title id={titleId}>Hello</title> : title ? <title id={titleId}>{title}</title> : null}</svg>;
export default SvgComponent;"
`)
@ -100,19 +70,14 @@ describe('preset', () => {
expect(
testPreset(`<svg><title>{"Hello"}</title></svg>`, {
titleProp: true,
state: {
componentName: 'SvgComponent',
},
}),
).toMatchInlineSnapshot(`
"import * as React from \\"react\\";
function SvgComponent({
const SvgComponent = ({
title,
titleId
}) {
return <svg aria-labelledby={titleId}>{title === undefined ? <title id={titleId}>{\\"Hello\\"}</title> : title ? <title id={titleId}>{title}</title> : null}</svg>;
}
}) => <svg aria-labelledby={titleId}>{title === undefined ? <title id={titleId}>{\\"Hello\\"}</title> : title ? <title id={titleId}>{title}</title> : null}</svg>;
export default SvgComponent;"
`)
@ -125,16 +90,11 @@ describe('preset', () => {
'#000': 'black',
'#fff': '{props.white}',
},
state: {
componentName: 'SvgComponent',
},
}),
).toMatchInlineSnapshot(`
"import * as React from \\"react\\";
function SvgComponent() {
return <svg a=\\"black\\" b={props.white} />;
}
const SvgComponent = () => <svg a=\\"black\\" b={props.white} />;
export default SvgComponent;"
`)
@ -146,16 +106,11 @@ describe('preset', () => {
expandProps: 'end',
icon: true,
dimensions: true,
state: {
componentName: 'SvgComponent',
},
}),
).toMatchInlineSnapshot(`
"import * as React from \\"react\\";
function SvgComponent(props) {
return <svg a=\\"#000\\" b=\\"#fff\\" width=\\"1em\\" height=\\"1em\\" {...props} />;
}
const SvgComponent = props => <svg a=\\"#000\\" b=\\"#fff\\" width=\\"1em\\" height=\\"1em\\" {...props} />;
export default SvgComponent;"
`)

View File

@ -1,35 +1,54 @@
import addJSXAttribute from '@svgr/babel-plugin-add-jsx-attribute'
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ConfigAPI } from '@babel/core'
import addJSXAttribute, {
Attribute,
} from '@svgr/babel-plugin-add-jsx-attribute'
import removeJSXAttribute from '@svgr/babel-plugin-remove-jsx-attribute'
import removeJSXEmptyExpression from '@svgr/babel-plugin-remove-jsx-empty-expression'
import replaceJSXAttributeValue from '@svgr/babel-plugin-replace-jsx-attribute-value'
import replaceJSXAttributeValue, {
Value,
} from '@svgr/babel-plugin-replace-jsx-attribute-value'
import svgDynamicTitle from '@svgr/babel-plugin-svg-dynamic-title'
import svgEmDimensions from '@svgr/babel-plugin-svg-em-dimensions'
import transformReactNativeSVG from '@svgr/babel-plugin-transform-react-native-svg'
import transformSvgComponent from '@svgr/babel-plugin-transform-svg-component'
import transformSvgComponent, {
Options as TransformOptions,
} from '@svgr/babel-plugin-transform-svg-component'
function getAttributeValue(value) {
export interface Options extends TransformOptions {
ref?: boolean
titleProp?: boolean
expandProps?: boolean | 'start' | 'end'
dimensions?: boolean
icon?: boolean
native?: boolean
svgProps?: { [key: string]: string }
replaceAttrValues?: { [key: string]: string }
}
const getAttributeValue = (value: string) => {
const literal =
typeof value === 'string' && value.startsWith('{') && value.endsWith('}')
return { value: literal ? value.slice(1, -1) : value, literal }
}
function propsToAttributes(props) {
const propsToAttributes = (props: { [key: string]: string }): Attribute[] => {
return Object.keys(props).map((name) => {
const { literal, value } = getAttributeValue(props[name])
return { name, literal, value }
})
}
function replaceMapToValues(replaceMap) {
function replaceMapToValues(replaceMap: { [key: string]: string }): Value[] {
return Object.keys(replaceMap).map((value) => {
const { literal, value: newValue } = getAttributeValue(replaceMap[value])
return { value, newValue, literal }
})
}
const plugin = (api, opts) => {
const plugin = (_: ConfigAPI, opts: Options) => {
let toRemoveAttributes = ['version']
let toAddAttributes = []
let toAddAttributes: Attribute[] = []
if (opts.svgProps) {
toAddAttributes = [...toAddAttributes, ...propsToAttributes(opts.svgProps)]
@ -63,7 +82,10 @@ const plugin = (api, opts) => {
{
name: 'props',
spread: true,
position: opts.expandProps,
position:
opts.expandProps === 'start' || opts.expandProps === 'end'
? opts.expandProps
: undefined,
},
]
}
@ -72,7 +94,7 @@ const plugin = (api, opts) => {
toRemoveAttributes = [...toRemoveAttributes, 'width', 'height']
}
const plugins = [
const plugins: any[] = [
[transformSvgComponent, opts],
...(opts.icon && opts.dimensions ? [svgEmDimensions] : []),
[
@ -98,11 +120,7 @@ const plugin = (api, opts) => {
}
if (opts.native) {
if (opts.native.expo) {
plugins.push([transformReactNativeSVG, opts.native])
} else {
plugins.push(transformReactNativeSVG)
}
plugins.push(transformReactNativeSVG)
}
return { plugins }

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig",
"include": ["src"]
}

View File

@ -1,2 +1,5 @@
src/
.*
/*
/dist/*
!/dist/index.{d.ts,js}
!/dist/index.js.map
!/bin/svgr

View File

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

View File

@ -26,9 +26,9 @@
"svgr": "./bin/svgr"
},
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "npm run build"
"reset": "rm -rf dist",
"build": "rollup -c ../../build/rollup.config.js",
"prepublishOnly": "npm run reset && npm run build"
},
"dependencies": {
"@svgr/core": "^6.0.0-alpha.0",
@ -42,6 +42,7 @@
"glob": "^7.1.7"
},
"devDependencies": {
"@types/glob": "^7.2.0",
"del": "^6.0.0"
}
}

View File

@ -1,607 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`cli should add Svg prefix to index.js exports staring with number 1`] = `
"export { default as Svg2File } from './2File'
export { default as File } from './File'"
`;
exports[`cli should not override config with cli defaults 1`] = `
"import * as React from \\"react\\";
function SvgFile() {
return <svg viewBox=\\"0 0 48 1\\" xmlns=\\"http://www.w3.org/2000/svg\\" xmlnsXlink=\\"http://www.w3.org/1999/xlink\\"><title>{\\"Rectangle 5\\"}</title><desc>{\\"Created with Sketch.\\"}</desc><defs /><g id=\\"Page-1\\" stroke=\\"none\\" strokeWidth={1} fill=\\"none\\" fillRule=\\"evenodd\\"><g id=\\"19-Separator\\" transform=\\"translate(-129.000000, -156.000000)\\" fill=\\"#063855\\"><g id=\\"Controls/Settings\\" transform=\\"translate(80.000000, 0.000000)\\"><g id=\\"Content\\" transform=\\"translate(0.000000, 64.000000)\\"><g id=\\"Group\\" transform=\\"translate(24.000000, 56.000000)\\"><g id=\\"Group-2\\"><rect id=\\"Rectangle-5\\" x={25} y={36} width={48} height={1} /></g></g></g></g></g></g></svg>;
}
export default SvgFile;
"
`;
exports[`cli should support --index-template in cli 1`] = `"export { File } from './File'"`;
exports[`cli should support --no-index 1`] = `
Array [
"File.js",
]
`;
exports[`cli should support --prettier-config as file 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support --prettier-config as json 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support --svgo-config as file 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<title>{'Rectangle 5'}</title>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support --svgo-config as json 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<title>{'Rectangle 5'}</title>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support custom file extension 1`] = `
Array [
"File.ts",
"index.ts",
]
`;
exports[`cli should support custom index.js with directory output 1`] = `"export { File } from './File'"`;
exports[`cli should support different filename cases with directory output 1`] = `
Array [
"CamelCase.js",
"KebabCase.js",
"MultipleDashes.js",
"PascalCase.js",
"index.js",
]
`;
exports[`cli should support different filename cases with directory output: --filename-case=camel 1`] = `
Array [
"camelCase.js",
"index.js",
"kebabCase.js",
"multipleDashes.js",
"pascalCase.js",
]
`;
exports[`cli should support different filename cases with directory output: --filename-case=kebab 1`] = `
Array [
"camel-case.js",
"index.js",
"kebab-case.js",
"multiple-dashes.js",
"pascal-case.js",
]
`;
exports[`cli should support different filename cases with directory output: --filename-case=pascal 1`] = `
Array [
"CamelCase.js",
"KebabCase.js",
"MultipleDashes.js",
"PascalCase.js",
"index.js",
]
`;
exports[`cli should support stdin filepath 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --expand-props none 1`] = `
"import * as React from 'react'
function SvgFile() {
return (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\">
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --expand-props start 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg {...props} width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\">
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --icon 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg
width=\\"1em\\"
height=\\"1em\\"
viewBox=\\"0 0 48 1\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --native --expand-props none 1`] = `
"import * as React from 'react'
import Svg, { Path } from 'react-native-svg'
function SvgFile() {
return (
<Svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\">
<Path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</Svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --native --icon 1`] = `
"import * as React from 'react'
import Svg, { Path } from 'react-native-svg'
function SvgFile(props) {
return (
<Svg
width=\\"1em\\"
height=\\"1em\\"
viewBox=\\"0 0 48 1\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<Path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</Svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --native --ref 1`] = `
"import * as React from 'react'
import Svg, { Path } from 'react-native-svg'
function SvgFile(props, svgRef) {
return (
<Svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
{...props}
>
<Path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</Svg>
)
}
const ForwardRef = React.forwardRef(SvgFile)
export default ForwardRef
"
`;
exports[`cli should support various args: --native 1`] = `
"import * as React from 'react'
import Svg, { Path } from 'react-native-svg'
function SvgFile(props) {
return (
<Svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<Path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</Svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --no-dimensions 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg viewBox=\\"0 0 48 1\\" xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --no-prettier 1`] = `
"import * as React from \\"react\\";
function SvgFile(props) {
return <svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}><path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" /></svg>;
}
export default SvgFile;
"
`;
exports[`cli should support various args: --no-svgo 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg
width=\\"48px\\"
height=\\"1px\\"
viewBox=\\"0 0 48 1\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
xmlnsXlink=\\"http://www.w3.org/1999/xlink\\"
{...props}
>
<title>{'Rectangle 5'}</title>
<desc>{'Created with Sketch.'}</desc>
<defs />
<g
id=\\"Page-1\\"
stroke=\\"none\\"
strokeWidth={1}
fill=\\"none\\"
fillRule=\\"evenodd\\"
>
<g
id=\\"19-Separator\\"
transform=\\"translate(-129.000000, -156.000000)\\"
fill=\\"#063855\\"
>
<g id=\\"Controls/Settings\\" transform=\\"translate(80.000000, 0.000000)\\">
<g id=\\"Content\\" transform=\\"translate(0.000000, 64.000000)\\">
<g id=\\"Group\\" transform=\\"translate(24.000000, 56.000000)\\">
<g id=\\"Group-2\\">
<rect id=\\"Rectangle-5\\" x={25} y={36} width={48} height={1} />
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --ref 1`] = `
"import * as React from 'react'
function SvgFile(props, svgRef) {
return (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
const ForwardRef = React.forwardRef(SvgFile)
export default ForwardRef
"
`;
exports[`cli should support various args: --replace-attr-values "#063855=currentColor" 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"currentColor\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --svg-props "hidden={true},id=hello" 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
hidden={true}
id=\\"hello\\"
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --title-prop 1`] = `
"import * as React from 'react'
function SvgFile({ title, titleId, ...props }) {
return (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
aria-labelledby={titleId}
{...props}
>
{title ? <title id={titleId}>{title}</title> : null}
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should support various args: --typescript --ref --title-prop 1`] = `
"import * as React from 'react'
interface SVGRProps {
title?: string;
titleId?: string;
}
function SvgFile(
{ title, titleId, ...props }: React.SVGProps<SVGSVGElement> & SVGRProps,
svgRef?: React.Ref<SVGSVGElement>,
) {
return (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
aria-labelledby={titleId}
{...props}
>
{title ? <title id={titleId}>{title}</title> : null}
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
const ForwardRef = React.forwardRef(SvgFile)
export default ForwardRef
"
`;
exports[`cli should support various args: --typescript --ref 1`] = `
"import * as React from 'react'
function SvgFile(
props: React.SVGProps<SVGSVGElement>,
svgRef?: React.Ref<SVGSVGElement>,
) {
return (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
const ForwardRef = React.forwardRef(SvgFile)
export default ForwardRef
"
`;
exports[`cli should support various args: --typescript 1`] = `
"import * as React from 'react'
function SvgFile(props: React.SVGProps<SVGSVGElement>) {
return (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should suppress output when transforming a directory with a --silent option 1`] = `""`;
exports[`cli should transform a whole directory and output relative destination paths 1`] = `
"
__fixtures__/cased/pascalcase.svg -> __fixtures_build__/whole/cased/pascalcase.js
__fixtures__/cased/camelcase.svg -> __fixtures_build__/whole/cased/camelcase.js
__fixtures__/cased/kebab-case.svg -> __fixtures_build__/whole/cased/kebabcase.js
__fixtures__/cased/multiple---dashes.svg -> __fixtures_build__/whole/cased/multipledashes.js
__fixtures__/complex/skype.svg -> __fixtures_build__/whole/complex/skype.js
__fixtures__/complex/telegram.svg -> __fixtures_build__/whole/complex/telegram.js
__fixtures__/nesting/a/c/three.svg -> __fixtures_build__/whole/nesting/a/c/three.js
__fixtures__/nesting/a/two.svg -> __fixtures_build__/whole/nesting/a/two.js
__fixtures__/nesting/one.svg -> __fixtures_build__/whole/nesting/one.js
__fixtures__/numeric/2.file.svg -> __fixtures_build__/whole/numeric/2file.js
__fixtures__/numeric/file.svg -> __fixtures_build__/whole/numeric/file.js
__fixtures__/simple/file.svg -> __fixtures_build__/whole/simple/file.js
__fixtures__/withprettierrc/file.svg -> __fixtures_build__/whole/withprettierrc/file.js
__fixtures__/withsvgoconfig/file.svg -> __fixtures_build__/whole/withsvgoconfig/file.js
__fixtures__/withsvgrrc/file.svg -> __fixtures_build__/whole/withsvgrrc/file.js"
`;
exports[`cli should transform a whole directory with --typescript 1`] = `
"
__fixtures__/cased/pascalcase.svg -> __fixtures_build__/whole/cased/pascalcase.tsx
__fixtures__/cased/camelcase.svg -> __fixtures_build__/whole/cased/camelcase.tsx
__fixtures__/cased/kebab-case.svg -> __fixtures_build__/whole/cased/kebabcase.tsx
__fixtures__/cased/multiple---dashes.svg -> __fixtures_build__/whole/cased/multipledashes.tsx
__fixtures__/complex/skype.svg -> __fixtures_build__/whole/complex/skype.tsx
__fixtures__/complex/telegram.svg -> __fixtures_build__/whole/complex/telegram.tsx
__fixtures__/nesting/a/c/three.svg -> __fixtures_build__/whole/nesting/a/c/three.tsx
__fixtures__/nesting/a/two.svg -> __fixtures_build__/whole/nesting/a/two.tsx
__fixtures__/nesting/one.svg -> __fixtures_build__/whole/nesting/one.tsx
__fixtures__/numeric/2.file.svg -> __fixtures_build__/whole/numeric/2file.tsx
__fixtures__/numeric/file.svg -> __fixtures_build__/whole/numeric/file.tsx
__fixtures__/simple/file.svg -> __fixtures_build__/whole/simple/file.tsx
__fixtures__/withprettierrc/file.svg -> __fixtures_build__/whole/withprettierrc/file.tsx
__fixtures__/withsvgoconfig/file.svg -> __fixtures_build__/whole/withsvgoconfig/file.tsx
__fixtures__/withsvgrrc/file.svg -> __fixtures_build__/whole/withsvgrrc/file.tsx"
`;
exports[`cli should work with a simple file 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`cli should work with stdin 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgComponent
"
`;

View File

@ -0,0 +1,548 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`cli should add Svg prefix to index.js exports staring with number 1`] = `
"export { default as Svg2File } from './2File'
export { default as File } from './File'"
`;
exports[`cli should not override config with cli defaults 1`] = `
"import * as React from 'react'
const SvgFile = () => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\">
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support --index-template in cli 1`] = `"export { File } from './File'"`;
exports[`cli should support --no-index 1`] = `
Array [
"File.js",
]
`;
exports[`cli should support --prettier-config as file 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support --prettier-config as json 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support --svgo-config as file 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<title>{'Rectangle 5'}</title>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support --svgo-config as json 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<title>{'Rectangle 5'}</title>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support custom file extension 1`] = `
Array [
"File.ts",
"index.ts",
]
`;
exports[`cli should support custom index.js with directory output 1`] = `"export { default as File } from './File'"`;
exports[`cli should support different filename cases with directory output 1`] = `
Array [
"CamelCase.js",
"KebabCase.js",
"MultipleDashes.js",
"PascalCase.js",
"index.js",
]
`;
exports[`cli should support different filename cases with directory output: --filename-case=camel 1`] = `
Array [
"camelCase.js",
"index.js",
"kebabCase.js",
"multipleDashes.js",
"pascalCase.js",
]
`;
exports[`cli should support different filename cases with directory output: --filename-case=kebab 1`] = `
Array [
"camel-case.js",
"index.js",
"kebab-case.js",
"multiple-dashes.js",
"pascal-case.js",
]
`;
exports[`cli should support different filename cases with directory output: --filename-case=pascal 1`] = `
Array [
"CamelCase.js",
"KebabCase.js",
"MultipleDashes.js",
"PascalCase.js",
"index.js",
]
`;
exports[`cli should support stdin filepath 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --expand-props none 1`] = `
"import * as React from 'react'
const SvgFile = () => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\">
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --expand-props start 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg {...props} width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\">
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --icon 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg
width=\\"1em\\"
height=\\"1em\\"
viewBox=\\"0 0 48 1\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --native --expand-props none 1`] = `
"import * as React from 'react'
import Svg, { Path } from 'react-native-svg'
const SvgFile = () => (
<Svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\">
<Path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</Svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --native --icon 1`] = `
"import * as React from 'react'
import Svg, { Path } from 'react-native-svg'
const SvgFile = (props) => (
<Svg
width=\\"1em\\"
height=\\"1em\\"
viewBox=\\"0 0 48 1\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<Path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</Svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --native --ref 1`] = `
"import * as React from 'react'
import Svg, { Path } from 'react-native-svg'
import { forwardRef } from 'react'
const SvgFile = (props, ref) => (
<Svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
{...props}
>
<Path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</Svg>
)
const ForwardRef = forwardRef(SvgFile)
export default ForwardRef
"
`;
exports[`cli should support various args: --native 1`] = `
"import * as React from 'react'
import Svg, { Path } from 'react-native-svg'
const SvgFile = (props) => (
<Svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<Path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</Svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --no-dimensions 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg viewBox=\\"0 0 48 1\\" xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --no-prettier 1`] = `
"import * as React from \\"react\\";
const SvgFile = props => <svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}><path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" /></svg>;
export default SvgFile;
"
`;
exports[`cli should support various args: --no-svgo 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg
width=\\"48px\\"
height=\\"1px\\"
viewBox=\\"0 0 48 1\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
xmlnsXlink=\\"http://www.w3.org/1999/xlink\\"
{...props}
>
<title>{'Rectangle 5'}</title>
<desc>{'Created with Sketch.'}</desc>
<defs />
<g id=\\"Page-1\\" stroke=\\"none\\" strokeWidth={1} fill=\\"none\\" fillRule=\\"evenodd\\">
<g
id=\\"19-Separator\\"
transform=\\"translate(-129.000000, -156.000000)\\"
fill=\\"#063855\\"
>
<g id=\\"Controls/Settings\\" transform=\\"translate(80.000000, 0.000000)\\">
<g id=\\"Content\\" transform=\\"translate(0.000000, 64.000000)\\">
<g id=\\"Group\\" transform=\\"translate(24.000000, 56.000000)\\">
<g id=\\"Group-2\\">
<rect id=\\"Rectangle-5\\" x={25} y={36} width={48} height={1} />
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --ref 1`] = `
"import * as React from 'react'
import { forwardRef } from 'react'
const SvgFile = (props, ref) => (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
const ForwardRef = forwardRef(SvgFile)
export default ForwardRef
"
`;
exports[`cli should support various args: --replace-attr-values "#063855=currentColor" 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"currentColor\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --svg-props "hidden={true},id=hello" 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
hidden={true}
id=\\"hello\\"
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --title-prop 1`] = `
"import * as React from 'react'
const SvgFile = ({ title, titleId, ...props }) => (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
aria-labelledby={titleId}
{...props}
>
{title ? <title id={titleId}>{title}</title> : null}
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should support various args: --typescript --ref --title-prop 1`] = `
"import * as React from 'react'
import { SVGProps, Ref, forwardRef } from 'react'
interface SVGRProps {
title?: string;
titleId?: string;
}
const SvgFile = (
{ title, titleId, ...props }: SVGProps<SVGSVGElement> & SVGRProps,
ref: Ref<SVGSVGElement>,
) => (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
aria-labelledby={titleId}
{...props}
>
{title ? <title id={titleId}>{title}</title> : null}
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
const ForwardRef = forwardRef(SvgFile)
export default ForwardRef
"
`;
exports[`cli should support various args: --typescript --ref 1`] = `
"import * as React from 'react'
import { SVGProps, Ref, forwardRef } from 'react'
const SvgFile = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
width={48}
height={1}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
const ForwardRef = forwardRef(SvgFile)
export default ForwardRef
"
`;
exports[`cli should support various args: --typescript 1`] = `
"import * as React from 'react'
import { SVGProps } from 'react'
const SvgFile = (props: SVGProps<SVGSVGElement>) => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should suppress output when transforming a directory with a --silent option 1`] = `""`;
exports[`cli should transform a whole directory and output relative destination paths 1`] = `
"
__fixtures__/cased/pascalcase.svg -> __fixtures_build__/whole/cased/pascalcase.js
__fixtures__/cased/camelcase.svg -> __fixtures_build__/whole/cased/camelcase.js
__fixtures__/cased/kebab-case.svg -> __fixtures_build__/whole/cased/kebabcase.js
__fixtures__/cased/multiple---dashes.svg -> __fixtures_build__/whole/cased/multipledashes.js
__fixtures__/complex/skype.svg -> __fixtures_build__/whole/complex/skype.js
__fixtures__/complex/telegram.svg -> __fixtures_build__/whole/complex/telegram.js
__fixtures__/nesting/a/c/three.svg -> __fixtures_build__/whole/nesting/a/c/three.js
__fixtures__/nesting/a/two.svg -> __fixtures_build__/whole/nesting/a/two.js
__fixtures__/nesting/one.svg -> __fixtures_build__/whole/nesting/one.js
__fixtures__/numeric/2.file.svg -> __fixtures_build__/whole/numeric/2file.js
__fixtures__/numeric/file.svg -> __fixtures_build__/whole/numeric/file.js
__fixtures__/simple/file.svg -> __fixtures_build__/whole/simple/file.js
__fixtures__/withprettierrc/file.svg -> __fixtures_build__/whole/withprettierrc/file.js
__fixtures__/withsvgoconfig/file.svg -> __fixtures_build__/whole/withsvgoconfig/file.js
__fixtures__/withsvgrrc/file.svg -> __fixtures_build__/whole/withsvgrrc/file.js"
`;
exports[`cli should transform a whole directory with --typescript 1`] = `
"
__fixtures__/cased/pascalcase.svg -> __fixtures_build__/whole/cased/pascalcase.tsx
__fixtures__/cased/camelcase.svg -> __fixtures_build__/whole/cased/camelcase.tsx
__fixtures__/cased/kebab-case.svg -> __fixtures_build__/whole/cased/kebabcase.tsx
__fixtures__/cased/multiple---dashes.svg -> __fixtures_build__/whole/cased/multipledashes.tsx
__fixtures__/complex/skype.svg -> __fixtures_build__/whole/complex/skype.tsx
__fixtures__/complex/telegram.svg -> __fixtures_build__/whole/complex/telegram.tsx
__fixtures__/nesting/a/c/three.svg -> __fixtures_build__/whole/nesting/a/c/three.tsx
__fixtures__/nesting/a/two.svg -> __fixtures_build__/whole/nesting/a/two.tsx
__fixtures__/nesting/one.svg -> __fixtures_build__/whole/nesting/one.tsx
__fixtures__/numeric/2.file.svg -> __fixtures_build__/whole/numeric/2file.tsx
__fixtures__/numeric/file.svg -> __fixtures_build__/whole/numeric/file.tsx
__fixtures__/simple/file.svg -> __fixtures_build__/whole/simple/file.tsx
__fixtures__/withprettierrc/file.svg -> __fixtures_build__/whole/withprettierrc/file.tsx
__fixtures__/withsvgoconfig/file.svg -> __fixtures_build__/whole/withsvgoconfig/file.tsx
__fixtures__/withsvgrrc/file.svg -> __fixtures_build__/whole/withsvgrrc/file.tsx"
`;
exports[`cli should work with a simple file 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`cli should work with stdin 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgComponent
"
`;

View File

@ -1,37 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`util #convertFile should convert a file 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;
exports[`util #convertFile should support a custom config path 1`] = `
"import * as React from 'react'
function SvgFile(props) {
return (
<svg
width=\\"1em\\"
height=\\"1em\\"
viewBox=\\"0 0 48 1\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
}
export default SvgFile
"
`;

View File

@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`util #convertFile should convert a file 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg width={48} height={1} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;
exports[`util #convertFile should support a custom config path 1`] = `
"import * as React from 'react'
const SvgFile = (props) => (
<svg
width=\\"1em\\"
height=\\"1em\\"
viewBox=\\"0 0 48 1\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<path d=\\"M0 0h48v1H0z\\" fill=\\"#063855\\" fillRule=\\"evenodd\\" />
</svg>
)
export default SvgFile
"
`;

View File

@ -1,119 +0,0 @@
/* eslint-disable no-underscore-dangle, no-console */
import { promises as fs } from 'fs'
import path from 'path'
import chalk from 'chalk'
import { loadConfig } from '@svgr/core'
import {
convertFile,
transformFilename,
CASE,
politeWrite,
formatExportName,
} from './util'
async function exists(file) {
try {
await fs.access(file)
return true
} catch (error) {
return false
}
}
function rename(relative, ext, filenameCase) {
const relativePath = path.parse(relative)
relativePath.ext = `.${ext}`
relativePath.base = null
relativePath.name = transformFilename(relativePath.name, filenameCase)
return path.format(relativePath)
}
const COMPILABLE_EXTENSIONS = ['.svg', '.SVG']
export function isCompilable(filename) {
const ext = path.extname(filename)
return COMPILABLE_EXTENSIONS.includes(ext)
}
function defaultIndexTemplate(filePaths) {
const exportEntries = filePaths.map((filePath) => {
const basename = path.basename(filePath, path.extname(filePath))
const exportName = formatExportName(basename)
return `export { default as ${exportName} } from './${basename}'`
})
return exportEntries.join('\n')
}
function getDefaultExtension(options) {
return options.typescript ? 'tsx' : 'js'
}
export default async function dirCommand(
opts,
program,
filenames,
{ ext, filenameCase = CASE.PASCAL, ...options },
) {
async function write(src, dest) {
if (!isCompilable(src)) return { transformed: false, dest: null }
ext = ext || getDefaultExtension(options)
dest = rename(dest, ext, filenameCase)
const code = await convertFile(src, options)
const cwdRelative = path.relative(process.cwd(), dest)
const logOutput = `${src} -> ${cwdRelative}\n`
if (opts.ignoreExisting && (await exists(dest))) {
politeWrite(opts, chalk.grey(logOutput))
return { transformed: false, dest }
}
await fs.mkdir(path.dirname(dest), { recursive: true })
await fs.writeFile(dest, code)
politeWrite(opts, chalk.white(logOutput))
return { transformed: true, dest }
}
async function generateIndex(dest, files, config) {
const indexFile = path.join(dest, `index.${ext}`)
const indexTemplate = config.indexTemplate || defaultIndexTemplate
await fs.writeFile(indexFile, indexTemplate(files))
}
async function handle(filename, root) {
const stats = await fs.stat(filename)
if (stats.isDirectory()) {
const dirname = filename
const files = await fs.readdir(dirname)
const results = await Promise.all(
files.map(async (relativeFile) => {
const absFile = path.join(dirname, relativeFile)
return handle(absFile, root)
}),
)
const transformed = results.filter((result) => result.transformed)
if (transformed.length) {
const destFiles = results.map((result) => result.dest).filter(Boolean)
const dest = path.resolve(opts.outDir, path.relative(root, dirname))
const config = loadConfig.sync(options, { filePath: dest })
if (config.index) {
await generateIndex(dest, destFiles, config)
}
}
return { transformed: false, dest: null }
}
const dest = path.resolve(opts.outDir, path.relative(root, filename))
return write(filename, dest)
}
await Promise.all(
filenames.map(async (file) => {
const stats = await fs.stat(file)
const root = stats.isDirectory() ? file : path.dirname(file)
await handle(file, root)
}),
)
}

View File

@ -0,0 +1,138 @@
/* eslint-disable no-underscore-dangle, no-console */
import { promises as fs } from 'fs'
import * as path from 'path'
import { grey, white } from 'chalk'
import { loadConfig, Config } from '@svgr/core'
import {
convertFile,
transformFilename,
politeWrite,
formatExportName,
} from './util'
import type { SvgrCommand } from './index'
const exists = async (filepath: string) => {
try {
await fs.access(filepath)
return true
} catch (error) {
return false
}
}
const rename = (relative: string, ext: string, filenameCase: string) => {
const relativePath = path.parse(relative)
relativePath.ext = `.${ext}`
relativePath.base = ''
relativePath.name = transformFilename(relativePath.name, filenameCase)
return path.format(relativePath)
}
export const isCompilable = (filename: string): boolean => {
const ext = path.extname(filename)
return ext === '.svg' || ext == '.SVG'
}
export interface IndexTemplate {
(paths: string[]): string
}
const defaultIndexTemplate: IndexTemplate = (paths) => {
const exportEntries = paths.map((filePath) => {
const basename = path.basename(filePath, path.extname(filePath))
const exportName = formatExportName(basename)
return `export { default as ${exportName} } from './${basename}'`
})
return exportEntries.join('\n')
}
const resolveExtension = (config: Config, ext?: string) =>
ext || (config.typescript ? 'tsx' : 'js')
export const dirCommand: SvgrCommand = async (
{
ext: extOpt,
filenameCase = 'pascal',
ignoreExisting,
silent,
indexTemplate: indexTemplateOpt,
configFile,
outDir,
},
_,
filenames,
config,
): Promise<void> => {
const ext = resolveExtension(config, extOpt)
const write = async (src: string, dest: string) => {
if (!isCompilable(src)) {
return { transformed: false, dest: null }
}
dest = rename(dest, ext, filenameCase)
const code = await convertFile(src, config)
const cwdRelative = path.relative(process.cwd(), dest)
const logOutput = `${src} -> ${cwdRelative}\n`
if (ignoreExisting && (await exists(dest))) {
politeWrite(grey(logOutput), silent)
return { transformed: false, dest }
}
await fs.mkdir(path.dirname(dest), { recursive: true })
await fs.writeFile(dest, code)
politeWrite(white(logOutput), silent)
return { transformed: true, dest }
}
const generateIndex = async (dest: string, files: string[]) => {
const indexFile = path.join(dest, `index.${ext}`)
const indexTemplate = indexTemplateOpt || defaultIndexTemplate
await fs.writeFile(indexFile, indexTemplate(files))
}
async function handle(filename: string, root: string) {
const stats = await fs.stat(filename)
if (stats.isDirectory()) {
const dirname = filename
const files = await fs.readdir(dirname)
const results = await Promise.all(
files.map(async (relativeFile) => {
const absFile = path.join(dirname, relativeFile)
return handle(absFile, root)
}),
)
const transformed = results.filter((result) => result.transformed)
if (transformed.length) {
const destFiles = results
.map((result) => result.dest)
.filter(Boolean) as string[]
const dest = path.resolve(
outDir as string,
path.relative(root, dirname),
)
const resolvedConfig = loadConfig.sync(
{ configFile, ...config },
{ filePath: dest },
)
if (resolvedConfig.index) {
await generateIndex(dest, destFiles)
}
}
return { transformed: false, dest: null }
}
const dest = path.resolve(outDir as string, path.relative(root, filename))
return write(filename, dest)
}
await Promise.all(
filenames.map(async (file) => {
const stats = await fs.stat(file)
const root = stats.isDirectory() ? file : path.dirname(file)
await handle(file, root)
}),
)
}

View File

@ -1,41 +1,42 @@
/* eslint-disable no-underscore-dangle */
import { promises as fs } from 'fs'
import { convert, convertFile, exitError } from './util'
import type { SvgrCommand } from './index'
async function output(promise) {
process.stdout.write(`${await promise}\n`)
}
async function fileCommand(opts, program, filenames, config) {
function stdin() {
const readStdin = async () => {
return new Promise<string>((resolve) => {
let code = ''
process.stdin.setEncoding('utf8')
process.stdin.on('readable', () => {
const chunk = process.stdin.read()
if (chunk !== null) code += chunk
})
process.stdin.on('end', () => {
output(convert(code, config, { filePath: opts.stdinFilepath }))
resolve(code)
})
}
})
}
export const fileCommand: SvgrCommand = async (
opts,
program,
filenames,
config,
): Promise<void> => {
if (opts.stdin || (filenames.length === 0 && !process.stdin.isTTY)) {
stdin()
const input = await readStdin()
const output = convert(input, config, { filePath: opts.stdinFilepath })
process.stdout.write(`${output}\n`)
return
}
if (filenames.length === 0) {
// eslint-disable-next-line no-console
console.log(program.helpInformation())
process.stdout.write(`${program.helpInformation()}\n`)
return
}
if (filenames.length > 1) {
exitError('Please specify only one filename or use `--out-dir` option.')
return
}
const [filename] = filenames
@ -45,7 +46,6 @@ async function fileCommand(opts, program, filenames, config) {
exitError('Directory are not supported without `--out-dir` option instead.')
}
output(convertFile(filename, config))
const output = await convertFile(filename, config)
process.stdout.write(`${output}\n`)
}
export default fileCommand

View File

@ -1,17 +1,17 @@
import { promises as fs } from 'fs'
import path from 'path'
import childProcess from 'child_process'
import util from 'util'
import * as path from 'path'
import { exec as execCb } from 'child_process'
import { promisify } from 'util'
// @ts-ignore
import del from 'del'
const exec = util.promisify(childProcess.exec)
const exec = promisify(execCb)
const svgr = path.join(__dirname, 'index.js')
const babelNode = path.join(__dirname, '../../../node_modules/.bin/babel-node')
const svgr = path.join(__dirname, '../bin/svgr')
describe('cli', () => {
const cli = async (args) => {
const { stdout } = await exec(`${babelNode} -- ${svgr} ${args}`)
const cli = async (args: string) => {
const { stdout } = await exec(`${svgr} ${args}`)
return stdout
}
@ -24,7 +24,7 @@ describe('cli', () => {
expect.assertions(1)
try {
await cli('__fixtures__/nesting')
} catch (error) {
} catch (error: any) {
expect(error.message).toMatch(
'Directory are not supported without `--out-dir` option instead',
)
@ -35,7 +35,7 @@ describe('cli', () => {
expect.assertions(1)
try {
await cli('__fixtures__/simple/file.svg __fixtures__/nesting/one.svg')
} catch (error) {
} catch (error: any) {
expect(error.message).toMatch(
'Please specify only one filename or use `--out-dir` option',
)

View File

@ -1,43 +1,44 @@
/* eslint-disable no-console */
import program from 'commander'
import path from 'path'
import glob from 'glob'
import fs, { promises as fsPromises } from 'fs'
import { loadConfig } from '@svgr/core'
import pkg from '../package.json'
import fileCommand from './fileCommand'
import dirCommand from './dirCommand'
import { program, Command } from 'commander'
import * as path from 'path'
import { glob } from 'glob'
import { readFileSync, promises as fsPromises } from 'fs'
import { loadConfig, Config } from '@svgr/core'
import { fileCommand } from './fileCommand'
import { dirCommand } from './dirCommand'
import { exitError } from './util'
import type { IndexTemplate } from './dirCommand'
import { version } from '../package.json'
function noUndefinedKeys(obj) {
const noUndefinedKeys = <T extends Record<string, any>>(obj: T): T => {
return Object.entries(obj).reduce((obj, [key, value]) => {
if (value !== undefined) {
// @ts-ignore
obj[key] = value
}
return obj
}, {})
}, {} as T)
}
function parseObject(arg, accumulation = {}) {
const parseObject = (arg: string, accumulation = {}) => {
const [name, value] = arg.split('=')
return { ...accumulation, [name]: value }
}
function parseObjectList(arg, accumulation = {}) {
const parseObjectList = (arg: string, accumulation = {}) => {
const args = arg.split(',').map((str) => str.trim())
return args.reduce((acc, arg) => parseObject(arg, acc), accumulation)
}
const parseConfig = (name) => (arg) => {
const parseConfig = (name: string) => (arg: string) => {
try {
if (arg.endsWith('rc')) {
const content = fs.readFileSync(arg, 'utf-8')
const content = readFileSync(arg, 'utf-8')
return JSON.parse(content)
}
const ext = path.extname(arg)
if (ext === '.js' || ext === '.json') {
// eslint-disable-next-line import/no-dynamic-require, global-require
return require(path.join(process.cwd(), arg))
}
@ -46,12 +47,51 @@ const parseConfig = (name) => (arg) => {
exitError(
`"${name}" is not valid, please specify a valid file or use a inline JSON.`,
)
return null
}
}
const parseExpandProps = (arg: string) => (arg === 'none' ? false : arg)
const parseTemplate = (name: string) => (arg: string) => {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const template = require(path.join(process.cwd(), arg))
const resolved = template.default || template
if (typeof resolved !== 'function') {
throw new Error(`${name} file must export a function`)
}
return resolved
} catch (error: any) {
console.error(`Error when loading "${name}": ${arg}\n`)
console.error(error.stack)
process.exit(2)
}
}
interface ProgramOpts extends Config {
configFile?: string
runtimeConfig?: boolean
outDir?: string
ignoreExisting?: boolean
ext?: string
filenameCase?: string
silent?: boolean
stdin?: boolean
stdinFilepath?: string
indexTemplate?: IndexTemplate
}
export interface SvgrCommand {
(
opts: ProgramOpts,
program: Command,
filenames: string[],
config: Config,
): Promise<void>
}
program
.version(pkg.version)
.version(version)
.usage('[options] <file|directory>')
.option('--config-file <file>', 'specify the path of the svgr config')
.option(
@ -74,6 +114,7 @@ program
.option(
'--expand-props [position]',
'disable props expanding ("start", "end", "none") (default: "end")',
parseExpandProps,
)
.option(
'--svg-props <property=value>',
@ -85,10 +126,15 @@ program
'replace an attribute value',
parseObjectList,
)
.option('--template <file>', 'specify a custom template to use')
.option(
'--template <file>',
'specify a custom template to use',
parseTemplate('--template'),
)
.option(
'--index-template <file>',
'specify a custom index.js template to use',
parseTemplate('--index-template'),
)
.option('--no-index', 'disable index file generation')
.option('--title-prop', 'create a title element linked with props')
@ -121,12 +167,12 @@ program.on('--help', () => {
program.parse(process.argv)
async function run() {
const errors = []
const errors: string[] = []
const filenames = program.args.reduce((globbed, input) => {
let files = glob.sync(input)
if (!files.length) files = [input]
return globbed.concat(files)
}, [])
return [...globbed, ...files]
}, [] as string[])
await Promise.all(
filenames.map(async (filename) => {
@ -143,17 +189,10 @@ async function run() {
process.exit(2)
}
const opts = noUndefinedKeys(program.opts())
const opts = noUndefinedKeys(program.opts<ProgramOpts>())
const config = await loadConfig(opts, { filePath: process.cwd() })
// Back config file
config.configFile = opts.configFile
if (opts.expandProps === 'none') {
config.expandProps = false
}
if (opts.dimensions === true) {
delete config.dimensions
}
@ -166,47 +205,11 @@ async function run() {
delete config.prettier
}
if (opts.template) {
try {
// eslint-disable-next-line global-require, import/no-dynamic-require
const template = require(path.join(process.cwd(), opts.template))
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: ${opts.template}\n`)
console.error(error.stack)
process.exit(2)
}
}
if (opts.indexTemplate) {
try {
// eslint-disable-next-line global-require, import/no-dynamic-require
const indexTemplate = require(path.join(
process.cwd(),
opts.indexTemplate,
))
if (indexTemplate.default) config.indexTemplate = indexTemplate.default
else config.indexTemplate = indexTemplate
if (typeof config.indexTemplate !== 'function')
throw new Error('indexTemplate must be a function')
} catch (error) {
console.error(`Error when loading indexTemplate: ${opts.indexTemplate}\n`)
console.error(error.stack)
process.exit(2)
}
}
if (opts.index === false) {
delete config.index
}
const command = opts.outDir ? dirCommand : fileCommand
await command(opts, program, filenames, config)
}

View File

@ -1,66 +0,0 @@
/* eslint-disable no-console */
import { promises as fs } from 'fs'
import chalk from 'chalk'
import svgrConvert from '@svgr/core'
import svgo from '@svgr/plugin-svgo'
import jsx from '@svgr/plugin-jsx'
import prettier from '@svgr/plugin-prettier'
import camelcase from 'camelcase'
import dashify from 'dashify'
export const CASE = {
KEBAB: 'kebab', // kebab-case
CAMEL: 'camel', // camelCase
PASCAL: 'pascal', // PascalCase
}
export function transformFilename(filename, filenameCase) {
switch (filenameCase) {
case CASE.KEBAB:
return dashify(filename.replace(/_/g, '-'), { condense: true })
case CASE.CAMEL:
return camelcase(filename)
case CASE.PASCAL:
return camelcase(filename, { pascalCase: true })
default:
throw new Error(`Unknown --filename-case ${filenameCase}`)
}
}
export function convert(code, config, state) {
return svgrConvert.sync(code, config, {
...state,
caller: {
name: '@svgr/cli',
defaultPlugins: [svgo, jsx, prettier],
},
})
}
export async function convertFile(filePath, config = {}) {
const code = await fs.readFile(filePath, 'utf-8')
return convert(code, config, { filePath })
}
export function exitError(error) {
console.error(chalk.red(error))
process.exit(1)
}
export function politeWrite(opts, data) {
if (!opts.silent) {
process.stdout.write(data)
}
}
export function formatExportName(name) {
if (/[-]/g.test(name) && /^\d/.test(name)) {
return `Svg${camelcase(name, { pascalCase: true })}`
}
if (/^\d/.test(name)) {
return `Svg${name}`
}
return camelcase(name, { pascalCase: true })
}

View File

@ -1,5 +1,5 @@
import path from 'path'
import { convertFile, transformFilename, CASE, formatExportName } from './util'
import * as path from 'path'
import { convertFile, transformFilename, formatExportName } from './util'
const FIXTURES = path.join(__dirname, '../../../__fixtures__')
@ -22,13 +22,13 @@ describe('util', () => {
describe('#transformFilename', () => {
it('should transform filename', () => {
expect(transformFilename('FooBar', CASE.CAMEL)).toBe('fooBar')
expect(transformFilename('FooBar', CASE.KEBAB)).toBe('foo-bar')
expect(transformFilename('FooBar', CASE.PASCAL)).toBe('FooBar')
expect(transformFilename('FooBar', 'camel')).toBe('fooBar')
expect(transformFilename('FooBar', 'kebab')).toBe('foo-bar')
expect(transformFilename('FooBar', 'pascal')).toBe('FooBar')
expect(transformFilename('foo_bar', CASE.CAMEL)).toBe('fooBar')
expect(transformFilename('foo_bar', CASE.KEBAB)).toBe('foo-bar')
expect(transformFilename('foo_bar', CASE.PASCAL)).toBe('FooBar')
expect(transformFilename('foo_bar', 'camel')).toBe('fooBar')
expect(transformFilename('foo_bar', 'kebab')).toBe('foo-bar')
expect(transformFilename('foo_bar', 'pascal')).toBe('FooBar')
})
})

72
packages/cli/src/util.ts Normal file
View File

@ -0,0 +1,72 @@
/* eslint-disable no-console */
import { promises as fs } from 'fs'
import { red } from 'chalk'
import svgrConvert, { Config, State } from '@svgr/core'
import svgo from '@svgr/plugin-svgo'
import jsx from '@svgr/plugin-jsx'
import prettier from '@svgr/plugin-prettier'
// @ts-ignore
import camelCase from 'camelcase'
// @ts-ignore
import dashify from 'dashify'
export function transformFilename(
filename: string,
filenameCase: string,
): string {
switch (filenameCase) {
case 'kebab':
return dashify(filename.replace(/_/g, '-'), { condense: true })
case 'camel':
return camelCase(filename)
case 'pascal':
return camelCase(filename, { pascalCase: true })
default:
throw new Error(`Unknown --filename-case ${filenameCase}`)
}
}
export const convert = (
code: string,
config: Config,
state: Partial<State>,
): string => {
return svgrConvert.sync(code, config, {
...state,
caller: {
name: '@svgr/cli',
defaultPlugins: [svgo, jsx, prettier],
},
})
}
export const convertFile = async (
filePath: string,
config: Config = {},
): Promise<string> => {
const code = await fs.readFile(filePath, 'utf-8')
return convert(code, config, { filePath })
}
export const exitError = (error: string): never => {
console.error(red(error))
process.exit(1)
}
export const politeWrite = (data: string, silent?: boolean): void => {
if (!silent) {
process.stdout.write(data)
}
}
export const formatExportName = (name: string): string => {
if (/[-]/g.test(name) && /^\d/.test(name)) {
return `Svg${camelCase(name, { pascalCase: true })}`
}
if (/^\d/.test(name)) {
return `Svg${name}`
}
return camelCase(name, { pascalCase: true })
}

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig",
"include": ["src"]
}

View File

@ -1,2 +1,4 @@
src/
.*
/*
/dist/*
!/dist/index.{d.ts,js}
!/dist/index.js.map

View File

@ -2,7 +2,9 @@
"name": "@svgr/core",
"description": "Transform SVG into React Components.",
"version": "6.0.0-alpha.0",
"main": "lib/index.js",
"main": "./dist/index.js",
"exports": "./dist/index.js",
"typings": "./dist/index.d.ts",
"repository": "https://github.com/gregberge/svgr/tree/master/packages/core",
"author": "Greg Bergé <berge.greg@gmail.com>",
"publishConfig": {
@ -26,13 +28,16 @@
},
"license": "MIT",
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src && cp src/index.d.ts lib/index.d.ts",
"prepublishOnly": "npm run build"
"reset": "rm -rf dist",
"build": "rollup -c ../../build/rollup.config.js",
"prepublishOnly": "npm run reset && npm run build"
},
"dependencies": {
"@svgr/plugin-jsx": "^6.0.0-alpha.0",
"camelcase": "^6.2.0",
"cosmiconfig": "^7.0.1"
},
"devDependencies": {
"@types/svgo": "^2.6.0"
}
}

View File

@ -11,9 +11,8 @@ Object {
"namedExport": "ReactComponent",
"native": false,
"noSemi": true,
"plugins": null,
"prettier": true,
"prettierConfig": null,
"prettierConfig": undefined,
"ref": false,
"replaceAttrValues": Array [
Array [
@ -22,10 +21,10 @@ Object {
],
],
"runtimeConfig": true,
"svgProps": null,
"svgProps": undefined,
"svgo": true,
"svgoConfig": null,
"template": null,
"svgoConfig": undefined,
"template": undefined,
"titleProp": false,
"typescript": false,
}
@ -42,9 +41,8 @@ Object {
"namedExport": "ReactComponent",
"native": false,
"noSemi": true,
"plugins": null,
"prettier": true,
"prettierConfig": null,
"prettierConfig": undefined,
"ref": false,
"replaceAttrValues": Array [
Array [
@ -53,10 +51,10 @@ Object {
],
],
"runtimeConfig": true,
"svgProps": null,
"svgProps": undefined,
"svgo": true,
"svgoConfig": null,
"template": null,
"svgoConfig": undefined,
"template": undefined,
"titleProp": false,
"typescript": false,
"useRuntimeConfig": false,
@ -73,16 +71,15 @@ Object {
"memo": false,
"namedExport": "ReactComponent",
"native": false,
"plugins": null,
"prettier": true,
"prettierConfig": null,
"prettierConfig": undefined,
"ref": false,
"replaceAttrValues": null,
"replaceAttrValues": undefined,
"runtimeConfig": true,
"svgProps": null,
"svgProps": undefined,
"svgo": true,
"svgoConfig": null,
"template": null,
"svgoConfig": undefined,
"template": undefined,
"titleProp": false,
"typescript": false,
}
@ -99,9 +96,8 @@ Object {
"namedExport": "ReactComponent",
"native": false,
"noSemi": true,
"plugins": null,
"prettier": true,
"prettierConfig": null,
"prettierConfig": undefined,
"ref": false,
"replaceAttrValues": Array [
Array [
@ -110,10 +106,10 @@ Object {
],
],
"runtimeConfig": true,
"svgProps": null,
"svgProps": undefined,
"svgo": true,
"svgoConfig": null,
"template": null,
"svgoConfig": undefined,
"template": undefined,
"titleProp": false,
"typescript": false,
}
@ -130,9 +126,8 @@ Object {
"namedExport": "ReactComponent",
"native": false,
"noSemi": true,
"plugins": null,
"prettier": true,
"prettierConfig": null,
"prettierConfig": undefined,
"ref": false,
"replaceAttrValues": Array [
Array [
@ -141,10 +136,10 @@ Object {
],
],
"runtimeConfig": true,
"svgProps": null,
"svgProps": undefined,
"svgo": true,
"svgoConfig": null,
"template": null,
"svgoConfig": undefined,
"template": undefined,
"titleProp": false,
"typescript": false,
}
@ -161,9 +156,8 @@ Object {
"namedExport": "ReactComponent",
"native": false,
"noSemi": true,
"plugins": null,
"prettier": true,
"prettierConfig": null,
"prettierConfig": undefined,
"ref": false,
"replaceAttrValues": Array [
Array [
@ -172,10 +166,10 @@ Object {
],
],
"runtimeConfig": true,
"svgProps": null,
"svgProps": undefined,
"svgo": true,
"svgoConfig": null,
"template": null,
"svgoConfig": undefined,
"template": undefined,
"titleProp": false,
"typescript": false,
"useRuntimeConfig": false,
@ -192,16 +186,15 @@ Object {
"memo": false,
"namedExport": "ReactComponent",
"native": false,
"plugins": null,
"prettier": true,
"prettierConfig": null,
"prettierConfig": undefined,
"ref": false,
"replaceAttrValues": null,
"replaceAttrValues": undefined,
"runtimeConfig": true,
"svgProps": null,
"svgProps": undefined,
"svgo": true,
"svgoConfig": null,
"template": null,
"svgoConfig": undefined,
"template": undefined,
"titleProp": false,
"typescript": false,
}
@ -218,9 +211,8 @@ Object {
"namedExport": "ReactComponent",
"native": false,
"noSemi": true,
"plugins": null,
"prettier": true,
"prettierConfig": null,
"prettierConfig": undefined,
"ref": false,
"replaceAttrValues": Array [
Array [
@ -229,10 +221,10 @@ Object {
],
],
"runtimeConfig": true,
"svgProps": null,
"svgProps": undefined,
"svgo": true,
"svgoConfig": null,
"template": null,
"svgoConfig": undefined,
"template": undefined,
"titleProp": false,
"typescript": false,
}

View File

@ -1,710 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`convert config should support options {"dimensions":false} 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg viewBox=\\"0 0 88 88\\" xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"expandProps":"start"} 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg {...props} width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\">
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"expandProps":false} 1`] = `
"import * as React from 'react'
function SvgComponent() {
return (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\">
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"exportType":"named"} 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
export { SvgComponent as ReactComponent }
"
`;
exports[`convert config should support options {"icon":true} 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg
width=\\"1em\\"
height=\\"1em\\"
viewBox=\\"0 0 88 88\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"memo":true} 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
const MemoSvgComponent = React.memo(SvgComponent)
export default MemoSvgComponent
"
`;
exports[`convert config should support options {"namedExport":"Component","state":{"caller":{"previousExport":"export default \\"logo.svg\\";"}}} 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"native":{"expo":true}} 1`] = `
"import * as React from 'react'
import { Svg } from 'expo'
function SvgComponent(props) {
return (
<Svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<Svg.G
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<Svg.Path d=\\"M51 37 37 51M51 51 37 37\\" />
</Svg.G>
</Svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"native":true,"expandProps":false} 1`] = `
"import * as React from 'react'
import Svg, { G, Path } from 'react-native-svg'
function SvgComponent() {
return (
<Svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\">
<G
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<Path d=\\"M51 37 37 51M51 51 37 37\\" />
</G>
</Svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"native":true,"icon":true} 1`] = `
"import * as React from 'react'
import Svg, { G, Path } from 'react-native-svg'
function SvgComponent(props) {
return (
<Svg
width=\\"1em\\"
height=\\"1em\\"
viewBox=\\"0 0 88 88\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<G
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<Path d=\\"M51 37 37 51M51 51 37 37\\" />
</G>
</Svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"native":true,"ref":true} 1`] = `
"import * as React from 'react'
import Svg, { G, Path } from 'react-native-svg'
function SvgComponent(props, svgRef) {
return (
<Svg
width={88}
height={88}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
{...props}
>
<G
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<Path d=\\"M51 37 37 51M51 51 37 37\\" />
</G>
</Svg>
)
}
const ForwardRef = React.forwardRef(SvgComponent)
export default ForwardRef
"
`;
exports[`convert config should support options {"native":true} 1`] = `
"import * as React from 'react'
import Svg, { G, Path } from 'react-native-svg'
function SvgComponent(props) {
return (
<Svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<G
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<Path d=\\"M51 37 37 51M51 51 37 37\\" />
</G>
</Svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"prettier":false} 1`] = `
"import * as React from \\"react\\";
function SvgComponent(props) {
return <svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}><g stroke=\\"#063855\\" strokeWidth={2} fill=\\"none\\" fillRule=\\"evenodd\\" strokeLinecap=\\"square\\"><path d=\\"M51 37 37 51M51 51 37 37\\" /></g></svg>;
}
export default SvgComponent;"
`;
exports[`convert config should support options {"ref":true} 1`] = `
"import * as React from 'react'
function SvgComponent(props, svgRef) {
return (
<svg
width={88}
height={88}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
{...props}
>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
const ForwardRef = React.forwardRef(SvgComponent)
export default ForwardRef
"
`;
exports[`convert config should support options {"replaceAttrValues":{"none":"{black}"}} 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill={black}
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"replaceAttrValues":{"none":"black"}} 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"black\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"svgProps":{"a":"b","b":"{props.b}"}} 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg
width={88}
height={88}
xmlns=\\"http://www.w3.org/2000/svg\\"
a=\\"b\\"
b={props.b}
{...props}
>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"svgo":false} 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg
width=\\"88px\\"
height=\\"88px\\"
viewBox=\\"0 0 88 88\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
xmlnsXlink=\\"http://www.w3.org/1999/xlink\\"
{...props}
>
<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=\\"#063855\\" strokeWidth={2}>
<path d=\\"M51,37 L37,51\\" id=\\"Shape\\" />
<path d=\\"M51,51 L37,37\\" id=\\"Shape\\" />
</g>
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {"titleProp":true} 1`] = `
"import * as React from 'react'
function SvgComponent({ title, titleId, ...props }) {
return (
<svg
width={88}
height={88}
xmlns=\\"http://www.w3.org/2000/svg\\"
aria-labelledby={titleId}
{...props}
>
{title ? <title id={titleId}>{title}</title> : null}
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert config should support options {} 1`] = `
"const noop = () => null
export default noop
"
`;
exports[`convert config titleProp: without title added 1`] = `
"import * as React from 'react'
function SvgComponent({ title, titleId, ...props }) {
return (
<svg
width={0}
height={0}
style={{
position: 'absolute',
}}
aria-labelledby={titleId}
{...props}
>
{title ? <title id={titleId}>{title}</title> : null}
<path d=\\"M0 0h24v24H0z\\" fill=\\"none\\" />
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert should convert 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert should convert style attribute 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg
xmlns=\\"http://www.w3.org/2000/svg\\"
xmlnsXlink=\\"http://www.w3.org/1999/xlink\\"
width={48}
height={48}
{...props}
>
<g transform=\\"translate(0 -1004.362)\\">
<g id=\\"prefix__a\\">
<rect
style={{
color: '#000',
fill: '#a3a3a3',
fillOpacity: 1,
fillRule: 'nonzero',
stroke: 'none',
strokeWidth: 8,
marker: 'none',
visibility: 'visible',
display: 'inline',
overflow: 'visible',
enableBackground: 'accumulate',
}}
width={4}
height={4}
x={4}
y={1010.362}
rx={0.2}
ry={0.2}
/>
<rect
ry={0.2}
rx={0.2}
y={1010.362}
x={12}
height={4}
width={32}
style={{
color: '#000',
fill: '#a3a3a3',
fillOpacity: 1,
fillRule: 'nonzero',
stroke: 'none',
strokeWidth: 8,
marker: 'none',
visibility: 'visible',
display: 'inline',
overflow: 'visible',
enableBackground: 'accumulate',
}}
/>
</g>
<use xlinkHref=\\"#prefix__a\\" />
<use xlinkHref=\\"#prefix__a\\" transform=\\"translate(0 8)\\" />
<use xlinkHref=\\"#prefix__a\\" transform=\\"translate(0 16)\\" />
<use xlinkHref=\\"#prefix__a\\" transform=\\"translate(0 24)\\" />
<use xlinkHref=\\"#prefix__a\\" transform=\\"translate(0 32)\\" />
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert should handle special SVG attributes 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path externalResourcesRequired=\\"false\\" d=\\"M10 10h100v100H10z\\" />
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert should not remove all style tags 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<style>{'path{fill:red}'}</style>
<g
id=\\"prefix__Blocks\\"
stroke=\\"none\\"
strokeWidth={1}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<g id=\\"prefix__Dismiss\\" stroke=\\"#063855\\" strokeWidth={2}>
<path d=\\"M51 37 37 51M51 51 37 37\\" id=\\"prefix__Shape\\" />
</g>
</g>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert should remove null characters 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg
xmlns=\\"http://www.w3.org/2000/svg\\"
width={25}
height={25}
style={{
enableBackground: 'new 0 0 25 25',
}}
xmlSpace=\\"preserve\\"
{...props}
>
<path
d=\\"M19.4 24.5H5.6c-2.8 0-5.1-2.3-5.1-5.1V5.6C.5 2.8 2.8.5 5.6.5h13.8c2.8 0 5.1 2.3 5.1 5.1v13.8c0 2.8-2.3 5.1-5.1 5.1z\\"
style={{
fill: '#fff',
stroke: '#434a54',
strokeMiterlimit: 10,
}}
/>
</svg>
)
}
export default SvgComponent
"
`;
exports[`convert should remove style tags 1`] = `
"import * as React from 'react'
function SvgComponent(props) {
return (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<style />
<g
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
style={{
fill: 'red',
}}
>
<g id=\\"prefix__Dismiss\\" stroke=\\"#063855\\" strokeWidth={2}>
<path d=\\"M51 37 37 51M51 51 37 37\\" id=\\"prefix__Shape\\" />
</g>
</g>
</svg>
)
}
export default SvgComponent
"
`;

View File

@ -0,0 +1,641 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`convert config should support options {"dimensions":false} 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg viewBox=\\"0 0 88 88\\" xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"expandProps":"start"} 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg {...props} width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\">
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"expandProps":false} 1`] = `
"import * as React from 'react'
const SvgComponent = () => (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\">
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"exportType":"named"} 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
export { SvgComponent as ReactComponent }
"
`;
exports[`convert config should support options {"icon":true} 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg
width=\\"1em\\"
height=\\"1em\\"
viewBox=\\"0 0 88 88\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"memo":true} 1`] = `
"import * as React from 'react'
import { memo } from 'react'
const SvgComponent = (props) => (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
const Memo = memo(SvgComponent)
export default Memo
"
`;
exports[`convert config should support options {"namedExport":"Component","state":{"caller":{"previousExport":"export default \\"logo.svg\\";"}}} 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"native":true,"expandProps":false} 1`] = `
"import * as React from 'react'
import Svg, { G, Path } from 'react-native-svg'
const SvgComponent = () => (
<Svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\">
<G
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<Path d=\\"M51 37 37 51M51 51 37 37\\" />
</G>
</Svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"native":true,"icon":true} 1`] = `
"import * as React from 'react'
import Svg, { G, Path } from 'react-native-svg'
const SvgComponent = (props) => (
<Svg
width=\\"1em\\"
height=\\"1em\\"
viewBox=\\"0 0 88 88\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
{...props}
>
<G
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<Path d=\\"M51 37 37 51M51 51 37 37\\" />
</G>
</Svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"native":true,"ref":true} 1`] = `
"import * as React from 'react'
import Svg, { G, Path } from 'react-native-svg'
import { forwardRef } from 'react'
const SvgComponent = (props, ref) => (
<Svg
width={88}
height={88}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
{...props}
>
<G
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<Path d=\\"M51 37 37 51M51 51 37 37\\" />
</G>
</Svg>
)
const ForwardRef = forwardRef(SvgComponent)
export default ForwardRef
"
`;
exports[`convert config should support options {"native":true} 1`] = `
"import * as React from 'react'
import Svg, { G, Path } from 'react-native-svg'
const SvgComponent = (props) => (
<Svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<G
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<Path d=\\"M51 37 37 51M51 51 37 37\\" />
</G>
</Svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"prettier":false} 1`] = `
"import * as React from \\"react\\";
const SvgComponent = props => <svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}><g stroke=\\"#063855\\" strokeWidth={2} fill=\\"none\\" fillRule=\\"evenodd\\" strokeLinecap=\\"square\\"><path d=\\"M51 37 37 51M51 51 37 37\\" /></g></svg>;
export default SvgComponent;"
`;
exports[`convert config should support options {"ref":true} 1`] = `
"import * as React from 'react'
import { forwardRef } from 'react'
const SvgComponent = (props, ref) => (
<svg
width={88}
height={88}
xmlns=\\"http://www.w3.org/2000/svg\\"
ref={svgRef}
{...props}
>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
const ForwardRef = forwardRef(SvgComponent)
export default ForwardRef
"
`;
exports[`convert config should support options {"replaceAttrValues":{"none":"{black}"}} 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill={black}
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"replaceAttrValues":{"none":"black"}} 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"black\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"svgProps":{"a":"b","b":"{props.b}"}} 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg
width={88}
height={88}
xmlns=\\"http://www.w3.org/2000/svg\\"
a=\\"b\\"
b={props.b}
{...props}
>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"svgo":false} 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg
width=\\"88px\\"
height=\\"88px\\"
viewBox=\\"0 0 88 88\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
xmlnsXlink=\\"http://www.w3.org/1999/xlink\\"
{...props}
>
<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=\\"#063855\\" strokeWidth={2}>
<path d=\\"M51,37 L37,51\\" id=\\"Shape\\" />
<path d=\\"M51,51 L37,37\\" id=\\"Shape\\" />
</g>
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {"titleProp":true} 1`] = `
"import * as React from 'react'
const SvgComponent = ({ title, titleId, ...props }) => (
<svg
width={88}
height={88}
xmlns=\\"http://www.w3.org/2000/svg\\"
aria-labelledby={titleId}
{...props}
>
{title ? <title id={titleId}>{title}</title> : null}
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert config should support options {} 1`] = `
"const noop = () => null
export default noop
"
`;
exports[`convert config titleProp: without title added 1`] = `
"import * as React from 'react'
const SvgComponent = ({ title, titleId, ...props }) => (
<svg
width={0}
height={0}
style={{
position: 'absolute',
}}
aria-labelledby={titleId}
{...props}
>
{title ? <title id={titleId}>{title}</title> : null}
<path d=\\"M0 0h24v24H0z\\" fill=\\"none\\" />
</svg>
)
export default SvgComponent
"
`;
exports[`convert should convert 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<g
stroke=\\"#063855\\"
strokeWidth={2}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<path d=\\"M51 37 37 51M51 51 37 37\\" />
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert should convert style attribute 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg
xmlns=\\"http://www.w3.org/2000/svg\\"
xmlnsXlink=\\"http://www.w3.org/1999/xlink\\"
width={48}
height={48}
{...props}
>
<g transform=\\"translate(0 -1004.362)\\">
<g id=\\"prefix__a\\">
<rect
style={{
color: '#000',
fill: '#a3a3a3',
fillOpacity: 1,
fillRule: 'nonzero',
stroke: 'none',
strokeWidth: 8,
marker: 'none',
visibility: 'visible',
display: 'inline',
overflow: 'visible',
enableBackground: 'accumulate',
}}
width={4}
height={4}
x={4}
y={1010.362}
rx={0.2}
ry={0.2}
/>
<rect
ry={0.2}
rx={0.2}
y={1010.362}
x={12}
height={4}
width={32}
style={{
color: '#000',
fill: '#a3a3a3',
fillOpacity: 1,
fillRule: 'nonzero',
stroke: 'none',
strokeWidth: 8,
marker: 'none',
visibility: 'visible',
display: 'inline',
overflow: 'visible',
enableBackground: 'accumulate',
}}
/>
</g>
<use xlinkHref=\\"#prefix__a\\" />
<use xlinkHref=\\"#prefix__a\\" transform=\\"translate(0 8)\\" />
<use xlinkHref=\\"#prefix__a\\" transform=\\"translate(0 16)\\" />
<use xlinkHref=\\"#prefix__a\\" transform=\\"translate(0 24)\\" />
<use xlinkHref=\\"#prefix__a\\" transform=\\"translate(0 32)\\" />
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert should handle special SVG attributes 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<path externalResourcesRequired=\\"false\\" d=\\"M10 10h100v100H10z\\" />
</svg>
)
export default SvgComponent
"
`;
exports[`convert should not remove all style tags 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<style>{'path{fill:red}'}</style>
<g
id=\\"prefix__Blocks\\"
stroke=\\"none\\"
strokeWidth={1}
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
>
<g id=\\"prefix__Dismiss\\" stroke=\\"#063855\\" strokeWidth={2}>
<path d=\\"M51 37 37 51\\" id=\\"prefix__Shape\\" />
<path d=\\"M51 51 37 37\\" />
</g>
</g>
</svg>
)
export default SvgComponent
"
`;
exports[`convert should remove null characters 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg
xmlns=\\"http://www.w3.org/2000/svg\\"
width={25}
height={25}
style={{
enableBackground: 'new 0 0 25 25',
}}
xmlSpace=\\"preserve\\"
{...props}
>
<path
d=\\"M19.4 24.5H5.6c-2.8 0-5.1-2.3-5.1-5.1V5.6C.5 2.8 2.8.5 5.6.5h13.8c2.8 0 5.1 2.3 5.1 5.1v13.8c0 2.8-2.3 5.1-5.1 5.1z\\"
style={{
fill: '#fff',
stroke: '#434a54',
strokeMiterlimit: 10,
}}
/>
</svg>
)
export default SvgComponent
"
`;
exports[`convert should remove style tags 1`] = `
"import * as React from 'react'
const SvgComponent = (props) => (
<svg width={88} height={88} xmlns=\\"http://www.w3.org/2000/svg\\" {...props}>
<style />
<g
fill=\\"none\\"
fillRule=\\"evenodd\\"
strokeLinecap=\\"square\\"
style={{
fill: 'red',
}}
>
<g id=\\"prefix__Dismiss\\" stroke=\\"#063855\\" strokeWidth={2}>
<path d=\\"M51 37 37 51\\" id=\\"prefix__Shape\\" />
<path d=\\"M51 51 37 37\\" />
</g>
</g>
</svg>
)
export default SvgComponent
"
`;

View File

@ -1,80 +0,0 @@
import { cosmiconfig, cosmiconfigSync } from 'cosmiconfig'
export const DEFAULT_CONFIG = {
dimensions: true,
expandProps: 'end',
icon: false,
native: false,
typescript: false,
prettier: true,
prettierConfig: null,
memo: false,
ref: false,
replaceAttrValues: null,
svgProps: null,
svgo: true,
svgoConfig: null,
template: null,
index: false,
titleProp: false,
runtimeConfig: true,
plugins: null,
namedExport: 'ReactComponent',
exportType: 'default',
}
const explorer = cosmiconfig('svgr', {
sync: true,
cache: true,
rcExtensions: true,
})
const explorerSync = cosmiconfigSync('svgr', {
sync: true,
cache: true,
rcExtensions: true,
})
export async function resolveConfig(searchFrom, configFile) {
if (configFile == null) {
const result = await explorer.search(searchFrom)
return result ? result.config : null
}
const result = await explorer.load(configFile)
return result ? result.config : null
}
resolveConfig.sync = (searchFrom, configFile) => {
if (configFile == null) {
const result = explorerSync.search(searchFrom)
return result ? result.config : null
}
const result = explorerSync.load(configFile)
return result ? result.config : null
}
export async function resolveConfigFile(filePath) {
const result = await explorer.search(filePath)
return result ? result.filepath : null
}
resolveConfigFile.sync = (filePath) => {
const result = explorerSync.search(filePath)
return result ? result.filepath : null
}
export async function loadConfig({ configFile, ...baseConfig }, state = {}) {
const rcConfig =
state.filePath && baseConfig.runtimeConfig !== false
? await resolveConfig(state.filePath, configFile)
: {}
return { ...DEFAULT_CONFIG, ...rcConfig, ...baseConfig }
}
loadConfig.sync = ({ configFile, ...baseConfig }, state = {}) => {
const rcConfig =
state.filePath && baseConfig.runtimeConfig !== false
? resolveConfig.sync(state.filePath, configFile)
: {}
return { ...DEFAULT_CONFIG, ...rcConfig, ...baseConfig }
}

View File

@ -1,7 +1,8 @@
import path from 'path'
import * as path from 'path'
import { resolveConfig, resolveConfigFile, loadConfig } from './config'
const getMethod = (method, mode) => (mode === 'sync' ? method.sync : method)
const getMethod = (method: any, mode: string) =>
mode === 'sync' ? method.sync : method
describe('svgo', () => {
describe.each([['sync'], ['async']])('%s', (mode) => {

109
packages/core/src/config.ts Normal file
View File

@ -0,0 +1,109 @@
import { cosmiconfig, cosmiconfigSync } from 'cosmiconfig'
import type { Options as PrettierOptions } from 'prettier'
import type { OptimizeOptions as SvgoOptions } from 'svgo'
import type { Options as TransformOptions } from '@svgr/babel-preset'
import type { TransformOptions as BabelTransformOptions } from '@babel/core'
import type { ConfigPlugin } from './plugins'
import type { State } from './state'
export interface Config extends Partial<Omit<TransformOptions, 'state'>> {
dimensions?: boolean
runtimeConfig?: boolean
native?: boolean
typescript?: boolean
prettier?: boolean
prettierConfig?: PrettierOptions
svgo?: boolean
svgoConfig?: SvgoOptions
configFile?: string
// CLI only
index?: boolean
plugins?: ConfigPlugin[]
// JSX
jsx?: { babelConfig?: BabelTransformOptions }
}
export const DEFAULT_CONFIG: Config = {
dimensions: true,
expandProps: 'end',
icon: false,
native: false,
typescript: false,
prettier: true,
prettierConfig: undefined,
memo: false,
ref: false,
replaceAttrValues: undefined,
svgProps: undefined,
svgo: true,
svgoConfig: undefined,
template: undefined,
index: false,
titleProp: false,
runtimeConfig: true,
namedExport: 'ReactComponent',
exportType: 'default',
}
const explorer = cosmiconfig('svgr')
const explorerSync = cosmiconfigSync('svgr')
export const resolveConfig = async (
searchFrom?: string,
configFile?: string,
): Promise<Config | null> => {
if (configFile == null) {
const result = await explorer.search(searchFrom)
return result ? result.config : null
}
const result = await explorer.load(configFile)
return result ? result.config : null
}
resolveConfig.sync = (
searchFrom?: string,
configFile?: string,
): Config | null => {
if (configFile == null) {
const result = explorerSync.search(searchFrom)
return result ? result.config : null
}
const result = explorerSync.load(configFile)
return result ? result.config : null
}
export const resolveConfigFile = async (
filePath: string,
): Promise<string | null> => {
const result = await explorer.search(filePath)
return result ? result.filepath : null
}
resolveConfigFile.sync = (filePath: string): string | null => {
const result = explorerSync.search(filePath)
return result ? result.filepath : null
}
export const loadConfig = async (
{ configFile, ...baseConfig }: Config,
state: Pick<State, 'filePath'> = {},
): Promise<Config> => {
const rcConfig =
state.filePath && baseConfig.runtimeConfig !== false
? await resolveConfig(state.filePath, configFile)
: {}
return { ...DEFAULT_CONFIG, ...rcConfig, ...baseConfig }
}
loadConfig.sync = (
{ configFile, ...baseConfig }: Config,
state: Pick<State, 'filePath'> = {},
): Config => {
const rcConfig =
state.filePath && baseConfig.runtimeConfig !== false
? resolveConfig.sync(state.filePath, configFile)
: {}
return { ...DEFAULT_CONFIG, ...rcConfig, ...baseConfig }
}

View File

@ -1,6 +1,10 @@
import convert from '.'
import convert, { Config, State } from '.'
function convertWithAllPlugins(code, config, state) {
function convertWithAllPlugins(
code: string,
config?: Config,
state?: Partial<State>,
) {
return convert(
code,
{
@ -15,7 +19,11 @@ function convertWithAllPlugins(code, config, state) {
)
}
function convertSyncWithAllPlugins(code, config, state) {
function convertSyncWithAllPlugins(
code: string,
config?: Config,
state?: Partial<State>,
) {
return convert.sync(
code,
{
@ -287,13 +295,12 @@ describe('convert', () => {
})
describe('config', () => {
const configs = [
const configs: (Config & { state?: Partial<State> })[] = [
{ dimensions: false },
{ expandProps: false },
{ expandProps: 'start' },
{ icon: true },
{ native: true },
{ native: { expo: true } },
{ native: true, icon: true },
{ native: true, expandProps: false },
{ native: true, ref: true },
@ -304,8 +311,8 @@ describe('convert', () => {
{ svgo: false },
{ prettier: false },
{
template: ({ template }) =>
template.ast`const noop = () => null; export default noop;`,
template: (_, { tpl }) =>
tpl`const noop = () => null; export default noop;`,
},
{ titleProp: true },
{ memo: true },

View File

@ -1,8 +1,10 @@
import { expandState } from './state'
import { loadConfig } from './config'
import { resolvePlugin, getPlugins } from './plugins'
import type { Config } from './config'
import type { State } from './state'
function run(code, config, state) {
const run = (code: string, config: Config, state: Partial<State>): string => {
const expandedState = expandState(state)
const plugins = getPlugins(config, state).map(resolvePlugin)
let nextCode = String(code).replace('\0', '')
@ -13,12 +15,20 @@ function run(code, config, state) {
return nextCode
}
async function convert(code, config = {}, state = {}) {
const convert = async (
code: string,
config: Config = {},
state: Partial<State> = {},
): Promise<string> => {
config = await loadConfig(config, state)
return run(code, config, state)
}
convert.sync = (code, config = {}, state = {}) => {
convert.sync = (
code: string,
config: Config = {},
state: Partial<State> = {},
): string => {
config = loadConfig.sync(config, state)
return run(code, config, state)
}

View File

@ -1,103 +0,0 @@
export interface TemplateOptions extends SvgrOpts {}
export interface TemplateData {
imports?: string[]
interfaces?: string[]
componentName?: string
props?: string[]
jsx?: string
exports?: string[]
}
export type TemplateFunc = (
templateOptions: { template: unknown },
opts: TemplateOptions,
data: TemplateData,
) => string
export interface SvgrOpts {
/** Specify a custom config file. */
configFile?: string
/** Replace SVG width and height with 1em to make SVG inherit text size. */
icon?: boolean
/** Custom extension for generated files (default "js"). */
ext?: string
/** Modify all SVG nodes with uppercase and use react-native-svg template. */
native?: boolean | { expo: boolean }
/** Generate .tsx files with TypeScript bindings. */
typescript?: boolean
/** Keep width and height attributes from the root SVG tag. */
dimensions?: boolean
/** Forward all properties on the React component to the SVG tag. */
expandProps?: 'start' | 'end' | false
/** Use Prettier to format JavaScript code output. */
prettier?: boolean
/** Specify prettier config. */
prettierConfig?: Record<string, unknown>
/** Use SVGO to optimize SVG code before transforming into a React component. Default: true. */
svgo?: boolean
/** Specify SVGO config. https://gist.github.com/pladaria/69321af86ce165c2c1fc1c718b098dd0 */
svgoConfig?: Record<string, unknown>
/** Forward the ref to the root SVG tag if true. */
ref?: boolean
/** Wrap the exported component in React.memo if true. */
memo?: boolean
/**
* Replace an attribute value by another. Intended for changing an Icon
* color to currentColor to inherit from text color.
*
* Specify dynamic property using curly braces: { '#000': "{props.color}" }
*/
replaceAttrValues?: Record<string, string>
/**
* Add props to the SVG tag.
*
* Specify dynamic property using curly braces: { focusable: "{true}" }.
* Particularly useful with a custom template.
*/
svgProps?: Record<string, string>
/**
* Add title tag via title property. If titleProp is set to true and no
* title is provided (title={undefined}) at render time, this will fallback
* to an existing title element in the svg if it exists.
*/
titleProp?: boolean
/**
* Specify a template file (CLI) or a template function (API) to use.
* For an example of template, see the default one.
* https://github.com/gregberge/svgr/blob/main/packages/babel-plugin-transform-svg-component/src/index.js
*/
template?: TemplateFunc
/** Disable index file generation. */
index?: boolean
/** Output files into a directory. */
outDir?: string
/**
* Specify a template function (API) to change default index.js output
* (when --out-dir is used).
*
* https://github.com/gregberge/svgr/blob/main/packages/cli/src/dirCommand.js#L39
*/
indexTemplate?: (filePaths: string[]) => string
/** When used with --out-dir, it ignores already existing files. */
ignoreExisting?: boolean
/**
* Specify a custom filename case. Possible values: pascal for PascalCase,
* kebab for kebab-case or camel for camelCase.
*/
filenameCase?: 'kebab' | 'camel' | 'pascal'
/**
* By default @svgr/core doesn't include svgo and prettier plugins,
* if you want them, you have to install them and include them in config.
*/
plugins?: string[]
}
type ConvertT = {
(svgCode: string, opts?: SvgrOpts, state?: TemplateData): Promise<string>
sync: (svgCode: string, opts?: SvgrOpts, state?: TemplateData) => string
}
declare const convert: ConvertT
export default convert

Some files were not shown because too many files have changed in this diff Show More