@ciderpress/ui 1.0.0-rc.4 → 1.0.0-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,41 +1,125 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import mdi from '@iconify-json/mdi/icons.json' with { type: 'json' }
|
|
6
|
-
import pixelarticons from '@iconify-json/pixelarticons/icons.json' with { type: 'json' }
|
|
7
|
-
import simpleIcons from '@iconify-json/simple-icons/icons.json' with { type: 'json' }
|
|
8
|
-
import skillIcons from '@iconify-json/skill-icons/icons.json' with { type: 'json' }
|
|
9
|
-
import vscodeIcons from '@iconify-json/vscode-icons/icons.json' with { type: 'json' }
|
|
10
|
-
import { addCollection, Icon } from '@iconify/react'
|
|
11
|
-
|
|
12
|
-
// Register all icon collections for offline Iconify resolution.
|
|
13
|
-
// `addCollection` is called purely for its side effect of mutating
|
|
14
|
-
// Iconify's internal registry. Holding the return values in a
|
|
15
|
-
// throwaway `const` keeps the call list a single expression statement
|
|
16
|
-
// rather than nine misleading named exports.
|
|
17
|
-
// oxlint-disable-next-line no-unused-vars
|
|
18
|
-
const _iconCollectionsLoaded = [
|
|
19
|
-
addCollection(cast(pixelarticons)),
|
|
20
|
-
addCollection(cast(devicon)),
|
|
21
|
-
addCollection(cast(mdi)),
|
|
22
|
-
addCollection(cast(simpleIcons)),
|
|
23
|
-
addCollection(cast(skillIcons)),
|
|
24
|
-
addCollection(cast(catppuccin)),
|
|
25
|
-
addCollection(cast(logos)),
|
|
26
|
-
addCollection(cast(vscodeIcons)),
|
|
27
|
-
addCollection(cast(materialIconTheme)),
|
|
28
|
-
] as const
|
|
29
|
-
|
|
30
|
-
export { Icon }
|
|
1
|
+
import { addCollection, Icon as IconifyIcon } from '@iconify/react'
|
|
2
|
+
import type { IconProps } from '@iconify/react'
|
|
3
|
+
import type React from 'react'
|
|
4
|
+
import { useEffect, useState } from 'react'
|
|
31
5
|
|
|
32
6
|
/**
|
|
33
|
-
*
|
|
7
|
+
* Per-collection lazy loaders keyed by Iconify prefix.
|
|
8
|
+
*
|
|
9
|
+
* Each entry is a bare dynamic `import()` so the consuming site's Rsbuild
|
|
10
|
+
* build emits **one async chunk per collection** instead of folding all nine
|
|
11
|
+
* `icons.json` files into a single eager ~30MB chunk pulled on every route.
|
|
12
|
+
* Two consequences fall out of that:
|
|
13
|
+
*
|
|
14
|
+
* - **Deployability** — the largest collection (`logos`, ~8MB) stays well
|
|
15
|
+
* under per-file host caps (Cloudflare Pages rejects files >25MB), where
|
|
16
|
+
* the combined blob failed outright.
|
|
17
|
+
* - **Performance** — a page only downloads the collections it actually
|
|
18
|
+
* references, not the full set on first paint.
|
|
19
|
+
*
|
|
20
|
+
* The specifiers are string literals (not computed) so the bundler can
|
|
21
|
+
* statically resolve every chunk at build time.
|
|
22
|
+
*
|
|
23
|
+
* @private
|
|
24
|
+
*/
|
|
25
|
+
const COLLECTION_LOADERS: Record<string, () => Promise<{ readonly default: unknown }>> = {
|
|
26
|
+
catppuccin: () => import('@iconify-json/catppuccin/icons.json'),
|
|
27
|
+
devicon: () => import('@iconify-json/devicon/icons.json'),
|
|
28
|
+
logos: () => import('@iconify-json/logos/icons.json'),
|
|
29
|
+
'material-icon-theme': () => import('@iconify-json/material-icon-theme/icons.json'),
|
|
30
|
+
mdi: () => import('@iconify-json/mdi/icons.json'),
|
|
31
|
+
pixelarticons: () => import('@iconify-json/pixelarticons/icons.json'),
|
|
32
|
+
'simple-icons': () => import('@iconify-json/simple-icons/icons.json'),
|
|
33
|
+
'skill-icons': () => import('@iconify-json/skill-icons/icons.json'),
|
|
34
|
+
'vscode-icons': () => import('@iconify-json/vscode-icons/icons.json'),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Cache of in-flight / settled collection registrations keyed by prefix.
|
|
39
|
+
* Guarantees each collection's chunk is fetched and merged into Iconify's
|
|
40
|
+
* registry exactly once, regardless of how many `<Icon>` instances on a
|
|
41
|
+
* page reference it.
|
|
42
|
+
*
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
const collectionCache = new Map<string, Promise<void>>()
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Offline-registered Iconify icon.
|
|
49
|
+
*
|
|
50
|
+
* Renders `@iconify/react`'s `Icon` unchanged, but registers the icon's
|
|
51
|
+
* collection on demand: the first time a prefix is seen the matching
|
|
52
|
+
* `@iconify-json` chunk is dynamically imported and merged into Iconify's
|
|
53
|
+
* registry, then a re-render paints the resolved SVG. Because `IconifyIcon`
|
|
54
|
+
* reads the live registry on every render, an icon appears as soon as its
|
|
55
|
+
* collection chunk resolves.
|
|
56
|
+
*
|
|
57
|
+
* @param props - Standard `@iconify/react` icon props; `icon` is the
|
|
58
|
+
* `prefix:name` identifier (e.g. `devicon:typescript`)
|
|
59
|
+
* @returns The Iconify icon element
|
|
60
|
+
*/
|
|
61
|
+
export function Icon(props: IconProps): React.ReactElement {
|
|
62
|
+
const prefix = resolvePrefix(props.icon)
|
|
63
|
+
const [, markRegistered] = useState(false)
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
ensureCollection(prefix).then(() => markRegistered(true))
|
|
67
|
+
}, [prefix])
|
|
68
|
+
|
|
69
|
+
return <IconifyIcon {...props} />
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Dynamically import and register the collection for a prefix, once.
|
|
74
|
+
*
|
|
75
|
+
* Returns the cached registration promise on repeat calls so the chunk is
|
|
76
|
+
* fetched a single time. Unknown prefixes (no bundled collection) resolve
|
|
77
|
+
* immediately — `IconifyIcon` falls back to its own resolution for those.
|
|
78
|
+
*
|
|
79
|
+
* @private
|
|
80
|
+
* @param prefix - Iconify collection prefix (e.g. `logos`)
|
|
81
|
+
* @returns Promise that settles once the collection is registered
|
|
82
|
+
*/
|
|
83
|
+
function ensureCollection(prefix: string): Promise<void> {
|
|
84
|
+
const cached = collectionCache.get(prefix)
|
|
85
|
+
if (cached !== undefined) {
|
|
86
|
+
return cached
|
|
87
|
+
}
|
|
88
|
+
const loader = COLLECTION_LOADERS[prefix]
|
|
89
|
+
if (loader === undefined) {
|
|
90
|
+
return Promise.resolve()
|
|
91
|
+
}
|
|
92
|
+
const registration = loader().then(registerModule)
|
|
93
|
+
collectionCache.set(prefix, registration)
|
|
94
|
+
return registration
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Merge a dynamically imported `icons.json` module into Iconify's registry.
|
|
99
|
+
*
|
|
100
|
+
* @private
|
|
101
|
+
* @param mod - Module namespace whose `default` export is the collection JSON
|
|
102
|
+
*/
|
|
103
|
+
function registerModule(mod: { readonly default: unknown }): void {
|
|
104
|
+
addCollection(mod.default as Parameters<typeof addCollection>[0])
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Extract the collection prefix from an Iconify identifier. Non-string icon
|
|
109
|
+
* inputs and identifiers without a `prefix:name` shape yield an empty string,
|
|
110
|
+
* which `ensureCollection` treats as "nothing to load".
|
|
34
111
|
*
|
|
35
112
|
* @private
|
|
36
|
-
* @param
|
|
37
|
-
* @returns
|
|
113
|
+
* @param icon - The `icon` prop passed to `<Icon>`
|
|
114
|
+
* @returns The collection prefix, or `''` when none can be determined
|
|
38
115
|
*/
|
|
39
|
-
function
|
|
40
|
-
|
|
116
|
+
function resolvePrefix(icon: IconProps['icon']): string {
|
|
117
|
+
if (typeof icon !== 'string') {
|
|
118
|
+
return ''
|
|
119
|
+
}
|
|
120
|
+
const parts = icon.split(':')
|
|
121
|
+
if (parts.length < 2) {
|
|
122
|
+
return ''
|
|
123
|
+
}
|
|
124
|
+
return parts[0]
|
|
41
125
|
}
|
package/package.json
CHANGED
|
@@ -1,41 +1,125 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import mdi from '@iconify-json/mdi/icons.json' with { type: 'json' }
|
|
6
|
-
import pixelarticons from '@iconify-json/pixelarticons/icons.json' with { type: 'json' }
|
|
7
|
-
import simpleIcons from '@iconify-json/simple-icons/icons.json' with { type: 'json' }
|
|
8
|
-
import skillIcons from '@iconify-json/skill-icons/icons.json' with { type: 'json' }
|
|
9
|
-
import vscodeIcons from '@iconify-json/vscode-icons/icons.json' with { type: 'json' }
|
|
10
|
-
import { addCollection, Icon } from '@iconify/react'
|
|
11
|
-
|
|
12
|
-
// Register all icon collections for offline Iconify resolution.
|
|
13
|
-
// `addCollection` is called purely for its side effect of mutating
|
|
14
|
-
// Iconify's internal registry. Holding the return values in a
|
|
15
|
-
// throwaway `const` keeps the call list a single expression statement
|
|
16
|
-
// rather than nine misleading named exports.
|
|
17
|
-
// oxlint-disable-next-line no-unused-vars
|
|
18
|
-
const _iconCollectionsLoaded = [
|
|
19
|
-
addCollection(cast(pixelarticons)),
|
|
20
|
-
addCollection(cast(devicon)),
|
|
21
|
-
addCollection(cast(mdi)),
|
|
22
|
-
addCollection(cast(simpleIcons)),
|
|
23
|
-
addCollection(cast(skillIcons)),
|
|
24
|
-
addCollection(cast(catppuccin)),
|
|
25
|
-
addCollection(cast(logos)),
|
|
26
|
-
addCollection(cast(vscodeIcons)),
|
|
27
|
-
addCollection(cast(materialIconTheme)),
|
|
28
|
-
] as const
|
|
29
|
-
|
|
30
|
-
export { Icon }
|
|
1
|
+
import { addCollection, Icon as IconifyIcon } from '@iconify/react'
|
|
2
|
+
import type { IconProps } from '@iconify/react'
|
|
3
|
+
import type React from 'react'
|
|
4
|
+
import { useEffect, useState } from 'react'
|
|
31
5
|
|
|
32
6
|
/**
|
|
33
|
-
*
|
|
7
|
+
* Per-collection lazy loaders keyed by Iconify prefix.
|
|
8
|
+
*
|
|
9
|
+
* Each entry is a bare dynamic `import()` so the consuming site's Rsbuild
|
|
10
|
+
* build emits **one async chunk per collection** instead of folding all nine
|
|
11
|
+
* `icons.json` files into a single eager ~30MB chunk pulled on every route.
|
|
12
|
+
* Two consequences fall out of that:
|
|
13
|
+
*
|
|
14
|
+
* - **Deployability** — the largest collection (`logos`, ~8MB) stays well
|
|
15
|
+
* under per-file host caps (Cloudflare Pages rejects files >25MB), where
|
|
16
|
+
* the combined blob failed outright.
|
|
17
|
+
* - **Performance** — a page only downloads the collections it actually
|
|
18
|
+
* references, not the full set on first paint.
|
|
19
|
+
*
|
|
20
|
+
* The specifiers are string literals (not computed) so the bundler can
|
|
21
|
+
* statically resolve every chunk at build time.
|
|
22
|
+
*
|
|
23
|
+
* @private
|
|
24
|
+
*/
|
|
25
|
+
const COLLECTION_LOADERS: Record<string, () => Promise<{ readonly default: unknown }>> = {
|
|
26
|
+
catppuccin: () => import('@iconify-json/catppuccin/icons.json'),
|
|
27
|
+
devicon: () => import('@iconify-json/devicon/icons.json'),
|
|
28
|
+
logos: () => import('@iconify-json/logos/icons.json'),
|
|
29
|
+
'material-icon-theme': () => import('@iconify-json/material-icon-theme/icons.json'),
|
|
30
|
+
mdi: () => import('@iconify-json/mdi/icons.json'),
|
|
31
|
+
pixelarticons: () => import('@iconify-json/pixelarticons/icons.json'),
|
|
32
|
+
'simple-icons': () => import('@iconify-json/simple-icons/icons.json'),
|
|
33
|
+
'skill-icons': () => import('@iconify-json/skill-icons/icons.json'),
|
|
34
|
+
'vscode-icons': () => import('@iconify-json/vscode-icons/icons.json'),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Cache of in-flight / settled collection registrations keyed by prefix.
|
|
39
|
+
* Guarantees each collection's chunk is fetched and merged into Iconify's
|
|
40
|
+
* registry exactly once, regardless of how many `<Icon>` instances on a
|
|
41
|
+
* page reference it.
|
|
42
|
+
*
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
const collectionCache = new Map<string, Promise<void>>()
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Offline-registered Iconify icon.
|
|
49
|
+
*
|
|
50
|
+
* Renders `@iconify/react`'s `Icon` unchanged, but registers the icon's
|
|
51
|
+
* collection on demand: the first time a prefix is seen the matching
|
|
52
|
+
* `@iconify-json` chunk is dynamically imported and merged into Iconify's
|
|
53
|
+
* registry, then a re-render paints the resolved SVG. Because `IconifyIcon`
|
|
54
|
+
* reads the live registry on every render, an icon appears as soon as its
|
|
55
|
+
* collection chunk resolves.
|
|
56
|
+
*
|
|
57
|
+
* @param props - Standard `@iconify/react` icon props; `icon` is the
|
|
58
|
+
* `prefix:name` identifier (e.g. `devicon:typescript`)
|
|
59
|
+
* @returns The Iconify icon element
|
|
60
|
+
*/
|
|
61
|
+
export function Icon(props: IconProps): React.ReactElement {
|
|
62
|
+
const prefix = resolvePrefix(props.icon)
|
|
63
|
+
const [, markRegistered] = useState(false)
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
ensureCollection(prefix).then(() => markRegistered(true))
|
|
67
|
+
}, [prefix])
|
|
68
|
+
|
|
69
|
+
return <IconifyIcon {...props} />
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Dynamically import and register the collection for a prefix, once.
|
|
74
|
+
*
|
|
75
|
+
* Returns the cached registration promise on repeat calls so the chunk is
|
|
76
|
+
* fetched a single time. Unknown prefixes (no bundled collection) resolve
|
|
77
|
+
* immediately — `IconifyIcon` falls back to its own resolution for those.
|
|
78
|
+
*
|
|
79
|
+
* @private
|
|
80
|
+
* @param prefix - Iconify collection prefix (e.g. `logos`)
|
|
81
|
+
* @returns Promise that settles once the collection is registered
|
|
82
|
+
*/
|
|
83
|
+
function ensureCollection(prefix: string): Promise<void> {
|
|
84
|
+
const cached = collectionCache.get(prefix)
|
|
85
|
+
if (cached !== undefined) {
|
|
86
|
+
return cached
|
|
87
|
+
}
|
|
88
|
+
const loader = COLLECTION_LOADERS[prefix]
|
|
89
|
+
if (loader === undefined) {
|
|
90
|
+
return Promise.resolve()
|
|
91
|
+
}
|
|
92
|
+
const registration = loader().then(registerModule)
|
|
93
|
+
collectionCache.set(prefix, registration)
|
|
94
|
+
return registration
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Merge a dynamically imported `icons.json` module into Iconify's registry.
|
|
99
|
+
*
|
|
100
|
+
* @private
|
|
101
|
+
* @param mod - Module namespace whose `default` export is the collection JSON
|
|
102
|
+
*/
|
|
103
|
+
function registerModule(mod: { readonly default: unknown }): void {
|
|
104
|
+
addCollection(mod.default as Parameters<typeof addCollection>[0])
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Extract the collection prefix from an Iconify identifier. Non-string icon
|
|
109
|
+
* inputs and identifiers without a `prefix:name` shape yield an empty string,
|
|
110
|
+
* which `ensureCollection` treats as "nothing to load".
|
|
34
111
|
*
|
|
35
112
|
* @private
|
|
36
|
-
* @param
|
|
37
|
-
* @returns
|
|
113
|
+
* @param icon - The `icon` prop passed to `<Icon>`
|
|
114
|
+
* @returns The collection prefix, or `''` when none can be determined
|
|
38
115
|
*/
|
|
39
|
-
function
|
|
40
|
-
|
|
116
|
+
function resolvePrefix(icon: IconProps['icon']): string {
|
|
117
|
+
if (typeof icon !== 'string') {
|
|
118
|
+
return ''
|
|
119
|
+
}
|
|
120
|
+
const parts = icon.split(':')
|
|
121
|
+
if (parts.length < 2) {
|
|
122
|
+
return ''
|
|
123
|
+
}
|
|
124
|
+
return parts[0]
|
|
41
125
|
}
|