Skip to content
This repository was archived by the owner on Aug 24, 2019. It is now read-only.

Commit a562744

Browse files
committed
feat(jsx): support style tag
Refers gregberge/svgr#7
1 parent 969aacb commit a562744

File tree

6 files changed

+157
-19
lines changed

6 files changed

+157
-19
lines changed

packages/h2x-plugin-jsx/src/generator.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import stringToObjectStyle from './stringToObjectStyle'
2+
13
const formatAttribute = jsxAttribute => {
24
if (jsxAttribute.spread) return `{...${jsxAttribute.name}}`
35
if (jsxAttribute.litteral)
46
return `${jsxAttribute.name}={${jsxAttribute.value}}`
7+
if (jsxAttribute.name === 'style')
8+
return `${jsxAttribute.name}={${JSON.stringify(
9+
stringToObjectStyle(jsxAttribute.value),
10+
)}}`
511
return `${jsxAttribute.name}="${jsxAttribute.value}"`
612
}
713

packages/h2x-plugin-jsx/src/index.test.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('transformJsx', () => {
5555

5656
expect(transform(code, { plugins: [transformJsx] }).trim())
5757
.toBe(`{/*?xml version="1.0" encoding="UTF-8"?*/}
58-
<svg width="88px" height="88px" viewBox="0 0 88 88" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
58+
<svg width="88px" height="88px" viewBox="0 0 88 88" version={1.1} xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
5959
{/*Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch*/}
6060
<title>
6161
Dismiss
@@ -70,18 +70,25 @@ describe('transformJsx', () => {
7070
</linearGradient>
7171
<filter id="b" width="157.1%" height="180%" x="-28.6%" y="-20%" filterUnits="objectBoundingBox">
7272
<feOffset dy={1} in="SourceAlpha" result="shadowOffsetOuter1" />
73-
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation=".5" />
73+
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation={.5} />
7474
<feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0" />
7575
</filter>
7676
</defs>
7777
<g id="Blocks" stroke="none" strokeWidth={1} fill="none" fillRule="evenodd" strokeLinecap="square">
7878
<g id="Dismiss" stroke="#063855" strokeWidth={2}>
7979
<path d="M51,37 L37,51" id="Shape" />
8080
<path d="M51,51 L37,37" id="Shape" />
81-
<circle cx="43.5" cy="42.5" r="31.5" fill="url(#a)" opacity=".1" />
82-
<circle fill="#f00" cx="43.5" cy="42.5" r="21.5" filter="url(#b)" opacity={1} />
81+
<circle cx={43.5} cy={42.5} r={31.5} fill="url(#a)" opacity={.1} />
82+
<circle fill="#f00" cx={43.5} cy={42.5} r={21.5} filter="url(#b)" opacity={1} />
8383
</g>
8484
</g>
8585
</svg>`)
8686
})
87+
88+
it('should handle convert style attribute to object', () => {
89+
const code = `<div id="foo" style="font-size: 10px; line-height: 1.2;"></div>`
90+
expect(transform(code, { plugins: [transformJsx] }).trim()).toBe(
91+
`<div id="foo" style={{"fontSize":10,"lineHeight":1.2}} />`,
92+
)
93+
})
8794
})
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Inspired by https://github.com/reactjs/react-magic/blob/master/src/htmltojsx.js
2+
import { hyphenToCamelCase, isNumeric, trimEnd } from './util'
3+
4+
/**
5+
* Determines if the CSS value can be converted from a
6+
* 'px' suffixed string to a numeric value.
7+
*
8+
* @param {string} value CSS property value
9+
* @return {boolean}
10+
*/
11+
function isConvertiblePixelValue(value) {
12+
return /^\d+px$/.test(value)
13+
}
14+
15+
/**
16+
* Format style key into JSX style object key.
17+
*
18+
* @param {string} key
19+
* @return {string}
20+
*/
21+
function formatKey(key) {
22+
key = key.toLowerCase()
23+
// Don't capitalize -ms- prefix
24+
if (/^-ms-/.test(key)) key = key.substr(1)
25+
return hyphenToCamelCase(key)
26+
}
27+
28+
/**
29+
* Format style value into JSX style object value.
30+
*
31+
* @param {string} key
32+
* @return {string}
33+
*/
34+
function formatValue(value) {
35+
if (isNumeric(value)) return Number(value)
36+
if (isConvertiblePixelValue(value)) return Number(trimEnd(value, 'px'))
37+
return value
38+
}
39+
40+
/**
41+
* Handle parsing of inline styles.
42+
*
43+
* @param {string} rawStyle
44+
* @returns {object}
45+
*/
46+
function stringToObjectStyle(rawStyle) {
47+
const entries = rawStyle.split(';')
48+
return entries.reduce((styles, style) => {
49+
style = style.trim()
50+
const firstColon = style.indexOf(':')
51+
const value = style.substr(firstColon + 1).trim()
52+
const key = style.substr(0, firstColon)
53+
if (key !== '') {
54+
styles[formatKey(key)] = formatValue(value)
55+
}
56+
return styles
57+
}, {})
58+
}
59+
60+
export default stringToObjectStyle
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import stringToObjectStyle from './stringToObjectStyle'
2+
3+
describe('stringToObjectStyle', () => {
4+
it('should handle single line', () => {
5+
expect(stringToObjectStyle('display: none')).toEqual({
6+
display: 'none',
7+
})
8+
})
9+
10+
it('should handle multi lines', () => {
11+
expect(
12+
stringToObjectStyle(`
13+
display: none;
14+
margin: 0 0 20px;
15+
`),
16+
).toEqual({
17+
display: 'none',
18+
margin: '0 0 20px',
19+
})
20+
})
21+
22+
it('should convert pixel value into number', () => {
23+
expect(stringToObjectStyle('margin: 20px')).toEqual({ margin: 20 })
24+
})
25+
26+
it('should keep numeric values', () => {
27+
expect(stringToObjectStyle('line-height: 1.2')).toEqual({ lineHeight: 1.2 })
28+
})
29+
30+
it('should handle prefixes', () => {
31+
expect(
32+
stringToObjectStyle(`
33+
-webkit-transition: all;
34+
-mz-transition: all;
35+
-ms-transition: all;
36+
`),
37+
).toEqual({
38+
WebkitTransition: 'all',
39+
MzTransition: 'all',
40+
msTransition: 'all',
41+
})
42+
})
43+
})
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Determines if the specified string consists entirely of numeric characters.
3+
*
4+
* @param {*} [value]
5+
* @returns {boolean}
6+
*/
7+
export function isNumeric(value) {
8+
return !isNaN(value - parseFloat(value))
9+
}
10+
11+
/**
12+
* Convert a hyphenated string to camelCase.
13+
*
14+
* @param {string} string
15+
* @returns {string}
16+
*/
17+
export function hyphenToCamelCase(string) {
18+
return string.replace(/-(.)/g, (match, chr) => chr.toUpperCase())
19+
}
20+
21+
/**
22+
* Trim the specified substring off the string. If the string does not end
23+
* with the specified substring, this is a no-op.
24+
*
25+
* @param {string} haystack String to search in
26+
* @param {string} needle String to search for
27+
* @return {string}
28+
*/
29+
export function trimEnd(haystack, needle) {
30+
return haystack.endsWith(needle)
31+
? haystack.slice(0, -needle.length)
32+
: haystack
33+
}

