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

View File

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

View File

@ -2,14 +2,26 @@ import type { IconifyJSON } from '@iconify/types';
import type { IconSetIconsListIcons } from '../../../types/icon-set/extra'; 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; const aliases = data.aliases;
if (!aliases) { if (aliases) {
return; 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];
}
}
} }
iconsList.failed.forEach((name) => {
delete aliases[name];
});
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -34,16 +34,16 @@ export function generateAPIv2CollectionResponse(query: FastifyRequest['query'],
// Filter prefixes // Filter prefixes
const response: APIv2CollectionResponse = { const response: APIv2CollectionResponse = {
...apiV2IconsCache.rendered, ...apiV2IconsCache,
}; };
if (!q.info) { if (!q.info) {
// Delete info // Delete info
delete response.info; delete response.info;
} }
if (q.chars && apiV2IconsCache.chars) { if (q.chars && iconSet.icons.chars) {
// Add characters map // Add characters map
response.chars = apiV2IconsCache.chars; response.chars = iconSet.icons.chars;
} }
sendJSONResponse(response, q, wrap, res); 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) { export function generateSVGResponse(prefix: string, name: string, query: FastifyRequest['query'], res: FastifyReply) {
// Get icon set // Get icon set
const iconSet = iconSets[prefix]; const iconSetItem = iconSets[prefix]?.item;
if (!iconSet) { if (!iconSetItem) {
// No such icon set // No such icon set
res.send(404); res.send(404);
return; return;
} }
// Check if icon exists
if (!iconSetItem.icons.names.has(name) && !iconSetItem.icons.chars?.[name]) {
// No such icon
res.send(404);
return;
}
// Get icon // Get icon
getStoredIconData(iconSet.item, name, (data) => { getStoredIconData(iconSetItem, name, (data) => {
if (!data) { if (!data) {
// Invalid icon // Invalid icon
res.send(404); res.send(404);

View File

@ -15,6 +15,9 @@ export interface IconSetIconsListTag {
* Icons * Icons
*/ */
export interface IconSetIconsListIcons { export interface IconSetIconsListIcons {
// All names: icons + aliases
names: Set<string>;
// Visible icons // Visible icons
visible: Set<string>; visible: Set<string>;
@ -31,46 +34,43 @@ export interface IconSetIconsListIcons {
// Tags // Tags
tags: IconSetIconsListTag[]; tags: IconSetIconsListTag[];
uncategorised: string[]; uncategorised: string[];
// Characters, key = character, value = icon name
chars?: Record<string, string>;
} }
/** /**
* Prepared icons list for API v2 response * Prepared icons list for API v2 response
*/ */
export interface IconSetAPIv2IconsList { export interface IconSetAPIv2IconsList {
// Prepared data // Icon set prefix
rendered: { prefix: string;
// Icon set prefix
prefix: string;
// Number of icons (duplicate of info?.total) // Number of icons (duplicate of info?.total)
total: number; total: number;
// Icon set title, if available (duplicate of info?.name) // Icon set title, if available (duplicate of info?.name)
title?: string; title?: string;
// Icon set info // Icon set info
info?: IconifyInfo; info?: IconifyInfo;
// List of icons without categories // List of icons without categories
uncategorized?: string[]; uncategorized?: string[];
// List of icons, sorted by category // List of icons, sorted by category
categories?: Record<string, string[]>; categories?: Record<string, string[]>;
// List of hidden icons // List of hidden icons
hidden?: string[]; hidden?: string[];
// List of aliases, key = alias, value = parent icon // List of aliases, key = alias, value = parent icon
aliases?: Record<string, string>; aliases?: Record<string, string>;
// Themes // Themes
themes?: IconifyJSON['themes']; themes?: IconifyJSON['themes'];
prefixes?: IconifyJSON['prefixes']; prefixes?: IconifyJSON['prefixes'];
suffixes?: IconifyJSON['suffixes']; 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 { 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', () => { describe('Validating icon set', () => {
test('Long chain of aliases, bad aliases', () => { 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 // Check aliases
expect(Object.keys(iconSet.aliases)).toEqual(['baz', 'baz2', 'baz3', 'baz4', 'baz5', 'baz6', 'bazz5']); expect(Object.keys(iconSet.aliases)).toEqual(['baz', 'baz2', 'baz3', 'baz4', 'baz5', 'baz6', 'bazz5']);