Skip to content

Commit

Permalink
feat: Add configure generate_mode
Browse files Browse the repository at this point in the history
  • Loading branch information
fwh1990 committed Aug 27, 2019
1 parent 78d6cf2 commit 302972d
Show file tree
Hide file tree
Showing 12 changed files with 125 additions and 57 deletions.
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ cd ios && pod install
此时项目根目录会生成一个`iconfont.json`的文件,内容如下:
```json
{
"symbol_url": "复制官网提供的JS链接",
"safe_dir": "./src/components/iconfont",
"trim_icon_prefix": "icon-",
"default_font_size": 18,
"use_typescript": false
"symbol_url": "请参考README.md,复制官网提供的JS链接",
"use_typescript": false,
"generate_mode": "all-in-one",
"safe_dir": "./src/components/iconfont",
"trim_icon_prefix": "icon-",
"default_font_size": 18
}
```
### 配置参数说明:
Expand All @@ -60,6 +61,16 @@ cd ios && pod install
<br />
![](https://github.com/fwh1990/react-native-iconfont-cli/blob/master/symbol-url.png?raw=true)

### use_typescript
如果您的项目使用Typescript编写,请设置为true。这个选项将决定生成的图标组件是`.tsx`还是`.jsx`后缀。

### generate_mode
生成组件的方式:
##### all-in-one
只生成一个`<Icon name="xxx" />` 组件,里面包含了所有图标信息。所以这个组件会比较大。
##### depends-on
每个图标都会生成一个组件`<IconXXX />`。这种模式也会生成一个`Icon`组件,但和all-in-one不同的是,这个Icon组件总是import其他的图标组件,它相当于一个门面。

### safe_dir
根据iconfont图标生成的组件存放的位置。每次生成组件之前,该文件夹都会被清空。

Expand All @@ -71,9 +82,6 @@ cd ios && pod install
### default_font_size
我们将为每个生成的图标组件加入默认的字体大小,当然,你也可以通过传入props的方式改变这个size值。

### use_typescript
是否使用typescript格式的文件。这个选项将决定生成的图标组件是`.tsx`还是`.jsx`后缀。


# Step 4
开始生成React组件
Expand All @@ -85,7 +93,7 @@ cd ios && pod install
# Step 5
使用这些图标。现在我们提供了两种引入方式供您选择:

1、使用汇总 `<Icon />`,它包含了所有的图标信息
1、使用汇总`Icon`组件
```typescript jsx
import Icon from '../src/iconfont/Icon';

Expand All @@ -99,7 +107,7 @@ export const App = () => {
};
```

2、使用单个图标,它更加精准,您上线时打出来的包也会更小
2、当您配置的`generate_mode=depends-on`时,您可以使用单个图标。这样可以避免没用到的图标也打包进App

```typescript jsx
import IconUser from '../src/iconfont/IconUser';
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-iconfont-cli",
"version": "0.0.3",
"version": "1.0.0",
"main": "index.js",
"repository": "[email protected]:fwh1990/react-native-iconfont-cli.git",
"author": "范文华 <[email protected]>",
Expand Down
91 changes: 53 additions & 38 deletions src/libs/generateComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,30 @@ import { getConfig } from './getConfig';
import { getTemplate } from './getTemplate';
import {
replaceCases,
replaceComponentName, replaceNames, replaceNamesArray,
replaceComponentName, replaceImports, replaceNames, replaceNamesArray,
replaceSingleIconContent,
replaceSize,
replaceSvgComponents,
replaceSvgComponents, replaceToDependsComments, replaceToOneComments,
} from './replace';
import { whitespace } from './whitespace';
import { GENERATE_MODE } from './generateMode';

const DOM_MAP = {
path: 'Path',
};

export const generateComponent = (data: XmlData) => {
const config = getConfig();
const svgComponents: Set<string> = new Set(['Svg']);
const svgComponents: Set<string> = new Set();
const names: string[] = [];
let cases: string = '';
const imports: string[] = [];
const saveDir = path.resolve(config.safe_dir);
const extension = config.use_typescript ? '.tsx' : '.jsx';
let cases: string = '';

if (config.generate_mode === GENERATE_MODE.allInOne) {
svgComponents.add('Svg');
}

mkdirp.sync(saveDir);
glob.sync(path.join(saveDir, '*')).forEach((file) => fs.unlinkSync(file));
Expand All @@ -38,69 +46,76 @@ export const generateComponent = (data: XmlData) => {
: iconId;
const componentName = upperFirst(camelCase(iconId));


names.push(iconIdAfterTrim);

for (const domName of Object.keys(item)) {
switch (domName) {
case 'path':
svgComponents.add('Path');
currentSvgComponents.add('Path');

if (config.generate_mode === GENERATE_MODE.allInOne) {
svgComponents.add('Path');
}
break;
default:
// no default
}
}

cases += `${' '.repeat(4)}case '${iconIdAfterTrim}':\n`;
cases += `${' '.repeat(6)}return (${generateCase(item, 8)}${' '.repeat(6)});\n`;
cases += `${whitespace(4)}case '${iconIdAfterTrim}':\n`;

if (config.generate_mode === GENERATE_MODE.allInOne) {
cases += `${whitespace(6)}return (${generateCase(item, 8)}${whitespace(6)});\n`;
return;
}

if (config.use_typescript) {
singleFile = getTemplate('SingleIcon.tsx');
singleFile = replaceSize(singleFile, config.default_font_size);
singleFile = replaceSvgComponents(singleFile, currentSvgComponents);
singleFile = replaceComponentName(singleFile, componentName);
singleFile = replaceSingleIconContent(singleFile, generateCase(item, 4));
imports.push(componentName);
cases += `${whitespace(6)}return <${componentName} size={size} />;\n`;

fs.writeFileSync(path.join(saveDir, componentName + '.tsx'), singleFile);
} else {
singleFile = getTemplate('SingleIcon.jsx');
singleFile = replaceSize(singleFile, config.default_font_size);
singleFile = replaceSvgComponents(singleFile, currentSvgComponents);
singleFile = replaceComponentName(singleFile, componentName);
singleFile = replaceSingleIconContent(singleFile, generateCase(item, 4));
singleFile = getTemplate('SingleIcon' + extension);
singleFile = replaceSize(singleFile, config.default_font_size);
singleFile = replaceSvgComponents(singleFile, currentSvgComponents);
singleFile = replaceComponentName(singleFile, componentName);
singleFile = replaceSingleIconContent(singleFile, generateCase(item, 4));

fs.writeFileSync(path.join(saveDir, componentName + '.jsx'), singleFile);
if (config.generate_mode === GENERATE_MODE.allInOne) {
singleFile = replaceToDependsComments(singleFile);
} else {
singleFile = replaceToOneComments(singleFile);
}

fs.writeFileSync(path.join(saveDir, componentName + extension), singleFile);

console.log(`${colors.green('√')} Generated icon "${colors.yellow(iconId)}"`);
});

let iconFile: string;
let iconFile = getTemplate('Icon' + extension);

iconFile = replaceSize(iconFile, config.default_font_size);
iconFile = replaceCases(iconFile, cases);
iconFile = replaceSvgComponents(iconFile, svgComponents);
iconFile = replaceImports(iconFile, imports);

if (config.use_typescript) {
iconFile = getTemplate('Icon.tsx');
iconFile = replaceSize(iconFile, config.default_font_size);
iconFile = replaceCases(iconFile, cases);
iconFile = replaceSvgComponents(iconFile, svgComponents);
iconFile = replaceNames(iconFile, names);

fs.writeFileSync(path.join(saveDir, 'Icon.tsx'), iconFile);
} else {
iconFile = getTemplate('Icon.jsx');
iconFile = replaceSize(iconFile, config.default_font_size);
iconFile = replaceCases(iconFile, cases);
iconFile = replaceSvgComponents(iconFile, svgComponents);
iconFile = replaceNames(iconFile, names);
iconFile = replaceNamesArray(iconFile, names);
}

fs.writeFileSync(path.join(saveDir, 'Icon.jsx'), iconFile);
if (config.generate_mode === GENERATE_MODE.allInOne) {
iconFile = replaceToDependsComments(iconFile);
} else {
iconFile = replaceToOneComments(iconFile);
}

fs.writeFileSync(path.join(saveDir, 'Icon' + extension), iconFile);

console.log(`\n${colors.green('√')} You will find all icons in dir: ${colors.green(config.safe_dir)}\n`);
};

const generateCase = (data: XmlData['svg']['symbol'][number], baseIdent: number) => {
let template = `\n${' '.repeat(baseIdent)}<Svg viewBox="${data.$.viewBox}" width={size} height={size}>\n`;
let template = `\n${whitespace(baseIdent)}<Svg viewBox="${data.$.viewBox}" width={size} height={size}>\n`;

for (const domName of Object.keys(data)) {
let realDomName = DOM_MAP[domName];
Expand All @@ -110,15 +125,15 @@ const generateCase = (data: XmlData['svg']['symbol'][number], baseIdent: number)
}

if (data[domName].$) {
template += `${' '.repeat(baseIdent + 2)}<${realDomName} ${addAttribute(data[domName])} />\n`;
template += `${whitespace(baseIdent + 2)}<${realDomName} ${addAttribute(data[domName])} />\n`;
} else if (Array.isArray(data[domName])) {
data[domName].forEach((sub) => {
template += `${' '.repeat(baseIdent + 2)}<${realDomName} ${addAttribute(sub)} />\n`;
template += `${whitespace(baseIdent + 2)}<${realDomName} ${addAttribute(sub)} />\n`;
});
}
}

template += `${' '.repeat(baseIdent)}</Svg>\n`;
template += `${whitespace(baseIdent)}</Svg>\n`;

return template;
};
Expand Down
4 changes: 4 additions & 0 deletions src/libs/generateMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum GENERATE_MODE {
allInOne = 'all-in-one',
dependsOn = 'depends-on',
}
8 changes: 7 additions & 1 deletion src/libs/getConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'path';
import fs from 'fs';
import colors from 'colors';
import defaultConfig from './iconfont.json';
import { GENERATE_MODE } from './generateMode';

let cacheConfig: typeof defaultConfig;

Expand All @@ -20,7 +21,7 @@ export const getConfig = () => {
const config = require(targetFile) as typeof defaultConfig;

if (!config.symbol_url || !/^(https?:)?\/\//.test(config.symbol_url)) {
console.warn(colors.red('You don\'t provide valid symbol url from iconfont.cn'));
console.warn(colors.red('You don\'t provide valid symbol_url from iconfont.cn'));
process.exit(1);
}

Expand All @@ -31,6 +32,11 @@ export const getConfig = () => {
config.safe_dir = config.safe_dir || defaultConfig.safe_dir;
config.default_font_size = config.default_font_size || defaultConfig.default_font_size;

if (!Object.values(GENERATE_MODE).includes(config.generate_mode)) {
console.warn(colors.red(`Property generate_mode should be only one of ${JSON.stringify(Object.values(GENERATE_MODE))}`));
process.exit(1);
}

cacheConfig = config;

return config;
Expand Down
5 changes: 3 additions & 2 deletions src/libs/iconfont.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"symbol_url": "请参考README.md,复制官网提供的JS链接",
"use_typescript": false,
"generate_mode": "all-in-one",
"safe_dir": "./src/components/iconfont",
"trim_icon_prefix": "icon-",
"default_font_size": 18,
"use_typescript": false
"default_font_size": 18
}
27 changes: 26 additions & 1 deletion src/libs/replace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ export const replaceCases = (content: string, cases: string) => {
};

export const replaceSvgComponents = (content: string, components: Set<string>) => {
return content.replace(/#svgComponents#/g, [...components].join(', '));
const used = Array.from(components);

return content.replace(
/#svgComponents#/g,
used.length
? `import { ${used.join(', ')} } from 'react-native-svg';`
: ''
);
};

export const replaceNames = (content: string, names: string[]) => {
Expand All @@ -30,3 +37,21 @@ export const replaceComponentName = (content: string, name: string) => {
export const replaceSingleIconContent = (content: string, render: string) => {
return content.replace(/#iconContent#/g, render);
};

export const replaceImports = (content: string, imports: string[]) => {
return content.replace(/#imports#/g, imports.map((item) => `import ${item} from './${item}';`).join('\n'));
};

export const replaceToOneComments = (content: string) => {
return content.replace(/#comments#/g,
'// If you don\'t like lots of icon files in your project,\n' +
'// try to set generate_mode to `all-in-one` in root file `iconfont.json`.\n' +
'// And then regenerate icons by using cli command.');
};

export const replaceToDependsComments = (content: string) => {
return content.replace(/#comments#/g,
'// If you don\'t want to make all icons in one file,\n' +
'// try to set generate_mode to `depends-on` in root file `iconfont.json`.\n' +
'// And then regenerate icons by using cli command.');
};
3 changes: 3 additions & 0 deletions src/libs/whitespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const whitespace = (repeat: number) => {
return ' '.repeat(repeat);
};
4 changes: 3 additions & 1 deletion src/templates/Icon.jsx.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

import React from 'react';
import { number, oneOf } from 'prop-types';
import { #svgComponents# } from 'react-native-svg';
#svgComponents#
#imports#

#comments#
const Icon = ({ size, name }) => {
switch (name) {
#cases#
Expand Down
4 changes: 3 additions & 1 deletion src/templates/Icon.tsx.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
/* eslint-disable */

import React, { FunctionComponent } from 'react';
import { #svgComponents# } from 'react-native-svg';
#svgComponents#
#imports#

interface Props {
size?: number;
name: '#names#';
}

#comments#
const Icon: FunctionComponent<Props> = ({ size, name }) => {
switch (name) {
#cases#
Expand Down
3 changes: 2 additions & 1 deletion src/templates/SingleIcon.jsx.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

import React from 'react';
import { number } from 'prop-types';
import { #svgComponents# } from 'react-native-svg';
#svgComponents#

#comments#
const #componentName# = ({ size }) => {
return (#iconContent# );
};
Expand Down
3 changes: 2 additions & 1 deletion src/templates/SingleIcon.tsx.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
/* eslint-disable */

import React, { FunctionComponent } from 'react';
import { #svgComponents# } from 'react-native-svg';
#svgComponents#

interface Props {
size?: number;
}

#comments#
const #componentName#: FunctionComponent<Props> = ({ size }) => {
return (#iconContent# );
};
Expand Down

0 comments on commit 302972d

Please sign in to comment.