spike: also support remote icons
This commit is contained in:
parent
c86ae12557
commit
04c563429c
4 changed files with 103 additions and 1 deletions
|
@ -1,4 +1,5 @@
|
|||
export const VIRTUAL_MODULE_ID = 'astro:assets';
|
||||
export const VIRTUAL_ICONS_ID = 'astro:assets/icons/';
|
||||
export const VIRTUAL_SERVICE_ID = 'virtual:image-service';
|
||||
export const VALID_INPUT_FORMATS = [
|
||||
// TODO: `image-size` does not support the following formats, so users can't import them.
|
||||
|
|
62
packages/astro/src/assets/utils/icon.ts
Normal file
62
packages/astro/src/assets/utils/icon.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import fs, { readFileSync } from 'node:fs';
|
||||
import type { AstroConfig, AstroSettings } from '../../@types/astro';
|
||||
import { prependForwardSlash } from '../../core/path.js';
|
||||
|
||||
interface IconCollection {
|
||||
prefix: string;
|
||||
info: Record<string, string>;
|
||||
lastModified: number;
|
||||
icons: Record<string, IconData>;
|
||||
}
|
||||
interface IconData {
|
||||
body: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
const ICONIFY_REPO = new URL(`https://raw.githubusercontent.com/iconify/icon-sets/master/json/`);
|
||||
function getIconifyUrl(collection: string) {
|
||||
return new URL(`./${collection}.json`, ICONIFY_REPO);
|
||||
}
|
||||
|
||||
export async function getIconData(
|
||||
settings: AstroSettings,
|
||||
collection: string,
|
||||
name: string
|
||||
): Promise<IconData | undefined> {
|
||||
const iconsCacheDir = new URL('assets/icons/', settings.config.cacheDir);
|
||||
|
||||
// Ensure that the cache directory exists
|
||||
try {
|
||||
await fs.promises.mkdir(iconsCacheDir, { recursive: true });
|
||||
} catch (err) {
|
||||
// logger.warn(
|
||||
// 'astro:assets',
|
||||
// `An error was encountered while creating the cache directory. Proceeding without caching. Error: ${err}`
|
||||
// );
|
||||
}
|
||||
|
||||
const cachedFileURL = new URL(`./${collection}.json`, iconsCacheDir);
|
||||
|
||||
let collectionData: IconCollection | undefined;
|
||||
if (fs.existsSync(cachedFileURL)) {
|
||||
try {
|
||||
collectionData = JSON.parse(fs.readFileSync(cachedFileURL, { encoding: 'utf8' })) as IconCollection;
|
||||
} catch {}
|
||||
}
|
||||
if (!collectionData) {
|
||||
try {
|
||||
const res = await fetch(getIconifyUrl(collection));
|
||||
collectionData = await res.json();
|
||||
await fs.promises.writeFile(cachedFileURL, JSON.stringify(collectionData), { encoding: 'utf8' })
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to locate icon "${collection}/${name}"`)
|
||||
}
|
||||
}
|
||||
const { icons } = collectionData!;
|
||||
if (icons[name] === undefined) {
|
||||
throw new Error(`Unable to locate icon "${collection}/${name}"`)
|
||||
}
|
||||
|
||||
return icons[name];
|
||||
}
|
|
@ -11,12 +11,14 @@ import {
|
|||
prependForwardSlash,
|
||||
removeQueryString,
|
||||
} from '../core/path.js';
|
||||
import { VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js';
|
||||
import { VIRTUAL_ICONS_ID, VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js';
|
||||
import { emitESMImage } from './utils/emitAsset.js';
|
||||
import { dropAttributes } from './utils/svg.js';
|
||||
import { hashTransform, propsToFilename } from './utils/transformToPath.js';
|
||||
import { getIconData } from './utils/icon.js';
|
||||
|
||||
const resolvedVirtualModuleId = '\0' + VIRTUAL_MODULE_ID;
|
||||
const resolvedVirtualIconsId = '\0' + VIRTUAL_ICONS_ID;
|
||||
|
||||
const rawRE = /(?:\?|&)raw(?:&|$)/;
|
||||
const urlRE = /(\?|&)url(?:&|$)/;
|
||||
|
@ -142,6 +144,26 @@ export default function assets({
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'astro:assets/icons',
|
||||
async resolveId(id) {
|
||||
if (id.startsWith(VIRTUAL_ICONS_ID)) {
|
||||
return `\0${id}`;
|
||||
}
|
||||
},
|
||||
async load(id) {
|
||||
if (id.startsWith(resolvedVirtualIconsId)) {
|
||||
const name = id.slice(resolvedVirtualIconsId.length);
|
||||
const [collection, icon] = name.split('/');
|
||||
|
||||
const data = await getIconData(settings, collection, icon);
|
||||
if (!data) return;
|
||||
const { width = 24, height = 24, body } = data;
|
||||
const svg = Buffer.from(`<svg data-name="${name}" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">${body}</svg>`, 'utf8');
|
||||
return makeSvgComponent({ src: `${collection}/${icon}`, format: 'svg', width, height }, svg);
|
||||
}
|
||||
},
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -332,6 +332,10 @@ importers:
|
|||
astro:
|
||||
specifier: ^3.0.8
|
||||
version: link:../../packages/astro
|
||||
devDependencies:
|
||||
'@iconify-icons/mdi':
|
||||
specifier: ^1.2.47
|
||||
version: 1.2.47
|
||||
|
||||
examples/non-html-pages:
|
||||
dependencies:
|
||||
|
@ -626,6 +630,9 @@ importers:
|
|||
tsconfig-resolver:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
ultrahtml:
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0
|
||||
undici:
|
||||
specifier: ^5.23.0
|
||||
version: 5.23.0
|
||||
|
@ -8000,6 +8007,16 @@ packages:
|
|||
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
|
||||
dev: true
|
||||
|
||||
/@iconify-icons/mdi@1.2.47:
|
||||
resolution: {integrity: sha512-XwrHxJb2GzToyyoI9gaVm6/yE3aRlxB2IolKXzTEf6qAtjv3S4xFAxYaOlm6iuylQv+WyquH9C4cBudNPRHApg==}
|
||||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
dev: true
|
||||
|
||||
/@iconify/types@2.0.0:
|
||||
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||
dev: true
|
||||
|
||||
/@jest/schemas@29.6.3:
|
||||
resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
|
Loading…
Reference in a new issue