/
svg2jsx.js
108 lines (97 loc) · 2.92 KB
/
svg2jsx.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
const SVGO = require('svgo')
const hash = require('string-hash')
const JSON5 = require('json5')
const postcss = require('postcss')
const postcssJs = require('postcss-js')
const toCamelCase = s =>
s.replace(/([-_:])([a-z])/g, (s, a, b) => b.toUpperCase())
// Custom Plugin: https://github.com/svg/svgo/issues/564#issuecomment-241468596
// 也可以用 DOMParser/cheerio 等遍历 DOM,但用 svgo 插件少一点依赖(更快)
// 并且 React v16+ 不再需要这个处理
const camelCaseProps = {
camelCaseProps: {
type: 'perItem',
description: 'convert attrs to camel case',
params: {},
fn(item) {
if (item.isElem()) {
item.eachAttr(attr => {
if (attr.name.includes('-')) {
const newAttr = Object.assign({}, attr, {
name: toCamelCase(attr.name),
})
item.attrs[newAttr.name] = newAttr
item.removeAttr(attr.name)
}
})
}
},
},
}
// `xlink:href` => `xlinkHref`
const camelCaseNamespacedProps = {
camelCaseProps: {
type: 'perItem',
description: 'convert namespaced attrs to camel case',
params: {},
fn(item) {
if (item.isElem()) {
item.eachAttr(attr => {
if (attr.name.includes(':')) {
const newAttr = Object.assign({}, attr, {
name: toCamelCase(attr.name),
})
item.attrs[newAttr.name] = newAttr
item.removeAttr(attr.name)
}
})
}
},
},
}
// svgo 默认就会启用一批插件,参考:
// https://github.com/svg/svgo/issues/646
// https://github.com/BohemianCoding/svgo-compressor/blob/develop/src/defaultConfig.js
const getDefaultSvgoPlugins = ({idPrefix}) => [
{removeDesc: {removeAny: true}},
{removeXMLNS: true},
{sortAttrs: true},
{cleanupIDs: {prefix: idPrefix}},
camelCaseNamespacedProps,
]
const defaults = {
svgoPlugins: [],
camelCaseProps: false,
idPrefix: '',
}
const styleToObject = styleText => {
const styleObject = postcssJs.objectify(postcss.parse(styleText))
return JSON5.stringify(styleObject)
}
const replaceInlineStyles = jsx =>
jsx.replace(
/style="([^"]+)"/g,
(match, str) => `style={${styleToObject(str)}}`
)
const createSvg2jsx = options => {
options = Object.assign({}, defaults, options)
const plugins = getDefaultSvgoPlugins({idPrefix: options.idPrefix})
.concat(options.svgoPlugins)
.concat(options.camelCaseProps ? camelCaseProps : [])
const svgo = new SVGO({plugins})
return svg =>
// 官方已支持 Promise API,但还未发布
new Promise((resolve, reject) =>
svgo.optimize(
svg,
({error, data}) =>
error ? reject(error) : resolve(replaceInlineStyles(data))
)
)
}
const svg2jsx = (svg, options) => {
const idPrefix = `id-${hash(svg)}-`
return createSvg2jsx(Object.assign({idPrefix}, options))(svg)
}
module.exports = svg2jsx
module.exports.createSvg2jsx = createSvg2jsx