-
-
Notifications
You must be signed in to change notification settings - Fork 79
/
generate.ts
159 lines (144 loc) · 6 KB
/
generate.ts
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import fs from 'fs-extra';
import path from 'path';
import { optimize } from 'svgo';
import { filterSvgFiles, toPascalCase } from './utils';
import { SvgToFontOptions } from './';
/**
* Generate Icon SVG Path Source
* <font-name>.json
*/
export async function generateIconsSource(options: SvgToFontOptions = {}){
const ICONS_PATH = filterSvgFiles(options.src)
const data = await buildPathsObject(ICONS_PATH, options);
const outPath = path.join(options.dist, `${options.fontName}.json`);
await fs.outputFile(outPath, `{${data}\n}`);
return outPath;
}
/**
* Loads SVG file for each icon, extracts path strings `d="path-string"`,
* and constructs map of icon name to array of path strings.
* @param {array} files
*/
async function buildPathsObject(files: string[], options: SvgToFontOptions = {}) {
const svgoOptions = options.svgoOptions || {};
return Promise.all(
files.map(async filepath => {
const name = path.basename(filepath, '.svg');
const svg = fs.readFileSync(filepath, 'utf-8');
const pathStrings = optimize(svg, {
path: filepath,
...options,
plugins: [
'convertTransform',
...(svgoOptions.plugins || [])
// 'convertShapeToPath'
],
});
const str: string[] = (pathStrings.data.match(/ d="[^"]+"/g) || []).map(s => s.slice(3));
return `\n"${name}": [${str.join(',\n')}]`;
}),
);
}
const reactSource = (name: string, size: string, fontName: string, source: string) => `import React from 'react';
export const ${name} = props => (
<svg viewBox="0 0 20 20" ${size ? `width="${size}" height="${size}"` : ''} {...props} className={\`${fontName} \${props.className ? props.className : ''}\`}>${source}</svg>
);
`;
const reactTypeSource = (name: string) => `import React from 'react';
export declare const ${name}: (props: React.SVGProps<SVGSVGElement>) => JSX.Element;
`;
/**
* Generate React Icon
* <font-name>.json
*/
export async function generateReactIcons(options: SvgToFontOptions = {}) {
const ICONS_PATH = filterSvgFiles(options.src);
const data = await outputReactFile(ICONS_PATH, options);
const outPath = path.join(options.dist, 'react', 'index.js');
fs.outputFileSync(outPath, data.join('\n'));
fs.outputFileSync(outPath.replace(/\.js$/, '.d.ts'), data.join('\n'));
return outPath;
}
async function outputReactFile(files: string[], options: SvgToFontOptions = {}) {
const svgoOptions = options.svgoOptions || {};
const fontSizeOpt = typeof options.css !== 'boolean' && options.css.fontSize
const fontSize = typeof fontSizeOpt === 'boolean' ? (fontSizeOpt === true ? '16px' : '') : fontSizeOpt;
const fontName = options.classNamePrefix || options.fontName
return Promise.all(
files.map(async filepath => {
let name = toPascalCase(path.basename(filepath, '.svg'));
if (/^[rR]eact$/.test(name)) {
name = name + toPascalCase(fontName);
}
const svg = fs.readFileSync(filepath, 'utf-8');
const pathData = optimize(svg, {
path: filepath,
...svgoOptions,
plugins: [
'removeXMLNS',
'removeEmptyAttrs',
'convertTransform',
// 'convertShapeToPath',
// 'removeViewBox'
...(svgoOptions.plugins || [])
]
});
const str: string[] = (pathData.data.match(/ d="[^"]+"/g) || []).map(s => s.slice(3));
const outDistPath = path.join(options.dist, 'react', `${name}.js`);
const pathStrings = str.map((d, i) => `<path d=${d} fillRule="evenodd" />`);
const comName = isNaN(Number(name.charAt(0))) ? name : toPascalCase(fontName) + name;
fs.outputFileSync(outDistPath, reactSource(comName, fontSize, fontName, pathStrings.join(',\n')));
fs.outputFileSync(outDistPath.replace(/\.js$/, '.d.ts'), reactTypeSource(comName));
return `export * from './${name}';`;
}),
);
}
const reactNativeSource = (fontName: string, defaultSize: number, iconMap: Map<string,string>) => `import { Text } from 'react-native';
const icons = ${JSON.stringify(Object.fromEntries(iconMap))};
export const ${fontName} = props => {
const {name, ...rest} = props;
return (<Text style={{fontFamily: '${fontName}', fontSize: ${defaultSize}, color: '#000000', ...rest}}>
{icons[name]}
</Text>);
};
`;
const reactNativeTypeSource = (name: string, iconMap: Map<string, string>) => `import { TextStyle } from 'react-native';
export type ${name}Names = ${[...iconMap.keys()].reduce((acc, key, index) => {
if (index === 0) {
acc = `'${key}'`
} else {
acc += `| '${key}'`
}
return acc;
}, `${'string'}`)}
export interface ${name}Props extends Omit<TextStyle, 'fontFamily' | 'fontStyle' | 'fontWeight'> {
name: ${name}Names
}
export declare const ${name}: (props: ${name}Props) => JSX.Element;
`;
/**
* Generate ReactNative Icon
* <font-name>.json
*/
export function generateReactNativeIcons(options: SvgToFontOptions = {}, unicodeObject: Record<string, string>) {
const ICONS_PATH = filterSvgFiles(options.src);
outputReactNativeFile(ICONS_PATH, options, unicodeObject);
}
function outputReactNativeFile(files: string[], options: SvgToFontOptions = {}, unicodeObject: Record<string, string>) {
const fontSizeOpt = typeof options.css !== 'boolean' && options.css.fontSize;
const fontSize = typeof fontSizeOpt === 'boolean' ? 16 : parseInt(fontSizeOpt);
const fontName = options.classNamePrefix || options.fontName
const iconMap = new Map<string, string>();
files.map(filepath => {
const baseFileName = path.basename(filepath, '.svg');
let name = toPascalCase(baseFileName);
if (/^[rR]eactNative$/.test(name)) {
name = name + toPascalCase(fontName);
}
iconMap.set(name, unicodeObject[baseFileName])
});
const outDistPath = path.join(options.dist, 'reactNative', `${fontName}.js`);
const comName = isNaN(Number(fontName.charAt(0))) ? fontName : toPascalCase(fontName) + name;
fs.outputFileSync(outDistPath, reactNativeSource(comName, fontSize, iconMap));
fs.outputFileSync(outDistPath.replace(/\.js$/, '.d.ts'), reactNativeTypeSource(comName, iconMap));
}