# core/loaders/icons.loader.ts

> 66 lines of code and 14 definitions.

Tree: Bane's Lab Build Scripts
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/build-scripts#file-build-scripts-core-loaders-icons-loader-ts
Source text: https://banes-lab.com/source/build-scripts/core/loaders/icons.loader.ts.txt

Listed in [core/loaders](https://banes-lab.com/api/source/build-scripts/core/loaders.md), after [core/loaders/heritage.loader.ts](https://banes-lab.com/source/build-scripts/core/loaders/heritage.loader.ts.md) and before [core/loaders/manifest.loader.ts](https://banes-lab.com/source/build-scripts/core/loaders/manifest.loader.ts.md).

## Definitions

- `iconPathsOf` (lexical_declaration, line 55, exported)
- `pathOf` (lexical_declaration, line 50)
- `MEMBER` (lexical_declaration, line 21)
- `MEMBER_BELOW_ROOT` (lexical_declaration, line 22)
- `iconFiles` (lexical_declaration, line 24)
- `loadModule` (lexical_declaration, line 36)
- `iconNamesOf` (lexical_declaration, line 41)
- `start` (lexical_declaration, line 51)
- `svg` (lexical_declaration, line 56, exported)
- `paths` (lexical_declaration, line 57, exported)
- `at` (lexical_declaration, line 58, exported)
- `end` (lexical_declaration, line 60, exported)
- `discoverIcons` (lexical_declaration, line 70, exported)
- `loaded` (lexical_declaration, line 71, exported)

## Contained in

- [core/loaders](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-loaders.md)

## Uses

- [configuration/strings/icons.strings.ts](https://banes-lab.com/source/build-scripts/configuration/strings/icons.strings.ts.md)

## Used by

- [core/coordinators/icons.coordinator.ts](https://banes-lab.com/source/build-scripts/core/coordinators/icons.coordinator.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-configuration-strings.md)
- [core/coordinators](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-coordinators.md)
- [core/loaders](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-loaders.md)

## Source

```typescript
import {
    EVEN_ODD,
    ICONS_CONCERN,
    ICONS_SUFFIX,
    ICON_PREFIX,
    PATH_DATA_CLOSE,
    PATH_DATA_OPEN,
    PATH_OPEN,
    TAG_END,
} from "#configuration/constants/icons.constants";
import { absolutePath, relativePath } from "@ssot/paths";
import { readFileSync, readdirSync } from "node:fs";
import type { IconPath } from "@banes-lab/web/types/learning.types.js";
import { concernFolders } from "@ssot/govlab/shared/resolvers/container.resolver.ts";
import { iconSource } from "#core/resolvers/icons.resolver";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { pathlessIcon } from "#configuration/strings/icons.strings";
import { projectDirs } from "@ssot/govlab/shared/resolvers/anchor.resolver.ts";

const MEMBER = relativePath("app.member");
const MEMBER_BELOW_ROOT = `${MEMBER.slice(MEMBER.indexOf("/") + 1)}/`;

const iconFiles = function iconFiles(): string[] {
    return concernFolders(ICONS_CONCERN, projectDirs())
        .filter((folder) => folder.startsWith(MEMBER_BELOW_ROOT))
        .map((folder) => absolutePath("app.root", folder))
        .flatMap((folder) =>
            readdirSync(folder)
                .filter((name) => name.endsWith(ICONS_SUFFIX))
                .sort((a, b) => a.localeCompare(b))
                .map((name) => join(folder, name)),
        );
};

const loadModule = async function loadModule(file: string): Promise<unknown> {
    const loaded: unknown = await import(pathToFileURL(file).href);
    return loaded;
};

const iconNamesOf = function iconNamesOf(module: unknown): string[] {
    if (typeof module !== "object" || module === null) {
        return [];
    }
    return Object.values(module)
        .filter((value): value is string => typeof value === "string" && value.startsWith(ICON_PREFIX))
        .map((value) => value.slice(ICON_PREFIX.length));
};

const pathOf = function pathOf(tag: string): IconPath {
    const start = tag.indexOf(PATH_DATA_OPEN) + PATH_DATA_OPEN.length;
    return { d: tag.slice(start, tag.indexOf(PATH_DATA_CLOSE, start)), evenOdd: tag.includes(EVEN_ODD) };
};

export const iconPathsOf = function iconPathsOf(name: string): readonly IconPath[] {
    const svg = readFileSync(iconSource(name), "utf8");
    const paths: IconPath[] = [];
    let at = svg.indexOf(PATH_OPEN);
    while (at !== -1) {
        const end = svg.indexOf(TAG_END, at);
        paths.push(pathOf(svg.slice(at, end)));
        at = svg.indexOf(PATH_OPEN, end);
    }
    if (paths.length === 0) {
        throw new TypeError(pathlessIcon(ICON_PREFIX + name));
    }
    return paths;
};

export const discoverIcons = async function discoverIcons(): Promise<readonly string[]> {
    const loaded = await Promise.all(iconFiles().map(loadModule));
    return [...new Set(loaded.flatMap(iconNamesOf))].sort((a, b) => a.localeCompare(b));
};
```