packages/h2x-plugin-jsx/src/visitor.js

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import JSXElement from './JSXElement'
22
import JSXAttribute from './JSXAttribute'
33
import JSXComment from './JSXComment'
44
import JSXText from './JSXText'
5+
import { isNumeric, hyphenToCamelCase } from './util'
56

67
const ATTRIBUTE_MAPPING = {
78
for: 'htmlFor',
@@ -114,19 +115,7 @@ const ELEMENT_TAG_NAME_MAPPING = {
114115
use: 'use',
115116
video: 'video',
116117
view: 'view',
117-
vkern: 'vkern'
118-
};
119-
120-
function isNumeric(input) {
121-
return (
122-
input !== undefined &&
123-
input !== null &&
124-
(typeof input === 'number' || parseInt(input, 10) == input) // eslint-disable-line eqeqeq
125-
)
126-
}
127-
128-
function hyphenToCamelCase(string) {
129-
return string.replace(/-(.)/g, (match, chr) => chr.toUpperCase())
118+
vkern: 'vkern',
130119
}
131120

132121
function getAttributeName(attribute, node) {
@@ -146,8 +135,8 @@ function getAttributeName(attribute, node) {
146135
}
147136

148137
function transformTagName(tagName) {
149-
const lowercaseTagName = tagName.toLowerCase();
150-
return ELEMENT_TAG_NAME_MAPPING[lowercaseTagName] || lowercaseTagName;
138+
const lowercaseTagName = tagName.toLowerCase()
139+
return ELEMENT_TAG_NAME_MAPPING[lowercaseTagName] || lowercaseTagName
151140
}
152141

153142
function getAttributeValue(attribute) {

0 commit comments

Comments
 (0)