Skip to content
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
15 changes: 15 additions & 0 deletions .changeset/chore-add-optimization-pass-in-svg-convertor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@clickhouse/click-ui': patch
---

Add an SVG normalization step to fix breaking issues before proceeding with conversion and optimization. The normalization is based in "conservative" optimisation steps, to reduce chances of visual changes.

**Before:**
```
SVG File β†’ SVGR (SVGO) β†’ React Component
```

**After:**
```
SVG File β†’ SVGO Normalize β†’ Create temp file β†’ SVGR (no SVGO) β†’ React Component β†’ Delete temp file
```
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ storybook-static
vite.config.ts.timestamp*
.storybook/out
tmp/*
.scripts/js/.temp/

.yarn/*
!.yarn/releases
Expand Down
109 changes: 105 additions & 4 deletions .scripts/js/convert-svg-to-react-component
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
import { optimize } from 'svgo';
import {
filenameToKebabCase,
filenameToComponentName,
Expand Down Expand Up @@ -136,8 +137,65 @@ const regenerateAssetRegistry = (config) => {
);
};

const convertSvgToComponent = (svgPath, componentName, outputPath, defaultSize) => {
const svgrConfig = path.join(__dirname, '../..', '.svgrrc.mjs');
const normalizeSvg = (svgPath, tempPath) => {
console.log('πŸ”§ Normalizing SVG...');

try {
const svgContent = fs.readFileSync(svgPath, 'utf-8');

const result = optimize(svgContent, {
multipass: true,
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false,
convertPathData: false
}
}
}
]
});

fs.writeFileSync(tempPath, result.data);
console.log('βœ… SVG normalized successfully');
return tempPath;
} catch (error) {
console.error('πŸ‘Ή Oops! SVG normalization failed:', error.message);
throw error;
}
};

const convertSvgToComponent = (svgPath, componentName, outputPath, defaultSize, preNormalized = false) => {
let svgrConfig;
let tempConfigPath = null;

if (preNormalized) {
tempConfigPath = path.join(__dirname, '.temp', `svgr-config-${Date.now()}.mjs`);
const configContent = `export default {
typescript: true,
ref: false,
memo: false,
svgo: false,
template: (variables, { tpl }) => {
return tpl\`
import { SVGAssetProps } from '../types';

const \${variables.componentName} = (props: SVGAssetProps) => (
\${variables.jsx}
);

export default \${variables.componentName};
\`;
},
};`;
fs.writeFileSync(tempConfigPath, configContent);
svgrConfig = tempConfigPath;
} else {
svgrConfig = path.join(__dirname, '../..', '.svgrrc.mjs');
}

const cmd = `npx @svgr/cli --config-file "${svgrConfig}" --typescript "${svgPath}"`;

let output;
Expand Down Expand Up @@ -175,6 +233,13 @@ const convertSvgToComponent = (svgPath, componentName, outputPath, defaultSize)
});

fs.writeFileSync(outputPath, output);

if (tempConfigPath && fs.existsSync(tempConfigPath)) {
try {
fs.unlinkSync(tempConfigPath);
} catch (error) {
}
}
};

const cleanUp = (files) => {
Expand Down Expand Up @@ -204,6 +269,7 @@ const cleanUp = (files) => {
let args = process.argv.slice(2);

const isRegenerate = args.includes('--regenerate');
const skipNormalize = args.includes('--skip-normalize');

// WARN: Do not change as this extracts and removes the --type flag from args if present, to allow args in package.json scripts
const typeFlagIndex = args.findIndex(arg => arg.startsWith('--type='));
Expand All @@ -213,6 +279,10 @@ if (typeFlagIndex !== -1) {
args = args.filter((_, i) => i !== typeFlagIndex);
}

if (skipNormalize) {
args = args.filter(arg => arg !== '--skip-normalize');
}

if (isRegenerate) {
let typesToRegenerate;
let titleMessage;
Expand Down Expand Up @@ -257,8 +327,9 @@ if (isRegenerate) {

if (args.length < 1) {
console.error('πŸ‘Ή Oops! The SVG file path is required');
console.error('πŸ’‘ Usage: convert-svg-to-react-component <svg-path> [component-name] [--type=logos|icons|flags]');
console.error('πŸ’‘ Usage: convert-svg-to-react-component <svg-path> [component-name] [--type=logos|icons|flags] [--skip-normalize]');
console.error('πŸ’‘ Or use: convert-svg-to-react-component --regenerate [--type=logos|icons|flags|payments]');
console.error('πŸ’‘ Note: SVGs are automatically normalized before conversion. Use --skip-normalize to disable.');
process.exit(1);
}

Expand Down Expand Up @@ -296,7 +367,37 @@ const darkFile = path.join(systemDir, `${config.registryName.replace('Light', 'D

console.log(`🍩 Baking ${componentName} for ${type}...`);

convertSvgToComponent(svgPath, componentName, outputPath, config.defaultSize);
let svgToConvert = svgPath;
let tempNormalizedPath = null;

if (!skipNormalize) {
const tempDir = path.join(__dirname, '.temp');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
tempNormalizedPath = path.join(tempDir, `normalized-${Date.now()}.svg`);

try {
svgToConvert = normalizeSvg(svgPath, tempNormalizedPath);
} catch (error) {
console.error('πŸ’‘ Try running with --skip-normalize if normalization causes issues');
process.exit(1);
}
} else {
console.log('⏭️ Skipping SVG normalization (--skip-normalize flag set)');
}

const didNormalize = !skipNormalize;
convertSvgToComponent(svgToConvert, componentName, outputPath, config.defaultSize, didNormalize);

if (tempNormalizedPath && fs.existsSync(tempNormalizedPath)) {
try {
fs.unlinkSync(tempNormalizedPath);
} catch (error) {
console.warn('⚠️ Failed to clean up temp SVGR config:', error.message);
}
}

generateAssetTypes(config);
regenerateAssetRegistry(config);

Expand Down
Loading