From 305910758b1cfcfad053726ae7cff8d7c6ab0cc1 Mon Sep 17 00:00:00 2001 From: Feez2403 Date: Sun, 24 Aug 2025 20:11:21 +0200 Subject: [PATCH] feat(mermaid): support elk layout (#11357) Co-authored-by: sebastien --- .../docusaurus-theme-mermaid/package.json | 8 +- .../src/client/index.ts | 8 +- .../src/client/layouts.ts | 34 ++++++++ .../docusaurus-theme-mermaid/src/index.ts | 36 +++++++- project-words.txt | 1 + website/_dogfooding/_pages tests/diagrams.mdx | 85 +++++++++++++++++++ .../markdown-features-diagrams.mdx | 33 +++++++ website/package.json | 1 + yarn.lock | 13 +++ 9 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 packages/docusaurus-theme-mermaid/src/client/layouts.ts diff --git a/packages/docusaurus-theme-mermaid/package.json b/packages/docusaurus-theme-mermaid/package.json index c97bc97f59..47ecf7dca5 100644 --- a/packages/docusaurus-theme-mermaid/package.json +++ b/packages/docusaurus-theme-mermaid/package.json @@ -46,7 +46,13 @@ }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "react-dom": "^18.0.0 || ^19.0.0", + "@mermaid-js/layout-elk": "^0.1.9" + }, + "peerDependenciesMeta": { + "@mermaid-js/layout-elk": { + "optional": true + } }, "engines": { "node": ">=18.0" diff --git a/packages/docusaurus-theme-mermaid/src/client/index.ts b/packages/docusaurus-theme-mermaid/src/client/index.ts index 5ab3871813..8e86fc6c45 100644 --- a/packages/docusaurus-theme-mermaid/src/client/index.ts +++ b/packages/docusaurus-theme-mermaid/src/client/index.ts @@ -8,6 +8,8 @@ import {useState, useEffect, useMemo} from 'react'; import {useColorMode, useThemeConfig} from '@docusaurus/theme-common'; import mermaid from 'mermaid'; +import {ensureLayoutsRegistered} from './layouts'; + import type {RenderResult, MermaidConfig} from 'mermaid'; import type {ThemeConfig} from '@docusaurus/theme-mermaid'; @@ -37,7 +39,7 @@ function useMermaidId(): string { Note: Mermaid doesn't like values provided by Rect.useId() and throws */ - // TODO 2025-2026: check if useId() now works + // TODO Docusaurus v4: check if useId() now works // It could work thanks to https://github.com/facebook/react/pull/32001 // return useId(); // tried that, doesn't work ('#d:re:' is not a valid selector.) @@ -53,6 +55,8 @@ async function renderMermaid({ text: string; config: MermaidConfig; }): Promise { + await ensureLayoutsRegistered(); + /* Mermaid API is really weird :s It is a big mutable singleton with multiple config levels @@ -71,7 +75,7 @@ async function renderMermaid({ To use a new mermaid config (on colorMode change for example) we should update siteConfig, and it can only be done with initialize() */ - mermaid.mermaidAPI.initialize(config); + mermaid.initialize(config); try { return await mermaid.render(id, text); diff --git a/packages/docusaurus-theme-mermaid/src/client/layouts.ts b/packages/docusaurus-theme-mermaid/src/client/layouts.ts new file mode 100644 index 0000000000..25a26d1501 --- /dev/null +++ b/packages/docusaurus-theme-mermaid/src/client/layouts.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import mermaid from 'mermaid'; + +declare global { + // Global variable provided by bundler DefinePlugin + /* eslint-disable-next-line no-underscore-dangle */ + const __DOCUSAURUS_MERMAID_LAYOUT_ELK_ENABLED__: boolean; +} + +async function registerOptionalElkLayout() { + // Mermaid does not support ELK layouts by default + // See https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid-layout-elk + // ELK layouts are heavy, so we made it an optional peer dependency + // See https://github.com/facebook/docusaurus/pull/11357 + if (__DOCUSAURUS_MERMAID_LAYOUT_ELK_ENABLED__) { + const elkLayout = (await import('@mermaid-js/layout-elk')).default; + mermaid.registerLayoutLoaders(elkLayout); + } +} + +// Ensure we only try to register layouts once +let layoutsRegistered = false; +export async function ensureLayoutsRegistered(): Promise { + if (!layoutsRegistered) { + await registerOptionalElkLayout(); + layoutsRegistered = true; + } +} diff --git a/packages/docusaurus-theme-mermaid/src/index.ts b/packages/docusaurus-theme-mermaid/src/index.ts index 82b1636c59..622c6b2ef7 100644 --- a/packages/docusaurus-theme-mermaid/src/index.ts +++ b/packages/docusaurus-theme-mermaid/src/index.ts @@ -7,7 +7,26 @@ import type {Plugin} from '@docusaurus/types'; -export default function themeMermaid(): Plugin { +/** + * Check if the optional @mermaid-js/layout-elk package is available. + * It's an optional peer dependency because it's heavy and most Mermaid users + * might not need it. + */ +async function isElkLayoutPackageAvailable() { + try { + await import('@mermaid-js/layout-elk'); + return true; + } catch (e) { + return false; + } +} + +export default async function themeMermaid(): Promise> { + // For now, we infer based on package availability + // In the future, we could make it configurable so that users can disable it + // even if the package is installed? + const elkLayoutEnabled = await isElkLayoutPackageAvailable(); + return { name: 'docusaurus-theme-mermaid', @@ -17,6 +36,21 @@ export default function themeMermaid(): Plugin { getTypeScriptThemePath() { return '../src/theme'; }, + + configureWebpack(config, isServer, utils) { + return { + plugins: [ + new utils.currentBundler.instance.DefinePlugin({ + __DOCUSAURUS_MERMAID_LAYOUT_ELK_ENABLED__: JSON.stringify( + // We only need to include the layout registration code on the + // client side. This also solves a weird Webpack-only bug when + // compiling the server config due to the module being ESM-only. + !isServer && elkLayoutEnabled, + ), + }), + ], + }; + }, }; } diff --git a/project-words.txt b/project-words.txt index 03eeb8db4d..14a45f9141 100644 --- a/project-words.txt +++ b/project-words.txt @@ -48,6 +48,7 @@ Csapo Csvg Dabit dabit +dagre Daishi Datagit datagit diff --git a/website/_dogfooding/_pages tests/diagrams.mdx b/website/_dogfooding/_pages tests/diagrams.mdx index 1b11aa8d37..aba7585578 100644 --- a/website/_dogfooding/_pages tests/diagrams.mdx +++ b/website/_dogfooding/_pages tests/diagrams.mdx @@ -447,3 +447,88 @@ architecture-beta disk1:T -- B:server disk2:T -- B:db ``` + +## ELK Styling + +Mermaid provides an [ELK layout](https://mermaid.js.org/syntax/entityRelationshipDiagram.html#layout) + +### Dagre + +This is a "classical" Mermaid diagram, using the default Dagre layout. + +```mermaid +erDiagram + + COMPANY ||--o{ DEPARTMENT : has + COMPANY ||--o{ PROJECT : undertakes + COMPANY ||--o{ LOCATION : operates_in + COMPANY ||--o{ CLIENT : serves + + DEPARTMENT ||--o{ EMPLOYEE : employs + DEPARTMENT ||--o{ PROJECT : manages + DEPARTMENT ||--o{ BUDGET : allocated + + EMPLOYEE }o--o{ PROJECT : works_on + EMPLOYEE ||--|| ADDRESS : lives_at + EMPLOYEE }o--o{ SKILL : has + EMPLOYEE ||--o{ DEPENDENT : supports + + PROJECT ||--o{ CLIENT : for + PROJECT ||--o{ TASK : contains + +``` + +### ELK er diagram layout + +This ER diagram should look different, using the ELK layout. + +```mermaid +--- +config: + layout: elk +--- +erDiagram + + COMPANY ||--o{ DEPARTMENT : has + COMPANY ||--o{ PROJECT : undertakes + COMPANY ||--o{ LOCATION : operates_in + COMPANY ||--o{ CLIENT : serves + + DEPARTMENT ||--o{ EMPLOYEE : employs + DEPARTMENT ||--o{ PROJECT : manages + DEPARTMENT ||--o{ BUDGET : allocated + + EMPLOYEE }o--o{ PROJECT : works_on + EMPLOYEE ||--|| ADDRESS : lives_at + EMPLOYEE }o--o{ SKILL : has + EMPLOYEE ||--o{ DEPENDENT : supports + + PROJECT ||--o{ CLIENT : for + PROJECT ||--o{ TASK : contains + +``` + +Mermaid also provides a way of setting config parameters using a directive `%%{init:{"layout":"elk"}}%%` + +```mermaid +%%{init:{"layout":"elk"}}%% +erDiagram + + COMPANY ||--o{ DEPARTMENT : has + COMPANY ||--o{ PROJECT : undertakes + COMPANY ||--o{ LOCATION : operates_in + COMPANY ||--o{ CLIENT : serves + + DEPARTMENT ||--o{ EMPLOYEE : employs + DEPARTMENT ||--o{ PROJECT : manages + DEPARTMENT ||--o{ BUDGET : allocated + + EMPLOYEE }o--o{ PROJECT : works_on + EMPLOYEE ||--|| ADDRESS : lives_at + EMPLOYEE }o--o{ SKILL : has + EMPLOYEE ||--o{ DEPENDENT : supports + + PROJECT ||--o{ CLIENT : for + PROJECT ||--o{ TASK : contains + +``` diff --git a/website/docs/guides/markdown-features/markdown-features-diagrams.mdx b/website/docs/guides/markdown-features/markdown-features-diagrams.mdx index b8d652c0a3..829f971eeb 100644 --- a/website/docs/guides/markdown-features/markdown-features-diagrams.mdx +++ b/website/docs/guides/markdown-features/markdown-features-diagrams.mdx @@ -99,3 +99,36 @@ import Mermaid from '@theme/Mermaid'; C-->D;`} /> ``` + +## Layouts + +Mermaid supports different [layout engines](https://mermaid.js.org/intro/syntax-reference.html#layout-and-look): + +- The `dagre` layout engine is supported by default in Docusaurus. +- The `elk` layout engine is heavier and can be enabled by installing the optional `@mermaid-js/layout-elk` dependency. + +````md +```mermaid +--- +config: + layout: elk +--- +graph TD; + A-->B; + A-->C; + B-->D; + C-->D; +``` +```` + +```mermaid +--- +config: + layout: elk +--- +graph TD; + A-->B; + A-->C; + B-->D; + C-->D; +``` diff --git a/website/package.json b/website/package.json index 15fe623c78..7c2f4aa96c 100644 --- a/website/package.json +++ b/website/package.json @@ -53,6 +53,7 @@ "@docusaurus/theme-mermaid": "3.8.1", "@docusaurus/utils": "3.8.1", "@docusaurus/utils-common": "3.8.1", + "@mermaid-js/layout-elk": "^0.1.9", "clsx": "^2.0.0", "color": "^4.2.3", "fs-extra": "^11.1.1", diff --git a/yarn.lock b/yarn.lock index 38003b5fcb..19295194bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2591,6 +2591,14 @@ dependencies: "@types/mdx" "^2.0.0" +"@mermaid-js/layout-elk@^0.1.9": + version "0.1.9" + resolved "https://registry.yarnpkg.com/@mermaid-js/layout-elk/-/layout-elk-0.1.9.tgz#c773b9454875858a2f45412fe04502bccec83cf2" + integrity sha512-HuvaqFZBr6yT9PpWYockvKAZPJVd89yn/UjOYPxhzbZxlybL2v+2BjVCg7MVH6vRs1irUohb/s42HEdec1CCZw== + dependencies: + d3 "^7.9.0" + elkjs "^0.9.3" + "@mermaid-js/parser@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@mermaid-js/parser/-/parser-0.4.0.tgz#c1de1f5669f8fcbd0d0c9d124927d36ddc00d8a6" @@ -7937,6 +7945,11 @@ electron-to-chromium@^1.5.160: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz#477b0957e42f071905a86f7c905a9848f95d2bdb" integrity sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw== +elkjs@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/elkjs/-/elkjs-0.9.3.tgz#16711f8ceb09f1b12b99e971b138a8384a529161" + integrity sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ== + emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad"