astro-mermaid 1.0.1 → 1.0.2

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,6 +1,6 @@
1
1
  # astro-mermaid
2
2
 
3
- An Astro integration for rendering Mermaid diagrams with automatic theme switching and client-side rendering. Instead of using playright, I used the approach done in https://github.com/cloudflare/cloudflare-docs
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)
4
4
 
5
5
  ## Features
6
6
 
@@ -10,6 +10,11 @@ An Astro integration for rendering Mermaid diagrams with automatic theme switchi
10
10
  - ⚡ Vite optimization for fast development
11
11
  - 🔧 Customizable mermaid configuration
12
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
13
18
 
14
19
  ## Installation
15
20
 
@@ -27,7 +32,28 @@ import mermaid from 'astro-mermaid';
27
32
 
28
33
  export default defineConfig({
29
34
  integrations: [
30
- mermaid()
35
+ mermaid({
36
+ theme: 'forest'
37
+ })
38
+ ]
39
+ });
40
+ ```
41
+
42
+ ### Important: Integration Order
43
+
44
+ When using with Starlight or other integrations that process markdown, make sure to place the mermaid integration **before** them:
45
+
46
+ ```js
47
+ import { defineConfig } from 'astro/config';
48
+ import starlight from '@astrojs/starlight';
49
+ import mermaid from 'astro-mermaid';
50
+
51
+ export default defineConfig({
52
+ integrations: [
53
+ mermaid(), // Must come BEFORE starlight
54
+ starlight({
55
+ title: 'My Docs'
56
+ })
31
57
  ]
32
58
  });
33
59
  ```
@@ -70,6 +96,39 @@ If `autoTheme` is enabled (default), the integration will automatically switch b
70
96
  - `data-theme="light"` → uses 'default' mermaid theme
71
97
  - `data-theme="dark"` → uses 'dark' mermaid theme
72
98
 
99
+ ## Client-Side Rendering & Security
100
+
101
+ ### 🔒 Privacy & Security Benefits
102
+
103
+ This integration uses **100% client-side rendering** with zero external dependencies at runtime:
104
+
105
+ - **No Data Transmission**: Your diagram content never leaves your browser
106
+ - **No External Servers**: No calls to mermaid.live or any external services
107
+ - **Offline Capable**: Works completely offline after initial page load
108
+ - **Zero Network Latency**: Instant diagram rendering without network delays
109
+ - **Corporate Firewall Friendly**: No external domains need to be whitelisted
110
+
111
+ ### ⚡ How It Works
112
+
113
+ 1. **Build Time**: Mermaid code blocks are transformed to `<pre class="mermaid">` elements
114
+ 2. **Runtime**: The bundled Mermaid JavaScript library renders diagrams locally
115
+ 3. **Output**: Pure SVG generated entirely in your browser
116
+
117
+ ```javascript
118
+ // All rendering happens locally - no network calls
119
+ import mermaid from 'mermaid';
120
+ const { svg } = await mermaid.render(id, diagramDefinition);
121
+ ```
122
+
123
+ ### 🛡️ Enterprise & Compliance
124
+
125
+ Perfect for:
126
+ - Corporate environments with strict security policies
127
+ - GDPR/privacy-compliant applications
128
+ - Air-gapped or restricted network environments
129
+ - Applications requiring data sovereignty
130
+ - High-security environments where external requests are prohibited
131
+
73
132
  ## Supported Diagrams
74
133
 
75
134
  All mermaid diagram types are supported:
@@ -90,6 +149,14 @@ All mermaid diagram types are supported:
90
149
  - Quadrant charts
91
150
  - And more!
92
151
 
152
+ ## Demo
153
+
154
+ Check out the [live demo](https://starlight-mermaid-demo.netlify.app/) built with Starlight.
155
+
156
+ ## Contributing
157
+
158
+ Contributions are welcome! Please feel free to submit a Pull Request.
159
+
93
160
  ## License
94
161
 
95
162
  MIT
@@ -92,129 +92,147 @@ export default function astroMermaid(options = {}) {
92
92
  }
93
93
  });
94
94
 
95
- // Inject client-side mermaid script
95
+ // Inject client-side mermaid script with conditional loading
96
96
  const mermaidScriptContent = `
