merslim 0.1.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/LICENSE +21 -0
- package/README.md +266 -0
- package/dist/index.cjs +4349 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +654 -0
- package/dist/index.d.ts +654 -0
- package/dist/index.js +4292 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 svgdiagram contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# merslim
|
|
2
|
+
|
|
3
|
+
A **slimmer mermaid** — SVG-first diagram renderer for mermaid-style syntax. 14 native diagram types, zero mermaid runtime dependency.
|
|
4
|
+
|
|
5
|
+
| | |
|
|
6
|
+
|---|---|
|
|
7
|
+
| Bundle | ~160 KB minified ESM (vs. mermaid's ~3 MB lazy-loaded / ~7 MB full) |
|
|
8
|
+
| Runtime deps | `dagre` only |
|
|
9
|
+
| Peer deps | `react ^18 \|\| ^19`, `react-dom ^18 \|\| ^19` |
|
|
10
|
+
| Mermaid | **none** — parsers and renderers are native |
|
|
11
|
+
| Output | Standalone SVG (computed styles inlined for fidelity) |
|
|
12
|
+
| License | MIT |
|
|
13
|
+
|
|
14
|
+
## Why
|
|
15
|
+
|
|
16
|
+
Mermaid is great, but it's heavy, opaque, and not easy to extend. `merslim` re-implements the popular subset of mermaid syntax with:
|
|
17
|
+
|
|
18
|
+
- A small, typed intermediate representation (IR) you can build programmatically — no need to round-trip through text if you have structured data.
|
|
19
|
+
- A pluggable renderer registry. Diagrams are React components; lazy-loaded by type so you only pay for what you use.
|
|
20
|
+
- A serializer that walks the live DOM, inlines `getComputedStyle()` values onto a clone, and emits a self-contained SVG that opens identically in browsers, Inkscape, and Office.
|
|
21
|
+
- No vendor lock-in to a single visual style — every renderer is ~150–400 lines of plain React/SVG, easy to fork.
|
|
22
|
+
|
|
23
|
+
## Supported diagram types
|
|
24
|
+
|
|
25
|
+
`flowchart` · `sequenceDiagram` · `erDiagram` · `classDiagram` · `stateDiagram-v2` · `gantt` · `timeline` · `pie` · `quadrantChart` · `journey` · `mindmap` · `architecture-beta` · `C4Context` (Container / Component / Deployment) · `gitGraph`
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install merslim
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { DiagramRenderer, bootstrapDiagramRenderers } from 'merslim';
|
|
37
|
+
|
|
38
|
+
bootstrapDiagramRenderers(); // call once at app startup
|
|
39
|
+
|
|
40
|
+
const source = `
|
|
41
|
+
flowchart LR
|
|
42
|
+
A[Edit] --> B{Render}
|
|
43
|
+
B --> C[Export]
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
export function App() {
|
|
47
|
+
return <DiagramRenderer source={source} />;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## With the export toolbar
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { useRef } from 'react';
|
|
55
|
+
import {
|
|
56
|
+
DiagramRenderer,
|
|
57
|
+
DiagramExportToolbar,
|
|
58
|
+
type RendererHandle,
|
|
59
|
+
} from 'merslim';
|
|
60
|
+
|
|
61
|
+
export function MyDiagram({ source }: { source: string }) {
|
|
62
|
+
const handleRef = useRef<RendererHandle | null>(null);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className="group relative">
|
|
66
|
+
<DiagramRenderer source={source} handleRef={handleRef} />
|
|
67
|
+
<DiagramExportToolbar
|
|
68
|
+
source={() => handleRef.current?.getSvgElement() ?? null}
|
|
69
|
+
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100"
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The toolbar gives you four buttons — copy SVG, copy PNG, download SVG, download PNG — all routed through the same standalone-SVG serializer.
|
|
77
|
+
|
|
78
|
+
## Headless / SSR
|
|
79
|
+
|
|
80
|
+
If you only need an SVG string (e.g. to generate diagrams at build time for an MDX blog), skip the React component entirely:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { parseToIR, flowchartToSvg, type FlowchartIR } from 'merslim';
|
|
84
|
+
|
|
85
|
+
const result = await parseToIR(`
|
|
86
|
+
flowchart LR
|
|
87
|
+
A --> B
|
|
88
|
+
`);
|
|
89
|
+
|
|
90
|
+
if (result.ok && result.type === 'flowchart') {
|
|
91
|
+
const svg = flowchartToSvg(result.ir as FlowchartIR, { dark: false });
|
|
92
|
+
// write to disk, embed, ship to a CDN...
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
> **Two builder flavors.** For each graph-shaped diagram there's a one-call
|
|
97
|
+
> convenience builder (`flowchartToSvg`, `classToSvg`, `erToSvg`) that runs
|
|
98
|
+
> layout internally, and a position-taking power-user builder
|
|
99
|
+
> (`buildFlowchartSvg(ir, positions, opts)`) for callers who want custom
|
|
100
|
+
> layout. Chart-shaped diagrams (pie, quadrant, journey, gantt, timeline,
|
|
101
|
+
> c4, architecture, gitgraph) are one-call already.
|
|
102
|
+
> See [`examples/headless/generate.ts`](./examples/headless/generate.ts) for
|
|
103
|
+
> the full pattern.
|
|
104
|
+
|
|
105
|
+
## Build your own IR
|
|
106
|
+
|
|
107
|
+
The parser is one way to produce an IR; you can produce one any way you like. If you have structured data (a list of orders, a service topology, a customer journey) you can skip mermaid syntax entirely:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { flowchartToSvg, type FlowchartIR } from 'merslim';
|
|
111
|
+
|
|
112
|
+
const ir: FlowchartIR = {
|
|
113
|
+
type: 'flowchart',
|
|
114
|
+
direction: 'LR',
|
|
115
|
+
nodes: [
|
|
116
|
+
{ id: 'a', label: 'Order received', kind: 'start' },
|
|
117
|
+
{ id: 'b', label: 'Validate payment', kind: 'decision' },
|
|
118
|
+
{ id: 'c', label: 'Ship', kind: 'end' },
|
|
119
|
+
],
|
|
120
|
+
edges: [
|
|
121
|
+
{ source: 'a', target: 'b' },
|
|
122
|
+
{ source: 'b', target: 'c', label: 'paid' },
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const svg = flowchartToSvg(ir, { dark: false });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Selective renderer registration
|
|
130
|
+
|
|
131
|
+
`bootstrapDiagramRenderers()` is a convenience that registers all 14 native
|
|
132
|
+
renderers (lazy-loaded, so unused ones stay out of your initial bundle).
|
|
133
|
+
If you only need a subset and want to skip even the lazy chunks, call
|
|
134
|
+
`register()` directly:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { register, DiagramRenderer } from 'merslim';
|
|
138
|
+
|
|
139
|
+
register({
|
|
140
|
+
type: 'flowchart',
|
|
141
|
+
loader: () =>
|
|
142
|
+
import('merslim').then((m) => ({ default: m.FlowchartRenderer })),
|
|
143
|
+
});
|
|
144
|
+
// Now <DiagramRenderer/> only knows about flowcharts. Any other diagram type
|
|
145
|
+
// surfaces a "no renderer registered" error.
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
If you already have an IR and want to skip the source-string parser entirely,
|
|
149
|
+
the 14 renderers are also exported directly and can be mounted as standalone
|
|
150
|
+
components — `<FlowchartRenderer ir={ir} dark={dark}/>`, `<PieRenderer/>`,
|
|
151
|
+
`<SequenceRenderer/>`, etc.
|
|
152
|
+
|
|
153
|
+
## Dark mode
|
|
154
|
+
|
|
155
|
+
Pass a `dark` prop to `<DiagramRenderer/>`, or use the helper to track a `.dark` class on `<html>`:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { isDarkMode, watchDarkMode } from 'merslim';
|
|
159
|
+
|
|
160
|
+
const [dark, setDark] = useState(isDarkMode);
|
|
161
|
+
useEffect(() => watchDarkMode(setDark), []);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## API surface
|
|
165
|
+
|
|
166
|
+
### Components
|
|
167
|
+
|
|
168
|
+
| Export | Purpose |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `<DiagramRenderer source dark handleRef onError/>` | Parses a source string, dispatches to the matching renderer, exposes a `RendererHandle` ref. |
|
|
171
|
+
| `<DiagramExportToolbar source filenameBase pngScale .../>` | 4-button copy/download toolbar. Accepts any `SvgSource`. |
|
|
172
|
+
| `<FlowchartRenderer/>` `<SequenceRenderer/>` `<ERRenderer/>` `<ClassRenderer/>` `<StateRenderer/>` `<GanttRenderer/>` `<TimelineRenderer/>` `<PieRenderer/>` `<QuadrantRenderer/>` `<JourneyRenderer/>` `<MindmapRenderer/>` `<ArchitectureRenderer/>` `<C4Renderer/>` `<GitGraphRenderer/>` | Direct-mount renderer per diagram type. Same `RendererProps<T>` signature: `{ ir, dark, handleRef }`. Use these when you already have an IR. |
|
|
173
|
+
|
|
174
|
+
### Parser / IR
|
|
175
|
+
|
|
176
|
+
| Export | Type | Purpose |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `parseToIR(source)` | `(string) => Promise<ParseResult>` | Mermaid syntax → typed IR. On success, narrows to `{ ok: true, type: DiagramType, ir: DiagramIR }`. |
|
|
179
|
+
| `detectDiagramType(source)` | `(string) => Promise<RecognizedDiagramType \| null>` | Lightweight first-line check. Returns `null` for empty input, `'unsupported'` for unrecognized headers. |
|
|
180
|
+
|
|
181
|
+
### Builders (headless)
|
|
182
|
+
|
|
183
|
+
| Convenience (auto-layout) | Power-user (explicit positions) |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `flowchartToSvg(ir, opts)` | `buildFlowchartSvg(ir, positions, opts)` |
|
|
186
|
+
| `classToSvg(ir, opts)` | `buildClassSvg(ir, positions, opts)` |
|
|
187
|
+
| `erToSvg(ir, opts)` | `buildErSvg(ir, positions, opts)` |
|
|
188
|
+
| — | `buildStateSvg(ir, { topLevel, children }, opts)` |
|
|
189
|
+
| — | `buildMindmapSvg(ir, positions, opts)` |
|
|
190
|
+
|
|
191
|
+
Plus the chart-shaped diagrams which never need positions:
|
|
192
|
+
`buildPieSvg`, `buildQuadrantSvg`, `buildJourneySvg`, `buildGanttSvg`,
|
|
193
|
+
`buildTimelineSvg`, `buildArchitectureSvg`, `buildC4Svg`, `buildGitGraphSvg`.
|
|
194
|
+
|
|
195
|
+
All builders take a final `{ dark, padding }` options object and return a
|
|
196
|
+
self-contained SVG string with `role="img"` and an `aria-label`.
|
|
197
|
+
|
|
198
|
+
### Export pipeline
|
|
199
|
+
|
|
200
|
+
| Export | Purpose |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `toSvgString(source, opts)` | Serialize any `SvgSource` to a standalone SVG string. |
|
|
203
|
+
| `svgToPngBlob(svg, opts)` | Rasterize an SVG string to a PNG `Blob`. |
|
|
204
|
+
| `downloadSvg / downloadPng` | Trigger a file download. |
|
|
205
|
+
| `copySvgToClipboard / copyPngToClipboard` | Write SVG (text) / PNG (image) to the clipboard. |
|
|
206
|
+
| `getSvgDimensions(svg)` | Best-effort intrinsic size from `viewBox` / attrs. |
|
|
207
|
+
|
|
208
|
+
### Registry
|
|
209
|
+
|
|
210
|
+
| Export | Purpose |
|
|
211
|
+
|---|---|
|
|
212
|
+
| `register({ type, loader })` | Register a renderer for a diagram type. |
|
|
213
|
+
| `bootstrapDiagramRenderers()` | One-shot registration of all built-in renderers. |
|
|
214
|
+
| `getRenderer(type) / hasRenderer(type)` | Introspect the registry. |
|
|
215
|
+
|
|
216
|
+
## Examples
|
|
217
|
+
|
|
218
|
+
- [`examples/playground/`](./examples/playground/) — Vite + React playground
|
|
219
|
+
with all 14 diagram types, a live source editor, dark-mode toggle, and
|
|
220
|
+
the export toolbar. `npm install && npm run dev` (or `npm run build` for a
|
|
221
|
+
static `dist/` that opens directly from `file://`).
|
|
222
|
+
- [`examples/headless/`](./examples/headless/) — Node script that generates
|
|
223
|
+
one self-contained SVG per diagram type. `npm install && npm start`.
|
|
224
|
+
- [`examples/react/App.tsx`](./examples/react/App.tsx) — Minimal React
|
|
225
|
+
snippet showing the same component wiring without the playground chrome.
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
npm install
|
|
231
|
+
npm run type-check # tsc --noEmit
|
|
232
|
+
npm test # vitest run
|
|
233
|
+
npm run build # tsup → dist/ (ESM + CJS + .d.ts)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Notes & limitations
|
|
237
|
+
|
|
238
|
+
- The built-in renderers use a handful of Tailwind utility classes for surrounding chrome (loading / error states). The diagrams themselves are pure SVG and render correctly without Tailwind; only the wrapper container styling looks bare. PRs to make this opt-out are welcome.
|
|
239
|
+
- `parseToIR` is asynchronous because some builders (gantt, timeline) defer parsing work. Today the body is synchronous but the signature is stable.
|
|
240
|
+
|
|
241
|
+
## Mermaid compatibility
|
|
242
|
+
|
|
243
|
+
merslim parses a curated subset of mermaid syntax. The full contract is enforced by [`test/compatCorpus.ts`](./test/compatCorpus.ts) — every entry there is a parse-time test that runs in CI.
|
|
244
|
+
|
|
245
|
+
**Known gaps** (parse but produce a partial IR, or fail outright):
|
|
246
|
+
|
|
247
|
+
| Diagram | Gap |
|
|
248
|
+
|---|---|
|
|
249
|
+
| flowchart | Multi-target shorthand `A & B --> C & D` |
|
|
250
|
+
| flowchart | Trapezoid shape `[/Foo\]` (falls back to rect) |
|
|
251
|
+
| flowchart | `<br/>` in labels treated as literal text, not a line break |
|
|
252
|
+
| sequence | `loop`/`alt`/`opt`/`par` blocks parse but render flat (no visual nesting) |
|
|
253
|
+
| sequence | `autonumber` keyword accepted but not honored |
|
|
254
|
+
| sequence | Bidirectional `<<->>` arrow |
|
|
255
|
+
| class | Generics `class Container~T~` |
|
|
256
|
+
| class | Cardinality labels `"1" --> "*"` |
|
|
257
|
+
| class | `namespace { ... }` blocks |
|
|
258
|
+
| state | Parallel/concurrent regions (`--` separator) |
|
|
259
|
+
| state | `<<choice>>`/`<<fork>>`/`<<join>>` pseudo-states |
|
|
260
|
+
| gantt | `excludes`/`todayMarker`/`tickInterval` accepted but not modeled |
|
|
261
|
+
|
|
262
|
+
If you hit a case not listed here, [add it to the corpus as a `known-gap` entry](./test/compatCorpus.ts) — that converts an issue into an executable spec.
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT. See [LICENSE](./LICENSE).
|