=> {
- return `
- `;
-};
diff --git a/demo/src/icons/heroicons.ts b/demo/src/icons/heroicons.ts
deleted file mode 100644
index 14da8fbf..00000000
--- a/demo/src/icons/heroicons.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { createIconPack } from "astro-icon/pack";
-
-export default createIconPack({ package: "heroicons", dir: "outline" });
diff --git a/demo/src/icons/logos/alpine.svg b/demo/src/icons/logos/alpine.svg
new file mode 100644
index 00000000..548bd454
--- /dev/null
+++ b/demo/src/icons/logos/alpine.svg
@@ -0,0 +1,14 @@
+
diff --git a/demo/src/icons/logos/deno.svg b/demo/src/icons/logos/deno.svg
new file mode 100644
index 00000000..8d409036
--- /dev/null
+++ b/demo/src/icons/logos/deno.svg
@@ -0,0 +1,11 @@
+
diff --git a/demo/src/icons/radix.ts b/demo/src/icons/radix.ts
deleted file mode 100644
index f846c8b9..00000000
--- a/demo/src/icons/radix.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { createIconPack } from "astro-icon/pack";
-
-export default createIconPack({
- url: "https://raw.githubusercontent.com/radix-ui/icons/master/packages/radix-icons/icons/",
-});
diff --git a/demo/src/pages/index.astro b/demo/src/pages/index.astro
index f624a2fd..711a44f1 100644
--- a/demo/src/pages/index.astro
+++ b/demo/src/pages/index.astro
@@ -1,70 +1,63 @@
---
-import { Icon } from 'astro-icon';
+import { Icon } from "astro-icon/components";
+
+const icon = "adjustment";
---
-
-
-
-
-
- Astro Icon
-
-
-
-
- Welcome to Astro Icon!
-
- The Icon
component will optimize and inline any SVG file inside of src/icons/
-
- Alternatively, see the Sprite method.
-
-
- Local
-
-
-
-
-
-
- Custom remote source
-
-
-
-
-
- Custom SVG resolver
-
-
-
-
-
- Local dependencies
-
-
-
-
-
-
-
-
- Automatic icons from service
- If you can find it on Icones, you can render it here!
-
-
-
-
-
-
-
+
+
+
+
+ Astro Icon
+
+
+
+
+ Welcome to Astro Icon!
+
+
+ The Icon
component will optimize and inline any SVG file inside
+ of src/icons/
+
+
+
+ Local
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Automatic icons from service
+
+ If you can find it on Icones, you
+ can render it here!
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/src/pages/map.astro b/demo/src/pages/map.astro
index 6acb96bb..f9cb3454 100644
--- a/demo/src/pages/map.astro
+++ b/demo/src/pages/map.astro
@@ -1,31 +1,29 @@
---
-import { Icon } from 'astro-icon';
+import { Icon } from "astro-icon/components";
---
+
+
+
+
+ Astro Icon
+
+
-
-
-
-
- Astro Icon
-
-
-
-
- Welcome to Astro Icon!
-
- {[1,2,3].map(() => )}
-
+
+ Welcome to Astro Icon!
+ {[1, 2, 3].map(() => )}
+
diff --git a/package.json b/package.json
index 3b3b0035..7de96269 100644
--- a/package.json
+++ b/package.json
@@ -7,10 +7,6 @@
"dev": "yarn workspace demo run dev",
"lint": "prettier \"**/*.{js,ts,md,json}\""
},
- "workspaces": [
- "packages/*",
- "demo"
- ],
"repository": {
"type": "git",
"url": "git+https://github.com/natemoo-re/astro-icon.git"
@@ -22,7 +18,7 @@
},
"homepage": "https://github.com/natemoo-re/astro-icon#readme",
"volta": {
- "node": "16.13.0",
+ "node": "16.19.1",
"yarn": "1.22.17"
},
"dependencies": {
diff --git a/packages/core/components.d.ts b/packages/core/components.d.ts
new file mode 100644
index 00000000..a9881337
--- /dev/null
+++ b/packages/core/components.d.ts
@@ -0,0 +1 @@
+export { default as Icon } from './lib/Icon.astro'
diff --git a/packages/core/components.mjs b/packages/core/components.mjs
new file mode 100644
index 00000000..a9881337
--- /dev/null
+++ b/packages/core/components.mjs
@@ -0,0 +1 @@
+export { default as Icon } from './lib/Icon.astro'
diff --git a/packages/core/index.ts b/packages/core/index.ts
deleted file mode 100644
index 2c0964a5..00000000
--- a/packages/core/index.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import Icon from "./lib/Icon.astro";
-import SpriteProvider from "./lib/SpriteProvider.astro";
-import SpriteComponent from "./lib/Sprite.astro";
-import Sheet from "./lib/Spritesheet.astro";
-
-const deprecate = (component: any, message: string) => {
- return (...args: any[]) => {
- console.warn(message);
- return component(...args);
- };
-};
-
-const Spritesheet = deprecate(
- Sheet,
- `Direct access to has been deprecated! Please wrap your contents in instead!`
-);
-const SpriteSheet = deprecate(
- Sheet,
- `Direct access to has been deprecated! Please wrap your contents in instead!`
-);
-
-const Sprite = Object.assign(SpriteComponent, { Provider: SpriteProvider });
-
-export {
- Icon as default,
- Icon,
- Spritesheet,
- SpriteSheet,
- SpriteProvider,
- Sprite,
-};
diff --git a/packages/core/integration.d.ts b/packages/core/integration.d.ts
new file mode 100644
index 00000000..091ee205
--- /dev/null
+++ b/packages/core/integration.d.ts
@@ -0,0 +1,7 @@
+import { AstroIntegration } from 'astro';
+
+export interface AstroIconOptions {
+ include: Record
+}
+
+export default function icon(opts: AstroIconOptions): AstroIntegration;
diff --git a/packages/core/integration.mjs b/packages/core/integration.mjs
new file mode 100644
index 00000000..d915ae1b
--- /dev/null
+++ b/packages/core/integration.mjs
@@ -0,0 +1,135 @@
+import { getIcons } from "@iconify/utils";
+import { loadCollection, locate } from "@iconify/json";
+import {
+ importDirectory,
+ cleanupSVG,
+ runSVGO,
+ parseColors,
+ isEmptyColor,
+} from "@iconify/tools";
+
+/** @returns {import('astro').AstroIntegration} */
+export default function icon(opts = {}) {
+ return {
+ name: "astro-icon",
+ hooks: {
+ async "astro:config:setup"({ updateConfig, command }) {
+ updateConfig({
+ vite: {
+ plugins: [await getVitePlugin(opts, command)],
+ },
+ });
+ },
+ },
+ };
+}
+
+/** @returns {import('vite').Plugin} */
+async function getVitePlugin({ include = {} }, command) {
+ const virtualModuleId = "virtual:astro-icon";
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
+
+ const fullCollections = await Promise.all(
+ Object.keys(include).map((collection) =>
+ loadCollection(locate(collection)).then((value) => [collection, value])
+ )
+ );
+ const collections = fullCollections.map(([name, icons]) => {
+ const reduced = include[name];
+ if (reduced.length === 1 && reduced[0] === "*") return icons;
+ return getIcons(icons, reduced);
+ });
+
+ return {
+ name: "astro-icon",
+ resolveId(id) {
+ if (id === virtualModuleId) {
+ return resolvedVirtualModuleId;
+ }
+ },
+ async load(id) {
+ if (id === resolvedVirtualModuleId) {
+ const local = await importDirectory("src/icons", {
+ prefix: "local",
+ });
+
+ await local.forEach(async (name, type) => {
+ if (type !== "icon") {
+ return;
+ }
+
+ const svg = local.toSVG(name);
+ if (!svg) {
+ // Invalid icon
+ local.remove(name);
+ return;
+ }
+
+ try {
+ await cleanupSVG(svg);
+
+ if (await isMonochrome(svg)) {
+ await normalizeColors(svg);
+ }
+
+ runSVGO(svg);
+ } catch (err) {
+ // Invalid icon
+ console.error(`Error parsing ${name}:`, err);
+ local.remove(name);
+ return;
+ }
+
+ // Update icon
+ local.fromSVG(name, svg);
+ });
+ collections.unshift(local.export())
+
+ return `import.meta.glob('/src/icons/**/*.svg');
+ export default ${JSON.stringify(
+ collections
+ )};\nexport const config = ${
+ command === "dev" ? JSON.stringify({ include }) : "undefined"
+ }`;
+ }
+ },
+ };
+}
+
+function normalizeColors(svg) {
+ return parseColors(svg, {
+ defaultColor: "currentColor",
+ callback: (_, colorStr, color) => {
+ return !color || isEmptyColor(color) || isWhite(color)
+ ? colorStr
+ : "currentColor";
+ },
+ });
+}
+
+async function isMonochrome(svg) {
+ let monochrome = true;
+ await parseColors(svg, {
+ defaultColor: "currentColor",
+ callback: (_, colorStr, color) => {
+ if (!monochrome) return colorStr;
+ monochrome = !color || isEmptyColor(color) || isWhite(color) || isBlack(color);
+ return colorStr;
+ },
+ });
+
+ return monochrome;
+}
+
+function isBlack(color) {
+ switch (color.type) {
+ case 'rgb': return color.r === 0 && color.r === color.g && color.g === color.b;
+ }
+ return false;
+}
+function isWhite(color) {
+ switch (color.type) {
+ case 'rgb': return color.r === 255 && color.r === color.g && color.g === color.b;
+ }
+ return false;
+}
diff --git a/packages/core/lib/Icon.astro b/packages/core/lib/Icon.astro
index cfd0d9cb..b98f9309 100644
--- a/packages/core/lib/Icon.astro
+++ b/packages/core/lib/Icon.astro
@@ -1,28 +1,90 @@
---
-import load, { fallback, normalizeProps } from './utils.ts';
-export { Props } from './Props.ts';
+// @ts-expect-error
+import icons, { config } from "virtual:astro-icon";
+import { getIconData, iconToSVG } from "@iconify/utils";
+import { cache } from "./cache.js";
-let { name, pack, title, optimize = true, class: className, ...inputProps } = Astro.props;
-let props = {};
-if (pack) {
- name = `${pack}:${name}`;
+interface Props {
+ name: string;
+ title?: string;
+ size?: number;
+ width?: number;
+ height?: number;
}
-let innerHTML = '';
-try {
- const svg = await load(name, { ...inputProps, class: className }, optimize);
- innerHTML = svg.innerHTML;
- props = svg.props;
-} catch (e) {
- if (import.meta.env.MODE === 'production') {
- throw new Error(`[astro-icon] Unable to load icon "${name}"!
-${e}`)
+const req = Astro.request;
+const { name = "", title, ...props } = Astro.props;
+const map = cache.get(req) ?? new Map();
+const i = map.get(name) ?? 0;
+map.set(name, i + 1);
+cache.set(req, map);
+
+const includeSymbol = i === 0;
+
+let [set, icon] = name.split(":");
+
+const collection = icons.find((collection) => {
+ if (icon === undefined) {
+ return set in collection.icons;
+ }
+ return collection.prefix === set;
+});
+
+if (!collection) {
+ const err: any = new Error(`Unable to locate "${name}" icon!`);
+
+ if (import.meta.env.DEV) {
+ const { include = {} } = config;
+ const sets = Object.keys(include);
+ if (set && icon) {
+ if (!sets.includes(set)) {
+ err.hint = `It looks like the "${set}" set is not included in your configuration.\n\nDo you need to add the "${set}" set?`;
+ }
+ } else {
+ if (sets.length === 1) {
+ err.hint = `The "${sets[0]}" set does not include a "${set}" icon.\n\nDid you forget to include the icon or make a typo?`;
+ } else {
+ err.hint = `None of your icon sets ("${sets.join(
+ `" | "`
+ )}") include a "${set}" icon.\n\nDid you forget to include this icon in your configuration?`;
+ }
+ }
+ }
+
+ throw err;
+}
+
+const iconData = getIconData(collection, icon ?? set);
+if (!iconData) {
+ const err: any = new Error(`Unable to locate "${name}" icon!`);
+
+ if (import.meta.env.DEV) {
+ const { include = {} } = config;
+ const [maybeStar] = include[set];
+ if (maybeStar === "*") {
+ err.hint = `The "${set}" set does not include an icon named "${icon}".\n\nIs this a typo? Is the "@iconify/json" dependency out of date?`;
+ } else {
+ err.hint = `The "${set}" set is not configured to include an icon named "${icon}".\n\nDo you need to add it to your configuration?`;
}
- innerHTML = fallback.innerHTML;
- props = { ...fallback.props, ...normalizeProps(inputProps) };
- title = `Failed to load "${name}"!`
- console.error(e);
+ }
+
+ throw err;
+}
+
+const id = `ai:${collection.prefix}:${icon ?? set}`;
+
+if (props.size) {
+ props.width = props.size;
+ props.height = props.size;
+ delete props.size;
}
+const renderData = iconToSVG(iconData);
+const normalizedProps = { ...renderData.attributes, ...props };
+const normalizedBody = renderData.body;
---
-