97
- import mermaid from 'mermaid';
98
-
99
- // Mermaid configuration
100
- const defaultConfig = ${JSON.stringify({
101
- startOnLoad: false,
102
- theme: theme,
103
- ...mermaidConfig
104
- })};
105
-
106
- // Theme mapping for auto-theme switching
107
- const themeMap = {
108
- 'light': 'default',
109
- 'dark': 'dark'
97
+ // Check if page has mermaid diagrams
98
+ const hasMermaidDiagrams = () => {
99
+ return document.querySelectorAll('pre.mermaid').length > 0;
110
100
  };
111
101
 
112
- // Initialize all mermaid diagrams
113
- async function initMermaid() {
114
- console.log('[astro-mermaid] Initializing mermaid diagrams...');
115
- const diagrams = document.querySelectorAll('pre.mermaid');
116
-
117
- console.log('[astro-mermaid] Found', diagrams.length, 'mermaid diagrams');
118
-
119
- if (diagrams.length === 0) {
120
- console.log('[astro-mermaid] No mermaid diagrams found. Looking for code blocks...');
121
- const codeBlocks = document.querySelectorAll('pre code.language-mermaid');
122
- console.log('[astro-mermaid] Found', codeBlocks.length, 'mermaid code blocks');
123
- return;
124
- }
125
-
126
- // Get current theme
127
- let currentTheme = defaultConfig.theme;
128
-
129
- if (${autoTheme}) {
130
- const dataTheme = document.documentElement.getAttribute('data-theme');
131
- currentTheme = themeMap[dataTheme] || defaultConfig.theme;
132
- console.log('[astro-mermaid] Using theme:', currentTheme);
133
- }
134
-
135
- // Configure mermaid
136
- mermaid.initialize({
137
- ...defaultConfig,
138
- theme: currentTheme
139
- });
102
+ // Only proceed if there are mermaid diagrams on the page
103
+ if (hasMermaidDiagrams()) {
104
+ console.log('[astro-mermaid] Mermaid diagrams detected, loading mermaid.js...');
140
105
 
141
- // Render each diagram
142
- for (const diagram of diagrams) {
143
- // Skip if already processed
144
- if (diagram.hasAttribute('data-processed')) continue;
145
-
146
- // Store original content
147
- if (!diagram.hasAttribute('data-diagram')) {
148
- diagram.setAttribute('data-diagram', diagram.textContent || '');
149
- }
150
-
151
- const diagramDefinition = diagram.getAttribute('data-diagram') || '';
152
- const id = 'mermaid-' + Math.random().toString(36).slice(2, 11);
153
-
154
- console.log('[astro-mermaid] Rendering diagram:', id);
155
-
156
- try {
157
- const { svg } = await mermaid.render(id, diagramDefinition);
158
- diagram.innerHTML = svg;
159
- diagram.setAttribute('data-processed', 'true');
160
- console.log('[astro-mermaid] Successfully rendered diagram:', id);
161
- } catch (error) {
162
- console.error('[astro-mermaid] Mermaid rendering error:', error);
163
- diagram.innerHTML = '<div style="color: red;">Error rendering diagram</div>';
106
+ // Dynamically import mermaid only when needed
107
+ import('mermaid').then(({ default: mermaid }) => {
108
+ // Mermaid configuration
109
+ const defaultConfig = ${JSON.stringify({
110
+ startOnLoad: false,
111
+ theme: theme,
112
+ ...mermaidConfig
113
+ })};
114
+
115
+ // Theme mapping for auto-theme switching
116
+ const themeMap = {
117
+ 'light': 'default',
118
+ 'dark': 'dark'
119
+ };
120
+
121
+ // Initialize all mermaid diagrams
122
+ async function initMermaid() {
123
+ console.log('[astro-mermaid] Initializing mermaid diagrams...');
124
+ const diagrams = document.querySelectorAll('pre.mermaid');
125
+
126
+ console.log('[astro-mermaid] Found', diagrams.length, 'mermaid diagrams');
127
+
128
+ if (diagrams.length === 0) {
129
+ return;
130
+ }
131
+
132
+ // Get current theme
133
+ let currentTheme = defaultConfig.theme;
134
+
135
+ if (${autoTheme}) {
136
+ const dataTheme = document.documentElement.getAttribute('data-theme');
137
+ currentTheme = themeMap[dataTheme] || defaultConfig.theme;
138
+ console.log('[astro-mermaid] Using theme:', currentTheme);
139
+ }
140
+
141
+ // Configure mermaid with gitGraph support
142
+ mermaid.initialize({
143
+ ...defaultConfig,
144
+ theme: currentTheme,
145
+ gitGraph: {
146
+ mainBranchName: 'main',
147
+ showCommitLabel: true,
148
+ showBranches: true,
149
+ rotateCommitLabel: true
150
+ }
151
+ });
152
+
153
+ // Render each diagram
154
+ for (const diagram of diagrams) {
155
+ // Skip if already processed
156
+ if (diagram.hasAttribute('data-processed')) continue;
157
+
158
+ // Store original content
159
+ if (!diagram.hasAttribute('data-diagram')) {
160
+ diagram.setAttribute('data-diagram', diagram.textContent || '');
161
+ }
162
+
163
+ const diagramDefinition = diagram.getAttribute('data-diagram') || '';
164
+ const id = 'mermaid-' + Math.random().toString(36).slice(2, 11);
165
+
166
+ console.log('[astro-mermaid] Rendering diagram:', id);
167
+
168
+ try {
169
+ // Clear any existing error state
170
+ const existingGraph = document.getElementById(id);
171
+ if (existingGraph) {
172
+ existingGraph.remove();
173
+ }
174
+
175
+ const { svg } = await mermaid.render(id, diagramDefinition);
176
+ diagram.innerHTML = svg;
177
+ diagram.setAttribute('data-processed', 'true');
178
+ console.log('[astro-mermaid] Successfully rendered diagram:', id);
179
+ } catch (error) {
180
+ console.error('[astro-mermaid] Mermaid rendering error for diagram:', id, error);
181
+ diagram.innerHTML = \`<div style="color: red; padding: 1rem; border: 1px solid red; border-radius: 0.5rem;">
182
+ <strong>Error rendering diagram:</strong><br/>
183
+ \${error.message || 'Unknown error'}
184
+ </div>\`;
185
+ diagram.setAttribute('data-processed', 'true');
186
+ }
187
+ }
164
188
  }
165
- }
166
- }
167
189
 
168
- // Initialize on DOM ready
169
- if (document.readyState === 'loading') {
170
- document.addEventListener('DOMContentLoaded', initMermaid);
171
- } else {
172
- initMermaid();
173
- }
190
+ // Initialize immediately since DOM is ready
191
+ initMermaid();
174
192
 
175
- // Re-render on theme change if auto-theme is enabled
176
- if (${autoTheme}) {
177
- const observer = new MutationObserver((mutations) => {
178
- for (const mutation of mutations) {
179
- if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') {
180
- // Reset processed state and re-render
181
- document.querySelectorAll('pre.mermaid[data-processed]').forEach(diagram => {
182
- diagram.removeAttribute('data-processed');
183
- });
193
+ // Re-render on theme change if auto-theme is enabled
194
+ if (${autoTheme}) {
195
+ const observer = new MutationObserver((mutations) => {
196
+ for (const mutation of mutations) {
197
+ if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') {
198
+ // Reset processed state and re-render
199
+ document.querySelectorAll('pre.mermaid[data-processed]').forEach(diagram => {
200
+ diagram.removeAttribute('data-processed');
201
+ });
202
+ initMermaid();
203
+ }
204
+ }
205
+ });
206
+
207
+ observer.observe(document.documentElement, {
208
+ attributes: true,
209
+ attributeFilter: ['data-theme']
210
+ });
211
+ }
212
+
213
+ // Handle view transitions (for Astro View Transitions API)
214
+ document.addEventListener('astro:after-swap', () => {
215
+ // Check again if new page has diagrams
216
+ if (hasMermaidDiagrams()) {
184
217
  initMermaid();
185
218
  }
186
- }
187
- });
188
-
189
- observer.observe(document.documentElement, {
190
- attributes: true,
191
- attributeFilter: ['data-theme']
219
+ });
220
+ }).catch(error => {
221
+ console.error('[astro-mermaid] Failed to load mermaid:', error);
192
222
  });
223
+ } else {
224
+ console.log('[astro-mermaid] No mermaid diagrams found on this page, skipping mermaid.js load');
193
225
  }
194
-
195
- // Handle view transitions (for Astro View Transitions API)
196
- document.addEventListener('astro:after-swap', initMermaid);
197
226
  `;
198
227
 
199
228
  injectScript('page', mermaidScriptContent);
200
229
 
201
- // Add CSS to the page
230
+ // Add CSS to the page with layout shift prevention
202
231
  injectScript('page', `
203
232
  // Add CSS for mermaid diagrams
204
233
  const style = document.createElement('style');
205
234
  style.textContent = \`
206
- /* Hide diagrams until processed to prevent flash of unstyled content */
207
- pre.mermaid:not([data-processed]) {
208
- opacity: 0;
209
- transition: opacity 0.3s ease-in-out;
210
- }
211
-
212
- /* Show processed diagrams */
213
- pre.mermaid[data-processed] {
214
- opacity: 1;
215
- }
216
-
217
- /* Center mermaid diagrams and add spacing */
235
+ /* Prevent layout shifts by setting minimum height */
218
236
  pre.mermaid {
219
237
  display: flex;
220
238
  justify-content: center;
@@ -224,6 +242,37 @@ document.addEventListener('astro:after-swap', initMermaid);
224
242
  background-color: transparent;
225
243
  border: none;
226
244
  overflow: auto;
245
+ min-height: 200px; /* Prevent layout shift */
246
+ position: relative;
247
+ }
248
+
249
+ /* Loading state with skeleton loader */
250
+ pre.mermaid:not([data-processed]) {
251
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
252
+ background-size: 200% 100%;
253
+ animation: shimmer 1.5s infinite;
254
+ }
255
+
256
+ /* Dark mode skeleton loader */
257
+ [data-theme="dark"] pre.mermaid:not([data-processed]) {
258
+ background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%);
259
+ background-size: 200% 100%;
260
+ }
261
+
262
+ @keyframes shimmer {
263
+ 0% {
264
+ background-position: -200% 0;
265
+ }
266
+ 100% {
267
+ background-position: 200% 0;
268
+ }
269
+ }
270
+
271
+ /* Show processed diagrams with smooth transition */
272
+ pre.mermaid[data-processed] {
273
+ animation: none;
274
+ background: transparent;
275
+ min-height: auto; /* Allow natural height after render */
227
276
  }
228
277
 
229
278
  /* Ensure responsive sizing for mermaid SVGs */
@@ -234,18 +283,29 @@ document.addEventListener('astro:after-swap', initMermaid);
234
283
 
235
284
  /* Optional: Add subtle background for better visibility */
236
285
  @media (prefers-color-scheme: dark) {
237
- pre.mermaid {
286
+ pre.mermaid[data-processed] {
238
287
  background-color: rgba(255, 255, 255, 0.02);
239
288
  border-radius: 0.5rem;
240
289
  }
241
290
  }
242
291
 
243
292
  @media (prefers-color-scheme: light) {
244
- pre.mermaid {
293
+ pre.mermaid[data-processed] {
245
294
  background-color: rgba(0, 0, 0, 0.02);
246
295
  border-radius: 0.5rem;
247
296
  }
248
297
  }
298
+
299
+ /* Respect user's color scheme preference */
300
+ [data-theme="dark"] pre.mermaid[data-processed] {
301
+ background-color: rgba(255, 255, 255, 0.02);
302
+ border-radius: 0.5rem;
303
+ }
304
+
305
+ [data-theme="light"] pre.mermaid[data-processed] {
306
+ background-color: rgba(0, 0, 0, 0.02);
307
+ border-radius: 0.5rem;
308
+ }
249
309
  \`;
250
310
  document.head.appendChild(style);
251
311
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-mermaid",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
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",