chore: change icon names list structure for better performance

This commit is contained in:
Vjacheslav Trushkin 2022-10-16 11:43:17 +03:00
parent aeb9cb70b9
commit ec354b9d33
11 changed files with 122 additions and 167 deletions

View File

@ -9,24 +9,24 @@ const themeKeys: ThemeKey[] = ['themes', 'prefixes', 'suffixes'];
*/
export function prepareAPIv2IconsList(iconSet: IconifyJSON, iconsList: IconSetIconsListIcons): IconSetAPIv2IconsList {
// Prepare data
const rendered: IconSetAPIv2IconsList['rendered'] = {
const result: IconSetAPIv2IconsList = {
prefix: iconSet.prefix,
total: iconsList.visible.size,
};
const info = iconSet.info;
if (info) {
rendered.title = info.name;
rendered.info = info;
result.title = info.name;
result.info = info;
}
if (iconsList.uncategorised.length) {
rendered.uncategorized = iconsList.uncategorised;
result.uncategorized = iconsList.uncategorised;
}
// Convert categories
if (iconsList.tags.length) {
const categories = (rendered.categories = Object.create(null) as Record<string, string[]>);
const categories = (result.categories = Object.create(null) as Record<string, string[]>);
for (let i = 0; i < iconsList.tags.length; i++) {
const tag = iconsList.tags[i];
categories[tag.title] = tag.icons;
@ -36,7 +36,7 @@ export function prepareAPIv2IconsList(iconSet: IconifyJSON, iconsList: IconSetIc
// Hidden icons
const hidden = Array.from(iconsList.hidden).concat(Object.keys(iconsList.hiddenAliases));
if (hidden.length) {
rendered.hidden = hidden;
result.hidden = hidden;
}
// Add aliases
@ -45,7 +45,7 @@ export function prepareAPIv2IconsList(iconSet: IconifyJSON, iconsList: IconSetIc
...iconsList.hiddenAliases,
};
for (const alias in aliases) {
rendered.aliases = aliases;
result.aliases = aliases;
break;
}
@ -53,19 +53,9 @@ export function prepareAPIv2IconsList(iconSet: IconifyJSON, iconsList: IconSetIc
for (let i = 0; i < themeKeys.length; i++) {
const key = themeKeys[i] as ThemeKey;
if (iconSet[key]) {
rendered[key as 'themes'] = iconSet[key as 'themes'];
result[key as 'themes'] = iconSet[key as 'themes'];
}
}
// Result
const result: IconSetAPIv2IconsList = {
rendered,
};
// Add characters
if (iconSet.chars) {
result.chars = iconSet.chars;
}
return result;
}

View File

@ -145,6 +145,7 @@ export function generateIconSetIconsTree(iconSet: IconifyJSON): IconSetIconsList
// Return data
return {
names: new Set([...visible, ...hidden, ...Object.keys(visibleAliases), ...Object.keys(hiddenAliases)]),
visible,
hidden,
visibleAliases,
@ -152,5 +153,6 @@ export function generateIconSetIconsTree(iconSet: IconifyJSON): IconSetIconsList
failed,
tags: tags.filter((tag) => tag.icons.length > 0),
uncategorised,
chars: iconSet.chars,
};
}

View File

@ -2,14 +2,26 @@ import type { IconifyJSON } from '@iconify/types';
import type { IconSetIconsListIcons } from '../../../types/icon-set/extra';
/**
* Removes bad aliases
* Removes bad items
*/
export function removeBadAliases(data: IconifyJSON, iconsList: IconSetIconsListIcons) {
export function removeBadIconSetItems(data: IconifyJSON, iconsList: IconSetIconsListIcons) {
// Remove bad aliases
const aliases = data.aliases;
if (!aliases) {
return;
}
if (aliases) {
iconsList.failed.forEach((name) => {
delete aliases[name];
});
}
// Remove bad characters
const chars = iconsList.chars;
if (chars) {
for (const key in chars) {
if (iconsList.names.has(key) || !iconsList.names.has(chars[key])) {
// Character matches existing icon or points to missing icon
// Also deletes data.chars[key] because it points to same object
delete chars[key];
}
}
}
}

View File

@ -8,7 +8,7 @@ import { createSplitRecordsTree, splitRecords } from '../../storage/split';
import { createStorage, createStoredItem } from '../../storage/create';
import { getIconSetSplitChunksCount, splitIconSetMainData } from './split';
import { generateIconSetIconsTree } from '../lists/icons';
import { removeBadAliases } from '../lists/validate';
import { removeBadIconSetItems } from '../lists/validate';
import { prepareAPIv2IconsList } from '../lists/icons-v2';
/**
@ -33,7 +33,7 @@ export function storeLoadedIconSet(
) {
// Get icons list and remove bad aliases
const icons = generateIconSetIconsTree(iconSet);
removeBadAliases(iconSet, icons);
removeBadIconSetItems(iconSet, icons);
// Fix icons counter
if (iconSet.info) {

View File

@ -74,7 +74,7 @@ export function getStoredIconData(
name = resolved.name;
} else {
props = {} as ExtendedIconifyAlias;
const charValue = iconSet.apiV2IconsCache.chars?.[name];
const charValue = iconSet.icons.chars?.[name];
if (charValue) {
// Character
const icons = iconSet.icons;

View File

@ -10,17 +10,18 @@ import { getStoredItem } from '../../storage/get';
export function getIconsToRetrieve(iconSet: StoredIconSet, names: string[], copyTo?: IconifyAliases): Set<string> {
const icons: Set<string> = new Set();
const iconSetData = iconSet.common;
const allNames = iconSet.icons.names;
const chars = iconSet.icons.chars;
const aliases = iconSetData.aliases || (Object.create(null) as IconifyAliases);
function resolve(name: string, nested: boolean) {
if (!aliases[name]) {
if (!nested) {
// Check for character
const charValue = iconSet.apiV2IconsCache.chars?.[name];
if (charValue) {
// Character
const icons = iconSet.icons;
if (!icons.visible.has(name) && !icons.hidden.has(name)) {
if (!allNames.has(name)) {
// No such icon: check for character
const charValue = chars?.[name];
if (!charValue) {
return;
}
// Resolve character instead of alias
copyTo &&
(copyTo[name] = {
@ -29,9 +30,9 @@ export function getIconsToRetrieve(iconSet: StoredIconSet, names: string[], copy
resolve(charValue, true);
return;
}
}
}
// Icon or alias exists
if (!aliases[name]) {
// Icon
icons.add(name);
return;
@ -52,72 +53,6 @@ export function getIconsToRetrieve(iconSet: StoredIconSet, names: string[], copy
return icons;
}
/**
* Extract icons from chunks of icon data
*/
export function getIconsData(
iconSetData: SplitIconifyJSONMainData,
names: string[],
sourceIcons: IconifyIcons[],
chars?: Record<string, string>
): IconifyJSON {
const sourceAliases = iconSetData.aliases;
const icons = Object.create(null) as IconifyJSON['icons'];
const aliases = Object.create(null) as IconifyAliases;
const result: IconifyJSON = {
...iconSetData,
icons,
aliases,
};
function resolve(name: string, nested: boolean): boolean {
if (!sourceAliases[name]) {
// Icon
for (let i = 0; i < sourceIcons.length; i++) {
const item = sourceIcons[i];
if (name in item) {
icons[name] = item[name];
return true;
}
}
// Check for character
if (!nested) {
const charValue = chars?.[name];
if (charValue) {
aliases[name] = {
parent: charValue,
};
return resolve(charValue, true);
}
}
} else if (name in sourceAliases) {
// Alias
if (name in aliases) {
// Already resolved
return true;
}
const item = sourceAliases[name];
if (resolve(item.parent, true)) {
aliases[name] = item;
return true;
}
}
// Missing
(result.not_found || (result.not_found = [])).push(name);
return false;
}
for (let i = 0; i < names.length; i++) {
resolve(names[i], false);
}
return result;
}
/**
* Get icons from stored icon set
*/
@ -139,22 +74,22 @@ export function getStoredIconsData(iconSet: StoredIconSet, names: string[], call
// Get map of chunks to load
const chunks = searchSplitRecordsTreeForSet(iconSet.tree, iconNames);
let pending = chunks.size;
let not_found: string[] | undefined;
let missing: Set<string> = new Set();
const icons = Object.create(null) as IconifyIcons;
const storage = iconSet.storage;
chunks.forEach((names, storedItem) => {
chunks.forEach((chunkNames, storedItem) => {
getStoredItem(storage, storedItem, (data) => {
// Copy data from chunk
if (!data) {
not_found = names.concat(not_found || []);
missing = new Set([...chunkNames, ...missing]);
} else {
for (let i = 0; i < names.length; i++) {
const name = names[i];
for (let i = 0; i < chunkNames.length; i++) {
const name = chunkNames[i];
if (data[name]) {
icons[name] = data[name];
} else {
(not_found || (not_found = [])).push(name);
missing.add(name);
}
}
}
@ -167,8 +102,17 @@ export function getStoredIconsData(iconSet: StoredIconSet, names: string[], call
icons,
aliases,
};
if (not_found) {
result.not_found = not_found;
// Add missing icons
for (let i = 0; i < names.length; i++) {
const name = names[i];
if (!icons[name] && !aliases[name]) {
missing.add(name);
}
}
if (missing.size) {
result.not_found = Array.from(missing);
}
callback(result);
}

View File

@ -33,35 +33,35 @@ export function generateAPIv1IconsListResponse(
}
function parse(prefix: string, iconSet: StoredIconSet): APIv1ListIconsResponse | APIv1ListIconsCategorisedResponse {
const icons = iconSet.icons;
const v2Cache = iconSet.apiV2IconsCache;
const rendered = v2Cache.rendered;
// Generate common data
const base: APIv1ListIconsBaseResponse = {
prefix,
total: rendered.total,
total: v2Cache.total,
};
if (rendered.title) {
base.title = rendered.title;
if (v2Cache.title) {
base.title = v2Cache.title;
}
if (q.info && rendered.info) {
base.info = rendered.info;
if (q.info && v2Cache.info) {
base.info = v2Cache.info;
}
if (q.aliases && rendered.aliases) {
base.aliases = rendered.aliases;
if (q.aliases && v2Cache.aliases) {
base.aliases = v2Cache.aliases;
}
if (q.chars && v2Cache.chars) {
base.chars = v2Cache.chars;
if (q.chars && icons.chars) {
base.chars = icons.chars;
}
// Add icons
if (categorised) {
const result = base as APIv1ListIconsCategorisedResponse;
if (rendered.categories) {
result.categories = rendered.categories;
if (v2Cache.categories) {
result.categories = v2Cache.categories;
}
if (rendered.uncategorized) {
result.uncategorized = rendered.uncategorized;
if (v2Cache.uncategorized) {
result.uncategorized = v2Cache.uncategorized;
}
return result;
}

View File

@ -34,16 +34,16 @@ export function generateAPIv2CollectionResponse(query: FastifyRequest['query'],
// Filter prefixes
const response: APIv2CollectionResponse = {
...apiV2IconsCache.rendered,
...apiV2IconsCache,
};
if (!q.info) {
// Delete info
delete response.info;
}
if (q.chars && apiV2IconsCache.chars) {
if (q.chars && iconSet.icons.chars) {
// Add characters map
response.chars = apiV2IconsCache.chars;
response.chars = iconSet.icons.chars;
}
sendJSONResponse(response, q, wrap, res);

View File

@ -16,15 +16,22 @@ import { iconSets } from '../../data/icon-sets';
*/
export function generateSVGResponse(prefix: string, name: string, query: FastifyRequest['query'], res: FastifyReply) {
// Get icon set
const iconSet = iconSets[prefix];
if (!iconSet) {
const iconSetItem = iconSets[prefix]?.item;
if (!iconSetItem) {
// No such icon set
res.send(404);
return;
}
// Check if icon exists
if (!iconSetItem.icons.names.has(name) && !iconSetItem.icons.chars?.[name]) {
// No such icon
res.send(404);
return;
}
// Get icon
getStoredIconData(iconSet.item, name, (data) => {
getStoredIconData(iconSetItem, name, (data) => {
if (!data) {
// Invalid icon
res.send(404);

View File

@ -15,6 +15,9 @@ export interface IconSetIconsListTag {
* Icons
*/
export interface IconSetIconsListIcons {
// All names: icons + aliases
names: Set<string>;
// Visible icons
visible: Set<string>;
@ -31,14 +34,15 @@ export interface IconSetIconsListIcons {
// Tags
tags: IconSetIconsListTag[];
uncategorised: string[];
// Characters, key = character, value = icon name
chars?: Record<string, string>;
}
/**
* Prepared icons list for API v2 response
*/
export interface IconSetAPIv2IconsList {
// Prepared data
rendered: {
// Icon set prefix
prefix: string;
@ -67,10 +71,6 @@ export interface IconSetAPIv2IconsList {
themes?: IconifyJSON['themes'];
prefixes?: IconifyJSON['prefixes'];
suffixes?: IconifyJSON['suffixes'];
};
// Characters, key = character, value = icon name
chars?: Record<string, string>;
}
/**

View File

@ -1,5 +1,5 @@
import { generateIconSetIconsTree } from '../../lib/data/icon-set/lists/icons';
import { removeBadAliases } from '../../lib/data/icon-set/store/validate';
import { removeBadIconSetItems } from '../../lib/data/icon-set/lists/validate';
describe('Validating icon set', () => {
test('Long chain of aliases, bad aliases', () => {
@ -56,7 +56,7 @@ describe('Validating icon set', () => {
},
},
};
removeBadAliases(iconSet, generateIconSetIconsTree(iconSet));
removeBadIconSetItems(iconSet, generateIconSetIconsTree(iconSet));
// Check aliases
expect(Object.keys(iconSet.aliases)).toEqual(['baz', 'baz2', 'baz3', 'baz4', 'baz5', 'baz6', 'bazz5']);