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 CHANGED
@@ -1,47 +1,83 @@
1
1
  # astro-mermaid
2
2
 
3
- An Astro integration for rendering Mermaid diagrams with automatic theme switching and client-side rendering. This follows the mermaid integration in [cloudflare-docs](https://github.com/cloudflare/cloudflare-docs)
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
- - 🎨 Automatic theme switching based on your site's theme
8
- - 🚀 Client-side rendering for optimal performance
9
- - 📝 Simple markdown syntax using code blocks
10
- - ⚡ Vite optimization for fast development
11
- - 🔧 Customizable mermaid configuration
12
- - 🎯 TypeScript support
13
- - 🔒 Privacy-focused with no external server dependencies
14
- - 🌐 Offline-capable rendering
15
- - Zero network latency for diagram generation
16
- - 📦 Conditional loading - mermaid.js only loads on pages with diagrams
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
- ## Installation
33
+ ## Quick Start
34
+
35
+ ### 1. Installation
20
36
 
21
37
  ```bash
22
38
  npm install astro-mermaid mermaid
23
39
  ```
24
40
 
25
- ## Usage
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
- ### Important: Integration Order
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
- When using with Starlight or other integrations that process markdown, make sure to place the mermaid integration **before** them:
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
- ## Demo
219
+ ## Version
220
+
221
+ **Current:** `v1.0.4` - Enhanced universal compatibility with dual plugin system
196
222
 
197
- Check out the [live demo](https://starlight-mermaid-demo.netlify.app/) built with Starlight.
223
+ See [changelog](https://github.com/joesaby/astro-mermaid/releases) for version history.
198
224
 
199
225
  ## Contributing
200
226
 
201
- Contributions are welcome! Please feel free to submit a Pull Request.
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 { fileURLToPath } from 'node:url';
2
- import path from 'node:path';
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
+ '<': '&lt;',
11
+ '>': '&gt;',
12
+ '"': '&quot;',
13
+ "'": '&#39;'
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
- const { toString } = await import('mdast-util-to-string');
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 = toString(codeNode);
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: ['mermaid']
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.4",
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
- "typescript": "^5.0.0"
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
+ }