Skip to content

Commit

Permalink
BREAKING CHANGE: rm svg sprite
Browse files Browse the repository at this point in the history
  • Loading branch information
SevereCloud committed Nov 29, 2024
1 parent 4768849 commit 4f29572
Show file tree
Hide file tree
Showing 34 changed files with 481 additions and 2,441 deletions.
9 changes: 5 additions & 4 deletions packages/icons-scripts/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ src/
"typings": "dist/typings/index.d.ts",
"sideEffects": ["*.css"],
"scripts": {
"build-icons": "node scripts/build-icons.js"
"build-icons": "node scripts/build-icons.mjs"
},
"dependencies": {
"@vkontakte/icons-sprite": "^1.0.1"
"@vkontakte/icons-sprite": "^1.0.1",
"@swc/helpers": "^0.5.15"
},
"peerDependencies": {
"react": "^18.0.0"
Expand All @@ -49,10 +50,10 @@ src/
}
```

**`scripts/build-icons.js`**:
**`scripts/build-icons.mjs`**:

```js
const { generateIcons } = require('@vkontakte/icons-scripts');
import { generateIcons } from '@vkontakte/icons-scripts';

generateIcons({
srcDirectory: './src',
Expand Down
9 changes: 2 additions & 7 deletions packages/icons-scripts/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,2 @@
const { generateIcons } = require('./scripts/icons');
const { createIconsMap } = require('./scripts/icons-map');

module.exports = {
generateIcons,
createIconsMap,
};
export { generateIcons } from './scripts/icons.js';
export { createIconsMap } from './scripts/icons-map.js';
7 changes: 5 additions & 2 deletions packages/icons-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@
"email": "[email protected]"
},
"dependencies": {
"@mapbox/hast-util-to-jsx": "^2.1.0",
"@swc/cli": "^0.5.1",
"@swc/core": "^1.9.3",
"glob": "^11.0.0",
"svg-baker": "^1.7.0",
"hast-util-from-html": "^2.0.3",
"svgo": "^3.3.2"
},
"devDependencies": {
"typescript": "^5.7.2"
},
"engines": {
"node": ">12.0.0"
"node": ">=20.11.0"
},
"type": "module",
"exports": "./index.js",
"publishConfig": {
"provenance": true
}
Expand Down
2 changes: 2 additions & 0 deletions packages/icons-scripts/scripts/configs/.swcrc
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{
"$schema": "https://swc.rs/schema.json",
"jsc": {
"externalHelpers": true,
"parser": {
"syntax": "typescript",
"tsx": true
},
"target": "es2017",
"experimental": {
"emitIsolatedDts": true
}
Expand Down
94 changes: 67 additions & 27 deletions packages/icons-scripts/scripts/icons-map.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const glob = require('glob');
const path = require('path');
const fs = require('fs');
const Compiler = require('svg-baker');
const { dashToCamel, sortArrayAlphabetically, longestCommonPrefix } = require('./utils');
import * as glob from 'glob';
import * as path from 'node:path';
import * as fs from 'node:fs';
import { dashToCamel, sortArrayAlphabetically, longestCommonPrefix } from './utils.js';
import { fromHtml } from 'hast-util-from-html';
import toJsx from '@mapbox/hast-util-to-jsx';

/**
* @typedef {Object} Icon
Expand Down Expand Up @@ -175,7 +176,7 @@ function sortIconsByLongestCommonPrefix(icons) {
* @param {(content: string) => string} [optimizeFn]
* @return {Icon[]}
*/
async function createIconsMap(
export async function createIconsMap(
src,
extraCategories = [],
prefix = '',
Expand All @@ -187,53 +188,92 @@ async function createIconsMap(
...extraCategories.map((category) => dirMap(src, category, prefix, deprecatedIcons)).flat(),
];

const compiler = new Compiler();

const promises = icons.map((icon) => prepareIconMapEntity(compiler, icon, optimizeFn));
const promises = icons.map((icon) => prepareIconMapEntity(icon, optimizeFn));

return await Promise.all(promises);
}

const urlRegex = /url\(#(.*?)\)/g;

/**
* Добавляет префикс для id внутри svg элемента
*
* @param {import('hast').RootContent} el
* @param {string} prefix
*/
function svgIdPrefix(el, prefix) {
if (!['element', 'root'].some((type) => type === el.type)) {
return;
}

for (const key in el.properties) {
if (!Object.prototype.hasOwnProperty.call(el.properties, key)) {
continue;
}

/**
* @type {string}
*/
const value = el.properties[key];

if (key === 'id') {
el.properties[key] = `${prefix}__${value}`;
continue;
}

if (key === 'xLinkHref') {
el.properties[key] = `#${prefix}__${value.replace(/^#/, '')}`;
continue;
}

if (urlRegex.test(value)) {
el.properties[key] = value.replace(urlRegex, (match, id) => `url(#${prefix}__${id})`);
}
}

el.children.forEach((el) => svgIdPrefix(el, prefix));
}

/**
* @param {Compiler} compiler
* @param {Icon} icon
* @param {(content: string) => string} [optimizeFn]
*/
async function prepareIconMapEntity(compiler, icon, optimizeFn) {
async function prepareIconMapEntity(icon, optimizeFn) {
const subcomponentsPromises = icon.subcomponents?.map((icon) =>
prepareIconMapEntity(compiler, icon, optimizeFn),
prepareIconMapEntity(icon, optimizeFn),
);
const subcomponents = subcomponentsPromises
? await Promise.all(subcomponentsPromises)
: undefined;

const content = optimizeFn(icon.content);

const symbol = await compiler.addSymbol({ content, id: icon.filename, path: '' });
const tree = fromHtml(content, { fragment: true, space: 'svg' });
svgIdPrefix(tree, icon.filename);

const viewBox = symbol.viewBox;
// Список поддерживаемых аттрибутов, которые дублируются с symbol-элемента на svg-элемент, который ссылается на symbol
const svg = tree.children[0];
const svgContent = svg.children.reduce((jsxContent, tree) => jsxContent + toJsx(tree), '');

const viewBox = svg.properties.viewBox;
// Список поддерживаемых аттрибутов
const attrs = Object.fromEntries(
Object.entries({
preserveAspectRatio: symbol.tree[0]?.attrs.preserveAspectRatio,
fill: svg.properties.fill,
preserveAspectRatio: svg.properties.preserveAspectRatio,
}).filter(([, value]) => value !== undefined),
);
const width = viewBox.split(' ')[2];
const height = viewBox.split(' ')[3];
const width = svg.properties.width;
const height = svg.properties.height;

return {
...icon,
width,
height,
content,
viewBox,
width: svg.properties.width,
height: svg.properties.height,
viewBox: svg.properties.viewBox,
attrs: Object.keys(attrs).length ? attrs : undefined,
subcomponents,
symbolId: symbol.id,
symbol: symbol.render(),
symbolId: icon.filename,
symbol: svgContent,
};
}

module.exports = {
createIconsMap,
};
39 changes: 18 additions & 21 deletions packages/icons-scripts/scripts/icons.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const { performance } = require('perf_hooks');
const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);
const { debugInfo, debugError, sortArrayAlphabetically } = require('./utils');
const { createIconsMap } = require('./icons-map');
const { prepareOptions } = require('./options');
const { optimize } = require('./optimize');
const { createReactIcon } = require('./output');
const { generateRasterIcons } = require('./raster/icons');
import * as fs from 'node:fs';
import * as path from 'node:path';
import { performance } from 'node:perf_hooks';
import * as util from 'node:util';
import * as childProcess from 'node:child_process';
import * as glob from 'glob';
import { debugInfo, debugError, sortArrayAlphabetically } from './utils.js';
import { createIconsMap } from './icons-map.js';
import { prepareOptions } from './options.js';
import { optimize } from './optimize.js';
import { createReactIcon } from './output/index.js';
import { generateRasterIcons } from './raster/icons.js';

const exec = util.promisify(childProcess.exec);

/**
* @typedef {import('./options').GenerateOptions} GenerateOptions
* @param {GenerateOptions} options
*/
function generateIcons(options) {
export function generateIcons(options) {
const {
srcDirectory,
distDirectory,
Expand Down Expand Up @@ -148,15 +150,15 @@ function generateIcons(options) {
}

const fileName = `${id}${size ? `_${size}` : ''}`;
fs.writeFileSync(path.join(iconDir, `${fileName}.ts`), reactSource);
fs.writeFileSync(path.join(iconDir, `${fileName}.tsx`), reactSource);

if (!isSubcomponent) {
exportsMap[exportName] = `./${dirname}/${fileName}`;
}
};

const compile = async () => {
const swcConfig = path.resolve(__dirname, './configs/.swcrc');
const swcConfig = path.resolve(import.meta.dirname, './configs/.swcrc');
if (!fs.existsSync(swcConfig)) {
debugError('swc config not found');
}
Expand Down Expand Up @@ -199,10 +201,7 @@ function generateIcons(options) {
* @param {string} dir
*/
function createIndexExports(exportsMap, dir) {
// TODO: v3 Удалить IconSettingsProvider
const exported = [
`export { IconSettingsProvider, IconAppearanceProvider } from '@vkontakte/icons-sprite';`,
];
const exported = [`export { IconAppearanceProvider } from '@vkontakte/icons-sprite';`];

const keys = Object.keys(exportsMap);
if (!keys) {
Expand All @@ -217,5 +216,3 @@ function createIndexExports(exportsMap, dir) {
const code = exported.join('\n');
fs.writeFileSync(path.join(dir, 'index.ts'), code);
}

module.exports = { generateIcons };
6 changes: 2 additions & 4 deletions packages/icons-scripts/scripts/optimize.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const { optimize: svgo } = require('svgo');
import { optimize as svgo } from 'svgo';

/**
* @param {string} svg
* @param {any[]} plugins
* @return {string}
*/
function optimize(svg, plugins) {
export function optimize(svg, plugins) {
return svgo(svg, {
plugins: [
{
Expand All @@ -20,5 +20,3 @@ function optimize(svg, plugins) {
],
}).data;
}

module.exports = { optimize };
10 changes: 4 additions & 6 deletions packages/icons-scripts/scripts/options.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const path = require('path');
const fs = require('fs');
const { debugError } = require('./utils');
import * as path from 'node:path';
import * as fs from 'node:fs';
import { debugError } from './utils.js';

/**
* @typedef {Object.<string, string | null>} DeprecatedIcons
Expand All @@ -22,7 +22,7 @@ const { debugError } = require('./utils');
* @param {GenerateOptions} options
* @return {GenerateOptions}
*/
function prepareOptions(options) {
export function prepareOptions(options) {
const {
srcDirectory,
distDirectory,
Expand Down Expand Up @@ -60,5 +60,3 @@ function prepareOptions(options) {
deprecatedIcons,
};
}

module.exports = { prepareOptions };
6 changes: 1 addition & 5 deletions packages/icons-scripts/scripts/output/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
const { createReactIcon } = require('./react');

module.exports = {
createReactIcon,
};
export { createReactIcon } from './react.js';
Loading

0 comments on commit 4f29572

Please sign in to comment.