I was very glad to see that v2.3.1 include Nuxt layers in icon scanner context. Though it only seems to work in ClientBundle mode and no layer support for the serverBundle.
packages/nuxt-base/ <-- a Nuxt Layer
app/assets/icons/logo.svg
nuxt.config.ts
examples/my-app/ <-- rootDir, extends nuxt-base
<NuxtIcon name="icon:logo" />
diff --git a/node_modules/@nuxt/icon/dist/module.mjs b/node_modules/@nuxt/icon/dist/module.mjs
index b233609a..ae5dc5d0 100644
--- a/node_modules/@nuxt/icon/dist/module.mjs
+++ b/node_modules/@nuxt/icon/dist/module.mjs
@@ -394,7 +394,7 @@ class NuxtIconModuleContext {
if (!resolved.collections)
resolved.collections = resolved.remote ? collectionNames : await discoverInstalledCollections(getResolvePaths(this.nuxt));
const collections = await Promise.all(
- (resolved.collections || []).map((c) => resolveCollection(c, this.nuxt.options.rootDir))
+ (resolved.collections || []).map((c) => resolveCollection(c, this.nuxt.options.rootDir, this.nuxt))
);
return {
disabled: false,
@@ -422,7 +422,7 @@ class NuxtIconModuleContext {
}
async _loadCustomCollection() {
return Promise.all(
- (this.options.customCollections || []).map((collection) => loadCustomCollection(collection, this.nuxt.options.rootDir))
+ (this.options.customCollections || []).map((collection) => loadCustomCollection(collection, this.nuxt.options.rootDir, this.nuxt))
);
}
async loadClientBundleCollections() {
diff --git a/node_modules/@nuxt/icon/dist/shared/icon.i156g29l.mjs b/node_modules/@nuxt/icon/dist/shared/icon.i156g29l.mjs
index 8deea070..75fb930f 100644
--- a/node_modules/@nuxt/icon/dist/shared/icon.i156g29l.mjs
+++ b/node_modules/@nuxt/icon/dist/shared/icon.i156g29l.mjs
@@ -1,4 +1,4 @@
-import { join, isAbsolute, parse, normalize } from 'node:path';
+import { join, parse, normalize } from 'node:path';
import fs from 'node:fs/promises';
import { consola } from 'consola';
import { glob } from 'tinyglobby';
@@ -233,11 +233,11 @@ const logger$1 = consola.withTag("nuxt:icon");
function hasFullCollection(resolvePaths) {
return isPackageExists("@iconify/json", { paths: resolvePaths });
}
-async function resolveCollection(collection, rootDir) {
+async function resolveCollection(collection, rootDir, nuxt) {
if (typeof collection === "string")
return collection;
if ("dir" in collection) {
- return await loadCustomCollection(collection, rootDir);
+ return await loadCustomCollection(collection, rootDir, nuxt);
}
return collection;
}
@@ -245,37 +245,47 @@ function getCollectionPath(collection, resolvePaths) {
return hasFullCollection(resolvePaths) ? `@iconify/json/json/${collection}.json` : `@iconify-json/${collection}/icons.json`;
}
const validIconNameRE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
-async function loadCustomCollection(collection, rootDir) {
+async function loadCustomCollection(collection, rootDir, nuxt) {
if ("dir" in collection) {
- return parseCustomCollection(collection, rootDir);
+ return parseCustomCollection(collection, rootDir, nuxt);
}
logger$1.success(`Nuxt Icon loaded local collection \`${collection.prefix}\` with ${Object.keys(collection.icons).length} icons`);
return collection;
}
-async function parseCustomCollection(collection, rootDir) {
- const dir = isAbsolute(collection.dir) ? collection.dir : join(rootDir, collection.dir);
+async function parseCustomCollection(collection, rootDir, nuxt) {
+ const layersCopy = [...nuxt.options._layers].toReversed();
const {
// TODO: next major flip this
normalizeIconName = true,
recursive = false
} = collection;
const pattern = recursive ? "**/*.svg" : "*.svg";
- const files = (await glob([pattern], {
- cwd: dir,
- onlyFiles: true,
- expandDirectories: recursive
- })).sort();
- const parsedIcons = await Promise.all(files.map(async (file) => {
- const { dir: path, name: filename } = parse(file);
- const pathNormalized = path ? normalize(path).replace(/[/\\]/g, "-") : "";
- let name = pathNormalized ? `${pathNormalized}-${filename}` : filename;
+ const files = []
+ for (const layer of layersCopy) {
+ const cwd = join(layer.cwd, collection.dir)
+ files.push(...(await glob([pattern], {
+ cwd,
+ onlyFiles: true,
+ expandDirectories: recursive,
+ })).sort().map(result => {
+ return {
+ absolutePath: cwd,
+ result: result,
+ };
+ }))
+ }
+ const parsedIcons = await Promise.all(files.map(async (result) => {
+ const { absolutePath, result: file } = result;
+ const { dir: path, name: filename } = parse(file)
+ const pathNormalized = path ? normalize(path).replace(/[/\\]/g, '-') : ''
+ let name = pathNormalized ? `${pathNormalized}-${filename}` : filename
if (normalizeIconName && !validIconNameRE.test(name)) {
const normalized = name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
if (normalized !== name)
logger$1.warn(`Custom icon \`${name}\` is normalized to \`${normalized}\`, we recommend to change the file name to match the icon name, or pass \`normalizeIconName: false\` to your custom collection options`);
name = normalized;
}
- let svg = await fs.readFile(join(dir, file), "utf-8");
+ let svg = await fs.readFile(join(absolutePath, file), "utf-8");
const cleanupIdx = svg.indexOf("<svg");
if (cleanupIdx > 0)
svg = svg.slice(cleanupIdx);
I was very glad to see that v2.3.1 include Nuxt layers in icon scanner context. Though it only seems to work in ClientBundle mode and no layer support for the serverBundle.
Environment
@nuxt/icon: 2.3.1server(default),serverBundle.externalizeIconsJson: trueReproduction
Base layer that ships icons, app that extends it:
Base layer
nuxt.config.ts:Use it and run the app from
examples/my-app:Describe the bug
icon:logolives in the base layer, but it is missing on the server (renders on the client only).Two directory-resolution strategies exist and only one is layer-aware:
loadClientBundleCollections()(dist/module.mjs) walks every layer:parseCustomCollection()(dist/shared/icon.*.mjs) resolves a single dir fromrootDirand ignoresnuxt.options._layers:resolveServerBundle → loadCustomCollection → parseCustomCollection), sodir: './app/assets/icons'resolves to<rootDir>/app/assets/iconsand the layer's SVGs are never found.Additional context
Suggested fix: make
parseCustomCollectioniteratenuxt.options._layersand globjoin(layer.cwd, collection.dir)per layer (app layers last so they can override), mirroring the scanner. Requires threadingnuxtthroughresolveCollection/loadCustomCollection. We currently run this as apatch-packagepatch and it restores client/server parity. Happy to open a PR.Local patch applied: (@nuxt+icon+2.3.1.patch)