Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/parse #9

Merged
merged 4 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .eslintignore

This file was deleted.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@changesets/cli": "^2",
"@commitlint/cli": "^18",
"@commitlint/config-conventional": "^18",
"@taoliujun/eslint-config": "^2.0.1",
"@taoliujun/eslint-config": "^2.1.0",
"husky": "^8",
"lint-staged": "^15",
"typescript": "^5"
Expand All @@ -23,8 +23,8 @@
"release": "npm exec changeset publish",
"release:token": "cp -f .npmrc .npmrc.bak && echo \"\n//registry.npmjs.org/:_authToken=${NPM_TOKEN}\" >> .npmrc && npm exec changeset publish && cp -f .npmrc.bak .npmrc && rm .npmrc.bak",
"build": "pnpm run -r build",
"lint": "pnpm run -r lint",
"lint:ts": "pnpm run -r lint:ts",
"lint": "eslint packages --ext .ts,.js --cache",
"test": "echo 'test'",
"prepare": "husky install"
}
Expand Down
4 changes: 2 additions & 2 deletions .eslintrc.js → packages/cli/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module.exports = {
extends: ['@taoliujun/eslint-config'],
parserOptions: { tsconfigRootDir: __dirname },
overrides: [
{
// page store components are temporarily unused
files: ['./packages/*/src/index.ts'],
files: ['./src/index.ts'],
rules: {
'import/no-unused-modules': ['off'],
'import/no-default-export': ['off'],
Expand Down
1 change: 1 addition & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# svg-to-component
51 changes: 51 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "svg-to-component",
"version": "1.0.0",
"description": "parse svg files into react components, support dynamic colors and preview.",
"keywords": [
"svg",
"parse svg",
"svg to component",
"react svg"
],
"homepage": "https://github.com/taoliujun/svg-to-component",
"repository": {
"type": "git",
"url": "https://github.com/taoliujun/svg-to-component.git"
},
"bugs": {
"url": "https://github.com/taoliujun/svg-to-component/issues"
},
"files": [
"lib",
"/*.md",
"package.json"
],
"main": "./lib/index.js",
"bin": {
"svg-to-component": "./lib/index.js"
},
"scripts": {
"lint": "eslint --cache src",
"lint:ts": "tsc --noEmit",
"build": "rm -rf ./lib && tsc"
},
"engines": {
"node": "^20"
},
"dependencies": {
"chalk": "^4.1.2",
"commander": "^11.1.0",
"fast-glob": "^3.3.2",
"lodash": "^4.17.21",
"progress": "^2.0.3",
"svg-to-component.parse": "workspace:^",
"svg-to-component.preview": "workspace:^"
},
"devDependencies": {
"@tsconfig/node20": "^20.1.2",
"@types/lodash": "^4.14.195",
"@types/node": "^20",
"@types/progress": "^2.0.7"
}
}
11 changes: 11 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Command } from 'commander';
import { commandParse } from './parse';
import { commandPreview } from './preview';

const program = new Command();

program.name('svg-to-component').description('svg file parse and preview.').version('1.0.0');
program.addCommand(commandParse);
program.addCommand(commandPreview);

program.parse();
6 changes: 6 additions & 0 deletions packages/cli/src/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import chalk from 'chalk';

export const log = console.log;
export const outputMain = chalk.white;
export const outputSecond = chalk.gray;
export const outputSuccess = chalk.green;
110 changes: 110 additions & 0 deletions packages/cli/src/parse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import path, { dirname } from 'path';
import fs from 'fs';
import { glob } from 'fast-glob';
import { Command } from 'commander';
import Progress from 'progress';
import { camelCase, upperFirst } from 'lodash';
import { generateReact, generateComponentUtils } from 'svg-to-component.parse';
import { log, outputMain, outputSecond, outputSuccess } from './log';

const cwdPath = path.resolve();

type Template = 'react' | 'vue';

interface GenerateParams {
sourcePath: string;
outputPath: string;
template?: Template;
debug?: boolean;
isPreview?: boolean;
}

const generateUtils = (opts: GenerateParams) => {
const { outputPath } = opts;

log(outputSecond(`> Generate util files:`));

const utilFiles = generateComponentUtils();
utilFiles.forEach((v) => {
const { targetFilePath, content } = v;
const file = path.resolve(outputPath, targetFilePath);
fs.mkdirSync(dirname(file), { recursive: true });
fs.writeFileSync(file, content, {
encoding: 'utf-8',
});
log(outputMain(file));
});
};

const generateComponentFiles = async (opts: GenerateParams) => {
const { sourcePath, outputPath, template = 'react', debug = false, isPreview = false } = opts;

log(outputSecond('> Origin SVG files path:'), outputMain(sourcePath));

const files = await glob(path.resolve(sourcePath, './**/*.svg'));
const filesLen = files.length;

log(outputSecond(`> Total files:`, outputMain.bold(filesLen)));

const bar = new Progress(':bar :percent ', { total: filesLen });

files.forEach((inputFile, index) => {
const fileInfo = path.parse(inputFile);
const relativePath = fileInfo.dir.replace(sourcePath, '');
const content = fs.readFileSync(inputFile, 'utf-8');

let outputFileName = '';
let outputFilePath = '';
let outputFile = '';
let outputContent = '';

if (template === 'react') {
outputFileName = upperFirst(camelCase(fileInfo.name));
outputFilePath = path.resolve(outputPath, `./${relativePath}`, outputFileName);
outputFile = path.resolve(outputFilePath, `index.tsx`);
outputContent = generateReact(outputFileName, content, {
isPreview,
});
}

fs.mkdirSync(outputFilePath, { recursive: true });
fs.writeFileSync(outputFile, outputContent, {
encoding: 'utf-8',
});

if (debug) {
log(`[${index + 1}/${filesLen}]`, outputMain.bold(fileInfo.base));
log(outputSecond('source file:'), outputMain(inputFile));
log(outputSecond('output file:'), outputMain(outputFile));
log(`\n`);
log(outputSecond(outputContent));
log(`\n`);
} else {
bar.tick();
}
});

bar.terminate();

generateUtils(opts);

log(`\n`);
log(outputSuccess(`> All done, the output ${template} components path: `), outputMain(outputPath));
};

const program = new Command('parse')
.description('CLI to parse SVG files to component files')
.argument('path <string>', 'the directory of the SVG files')
.option('-t, --template <string>', 'the component type, supports react', 'react')
.option('-o, --output <string>', 'the output directory of the component files', './svg-component-output')
.option('--debug', 'output the debug log')
.action(async (args, opts) => {
await generateComponentFiles({
sourcePath: path.resolve(cwdPath, args),
outputPath: path.resolve(cwdPath, opts.output),
template: opts.template,
debug: opts.debug,
});
});

export { program as commandParse, generateComponentFiles };
40 changes: 40 additions & 0 deletions packages/cli/src/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import path from 'path';
import { Command } from 'commander';
import { packagePath as previewPackagePath, componentsPath } from 'svg-to-component.preview';
import { log, outputMain } from './log';
import { generateComponentFiles } from './parse';
import { spawn } from 'child_process';

const cwdPath = path.resolve();

const program = new Command('preview')
.description('CLI to preview the svg files, and use the coloring tool')
.argument('path <string>', 'the directory of the SVG files')
.option('--debug', 'output the debug log')
.action(async (args, opts) => {
await generateComponentFiles({
sourcePath: path.resolve(cwdPath, args),
outputPath: componentsPath,
template: 'react',
debug: opts.debug,
isPreview: true,
});

log(outputMain(`> run ${previewPackagePath} server...`));

const wp = spawn(`pnpm`, ['run', 'dev'], {
cwd: previewPackagePath,
stdio: 'inherit',
});
wp.stdout?.on('data', (data) => {
process.stdout.write(data);
});
wp.stderr?.on('data', (data) => {
process.stderr.write(data);
});
wp.on('exit', (code) => {
log(code);
});
});

export const commandPreview = program;
20 changes: 20 additions & 0 deletions packages/cli/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": {
"outDir": "./lib",
"declaration": true,
// some lints
"allowUnusedLabels": false,
"allowUnreachableCode": false,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noUnusedLocals": true
},
"exclude": [
"node_modules",
"lib"
],
"include": [
"./src/**/*.ts"
]
}
13 changes: 13 additions & 0 deletions packages/parse/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
extends: ['@taoliujun/eslint-config'],
parserOptions: { tsconfigRootDir: __dirname },
overrides: [
{
files: ['./src/index.ts'],
rules: {
'import/no-unused-modules': ['off'],
'import/no-default-export': ['off'],
},
},
],
};
30 changes: 30 additions & 0 deletions packages/parse/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "svg-to-component.parse",
"version": "1.0.0",
"main": "./lib/index.js",
"files": [
"lib",
"/*.md",
"package.json"
],
"scripts": {
"lint": "eslint --cache src",
"lint:ts": "tsc --noEmit",
"build": "rm -rf ./lib && tsc && npm run build:tpl",
"build:tpl": "cp -rf ./src/template ./lib"
},
"engines": {
"node": "^20"
},
"dependencies": {
"fast-xml-parser": "^4.2.2",
"lodash": "^4.17.21",
"prettier": "^2.8.8"
},
"devDependencies": {
"@tsconfig/node20": "^20.1.2",
"@types/lodash": "^4.14.195",
"@types/node": "^20.2.5",
"@types/prettier": "^2.7.3"
}
}
39 changes: 39 additions & 0 deletions packages/parse/src/convert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { X2jOptions, XMLBuilder, XMLParser, XmlBuilderOptions } from 'fast-xml-parser';

type SVGTagNames = keyof SVGElementTagNameMap;
type SVGAttributes = Record<string, string>;
type SVGObject = Record<SVGTagNames, SVGObject[]> & {
':@'?: SVGAttributes;
};
type FormatSVGAttributes = (attributes: SVGAttributes) => SVGAttributes;

const ROOT_KEY = ':@';

// Code string to xml object
const svgToObj = (content: string, opt?: X2jOptions) => {
const parser = new XMLParser({
allowBooleanAttributes: true,
attributeNamePrefix: '',
ignoreAttributes: false,
preserveOrder: true,
...opt,
});

return parser.parse(content) as SVGObject[];
};

// XML object to code string
const objToSvg = (obj?: SVGObject[], opt?: XmlBuilderOptions): string => {
const builder = new XMLBuilder({
attributeNamePrefix: '',
ignoreAttributes: false,
suppressEmptyNode: true,
preserveOrder: true,
...opt,
});

return builder.build(obj);
};

export { ROOT_KEY, svgToObj, objToSvg };
export type { SVGObject, SVGAttributes, FormatSVGAttributes };
1 change: 1 addition & 0 deletions packages/parse/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './react';
Loading
Loading