mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +08:00
feat: Lazy load icons
Co-authored-by: Alois Klink <alois@aloisklink.com>
This commit is contained in:
parent
c68ae309e5
commit
0edfab1048
@ -28,8 +28,13 @@
|
||||
startOnLoad: false,
|
||||
logLevel: 0,
|
||||
});
|
||||
const logos = await fetch('https://unpkg.com/@iconify-json/logos/icons.json');
|
||||
mermaid.registerIconPacks(await logos.json());
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: 'logos',
|
||||
loader: () =>
|
||||
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
|
||||
},
|
||||
]);
|
||||
await mermaid.run();
|
||||
if (window.Cypress) {
|
||||
window.rendered = true;
|
||||
|
@ -232,17 +232,25 @@
|
||||
service s3(logos:aws-s3)[Cloud Store]
|
||||
service ec2(logos:aws-ec2)[Server]
|
||||
service api(logos:aws-api-gateway)[Api Gateway]
|
||||
service fa(fa:image)[Font Awesome Icon]
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
logLevel: 0,
|
||||
});
|
||||
const logos = await fetch('https://unpkg.com/@iconify-json/logos/icons.json');
|
||||
mermaid.registerIconPacks(await logos.json());
|
||||
mermaid.init();
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: 'logos',
|
||||
loader: () =>
|
||||
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
|
||||
},
|
||||
{
|
||||
name: 'fa',
|
||||
loader: () =>
|
||||
fetch('https://unpkg.com/@iconify-json/fa6-regular/icons.json').then((res) =>
|
||||
res.json()
|
||||
),
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -224,17 +224,17 @@ Used to register external diagram types.
|
||||
|
||||
### registerIconPacks
|
||||
|
||||
• **registerIconPacks**: (...`iconPacks`: `IconifyJSON`\[]) => `void`
|
||||
• **registerIconPacks**: (`iconLoaders`: `IconLoader`\[]) => `void`
|
||||
|
||||
#### Type declaration
|
||||
|
||||
▸ (`...iconPacks`): `void`
|
||||
▸ (`iconLoaders`): `void`
|
||||
|
||||
##### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------------- | :--------------- |
|
||||
| `...iconPacks` | `IconifyJSON`\[] |
|
||||
| Name | Type |
|
||||
| :------------ | :-------------- |
|
||||
| `iconLoaders` | `IconLoader`\[] |
|
||||
|
||||
##### Returns
|
||||
|
||||
|
@ -197,41 +197,69 @@ By default, architecture diagram supports the following icons: `cloud`, `databas
|
||||
Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps below.
|
||||
|
||||
The icon packs available can be found at [icones.js.org](https://icones.js.org/).
|
||||
We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram.
|
||||
|
||||
Using JSON file directly from CDN:
|
||||
|
||||
```js
|
||||
import mermaid from 'CDN/mermaid.esm.mjs';
|
||||
|
||||
// You have to call `initialize` with startOnLoad:false before calling `registerIconPacks`,
|
||||
// to prevent mermaid from starting before the icons are loaded
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
logLevel: 0,
|
||||
});
|
||||
const logos = await fetch('https://unpkg.com/@iconify-json/logos/icons.json');
|
||||
mermaid.registerIconPacks(await logos.json());
|
||||
mermaid.init();
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: 'logos',
|
||||
loader: () =>
|
||||
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
Using packages and a bundler:
|
||||
|
||||
```js
|
||||
import mermaid from 'mermaid';
|
||||
// npm install @iconify-json/logos
|
||||
import { icons as logos } from '@iconify-json/logos';
|
||||
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
logLevel: 0,
|
||||
});
|
||||
mermaid.registerIconPacks(logos);
|
||||
mermaid.init();
|
||||
```bash
|
||||
npm install @iconify-json/logos
|
||||
```
|
||||
|
||||
After the icons are installed, they can be used in the architecture diagram by using the format "prefix:icon-name", where prefix comes from the icon pack you selected.
|
||||
With lazy loading
|
||||
|
||||
```js
|
||||
import mermaid from 'mermaid';
|
||||
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: 'logos',
|
||||
loader: () => import('@iconify-json/logos').then((module) => module.icons),
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
Without lazy loading
|
||||
|
||||
```js
|
||||
import mermaid from 'mermaid';
|
||||
import { icons } from '@iconify-json/logos';
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: icons.prefix, // To use the prefix defined in the icon pack
|
||||
icons,
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.
|
||||
|
||||
```mermaid-example
|
||||
architecture-beta
|
||||
group api(logos:aws-lambda)[API]
|
||||
|
||||
service db(logos:aws-aurora)[Database] in api
|
||||
service disk1(logos:aws-glacier)[Storage] in api
|
||||
service disk2(logos:aws-s3)[Storage] in api
|
||||
service server(logos:aws-ec2)[Server] in api
|
||||
|
||||
db:L -- R:server
|
||||
disk1:T -- B:server
|
||||
disk2:T -- B:db
|
||||
```
|
||||
|
||||
````
|
||||
```mermaid
|
||||
architecture-beta
|
||||
group api(logos:aws-lambda)[API]
|
||||
@ -245,29 +273,3 @@ architecture-beta
|
||||
disk1:T -- B:server
|
||||
disk2:T -- B:db
|
||||
```
|
||||
````
|
||||
|
||||
<div id="arch-example">loading...</div>
|
||||
|
||||
<script>
|
||||
const main = async () => {
|
||||
const logos = await fetch('https://unpkg.com/@iconify-json/logos/icons.json');
|
||||
mermaid.registerIconPacks(await logos.json());
|
||||
const svg = await window.render('d'+Date.now().toString(), `architecture-beta
|
||||
group api(logos:aws-api-gateway)[API]
|
||||
|
||||
service db(logos:aws-aurora)[Database] in api
|
||||
service disk1(logos:aws-glacier)[Storage] in api
|
||||
service disk2(logos:aws-s3)[Storage] in api
|
||||
service server(logos:aws-ec2)[Server] in api
|
||||
|
||||
db:L -- R:server
|
||||
disk1:T -- B:server
|
||||
disk2:T -- B:db`, {});
|
||||
document.getElementById('arch-example').innerHTML = svg;
|
||||
};
|
||||
|
||||
if (!import.meta.env.SSR) {
|
||||
setTimeout(main, 100);
|
||||
}
|
||||
</script>
|
||||
|
@ -34,7 +34,12 @@ import {
|
||||
} from './architectureTypes.js';
|
||||
import { drawEdges, drawGroups, drawJunctions, drawServices } from './svgDraw.js';
|
||||
|
||||
registerIconPacks(architectureIcons);
|
||||
registerIconPacks([
|
||||
{
|
||||
name: architectureIcons.prefix,
|
||||
icons: architectureIcons,
|
||||
},
|
||||
]);
|
||||
cytoscape.use(fcose);
|
||||
|
||||
function addServices(services: ArchitectureService[], cy: cytoscape.Core) {
|
||||
|
@ -212,7 +212,7 @@ export const drawGroups = async function (groupsEl: D3Element, cy: cytoscape.Cor
|
||||
if (data.icon) {
|
||||
const bkgElem = groupLabelContainer.append('g');
|
||||
bkgElem.html(
|
||||
`<g>${getIconSVG(data.icon, { height: groupIconSize, width: groupIconSize, fallbackPrefix: architectureIcons.prefix })}</g>`
|
||||
`<g>${await getIconSVG(data.icon, { height: groupIconSize, width: groupIconSize, fallbackPrefix: architectureIcons.prefix })}</g>`
|
||||
);
|
||||
bkgElem.attr(
|
||||
'transform',
|
||||
@ -297,11 +297,11 @@ export const drawServices = async function (
|
||||
// throw new Error(`Invalid SVG Icon name: "${service.icon}"`);
|
||||
// }
|
||||
bkgElem.html(
|
||||
`<g>${getIconSVG(service.icon, { height: iconSize, width: iconSize, fallbackPrefix: architectureIcons.prefix })}</g>`
|
||||
`<g>${await getIconSVG(service.icon, { height: iconSize, width: iconSize, fallbackPrefix: architectureIcons.prefix })}</g>`
|
||||
);
|
||||
} else if (service.iconText) {
|
||||
bkgElem.html(
|
||||
`<g>${getIconSVG('blank', { height: iconSize, width: iconSize, fallbackPrefix: architectureIcons.prefix })}</g>`
|
||||
`<g>${await getIconSVG('blank', { height: iconSize, width: iconSize, fallbackPrefix: architectureIcons.prefix })}</g>`
|
||||
);
|
||||
const textElemContainer = bkgElem.append('g');
|
||||
const fo = textElemContainer
|
||||
|
@ -86,9 +86,11 @@ onUnmounted(() => mut.disconnect());
|
||||
|
||||
const renderChart = async () => {
|
||||
console.log('rendering chart' + props.id + code.value);
|
||||
const hasDarkClass = document.documentElement.classList.contains('dark');
|
||||
const mermaidConfig = {
|
||||
securityLevel: 'loose',
|
||||
startOnLoad: false,
|
||||
theme: hasDarkClass ? 'dark' : 'default',
|
||||
};
|
||||
let svgCode = await render(props.id, code.value, mermaidConfig);
|
||||
// This is a hack to force v-html to re-render, otherwise the diagram disappears
|
||||
|
@ -2,29 +2,17 @@ import mermaid, { type MermaidConfig } from 'mermaid';
|
||||
import zenuml from '../../../../../mermaid-zenuml/dist/mermaid-zenuml.core.mjs';
|
||||
|
||||
const init = mermaid.registerExternalDiagrams([zenuml]);
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: 'logos',
|
||||
loader: () =>
|
||||
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
|
||||
},
|
||||
]);
|
||||
|
||||
export const render = async (id: string, code: string, config: MermaidConfig): Promise<string> => {
|
||||
await init;
|
||||
const hasDarkClass = document.documentElement.classList.contains('dark');
|
||||
const theme = hasDarkClass ? 'dark' : 'default';
|
||||
mermaid.initialize({ ...config, theme });
|
||||
mermaid.initialize(config);
|
||||
const { svg } = await mermaid.render(id, code);
|
||||
return svg;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
mermaid: typeof mermaid;
|
||||
render: typeof render;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
env: {
|
||||
SSR: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!import.meta.env.SSR) {
|
||||
window.mermaid = mermaid;
|
||||
window.render = render;
|
||||
}
|
||||
|
@ -159,42 +159,56 @@ By default, architecture diagram supports the following icons: `cloud`, `databas
|
||||
Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps below.
|
||||
|
||||
The icon packs available can be found at [icones.js.org](https://icones.js.org/).
|
||||
We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram.
|
||||
|
||||
Using JSON file directly from CDN:
|
||||
|
||||
```js
|
||||
import mermaid from 'CDN/mermaid.esm.mjs';
|
||||
|
||||
// You have to call `initialize` with startOnLoad:false before calling `registerIconPacks`,
|
||||
// to prevent mermaid from starting before the icons are loaded
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
logLevel: 0,
|
||||
});
|
||||
const logos = await fetch('https://unpkg.com/@iconify-json/logos/icons.json');
|
||||
mermaid.registerIconPacks(await logos.json());
|
||||
mermaid.init();
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: 'logos',
|
||||
loader: () =>
|
||||
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
Using packages and a bundler:
|
||||
|
||||
```js
|
||||
import mermaid from 'mermaid';
|
||||
// npm install @iconify-json/logos
|
||||
import { icons as logos } from '@iconify-json/logos';
|
||||
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
logLevel: 0,
|
||||
});
|
||||
mermaid.registerIconPacks(logos);
|
||||
mermaid.init();
|
||||
```bash
|
||||
npm install @iconify-json/logos
|
||||
```
|
||||
|
||||
After the icons are installed, they can be used in the architecture diagram by using the format "prefix:icon-name", where prefix comes from the icon pack you selected.
|
||||
With lazy loading
|
||||
|
||||
````
|
||||
```mermaid
|
||||
```js
|
||||
import mermaid from 'mermaid';
|
||||
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: 'logos',
|
||||
loader: () => import('@iconify-json/logos').then((module) => module.icons),
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
Without lazy loading
|
||||
|
||||
```js
|
||||
import mermaid from 'mermaid';
|
||||
import { icons } from '@iconify-json/logos';
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: icons.prefix, // To use the prefix defined in the icon pack
|
||||
icons,
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.
|
||||
|
||||
```mermaid-example
|
||||
architecture-beta
|
||||
group api(logos:aws-lambda)[API]
|
||||
|
||||
@ -207,29 +221,3 @@ architecture-beta
|
||||
disk1:T -- B:server
|
||||
disk2:T -- B:db
|
||||
```
|
||||
````
|
||||
|
||||
<div id="arch-example">loading...</div>
|
||||
|
||||
<script>
|
||||
const main = async () => {
|
||||
const logos = await fetch('https://unpkg.com/@iconify-json/logos/icons.json');
|
||||
mermaid.registerIconPacks(await logos.json());
|
||||
const svg = await window.render('d'+Date.now().toString(), `architecture-beta
|
||||
group api(logos:aws-api-gateway)[API]
|
||||
|
||||
service db(logos:aws-aurora)[Database] in api
|
||||
service disk1(logos:aws-glacier)[Storage] in api
|
||||
service disk2(logos:aws-s3)[Storage] in api
|
||||
service server(logos:aws-ec2)[Server] in api
|
||||
|
||||
db:L -- R:server
|
||||
disk1:T -- B:server
|
||||
disk2:T -- B:db`, {});
|
||||
document.getElementById('arch-example').innerHTML = svg;
|
||||
};
|
||||
|
||||
if (!import.meta.env.SSR) {
|
||||
setTimeout(main, 100);
|
||||
}
|
||||
</script>
|
||||
|
@ -3,21 +3,47 @@ import type { ExtendedIconifyIcon, IconifyIcon, IconifyJSON } from '@iconify/typ
|
||||
import type { IconifyIconCustomisations } from '@iconify/utils';
|
||||
import { getIconData, iconToHTML, iconToSVG, replaceIDs, stringToIcon } from '@iconify/utils';
|
||||
|
||||
interface AsyncIconLoader {
|
||||
name: string;
|
||||
loader: () => Promise<IconifyJSON>;
|
||||
}
|
||||
|
||||
interface SyncIconLoader {
|
||||
name: string;
|
||||
icons: IconifyJSON;
|
||||
}
|
||||
|
||||
export type IconLoader = AsyncIconLoader | SyncIconLoader;
|
||||
|
||||
export const unknownIcon: IconifyIcon = {
|
||||
body: '<g><rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/><text transform="translate(21.16 64.67)" style="fill: #fff; font-family: ArialMT, Arial; font-size: 67.75px;"><tspan x="0" y="0">?</tspan></text></g>',
|
||||
height: 80,
|
||||
width: 80,
|
||||
};
|
||||
|
||||
export const iconsStore = new Map<string, IconifyJSON>();
|
||||
const iconsStore = new Map<string, IconifyJSON>();
|
||||
const loaderStore = new Map<string, AsyncIconLoader['loader']>();
|
||||
|
||||
export const registerIconPacks = (...iconPacks: IconifyJSON[]) => {
|
||||
for (const pack of iconPacks) {
|
||||
iconsStore.set(pack.prefix, pack);
|
||||
export const registerIconPacks = (iconLoaders: IconLoader[]) => {
|
||||
for (const iconLoader of iconLoaders) {
|
||||
if (!iconLoader.name) {
|
||||
throw new Error(
|
||||
'Invalid icon loader. Must have a "name" property with non-empty string value.'
|
||||
);
|
||||
}
|
||||
log.debug('Registering icon pack:', iconLoader.name);
|
||||
if ('loader' in iconLoader) {
|
||||
loaderStore.set(iconLoader.name, iconLoader.loader);
|
||||
} else if ('icons' in iconLoader) {
|
||||
iconsStore.set(iconLoader.name, iconLoader.icons);
|
||||
} else {
|
||||
log.error('Invalid icon loader:', iconLoader);
|
||||
throw new Error('Invalid icon loader. Must have either "icons" or "loader" property.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getRegisteredIconData = (iconName: string, fallbackPrefix?: string) => {
|
||||
const getRegisteredIconData = async (iconName: string, fallbackPrefix?: string) => {
|
||||
const data = stringToIcon(iconName, true, fallbackPrefix !== undefined);
|
||||
if (!data) {
|
||||
throw new Error(`Invalid icon name: ${iconName}`);
|
||||
@ -26,9 +52,20 @@ const getRegisteredIconData = (iconName: string, fallbackPrefix?: string) => {
|
||||
if (!prefix) {
|
||||
throw new Error(`Icon name must contain a prefix: ${iconName}`);
|
||||
}
|
||||
const icons = iconsStore.get(prefix);
|
||||
let icons = iconsStore.get(prefix);
|
||||
if (!icons) {
|
||||
throw new Error(`Icon set not found: ${data.prefix}`);
|
||||
const loader = loaderStore.get(prefix);
|
||||
if (!loader) {
|
||||
throw new Error(`Icon set not found: ${data.prefix}`);
|
||||
}
|
||||
try {
|
||||
const loaded = await loader();
|
||||
icons = { ...loaded, prefix };
|
||||
iconsStore.set(prefix, icons);
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
throw new Error(`Failed to load icon set: ${data.prefix}`);
|
||||
}
|
||||
}
|
||||
const iconData = getIconData(icons, data.name);
|
||||
if (!iconData) {
|
||||
@ -37,22 +74,22 @@ const getRegisteredIconData = (iconName: string, fallbackPrefix?: string) => {
|
||||
return iconData;
|
||||
};
|
||||
|
||||
export const isIconAvailable = (iconName: string) => {
|
||||
export const isIconAvailable = async (iconName: string) => {
|
||||
try {
|
||||
getRegisteredIconData(iconName);
|
||||
await getRegisteredIconData(iconName);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const getIconSVG = (
|
||||
export const getIconSVG = async (
|
||||
iconName: string,
|
||||
customisations?: IconifyIconCustomisations & { fallbackPrefix?: string }
|
||||
) => {
|
||||
let iconData: ExtendedIconifyIcon;
|
||||
try {
|
||||
iconData = getRegisteredIconData(iconName, customisations?.fallbackPrefix);
|
||||
iconData = await getRegisteredIconData(iconName, customisations?.fallbackPrefix);
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
iconData = unknownIcon;
|
||||
|
Loading…
x
Reference in New Issue
Block a user