Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,31 @@ import { getLayerDirectories } from '@nuxt/kit'
import type { Nuxt } from '@nuxt/schema'
import type { IconifyJSON } from '@iconify/types'
import type { CustomCollection, ModuleOptions, RemoteCollection } from './types'
import { fullCollectionCache } from './core/collections'

const fullCollectionCacheByNuxt = new WeakMap<Nuxt, { paths: string, value?: boolean }>()

export function getResolvePaths(nuxt: Nuxt): string[] {
const layerDirs = getLayerDirectories(nuxt).map(
dir => dir.root,
)

return Array.from(
const paths = Array.from(
new Set(
[nuxt.options.rootDir, nuxt.options.workspaceDir, ...layerDirs].filter(
Boolean,
),
),
)

const key = paths.join('\0')
let cache = fullCollectionCacheByNuxt.get(nuxt)
Comment thread
onmax marked this conversation as resolved.
Resolved
if (cache?.paths !== key) {
cache = { paths: key }
fullCollectionCacheByNuxt.set(nuxt, cache)
}
fullCollectionCache.set(paths, cache)
return paths
}

type ConfiguredCollection = string | IconifyJSON | CustomCollection | RemoteCollection
Expand Down
9 changes: 9 additions & 0 deletions src/core/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ import type { CustomCollection, ServerBundleOptions, RemoteCollection } from './

const logger = consola.withTag('nuxt:icon')

/**
* Answers for path lists from `getResolvePaths()`, shared by one Nuxt instance. Each check
* resolves `@iconify/json` from every layer root, and the module asks once per collection.
*/
export const fullCollectionCache = new WeakMap<string[], { value?: boolean }>()

export function hasFullCollection(resolvePaths: string[]): boolean {
const cache = fullCollectionCache.get(resolvePaths)
if (cache)
return cache.value ??= isPackageExists('@iconify/json', { paths: resolvePaths })
return isPackageExists('@iconify/json', { paths: resolvePaths })
}

Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/full-collection-check/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// The test installs `@nuxt/icon` through `overrides`, so the module is loaded by Vitest and its `local-pkg` mock applies
export default defineNuxtConfig({})
87 changes: 87 additions & 0 deletions test/full-collection-check.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { fileURLToPath } from 'node:url'
import { loadNuxt } from '@nuxt/kit'
import { isPackageExists } from 'local-pkg'
import { beforeEach, expect, it, onTestFinished, vi } from 'vitest'
import { getResolvePaths } from '../src/collections'
import { getCollectionPath } from '../src/core/collections'
import Module from '../src/module'

vi.mock('local-pkg', async (importOriginal) => {
const mod = await importOriginal<typeof import('local-pkg')>()
return { ...mod, isPackageExists: vi.fn(mod.isPackageExists) }
})

beforeEach(() => {
vi.mocked(isPackageExists).mockClear()
})

function fullCollectionChecks() {
return vi.mocked(isPackageExists).mock.calls.filter(([name]) => name === '@iconify/json')
}

function createNuxt() {
return { options: { rootDir: process.cwd(), workspaceDir: process.cwd(), _layers: [] } } as never
}

it('checks for @iconify/json once per build', async () => {
// Answer as if @iconify/json is not installed, so the generated imports don't depend on the checkout
const { isPackageExists: actual } = await vi.importActual<typeof import('local-pkg')>('local-pkg')
vi.mocked(isPackageExists).mockImplementation((name, options) => name === '@iconify/json' ? false : actual(name, options))
onTestFinished(() => {
vi.mocked(isPackageExists).mockImplementation(actual)
})

// `loadNuxt` runs the `nitro:config` hook, and collections are discovered from the `@iconify-json/*` dev dependencies
const nuxt = await loadNuxt({
cwd: fileURLToPath(new URL('./fixtures/full-collection-check', import.meta.url)),
dev: false,
overrides: {
modules: [Module],
icon: {
serverBundle: { externalizeIconsJson: true },
},
},
})
onTestFinished(() => nuxt.close())

const template = nuxt.options.build.templates.find(t => t.filename === 'nuxt-icon-server-bundle.mjs')!
const code = await template.getContents!({ nuxt, app: {}, options: {} } as never)
const traceInclude = (nuxt as unknown as { _nitro: { options: { externals: { traceInclude: string[] } } } })._nitro.options.externals.traceInclude

expect(fullCollectionChecks()).toHaveLength(1)
for (const collection of ['logos', 'ph', 'uil']) {
expect(code).toContain(`'${collection}': () => import('@iconify-json/${collection}/icons.json', { with: { type: 'json' } })`)
Comment thread
onmax marked this conversation as resolved.
Resolved
expect(traceInclude).toContainEqual(expect.stringMatching(`/@iconify-json/${collection}/icons.json$`))
}
})

it('checks again when the paths of a Nuxt instance change', () => {
const nuxt = createNuxt() as { options: { workspaceDir: string } }
vi.mocked(isPackageExists).mockReturnValueOnce(true)
expect(getCollectionPath('ph', getResolvePaths(nuxt as never))).toBe('@iconify/json/json/ph.json')
nuxt.options.workspaceDir = '/elsewhere'
vi.mocked(isPackageExists).mockReturnValueOnce(false)
expect(getCollectionPath('ph', getResolvePaths(nuxt as never))).toBe('@iconify-json/ph/icons.json')
expect(fullCollectionChecks()).toHaveLength(2)
})

it('reuses the answer within a Nuxt instance and checks again in a new one', () => {
const nuxt = createNuxt()
vi.mocked(isPackageExists).mockReturnValueOnce(true)
expect(getCollectionPath('ph', getResolvePaths(nuxt))).toBe('@iconify/json/json/ph.json')
expect(getCollectionPath('uil', getResolvePaths(nuxt))).toBe('@iconify/json/json/uil.json')
expect(fullCollectionChecks()).toHaveLength(1)

vi.mocked(isPackageExists).mockReturnValueOnce(false)
expect(getCollectionPath('ph', getResolvePaths(createNuxt()))).toBe('@iconify-json/ph/icons.json')
expect(fullCollectionChecks()).toHaveLength(2)
})

it('checks on every call for paths that do not come from getResolvePaths', () => {
const resolvePaths = [process.cwd()]
vi.mocked(isPackageExists).mockReturnValueOnce(true)
expect(getCollectionPath('ph', resolvePaths)).toBe('@iconify/json/json/ph.json')
vi.mocked(isPackageExists).mockReturnValueOnce(false)
expect(getCollectionPath('ph', resolvePaths)).toBe('@iconify-json/ph/icons.json')
expect(fullCollectionChecks()).toHaveLength(2)
})
Loading