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

BREAKING CHANGE: rm svg sprite #1039

Merged
merged 1 commit into from
Dec 5, 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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
node_modules/
.DS_Store
dist/
dist_es6/
ts/
packages/icons/docs/
.cache
Expand Down
17 changes: 12 additions & 5 deletions packages/icons-scripts/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,23 @@ src/
{
"name": "@scope/react-icons-library",
"version": "0.0.0",
"files": ["dist", "dist_es6", "src/svg"],
"files": ["dist", "src/svg"],
"type": "module",
"main": "dist/index.js",
"module": "dist_es6/index.js",
"module": "dist/index.js",
"typings": "dist/typings/index.d.ts",
"sideEffects": ["*.css"],
"exports": {
".": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/index.js"
}
},
"scripts": {
"build-icons": "node scripts/build-icons.js"
},
"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 @@ -52,7 +59,7 @@ src/
**`scripts/build-icons.js`**:

```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": "ig.fedorov@corp.vk.com"
},
"dependencies": {
"@mapbox/hast-util-to-jsx": "^2.1.0",
"@swc/cli": "^0.5.2",
"@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
17 changes: 14 additions & 3 deletions packages/icons-scripts/scripts/configs/.swcrc
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
{
"$schema": "https://swc.rs/schema.json",
"module": {
"type": "es6",
"resolveFully": true
},
"jsc": {
"externalHelpers": true,
"parser": {
"syntax": "typescript",
"tsx": true
},
"experimental": {
"emitIsolatedDts": true
},
"transform": {
"react": {
"runtime": "automatic"
}
},
"target": "es2017",
"baseUrl": "./",
"paths": {
"*": ["node_modules", "src/*"]
},
"preserveAllComments": true,
"experimental": {
"emitIsolatedDts": true
}
}
}
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,
};
47 changes: 20 additions & 27 deletions packages/icons-scripts/scripts/icons.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
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,
distES6Directory,
tsFilesDirectory,
extraCategories,
svgoPlugins,
Expand All @@ -30,7 +31,7 @@ function generateIcons(options) {
const start = performance.now();

debugInfo('Preparing directories...');
[distDirectory, distES6Directory, tsFilesDirectory].forEach((dir) => {
[distDirectory, tsFilesDirectory].forEach((dir) => {
fs.rmSync(dir, {
force: true,
recursive: true,
Expand Down Expand Up @@ -148,15 +149,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 All @@ -165,10 +166,7 @@ function generateIcons(options) {

await Promise.all([
exec(
`swc ${tsFilesDirectory} --strip-leading-paths -d ${distDirectory}/ --config-file ${swcConfig} -C module.type=commonjs`,
),
exec(
`swc ${tsFilesDirectory} --strip-leading-paths -d ${distES6Directory}/ --config-file ${swcConfig}`,
`swc ${tsFilesDirectory} --strip-leading-paths -d ${distDirectory}/ --config-file ${swcConfig}`,
),
]);

Expand Down Expand Up @@ -199,10 +197,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 +212,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 };
11 changes: 4 additions & 7 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 All @@ -49,7 +49,6 @@ function prepareOptions(options) {
return {
srcDirectory: directoryPath(srcDirectory),
distDirectory: directoryPath(distDirectory),
distES6Directory: directoryPath(`${distDirectory}_es6`),
keepTSSources: keepTSSources == null ? !!tsFilesDirectory : keepTSSources,
tsFilesDirectory: tsFilesDirectory
? directoryPath(tsFilesDirectory)
Expand All @@ -60,5 +59,3 @@ function prepareOptions(options) {
deprecatedIcons,
};
}

module.exports = { prepareOptions };
Loading
Loading