astro-mermaid 1.0.4 → 1.2.0
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.
- package/README.md +62 -36
- package/astro-mermaid-integration.js +111 -23
- package/package.json +26 -4
package/README.md
CHANGED
|
@@ -1,47 +1,83 @@
|
|
|
1
1
|
# astro-mermaid
|
|
2
2
|
|
|
3
|
-
An Astro integration for rendering Mermaid diagrams with automatic theme switching
|
|
3
|
+
An Astro integration for rendering Mermaid diagrams with automatic theme switching, client-side rendering, and universal compatibility. Works seamlessly with both standalone Astro projects and documentation frameworks like Starlight.
|
|
4
|
+
|
|
5
|
+
## Live Demos
|
|
6
|
+
|
|
7
|
+
| Demo Type | URL | Description |
|
|
8
|
+
|-----------|-----|-------------|
|
|
9
|
+
| **Starlight Integration** | [starlight-mermaid-demo.netlify.app](https://starlight-mermaid-demo.netlify.app/) | Full documentation site with Starlight |
|
|
10
|
+
| **Standalone Template** | [astro-mermaid-demo.netlify.app](https://astro-mermaid-demo.netlify.app/) | Pure Astro project template |
|
|
11
|
+
|
|
12
|
+
Both demos showcase:
|
|
13
|
+
- ✅ All diagram types with live examples
|
|
14
|
+
- ✅ Theme switching (light/dark modes)
|
|
15
|
+
- ✅ Icon pack integration
|
|
16
|
+
- ✅ Responsive design
|
|
17
|
+
- ✅ Content collections and direct `.astro` usage
|
|
18
|
+
|
|
4
19
|
|
|
5
20
|
## Features
|
|
6
21
|
|
|
7
|
-
- 🎨
|
|
8
|
-
- 🚀
|
|
9
|
-
- 📝
|
|
10
|
-
- ⚡
|
|
11
|
-
- 🔧
|
|
12
|
-
- 🎯 TypeScript
|
|
13
|
-
- 🔒 Privacy-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
- 🎭 Smooth loading animations to prevent layout shifts
|
|
22
|
+
- 🎨 **Universal Theme Detection** - Works with both `html[data-theme]` and `body[data-theme]` attributes
|
|
23
|
+
- 🚀 **Dual Plugin System** - Remark + Rehype plugins for comprehensive markdown processing
|
|
24
|
+
- 📝 **Universal File Support** - Works with `.md`, `.mdx`, and `.astro` files
|
|
25
|
+
- ⚡ **Performance Optimized** - Conditional loading and client-side rendering
|
|
26
|
+
- 🔧 **Highly Configurable** - Full mermaid.js configuration support
|
|
27
|
+
- 🎯 **TypeScript Ready** - Complete type definitions included
|
|
28
|
+
- 🔒 **Privacy-Focused** - No external dependencies, fully offline-capable
|
|
29
|
+
- 📦 **Zero Configuration** - Works out of the box with sensible defaults
|
|
30
|
+
- 🎭 **Smooth UX** - Loading animations and layout shift prevention
|
|
31
|
+
- 🦌 **ELK Support** - Optionally works with the `elk` layout ([The Eclipse Layout Kernel](https://eclipse.dev/elk/))
|
|
18
32
|
|
|
19
|
-
##
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### 1. Installation
|
|
20
36
|
|
|
21
37
|
```bash
|
|
22
38
|
npm install astro-mermaid mermaid
|
|
23
39
|
```
|
|
24
40
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Add the integration to your `astro.config.mjs`:
|
|
41
|
+
### 2. Add to Astro Config
|
|
28
42
|
|
|
29
43
|
```js
|
|
44
|
+
// astro.config.mjs
|
|
30
45
|
import { defineConfig } from 'astro/config';
|
|
31
46
|
import mermaid from 'astro-mermaid';
|
|
32
47
|
|
|
33
48
|
export default defineConfig({
|
|
34
49
|
integrations: [
|
|
35
50
|
mermaid({
|
|
36
|
-
theme: 'forest'
|
|
51
|
+
theme: 'forest',
|
|
52
|
+
autoTheme: true
|
|
37
53
|
})
|
|
38
54
|
]
|
|
39
55
|
});
|
|
40
56
|
```
|
|
41
57
|
|
|
42
|
-
###
|
|
58
|
+
### 3. Use in Markdown
|
|
59
|
+
|
|
60
|
+
````markdown
|
|
61
|
+
```mermaid
|
|
62
|
+
graph TD
|
|
63
|
+
A[Start] --> B[Process]
|
|
64
|
+
B --> C[End]
|
|
65
|
+
```
|
|
66
|
+
````
|
|
67
|
+
|
|
68
|
+
### 4. (Optional) Use ELK layout
|
|
43
69
|
|
|
44
|
-
|
|
70
|
+
To enable the `elk` layout in Mermaid diagrams, install the `@mermaid-js/layout-elk` package.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install @mermaid-js/layout-elk
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Learn more about [Mermaid layouts](https://mermaid.js.org/config/layouts.html) or [The Eclipse Layout Kernel](https://eclipse.dev/elk/).
|
|
77
|
+
|
|
78
|
+
## Integration Order (Important!)
|
|
79
|
+
|
|
80
|
+
When using with Starlight or other markdown-processing integrations, place mermaid **first**:
|
|
45
81
|
|
|
46
82
|
```js
|
|
47
83
|
import { defineConfig } from 'astro/config';
|
|
@@ -50,7 +86,7 @@ import mermaid from 'astro-mermaid';
|
|
|
50
86
|
|
|
51
87
|
export default defineConfig({
|
|
52
88
|
integrations: [
|
|
53
|
-
mermaid(), // Must come BEFORE starlight
|
|
89
|
+
mermaid(), // ⚠️ Must come BEFORE starlight
|
|
54
90
|
starlight({
|
|
55
91
|
title: 'My Docs'
|
|
56
92
|
})
|
|
@@ -58,18 +94,6 @@ export default defineConfig({
|
|
|
58
94
|
});
|
|
59
95
|
```
|
|
60
96
|
|
|
61
|
-
Then use mermaid code blocks in your markdown files:
|
|
62
|
-
|
|
63
|
-
````markdown
|
|
64
|
-
```mermaid
|
|
65
|
-
graph TD
|
|
66
|
-
A[Start] --> B{Is it working?}
|
|
67
|
-
B -->|Yes| C[Great!]
|
|
68
|
-
B -->|No| D[Debug]
|
|
69
|
-
D --> A
|
|
70
|
-
```
|
|
71
|
-
````
|
|
72
|
-
|
|
73
97
|
## Configuration
|
|
74
98
|
|
|
75
99
|
```js
|
|
@@ -192,14 +216,16 @@ All mermaid diagram types are supported:
|
|
|
192
216
|
- Quadrant charts
|
|
193
217
|
- And more!
|
|
194
218
|
|
|
195
|
-
##
|
|
219
|
+
## Version
|
|
220
|
+
|
|
221
|
+
**Current:** `v1.0.4` - Enhanced universal compatibility with dual plugin system
|
|
196
222
|
|
|
197
|
-
|
|
223
|
+
See [changelog](https://github.com/joesaby/astro-mermaid/releases) for version history.
|
|
198
224
|
|
|
199
225
|
## Contributing
|
|
200
226
|
|
|
201
|
-
Contributions
|
|
227
|
+
Contributions welcome! See our [demos](https://astro-mermaid-demo.netlify.app/) for examples.
|
|
202
228
|
|
|
203
229
|
## License
|
|
204
230
|
|
|
205
|
-
MIT
|
|
231
|
+
MIT © [Jose Sebastian](https://github.com/joesaby)
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { resolve } from 'import-meta-resolve';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper function to HTML-escape text content
|
|
5
|
+
* This ensures HTML tags in mermaid diagrams are preserved as text
|
|
6
|
+
*/
|
|
7
|
+
function escapeHtml(text) {
|
|
8
|
+
const htmlEntities = {
|
|
9
|
+
'&': '&',
|
|
10
|
+
'<': '<',
|
|
11
|
+
'>': '>',
|
|
12
|
+
'"': '"',
|
|
13
|
+
"'": '''
|
|
14
|
+
};
|
|
15
|
+
return text.replace(/[&<>"']/g, char => htmlEntities[char]);
|
|
16
|
+
}
|
|
3
17
|
|
|
4
18
|
/**
|
|
5
19
|
* Remark plugin to transform mermaid code blocks at the markdown level
|
|
@@ -7,36 +21,79 @@ import path from 'node:path';
|
|
|
7
21
|
function remarkMermaidPlugin(options = {}) {
|
|
8
22
|
return async function transformer(tree, file) {
|
|
9
23
|
const { visit } = await import('unist-util-visit');
|
|
10
|
-
|
|
24
|
+
|
|
11
25
|
let mermaidCount = 0;
|
|
12
|
-
|
|
26
|
+
|
|
13
27
|
visit(tree, 'code', (node, index, parent) => {
|
|
14
28
|
if (node.lang === 'mermaid') {
|
|
15
29
|
mermaidCount++;
|
|
16
|
-
|
|
17
|
-
// Transform to html node with pre.mermaid
|
|
30
|
+
|
|
31
|
+
// Transform to html node with pre.mermaid, escaping HTML content
|
|
18
32
|
const htmlNode = {
|
|
19
33
|
type: 'html',
|
|
20
|
-
value: `<pre class="mermaid">${node.value}</pre>`
|
|
34
|
+
value: `<pre class="mermaid">${escapeHtml(node.value)}</pre>`
|
|
21
35
|
};
|
|
22
|
-
|
|
36
|
+
|
|
23
37
|
// Replace the code node with html node
|
|
24
38
|
if (parent && typeof index === 'number') {
|
|
25
39
|
parent.children[index] = htmlNode;
|
|
26
40
|
}
|
|
27
|
-
|
|
41
|
+
|
|
28
42
|
if (options.logger) {
|
|
29
43
|
options.logger.info(`Remark transformed mermaid block #${mermaidCount} in ${file.path || 'unknown file'}`);
|
|
30
44
|
}
|
|
31
45
|
}
|
|
32
46
|
});
|
|
33
|
-
|
|
47
|
+
|
|
34
48
|
if (mermaidCount > 0 && options.logger) {
|
|
35
49
|
options.logger.info(`Remark total mermaid blocks transformed: ${mermaidCount}`);
|
|
36
50
|
}
|
|
37
51
|
};
|
|
38
52
|
}
|
|
39
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Helper function to serialize HAST nodes back to HTML text
|
|
56
|
+
* This preserves HTML tags within the mermaid content
|
|
57
|
+
*/
|
|
58
|
+
function serializeHastChildren(children) {
|
|
59
|
+
let result = '';
|
|
60
|
+
|
|
61
|
+
for (const child of children) {
|
|
62
|
+
if (child.type === 'text') {
|
|
63
|
+
result += child.value;
|
|
64
|
+
} else if (child.type === 'element') {
|
|
65
|
+
// Reconstruct the HTML tag
|
|
66
|
+
const tagName = child.tagName;
|
|
67
|
+
const selfClosing = ['br', 'hr', 'img', 'input', 'meta', 'link'].includes(tagName);
|
|
68
|
+
|
|
69
|
+
result += `<${tagName}`;
|
|
70
|
+
|
|
71
|
+
// Add attributes if any
|
|
72
|
+
if (child.properties) {
|
|
73
|
+
for (const [key, value] of Object.entries(child.properties)) {
|
|
74
|
+
if (key !== 'className') {
|
|
75
|
+
result += ` ${key}="${value}"`;
|
|
76
|
+
} else if (Array.isArray(value)) {
|
|
77
|
+
result += ` class="${value.join(' ')}"`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (selfClosing) {
|
|
83
|
+
result += '/>';
|
|
84
|
+
} else {
|
|
85
|
+
result += '>';
|
|
86
|
+
if (child.children && child.children.length > 0) {
|
|
87
|
+
result += serializeHastChildren(child.children);
|
|
88
|
+
}
|
|
89
|
+
result += `</${tagName}>`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
40
97
|
/**
|
|
41
98
|
* Rehype plugin to transform mermaid code blocks
|
|
42
99
|
* Converts ```mermaid code blocks to <pre class="mermaid">
|
|
@@ -44,10 +101,9 @@ function remarkMermaidPlugin(options = {}) {
|
|
|
44
101
|
function rehypeMermaidPlugin(options = {}) {
|
|
45
102
|
return async function transformer(tree, file) {
|
|
46
103
|
const { visit } = await import('unist-util-visit');
|
|
47
|
-
|
|
48
|
-
|
|
104
|
+
|
|
49
105
|
let mermaidCount = 0;
|
|
50
|
-
|
|
106
|
+
|
|
51
107
|
visit(tree, 'element', (node, index, parent) => {
|
|
52
108
|
// Look for <pre><code class="language-mermaid">
|
|
53
109
|
if (
|
|
@@ -57,40 +113,53 @@ function rehypeMermaidPlugin(options = {}) {
|
|
|
57
113
|
) {
|
|
58
114
|
const codeNode = node.children[0];
|
|
59
115
|
const className = codeNode.properties?.className;
|
|
60
|
-
|
|
116
|
+
|
|
61
117
|
if (Array.isArray(className) && className.includes('language-mermaid')) {
|
|
62
118
|
mermaidCount++;
|
|
63
|
-
// Get the mermaid diagram content
|
|
64
|
-
const diagramContent =
|
|
65
|
-
|
|
119
|
+
// Get the mermaid diagram content, preserving HTML tags
|
|
120
|
+
const diagramContent = serializeHastChildren(codeNode.children || []);
|
|
121
|
+
|
|
66
122
|
// Transform to <pre class="mermaid">
|
|
67
123
|
node.properties = {
|
|
68
124
|
...node.properties,
|
|
69
125
|
className: ['mermaid']
|
|
70
126
|
};
|
|
71
|
-
|
|
127
|
+
|
|
128
|
+
// Escape HTML to preserve it as text content
|
|
72
129
|
node.children = [{
|
|
73
130
|
type: 'text',
|
|
74
|
-
value: diagramContent
|
|
131
|
+
value: escapeHtml(diagramContent)
|
|
75
132
|
}];
|
|
76
|
-
|
|
133
|
+
|
|
77
134
|
if (options.logger) {
|
|
78
135
|
options.logger.info(`Rehype transformed mermaid block #${mermaidCount} in ${file.path || 'unknown file'}`);
|
|
79
136
|
}
|
|
80
137
|
}
|
|
81
138
|
}
|
|
82
139
|
});
|
|
83
|
-
|
|
140
|
+
|
|
84
141
|
if (mermaidCount > 0 && options.logger) {
|
|
85
142
|
options.logger.info(`Rehype total mermaid blocks transformed: ${mermaidCount}`);
|
|
86
143
|
}
|
|
87
144
|
};
|
|
88
145
|
}
|
|
89
146
|
|
|
147
|
+
/** Detect if optional peer dependency `@mermaid-js/layout-elk` is available. */
|
|
148
|
+
async function isElkInstalled(logger, consumerRoot) {
|
|
149
|
+
try {
|
|
150
|
+
resolve('@mermaid-js/layout-elk', `${consumerRoot.href}package.json`);
|
|
151
|
+
logger.info('Enabling ELK support');
|
|
152
|
+
return true;
|
|
153
|
+
} catch {
|
|
154
|
+
logger.info('Skipping ELK support');
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
90
159
|
/**
|
|
91
160
|
* Astro integration for rendering Mermaid diagrams
|
|
92
161
|
* Supports automatic theme switching and client-side rendering
|
|
93
|
-
*
|
|
162
|
+
*
|
|
94
163
|
* @param {Object} options - Configuration options
|
|
95
164
|
* @param {string} [options.theme='default'] - Default theme ('default', 'dark', 'forest', 'neutral')
|
|
96
165
|
* @param {boolean} [options.autoTheme=true] - Enable automatic theme switching based on data-theme attribute
|
|
@@ -114,6 +183,15 @@ export default function astroMermaid(options = {}) {
|
|
|
114
183
|
// Log existing rehype plugins
|
|
115
184
|
logger.info('Existing rehype plugins:', config.markdown?.rehypePlugins?.length || 0);
|
|
116
185
|
|
|
186
|
+
// Always include mermaid.
|
|
187
|
+
const viteOptimizeDepsInclude = ['mermaid'];
|
|
188
|
+
|
|
189
|
+
// Conditionally include ELK
|
|
190
|
+
const useElk = await isElkInstalled(logger, config.root);
|
|
191
|
+
if (useElk) {
|
|
192
|
+
viteOptimizeDepsInclude.push('@mermaid-js/layout-elk');
|
|
193
|
+
}
|
|
194
|
+
|
|
117
195
|
// Update markdown config to use both remark and rehype plugins
|
|
118
196
|
updateConfig({
|
|
119
197
|
markdown: {
|
|
@@ -128,7 +206,7 @@ export default function astroMermaid(options = {}) {
|
|
|
128
206
|
},
|
|
129
207
|
vite: {
|
|
130
208
|
optimizeDeps: {
|
|
131
|
-
include:
|
|
209
|
+
include: viteOptimizeDepsInclude
|
|
132
210
|
}
|
|
133
211
|
}
|
|
134
212
|
});
|
|
@@ -162,6 +240,16 @@ if (hasMermaidDiagrams()) {
|
|
|
162
240
|
}));
|
|
163
241
|
await mermaid.registerIconPacks(packs);
|
|
164
242
|
}
|
|
243
|
+
|
|
244
|
+
// Register ELK layouts if the optional peer is available at build-time
|
|
245
|
+
${useElk ? `
|
|
246
|
+
const elkModule = await import("@mermaid-js/layout-elk").catch(() => null);
|
|
247
|
+
if (elkModule?.default) {
|
|
248
|
+
console.log("[astro-mermaid] Registering elk layouts");
|
|
249
|
+
mermaid.registerLayoutLoaders(elkModule.default);
|
|
250
|
+
}
|
|
251
|
+
` : ``}
|
|
252
|
+
|
|
165
253
|
// Mermaid configuration
|
|
166
254
|
const defaultConfig = ${JSON.stringify({
|
|
167
255
|
startOnLoad: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-mermaid",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "An Astro integration for rendering Mermaid diagrams with automatic theme switching and client-side rendering",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./astro-mermaid-integration.js",
|
|
@@ -29,25 +29,47 @@
|
|
|
29
29
|
"author": "Jose Sebastian",
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"peerDependencies": {
|
|
32
|
+
"@mermaid-js/layout-elk": "^0.2.0",
|
|
32
33
|
"astro": "^4.0.0 || ^5.0.0",
|
|
33
34
|
"mermaid": "^10.0.0 || ^11.0.0"
|
|
34
35
|
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"@mermaid-js/layout-elk": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
35
41
|
"dependencies": {
|
|
42
|
+
"@anthropic-ai/claude-code": "^1.0.128",
|
|
43
|
+
"import-meta-resolve": "^4.2.0",
|
|
36
44
|
"mdast-util-to-string": "^4.0.0",
|
|
37
45
|
"unist-util-visit": "^5.0.0"
|
|
38
46
|
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"claude": "claude",
|
|
49
|
+
"test": "vitest",
|
|
50
|
+
"test:ui": "vitest --ui",
|
|
51
|
+
"test:coverage": "vitest --coverage"
|
|
52
|
+
},
|
|
39
53
|
"devDependencies": {
|
|
40
54
|
"@types/hast": "^3.0.4",
|
|
55
|
+
"@vitest/ui": "^3.2.4",
|
|
41
56
|
"astro": "^5.0.0",
|
|
57
|
+
"hast-util-from-html": "^2.0.3",
|
|
42
58
|
"mermaid": "^11.0.0",
|
|
43
|
-
"
|
|
59
|
+
"rehype-parse": "^9.0.1",
|
|
60
|
+
"rehype-stringify": "^10.0.1",
|
|
61
|
+
"remark-mermaid": "^0.2.0",
|
|
62
|
+
"remark-parse": "^11.0.0",
|
|
63
|
+
"typescript": "^5.0.0",
|
|
64
|
+
"unified": "^11.0.5",
|
|
65
|
+
"vitest": "^3.2.4"
|
|
44
66
|
},
|
|
45
67
|
"repository": {
|
|
46
68
|
"type": "git",
|
|
47
|
-
"url": "https://github.com/joesaby/astro-mermaid.git"
|
|
69
|
+
"url": "git+https://github.com/joesaby/astro-mermaid.git"
|
|
48
70
|
},
|
|
49
71
|
"homepage": "https://github.com/joesaby/astro-mermaid#readme",
|
|
50
72
|
"bugs": {
|
|
51
73
|
"url": "https://github.com/joesaby/astro-mermaid/issues"
|
|
52
74
|
}
|
|
53
|
-
}
|
|
75
|
+
}
|