├── doc ├── static │ ├── .nojekyll │ └── img │ │ ├── favicon.ico │ │ ├── docusaurus.png │ │ ├── tutorial │ │ ├── localeDropdown.png │ │ └── docsVersionDropdown.png │ │ └── logo.svg ├── versions.json ├── babel.config.js ├── src │ ├── pages │ │ ├── markdown-page.md │ │ ├── index.module.css │ │ └── index.js │ ├── components │ │ ├── HomepageFeatures.module.css │ │ └── HomepageFeatures.js │ └── css │ │ └── custom.css ├── versioned_sidebars │ ├── version-1.2.3-sidebars.json │ └── version-1.3.0-sidebars.json ├── docs │ ├── theme.mdx │ ├── mermaid-versions.md │ ├── intro.md │ └── examples.mdx ├── .gitignore ├── versioned_docs │ ├── version-1.2.3 │ │ ├── mermaid-versions.md │ │ ├── theme.mdx │ │ ├── intro.md │ │ └── examples.mdx │ └── version-1.3.0 │ │ ├── mermaid-versions.md │ │ ├── theme.mdx │ │ ├── intro.md │ │ └── examples.mdx ├── sidebars.js ├── README.md ├── package.json └── docusaurus.config.js ├── .devcontainer ├── library-scripts │ └── meta.env ├── Dockerfile └── devcontainer.json ├── examples ├── react-example-app │ ├── .env │ ├── src │ │ ├── react-app-env.d.ts │ │ ├── setupTests.ts │ │ ├── index.css │ │ ├── reportWebVitals.ts │ │ ├── index.tsx │ │ ├── App.css │ │ ├── App.tsx │ │ └── logo.svg │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── index.html │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ └── README.md └── mdx-example │ ├── babel.config.js │ ├── example.md │ ├── example.mdx │ ├── rollup.config.js │ ├── index.js │ ├── package.json │ └── .gitignore ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── build.yml ├── changelog.md ├── .gitattributes ├── .yarnrc.yml ├── .editorconfig ├── .gitignore ├── .vscode ├── extensions.json └── launch.json ├── package.json ├── lib ├── jest.config.js ├── .eslintrc.js ├── rollup.config.js ├── index.d.ts ├── tsconfig.json ├── src │ ├── theme.helper.ts │ ├── config.model.ts │ ├── theme.helper.spec.ts │ ├── Mermaid.tsx │ ├── mdxast-mermaid.spec.ts │ ├── Mermaid.spec.tsx │ ├── __snapshots__ │ │ └── Mermaid.spec.tsx.snap │ └── mdxast-mermaid.ts └── package.json ├── license └── readme.md /doc/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.devcontainer/library-scripts/meta.env: -------------------------------------------------------------------------------- 1 | VERSION='dev' -------------------------------------------------------------------------------- /doc/versions.json: -------------------------------------------------------------------------------- 1 | [ 2 | "1.3.0", 3 | "1.2.3" 4 | ] 5 | -------------------------------------------------------------------------------- /examples/react-example-app/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: sjwall -------------------------------------------------------------------------------- /examples/react-example-app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /doc/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjwall/mdx-mermaid/HEAD/doc/static/img/favicon.ico -------------------------------------------------------------------------------- /doc/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjwall/mdx-mermaid/HEAD/doc/static/img/docusaurus.png -------------------------------------------------------------------------------- /doc/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /examples/react-example-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /doc/static/img/tutorial/localeDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjwall/mdx-mermaid/HEAD/doc/static/img/tutorial/localeDropdown.png -------------------------------------------------------------------------------- /doc/static/img/tutorial/docsVersionDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjwall/mdx-mermaid/HEAD/doc/static/img/tutorial/docsVersionDropdown.png -------------------------------------------------------------------------------- /examples/react-example-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjwall/mdx-mermaid/HEAD/examples/react-example-app/public/favicon.ico -------------------------------------------------------------------------------- /examples/react-example-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjwall/mdx-mermaid/HEAD/examples/react-example-app/public/logo192.png -------------------------------------------------------------------------------- /examples/react-example-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjwall/mdx-mermaid/HEAD/examples/react-example-app/public/logo512.png -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | See [GitHub Releases][releases] for the changelog. 4 | 5 | [releases]: https://github.com/sjwall/mdx-mermaid/releases 6 | -------------------------------------------------------------------------------- /examples/mdx-example/babel.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | presets: [ 3 | ['@babel/preset-env', { targets: { node: 'current' } }], 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /examples/mdx-example/example.md: -------------------------------------------------------------------------------- 1 | ## Framework AARRR 2 | 3 | ```mermaid 4 | graph LR; 5 | Acquisition-->Activation-->Rétention-->Recommandation-->Revenu 6 | ``` 7 | -------------------------------------------------------------------------------- /examples/mdx-example/example.mdx: -------------------------------------------------------------------------------- 1 | ## Framework AARRR 2 | 3 | ```mermaid 4 | graph LR; 5 | Acquisition-->Activation-->Rétention-->Recommandation-->Revenu 6 | ``` 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/** linguist-vendored 2 | /.yarn/releases/* binary 3 | /.yarn/plugins/**/* binary 4 | /.pnp.* binary linguist-generated 5 | -------------------------------------------------------------------------------- /doc/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /doc/versioned_sidebars/version-1.2.3-sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "tutorialSidebar": [ 3 | { 4 | "type": "autogenerated", 5 | "dirName": "." 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /doc/versioned_sidebars/version-1.3.0-sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "tutorialSidebar": [ 3 | { 4 | "type": "autogenerated", 5 | "dirName": "." 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | plugins: 4 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 5 | spec: "@yarnpkg/plugin-interactive-tools" 6 | 7 | yarnPath: .yarn/releases/yarn-3.4.1.cjs 8 | -------------------------------------------------------------------------------- /doc/docs/theme.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Theme 6 | 7 | The theme can be changed between dark and light themes by setting `theme` values in the configuration: 8 | 9 | ```js 10 | { 11 | theme: { light: 'neutral', dark: 'forest' } 12 | } 13 | ``` 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | -------------------------------------------------------------------------------- /doc/src/components/HomepageFeatures.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | .features { 4 | display: flex; 5 | align-items: center; 6 | padding: 2rem 0; 7 | width: 100%; 8 | } 9 | 10 | .featureSvg { 11 | height: 200px; 12 | width: 200px; 13 | } 14 | -------------------------------------------------------------------------------- /examples/react-example-app/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Dependencies 3 | node_modules 4 | 5 | /lib/lib 6 | /lib/dist 7 | /lib/coverage 8 | /lib/temp 9 | 10 | # Misc 11 | .DS_Store 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | 21 | .yarn/cache 22 | .yarn/install-state.gz 23 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "eamodio.gitlens", 4 | "dbaeumer.vscode-eslint", 5 | "editorconfig.editorconfig", 6 | "ms-azuretools.vscode-docker", 7 | "davidanson.vscode-markdownlint", 8 | "streetsidesoftware.code-spell-checker", 9 | "silvenon.mdx", 10 | "redhat.vscode-yaml", 11 | "orta.vscode-jest" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /examples/mdx-example/rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel'; 2 | 3 | export default { 4 | input: 'index.js', 5 | output: [ 6 | { format: 'esm', file: 'dist/esm/index.mjs' }, 7 | ], 8 | external: ['react', 'react-dom'], 9 | plugins: [ 10 | babel({ 11 | babelHelpers: 'bundled', 12 | presets: ['@babel/preset-react'], 13 | }), 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /doc/docs/mermaid-versions.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Supported Mermaid versions 6 | 7 | This library supports Mermaid versions `>= 8.11.0` 8 | 9 | The version needs to be at least `8.11.0` due to this issue: https://github.com/sjwall/mdx-mermaid/issues/20 10 | 11 | To use Mermaid versions `>=8.12.0` ESM must be used, due to the `d3` version upgrade to modules https://github.com/mermaid-js/mermaid/issues/2559 12 | -------------------------------------------------------------------------------- /examples/react-example-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/react-example-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /doc/versioned_docs/version-1.2.3/mermaid-versions.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Supported Mermaid versions 6 | 7 | This library supports Mermaid versions `>= 8.11.0 < 8.12.0` 8 | 9 | The version needs to be at least `8.11.0` due to this issue: https://github.com/sjwall/mdx-mermaid/issues/20 10 | 11 | The version needs to be less than `8.12.0` due to the `d3` version upgrade to modules https://github.com/mermaid-js/mermaid/issues/2559 12 | -------------------------------------------------------------------------------- /doc/versioned_docs/version-1.3.0/mermaid-versions.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Supported Mermaid versions 6 | 7 | This library supports Mermaid versions `>= 8.11.0` 8 | 9 | The version needs to be at least `8.11.0` due to this issue: https://github.com/sjwall/mdx-mermaid/issues/20 10 | 11 | To use Mermaid versions `>=8.12.0` ESM must be used, due to the `d3` version upgrade to modules https://github.com/mermaid-js/mermaid/issues/2559 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdx-mermaid-workspace", 3 | "version": "2.0.3", 4 | "packageManager": "yarn@3.4.1", 5 | "private": true, 6 | "workspaces": [ 7 | "doc", 8 | "examples/*", 9 | "lib" 10 | ], 11 | "devDependencies": { 12 | "commitizen": "^4.3.1", 13 | "cz-conventional-changelog": "^3.3.0" 14 | }, 15 | "config": { 16 | "commitizen": { 17 | "path": "./node_modules/cz-conventional-changelog" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | **Describe the solution you'd like** 7 | A clear and concise description of what you want to happen. 8 | 9 | **Describe alternatives you've considered** 10 | A clear and concise description of any alternative solutions or features you've considered. 11 | 12 | **Additional context** 13 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /lib/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */ 2 | export default { 3 | preset: 'ts-jest/presets/default-esm', 4 | transform: {}, 5 | globals: { 6 | 'ts-jest': { 7 | useESM: true 8 | } 9 | }, 10 | testEnvironment: 'jsdom', 11 | coverageThreshold: { 12 | global: { 13 | branches: 100, 14 | functions: 100, 15 | lines: 100, 16 | statements: 100 17 | } 18 | }, 19 | roots: [ 20 | 'src' 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /examples/react-example-app/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /examples/react-example-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /lib/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | jest: true 6 | }, 7 | extends: [ 8 | 'plugin:react/recommended', 9 | 'standard' 10 | ], 11 | parser: '@typescript-eslint/parser', 12 | parserOptions: { 13 | ecmaFeatures: { 14 | jsx: true 15 | }, 16 | ecmaVersion: 12, 17 | sourceType: 'module' 18 | }, 19 | plugins: [ 20 | 'react', 21 | '@typescript-eslint', 22 | 'jsdoc' 23 | ], 24 | rules: { 25 | 'no-use-before-define': 'off', 26 | '@typescript-eslint/no-use-before-define': ['error'] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript' 2 | 3 | /** @type {import('rollup').RollupOptions} */ 4 | const createConfig = (path, x) => ({ 5 | input: `src/${path}.ts${x ? 'x' : ''}`, 6 | output: [ 7 | { format: 'esm', file: `lib/${path}.mjs` }, 8 | { 9 | format: 'cjs', file: `lib/${path}.cjs` 10 | } 11 | ], 12 | external: ['react', 'react-dom'], 13 | plugins: [ 14 | typescript() 15 | ] 16 | }) 17 | 18 | /** @type {import('rollup').RollupOptions[]} */ 19 | export default [ 20 | createConfig('mdxast-mermaid'), 21 | createConfig('Mermaid', true) 22 | ] 23 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Samuel Wall. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * license file in the root directory of this source tree. 6 | */ 7 | 8 | import plugin from './lib/mdxast-mermaid' 9 | import { Config as mConfig } from './lib/config.model' 10 | import { Mermaid as mMermaid, MermaidProps as mMermaidProps } from './lib/Mermaid' 11 | 12 | declare module 'mdx-mermaid' { 13 | namespace mdxmermaid { 14 | export type Config = mConfig 15 | export type MermaidProps = mMermaidProps 16 | export type Mermaid = typeof mMermaid 17 | } 18 | } 19 | export default plugin 20 | -------------------------------------------------------------------------------- /examples/react-example-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "jsx": "react", 5 | "lib": ["ESNext", "DOM"], 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "noImplicitAny": false, 9 | "rootDir": "src", 10 | "skipLibCheck": true, 11 | "sourceMap": true, 12 | "strict": true, 13 | "target": "ES2020" 14 | }, 15 | "include": [ 16 | "src/**/*.ts", 17 | "src/**/*.tsx" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | "index.d.ts", 22 | "**/*.spec.ts", 23 | "**/*.spec.tsx" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /examples/react-example-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /doc/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | /** 4 | * CSS files with the .module.css suffix will be treated as CSS modules 5 | * and scoped locally. 6 | */ 7 | 8 | .heroBanner { 9 | padding: 4rem 0; 10 | text-align: center; 11 | position: relative; 12 | overflow: hidden; 13 | } 14 | 15 | @media screen and (max-width: 966px) { 16 | .heroBanner { 17 | padding: 2rem; 18 | } 19 | } 20 | 21 | .buttons { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | } 26 | 27 | .banner { 28 | font-weight: bold; 29 | font-size: 20px; 30 | padding: 20px; 31 | max-width: 768px; 32 | margin: 0 auto; 33 | text-align: center; 34 | } 35 | -------------------------------------------------------------------------------- /doc/src/components/HomepageFeatures.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './HomepageFeatures.module.css' 3 | import { Mermaid } from 'mdx-mermaid/Mermaid' 4 | 5 | export default function HomepageFeatures () { 6 | return ( 7 |
8 |
9 |
10 | mf[Markdown file]; 13 | mf-->cm[\`\`\`mermaid \`\`\`]; 14 | cm-->mdx[mdx-mermaid]; 15 | mdx-->Mermaid; 16 | Mermaid-->SVG; 17 | `} 18 | // This isn't processed by the parser so needs config passing if it's to be configured 19 | config={{}}/> 20 |
21 |
22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /examples/react-example-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/mdx-example/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { runSync, compile } from '@mdx-js/mdx'; 3 | import * as runtime from 'react/jsx-runtime.js'; 4 | import { renderToStaticMarkup } from 'react-dom/server.js'; 5 | import {Mermaid} from 'mdx-mermaid/lib/Mermaid' 6 | 7 | (async () => { 8 | const mdxMermaid = await import('mdx-mermaid'); 9 | 10 | const markdown = fs.readFileSync('./example.md', 'utf-8'); 11 | 12 | const code = String(await compile(markdown, { 13 | outputFormat: 'function-body', 14 | remarkPlugins: [[mdxMermaid.default, {output: 'svg'}]], 15 | })); 16 | 17 | const { default: Content } = runSync(code, runtime); 18 | 19 | const html = renderToStaticMarkup(Content({components: {mermaid: Mermaid, Mermaid}})); 20 | 21 | console.log(html); 22 | })(); 23 | -------------------------------------------------------------------------------- /doc/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['hello'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | module.exports = sidebars; 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behaviour including how the markdown file is processed: 12 | 1. Setup themes variables '...' 13 | 2. Create diagram like '....' 14 | 3. Build with '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Stack trace** 24 | If applicable, add the stack trace for the exception thrown to help trace your problem. 25 | 26 | **Tool chain used:** 27 | What tool chain is used. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. -------------------------------------------------------------------------------- /doc/versioned_docs/version-1.2.3/theme.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Theme 6 | 7 | The theme can be changed between dark and light themes by setting `theme` values in the configuration: 8 | 9 | ```js title=docusaurus.config.js 10 | remarkPlugins: [[require('mdx-mermaid'), { 11 | theme: { light: 'neutral', dark: 'forest' } 12 | }]], 13 | ``` 14 | 15 | Toggle the theme in the top right corner to change this diagrams theme between `neutral` and `forest`: 16 | 17 | >John: Hello John, how are you? 21 | loop Healthcheck 22 | John->>John: Fight against hypochondria 23 | end 24 | Note right of John: Rational thoughts
prevail! 25 | John-->>Alice: Great! 26 | John->>Bob: How about you? 27 | Bob-->>John: Jolly good!`} config={ { theme: { light: 'neutral', dark: 'forest' } } } /> 28 | -------------------------------------------------------------------------------- /doc/versioned_docs/version-1.3.0/theme.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Theme 6 | 7 | The theme can be changed between dark and light themes by setting `theme` values in the configuration: 8 | 9 | ```js title=docusaurus.config.js 10 | remarkPlugins: [[await import('mdx-mermaid'), { 11 | theme: { light: 'neutral', dark: 'forest' } 12 | }]], 13 | ``` 14 | 15 | Toggle the theme in the top right corner to change this diagrams theme between `neutral` and `forest`: 16 | 17 | >John: Hello John, how are you? 21 | loop Healthcheck 22 | John->>John: Fight against hypochondria 23 | end 24 | Note right of John: Rational thoughts
prevail! 25 | John-->>Alice: Great! 26 | John->>Bob: How about you? 27 | Bob-->>John: Jolly good!`} config={ { theme: { light: 'neutral', dark: 'forest' } } } /> 28 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ## Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ## Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ## Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ## Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /examples/react-example-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Mermaid } from 'mdx-mermaid/lib/Mermaid' 3 | import ReactMarkdown from 'react-markdown' 4 | import mermaid from 'mdx-mermaid' 5 | import logo from './logo.svg'; 6 | import './App.css'; 7 | 8 | const markdown = `\`\`\`mermaid 9 | graph TD; 10 | A-->B; 11 | A-->C; 12 | B-->D; 13 | C-->D; 14 | \`\`\`` 15 | 16 | function App() { 17 | return ( 18 |
19 |
20 | logo 21 | >React: Hello React, how are you?`} /> 25 | 26 | 27 |
28 |
29 | ); 30 | } 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /examples/mdx-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdx-mermaid-example", 3 | "version": "2.0.3", 4 | "module": "dist/esm/index.mjs", 5 | "type": "module", 6 | "scripts": { 7 | "build": "rollup -c", 8 | "watch": "rollup -c -w", 9 | "start": "node ./dist/esm/index.mjs" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.26.0", 13 | "@babel/preset-env": "^7.26.0", 14 | "@babel/preset-react": "^7.25.9", 15 | "@rollup/plugin-babel": "^6.0.4", 16 | "@rollup/plugin-commonjs": "^24.1.0", 17 | "eslint": "^8.57.1", 18 | "eslint-config-airbnb-base": "^15.0.0", 19 | "eslint-plugin-import": "^2.31.0", 20 | "rollup": "^3.29.5" 21 | }, 22 | "dependencies": { 23 | "@mdx-js/mdx": "^2.3.0", 24 | "hast-util-from-html": "^1.0.2", 25 | "hast-util-to-html": "^8.0.4", 26 | "hast-util-to-mdast": "^8.4.1", 27 | "mdx-mermaid": "workspace:*", 28 | "puppeteer": "^22.15.0", 29 | "react": "^17.0.2", 30 | "react-dom": "^17.0.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/theme.helper.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from './config.model' 2 | 3 | export const DEFAULT_DARK_THEME = 'dark' 4 | export const DEFAULT_LIGHT_THEME = 'default' 5 | 6 | export const DARK_THEME_KEY = 'dark' 7 | export const LIGHT_THEME_KEY = 'light' 8 | 9 | export const HTML_THEME_ATTRIBUTE = 'data-theme' 10 | 11 | /** 12 | * Gets the theme based on config and current data-theme of the HTML. 13 | * 14 | * @param html The HTML element of the page. 15 | * @param config The configuration for this chart. 16 | */ 17 | export function getTheme (html: HTMLHtmlElement, config?: Config): string { 18 | let htmlTheme = html.getAttribute(HTML_THEME_ATTRIBUTE) ?? LIGHT_THEME_KEY 19 | 20 | if (!(htmlTheme === LIGHT_THEME_KEY || htmlTheme === DARK_THEME_KEY)) { 21 | htmlTheme = LIGHT_THEME_KEY 22 | } 23 | 24 | const defaultTheme = htmlTheme === LIGHT_THEME_KEY 25 | ? DEFAULT_LIGHT_THEME 26 | : DEFAULT_DARK_THEME 27 | 28 | return config?.theme?.[htmlTheme] ?? 29 | config?.mermaid?.theme ?? 30 | defaultTheme 31 | } 32 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2021 Samuel Wall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /examples/react-example-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdx-mermaid-react-example-app", 3 | "version": "2.0.3", 4 | "private": true, 5 | "dependencies": { 6 | "@types/node": "^18.19.64", 7 | "@types/react": "^17.0.83", 8 | "@types/react-dom": "^17.0.25", 9 | "mdx-mermaid": "workspace:*", 10 | "mermaid": "^9.4.3", 11 | "react": "^17.0.2", 12 | "react-dom": "^17.0.2", 13 | "react-markdown": "^8.0.7", 14 | "react-scripts": "5.0.1", 15 | "typescript": "^4.9.5", 16 | "web-vitals": "^3.5.2" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": [ 26 | "react-app", 27 | "react-app/jest" 28 | ] 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /doc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdx-mermaid-doc", 3 | "version": "2.0.3", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^2.4.3", 18 | "@docusaurus/preset-classic": "^2.4.3", 19 | "@docusaurus/theme-mermaid": "^2.4.3", 20 | "@mdx-js/react": "^1.6.22", 21 | "clsx": "^1.2.1", 22 | "mdx-mermaid": "^1.3.2", 23 | "mermaid": "^9.4.3", 24 | "prism-react-renderer": "^1.3.5", 25 | "react": "^17.0.2", 26 | "react-dom": "^17.0.2" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.5%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /doc/versioned_docs/version-1.2.3/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Tutorial 6 | 7 | Setup mdx-mermaid in Docusaurus. 8 | 9 | ## Installing 10 | 11 | Use your preferred package manager to install. 12 | 13 | ```shell title=NPM 14 | npm i mdx-mermaid mermaid 15 | ``` 16 | 17 | ```shell title=Yarn 18 | yarn add mdx-mermaid mermaid 19 | ``` 20 | 21 | ```shell title=PNPM 22 | pnpm add mdx-mermaid mermaid 23 | ``` 24 | 25 | ## Configure in Docusaurus 26 | 27 | Add 28 | 29 | ```js 30 | require('mdx-mermaid') 31 | ``` 32 | 33 | to `remarkPlugins` 34 | 35 | ```js title=docusaurus.config.js 36 | presets: [ 37 | [ 38 | '@docusaurus/preset-classic', 39 | { 40 | docs: { 41 | remarkPlugins: [require('mdx-mermaid')], 42 | ``` 43 | 44 | ## Add a Diagram 45 | 46 | Add a Mermaid diagram to a `.md` or `.mdx` file. 47 | 48 | ````md title="Example Mermaid diagram" 49 | ```mermaid 50 | graph TD; 51 | A-->B; 52 | A-->C; 53 | B-->D; 54 | C-->D; 55 | ``` 56 | ```` 57 | 58 | ## Admire your diagram 59 | 60 | Take the time to appreciate your diagram. 61 | 62 | ```mermaid 63 | graph TD; 64 | A-->B; 65 | A-->C; 66 | B-->D; 67 | C-->D; 68 | ``` 69 | -------------------------------------------------------------------------------- /doc/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | } 18 | 19 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 20 | [data-theme='dark'] { 21 | --ifm-color-primary: #25c2a0; 22 | --ifm-color-primary-dark: #21af90; 23 | --ifm-color-primary-darker: #1fa588; 24 | --ifm-color-primary-darkest: #1a8870; 25 | --ifm-color-primary-light: #29d5b0; 26 | --ifm-color-primary-lighter: #32d8b4; 27 | --ifm-color-primary-lightest: #4fddbf; 28 | } 29 | 30 | .docusaurus-highlight-code-line { 31 | background-color: rgba(0, 0, 0, 0.1); 32 | display: block; 33 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 34 | padding: 0 var(--ifm-pre-padding); 35 | } 36 | 37 | [data-theme='dark'] .docusaurus-highlight-code-line { 38 | background-color: rgba(0, 0, 0, 0.3); 39 | } 40 | -------------------------------------------------------------------------------- /doc/docs/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Tutorial 6 | 7 | Setup mdx-mermaid. 8 | 9 | ## Installing 10 | 11 | Use your preferred package manager to install. 12 | 13 | ```shell title=NPM 14 | npm i mdx-mermaid mermaid 15 | ``` 16 | 17 | ```shell title=Yarn 18 | yarn add mdx-mermaid mermaid 19 | ``` 20 | 21 | ```shell title=PNPM 22 | pnpm add mdx-mermaid mermaid 23 | ``` 24 | 25 | :::danger 26 | 27 | For Docusaurus use `@docusaurus/theme-mermaid@^2.3.1` and @mdxjs/mdx v1 use version `mdx-mermaid@^1.3.0` 28 | 29 | ::: 30 | 31 | :::info 32 | 33 | For @mdxjs/mdx v2 use version `mdx-mermaid@^2.0.0` 34 | 35 | ::: 36 | 37 | ## Configure 38 | 39 | Configure the plugin: 40 | 41 | ```js 42 | import mdxMermaid from 'mdx-mermaid' 43 | import {Mermaid} from 'mdx-mermaid/lib/Mermaid' 44 | 45 | { 46 | remarkPlugins: [[mdxMermaid.default, {output: 'svg'}]], 47 | components: {mermaid: Mermaid, Mermaid} 48 | } 49 | ``` 50 | 51 | ## Add a Diagram 52 | 53 | Add a Mermaid diagram to a `.md` or `.mdx` file. 54 | 55 | ````md title="Example Mermaid diagram" 56 | ```mermaid 57 | graph TD; 58 | A-->B; 59 | A-->C; 60 | B-->D; 61 | C-->D; 62 | ``` 63 | ```` 64 | 65 | ## Admire your diagram 66 | 67 | Take the time to appreciate your diagram. 68 | 69 | ```mermaid 70 | graph TD; 71 | A-->B; 72 | A-->C; 73 | B-->D; 74 | C-->D; 75 | ``` 76 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/blob/main/containers/typescript-node/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster 4 | ARG VARIANT="16-buster" 5 | FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} 6 | 7 | # Install tslint, typescript. eslint is installed by javascript image 8 | ARG NODE_MODULES="tslint-to-eslint-config typescript" 9 | COPY library-scripts/meta.env /usr/local/etc/vscode-dev-containers 10 | RUN su node -c "umask 0002 && npm install -g ${NODE_MODULES}" \ 11 | && npm cache clean --force > /dev/null 2>&1 12 | 13 | # [Optional] Uncomment this section to install additional OS packages. 14 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 15 | && apt-get -y install --no-install-recommends libnss3 libatk1.0-0 libgbm-dev libatk-bridge2.0-0 libgtk-3.0 libasound2 16 | 17 | # [Optional] Uncomment if you want to install an additional version of node using nvm 18 | # ARG EXTRA_NODE_VERSION=10 19 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 20 | 21 | # [Optional] Uncomment if you want to install more global node packages 22 | RUN su node -c "yarn config set --home enableTelemetry 0" 23 | -------------------------------------------------------------------------------- /doc/versioned_docs/version-1.3.0/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Tutorial 6 | 7 | Setup mdx-mermaid in Docusaurus. 8 | 9 | ## Installing 10 | 11 | Use your preferred package manager to install. 12 | 13 | ```shell title=NPM 14 | npm i mdx-mermaid@^1.3.0 mermaid 15 | ``` 16 | 17 | ```shell title=Yarn 18 | yarn add mdx-mermaid@^1.3.0 mermaid 19 | ``` 20 | 21 | ```shell title=PNPM 22 | pnpm add mdx-mermaid@^1.3.0 mermaid 23 | ``` 24 | 25 | ## Configure in Docusaurus 26 | 27 | Import the module and pass it to `remarkPlugins`: 28 | 29 | ```js title=docusaurus.config.js 30 | 31 | async function createConfig() { 32 | const mdxMermaid = await import('mdx-mermaid') 33 | 34 | return { 35 | presets: [ 36 | [ 37 | 'classic', 38 | { 39 | docs: { 40 | remarkPlugins: [mdxMermaid.default], 41 | } 42 | } 43 | ] 44 | ] 45 | } 46 | } 47 | 48 | module.exports = createConfig; 49 | ``` 50 | 51 | ## Add a Diagram 52 | 53 | Add a Mermaid diagram to a `.md` or `.mdx` file. 54 | 55 | ````md title="Example Mermaid diagram" 56 | ```mermaid 57 | graph TD; 58 | A-->B; 59 | A-->C; 60 | B-->D; 61 | C-->D; 62 | ``` 63 | ```` 64 | 65 | ## Admire your diagram 66 | 67 | Take the time to appreciate your diagram. 68 | 69 | ```mermaid 70 | graph TD; 71 | A-->B; 72 | A-->C; 73 | B-->D; 74 | C-->D; 75 | ``` 76 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Build via yarn", 11 | "runtimeExecutable": "yarn", 12 | "runtimeArgs": [ 13 | "build" 14 | ], 15 | "port": 9229, 16 | "skipFiles": [ 17 | "/**" 18 | ] 19 | }, 20 | { 21 | "type": "node", 22 | "request": "launch", 23 | "name": "Test via yarn", 24 | "runtimeExecutable": "yarn", 25 | "runtimeArgs": [ 26 | "test" 27 | ], 28 | "port": 9229, 29 | "skipFiles": [ 30 | "/**" 31 | ] 32 | }, 33 | { 34 | "type": "node", 35 | "request": "launch", 36 | "name": "Launch via yarn", 37 | "cwd": "${workspaceFolder}/doc", 38 | "runtimeExecutable": "yarn", 39 | "runtimeArgs": [ 40 | "start" 41 | ], 42 | "port": 9229, 43 | "skipFiles": [ 44 | "/**" 45 | ] 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/config.model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Samuel Wall. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * license file in the root directory of this source tree. 6 | */ 7 | 8 | import type { MermaidConfig } from 'mermaid' 9 | 10 | /** 11 | * mdx-mermaid config 12 | */ 13 | export type Config = { 14 | /** 15 | * Theme to use. 16 | * 17 | * For available themes, see: https://github.com/mermaid-js/mermaid/blob/develop/src/themes/index.js. 18 | * 19 | * If set, this `theme` member overrides anything set by `mermaid.theme`. 20 | */ 21 | theme?: { 22 | /** 23 | * Theme to use when HTML data theme is 'light'. 24 | * 25 | * Defaults to `DEFAULT_LIGHT_THEME`. 26 | */ 27 | light: string 28 | 29 | /** 30 | * Theme to use when HTML data theme is 'dark'. 31 | * 32 | * Defaults to `DEFAULT_DARK_THEME`. 33 | */ 34 | dark: string 35 | }; 36 | 37 | /** 38 | * Mermaid configuration. 39 | */ 40 | mermaid?: MermaidConfig 41 | 42 | /** 43 | * What format to output into the mdast tree as. 44 | * 45 | * ast - ast format where a `Mermaid` component must be supplied in the parser. 46 | * svg - Converts the diagram to a jsx component that renders the svg. 47 | * 48 | * @default 'ast' 49 | */ 50 | output?: 'ast' | 'svg' 51 | 52 | /** 53 | * URL to the mermaid build to use in the svg render. 54 | * 55 | * Default is: https://cdn.jsdelivr.net/npm/mermaid@9.3.0/dist/mermaid.min.js 56 | */ 57 | svgMermaidSrc?: string 58 | }; 59 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node 3 | { 4 | "name": "mdx-mermaid", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "build": { 7 | "dockerfile": "Dockerfile", 8 | // Update 'VARIANT' to pick a Node version: 12, 14, 16 9 | "args": { 10 | "VARIANT": "18" 11 | } 12 | }, 13 | 14 | // Features to add to the dev container. More info: https://containers.dev/features. 15 | // "features": {}, 16 | 17 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 18 | // "forwardPorts": [], 19 | 20 | // Use 'postCreateCommand' to run commands after the container is created. 21 | "postCreateCommand": "git config commit.gpgsign true && yarn config set --home enableTelemetry 0 && yarn install", 22 | "postStartCommand": "git config --global gpg.program $(which gpg)", 23 | 24 | // Configure tool-specific properties. 25 | "customizations": { 26 | "vscode": { 27 | "extensions": [ 28 | "eamodio.gitlens", 29 | "dbaeumer.vscode-eslint", 30 | "editorconfig.editorconfig", 31 | "ms-azuretools.vscode-docker", 32 | "davidanson.vscode-markdownlint", 33 | "streetsidesoftware.code-spell-checker", 34 | "silvenon.mdx", 35 | "redhat.vscode-yaml", 36 | "orta.vscode-jest" 37 | ] 38 | } 39 | }, 40 | 41 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 42 | "remoteUser": "node" 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | push: 7 | branches: [main] 8 | release: 9 | branches: [main] 10 | 11 | jobs: 12 | checks: 13 | if: github.event_name != 'release' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: 18 20 | - name: Build 21 | run: | 22 | yarn install --immutable 23 | cd lib 24 | yarn build 25 | cd .. 26 | # - name: Test 27 | # run: | 28 | # cd lib 29 | # yarn test 30 | - name: Build docs 31 | run: | 32 | cd doc 33 | yarn build 34 | cd .. 35 | # - name: Upload coverage to Codecov 36 | # uses: codecov/codecov-action@v2 37 | # with: 38 | # token: ${{ secrets.CODECOV_TOKEN }} 39 | gh-release: 40 | if: github.event_name == 'push' 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v4 44 | - uses: actions/setup-node@v4 45 | with: 46 | node-version: 18 47 | - uses: webfactory/ssh-agent@v0.9.0 48 | with: 49 | ssh-private-key: ${{ secrets.GH_PAGES_DEPLOY }} 50 | - name: Release to GitHub Pages 51 | env: 52 | USE_SSH: true 53 | GIT_USER: git 54 | run: | 55 | git config --global user.email "oss@samuelwall.co.uk" 56 | git config --global user.name "sjwall" 57 | yarn install --immutable 58 | cd doc 59 | yarn deploy 60 | -------------------------------------------------------------------------------- /doc/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Layout from '@theme/Layout'; 4 | import Link from '@docusaurus/Link'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | import styles from './index.module.css'; 7 | import HomepageFeatures from '../components/HomepageFeatures'; 8 | 9 | function HomepageHeader() { 10 | const {siteConfig} = useDocusaurusContext(); 11 | return ( 12 |
13 |
14 |

{siteConfig.title}

15 |

{siteConfig.tagline}

16 |
17 | 20 | mdx-mermaid Tutorial - 5min ⏱️ 21 | 22 |
23 |
24 |
25 | ); 26 | } 27 | 28 | export default function Home() { 29 | const {siteConfig} = useDocusaurusContext(); 30 | return ( 31 | 34 |
35 |
36 | Support Ukraine 🇺🇦{' '} 37 | 38 | Help Provide Humanitarian Aid to Ukraine 39 | 40 | . 41 |
42 |
43 | 44 |
45 | 46 |
47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /examples/react-example-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /lib/src/theme.helper.spec.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from './config.model' 2 | import { 3 | DARK_THEME_KEY, 4 | DEFAULT_DARK_THEME, 5 | DEFAULT_LIGHT_THEME, 6 | getTheme, 7 | HTML_THEME_ATTRIBUTE, 8 | LIGHT_THEME_KEY 9 | } from './theme.helper' 10 | 11 | describe('theme.helper', () => { 12 | it('returns the default light theme when data-theme is incorrectly configured', () => { 13 | const html = document.createElement('html') 14 | html.setAttribute(HTML_THEME_ATTRIBUTE, 'some bad key') 15 | 16 | expect(getTheme(html)).toEqual(DEFAULT_LIGHT_THEME) 17 | }) 18 | 19 | it('returns the default light theme', () => { 20 | const html = document.createElement('html') 21 | html.setAttribute(HTML_THEME_ATTRIBUTE, LIGHT_THEME_KEY) 22 | 23 | expect(getTheme(html)).toEqual(DEFAULT_LIGHT_THEME) 24 | }) 25 | 26 | it('returns the default dark theme', () => { 27 | const html = document.createElement('html') 28 | html.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY) 29 | 30 | expect(getTheme(html)).toEqual(DEFAULT_DARK_THEME) 31 | }) 32 | 33 | it('returns the configured light theme', () => { 34 | const html = document.createElement('html') 35 | html.setAttribute(HTML_THEME_ATTRIBUTE, LIGHT_THEME_KEY) 36 | 37 | const config: Config = { 38 | theme: { 39 | light: 'forest', 40 | dark: 'default' 41 | } 42 | } 43 | 44 | expect(getTheme(html, config)).toEqual(config.theme?.light) 45 | }) 46 | 47 | it('returns the configured dark theme', () => { 48 | const html = document.createElement('html') 49 | html.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY) 50 | 51 | const config: Config = { 52 | theme: { 53 | light: 'forest', 54 | dark: 'default' 55 | } 56 | } 57 | 58 | expect(getTheme(html, config)).toEqual(config.theme?.dark) 59 | }) 60 | 61 | it('returns the mermaid config theme', () => { 62 | const html = document.createElement('html') 63 | html.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY) 64 | 65 | const config: Config = { 66 | mermaid: { 67 | theme: 'forest' 68 | } 69 | } 70 | 71 | expect(getTheme(html, config)).toEqual(config.mermaid?.theme) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /examples/react-example-app/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /examples/react-example-app/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/versioned_docs/version-1.2.3/examples.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Examples 6 | 7 | The documentation for this library is a working example. 8 | 9 | This file, for example, has a diagram using the component and code block. 10 | 11 | ## Component 12 | 13 | ```jsx title="Component example" 14 | import { Mermaid } from 'mdx-mermaid/Mermaid' 15 | 16 | >John: Hello John, how are you? 20 | loop Healthcheck 21 | John->>John: Fight against hypochondria 22 | end 23 | Note right of John: Rational thoughts
prevail! 24 | John-->>Alice: Great! 25 | John->>Bob: How about you? 26 | Bob-->>John: Jolly good!`} /> 27 | ``` 28 | 29 | >John: Hello John, how are you? 33 | loop Healthcheck 34 | John->>John: Fight against hypochondria 35 | end 36 | Note right of John: Rational thoughts
prevail! 37 | John-->>Alice: Great! 38 | John->>Bob: How about you? 39 | Bob-->>John: Jolly good!`} /> 40 | 41 | ## Code block 42 | 43 | The component doesn't need to be imported as this will be auto inserted. 44 | 45 | ````md title="Code block example" 46 | ```mermaid 47 | gantt 48 | dateFormat YYYY-MM-DD 49 | title Adding GANTT diagram to mermaid 50 | excludes weekdays 2014-01-10 51 | 52 | section A section 53 | Completed task :done, des1, 2014-01-06,2014-01-08 54 | Active task :active, des2, 2014-01-09, 3d 55 | Future task : des3, after des2, 5d 56 | Future task2 : des4, after des3, 5d 57 | ``` 58 | ```` 59 | 60 | ```mermaid 61 | gantt 62 | dateFormat YYYY-MM-DD 63 | title Adding GANTT diagram to mermaid 64 | excludes weekdays 2014-01-10 65 | 66 | section A section 67 | Completed task :done, des1, 2014-01-06,2014-01-08 68 | Active task :active, des2, 2014-01-09, 3d 69 | Future task : des3, after des2, 5d 70 | Future task2 : des4, after des3, 5d 71 | ``` 72 | 73 | ## Mermaid Config 74 | 75 | Mermaid config can be configured through the plugin config: 76 | 77 | ```js title=docusaurus.config.js 78 | remarkPlugins: [[require('mdx-mermaid'), { mermaid: { theme: 'dark' } }]], 79 | ``` 80 | 81 | :::caution 82 | 83 | When passing config to the `` component only the first instance should have the config passed to it. 84 | If no config is passed to any component then Mermaid will not initialize. 85 | This is not an issue when using in `.mdx` files as the parser will handle this. 86 | -------------------------------------------------------------------------------- /doc/versioned_docs/version-1.3.0/examples.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Examples 6 | 7 | The documentation for this library is a working example. 8 | 9 | This file, for example, has a diagram using the component and code block. 10 | 11 | ## Component 12 | 13 | ```jsx title="Component example" 14 | import { Mermaid } from 'mdx-mermaid/Mermaid' 15 | 16 | >John: Hello John, how are you? 20 | loop Healthcheck 21 | John->>John: Fight against hypochondria 22 | end 23 | Note right of John: Rational thoughts
prevail! 24 | John-->>Alice: Great! 25 | John->>Bob: How about you? 26 | Bob-->>John: Jolly good!`} /> 27 | ``` 28 | 29 | >John: Hello John, how are you? 33 | loop Healthcheck 34 | John->>John: Fight against hypochondria 35 | end 36 | Note right of John: Rational thoughts
prevail! 37 | John-->>Alice: Great! 38 | John->>Bob: How about you? 39 | Bob-->>John: Jolly good!`} /> 40 | 41 | ## Code block 42 | 43 | The component doesn't need to be imported as this will be auto inserted. 44 | 45 | ````md title="Code block example" 46 | ```mermaid 47 | gantt 48 | dateFormat YYYY-MM-DD 49 | title Adding GANTT diagram to mermaid 50 | excludes weekdays 2014-01-10 51 | 52 | section A section 53 | Completed task :done, des1, 2014-01-06,2014-01-08 54 | Active task :active, des2, 2014-01-09, 3d 55 | Future task : des3, after des2, 5d 56 | Future task2 : des4, after des3, 5d 57 | ``` 58 | ```` 59 | 60 | ```mermaid 61 | gantt 62 | dateFormat YYYY-MM-DD 63 | title Adding GANTT diagram to mermaid 64 | excludes weekdays 2014-01-10 65 | 66 | section A section 67 | Completed task :done, des1, 2014-01-06,2014-01-08 68 | Active task :active, des2, 2014-01-09, 3d 69 | Future task : des3, after des2, 5d 70 | Future task2 : des4, after des3, 5d 71 | ``` 72 | 73 | ## Mermaid Config 74 | 75 | Mermaid config can be configured through the plugin config: 76 | 77 | ```js title=docusaurus.config.js 78 | remarkPlugins: [[await import('mdx-mermaid').default, { mermaid: { theme: 'dark' } }]], 79 | ``` 80 | 81 | :::caution 82 | 83 | When passing config to the `` component only the first instance should have the config passed to it. 84 | If no config is passed to any component then Mermaid will not initialize. 85 | This is not an issue when using in `.mdx` files as the parser will handle this. 86 | -------------------------------------------------------------------------------- /lib/src/Mermaid.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Samuel Wall. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * license file in the root directory of this source tree. 6 | */ 7 | 8 | import React, { useEffect, useState, ReactElement, useMemo } from 'react' 9 | import mermaid from 'mermaid' 10 | 11 | import type { Config } from './config.model' 12 | import { getTheme } from './theme.helper' 13 | 14 | /** 15 | * Properties for Mermaid component. 16 | */ 17 | export type MermaidProps = { 18 | /** 19 | * Mermaid diagram. 20 | */ 21 | chart: string 22 | 23 | /** 24 | * Config to initialize mermaid with. 25 | */ 26 | config?: Config | string 27 | } 28 | 29 | /** 30 | * Component to display Mermaid diagrams. 31 | * 32 | * @param param0 Diagram to display. 33 | * @param param1 Config. 34 | * @returns The component. 35 | */ 36 | export const Mermaid = ({ chart, config: configSrc }: MermaidProps): ReactElement => { 37 | // Mermaid doesn't support server-side rendering 38 | /* istanbul ignore next */ 39 | if (typeof window === 'undefined') { 40 | return
{chart}
41 | } 42 | 43 | const config: Config = useMemo(() => typeof configSrc === 'string' ? JSON.parse(configSrc) : configSrc, [configSrc]) 44 | 45 | const html: HTMLHtmlElement = document.querySelector('html')! 46 | 47 | const [rerender, setRerender] = useState(false) 48 | 49 | const theme = useMemo(() => getTheme(html, config), [config, rerender]) 50 | 51 | useEffect(() => { 52 | const observer = new MutationObserver((mutations) => { 53 | for (const mutation of mutations) { 54 | if (mutation.type !== 'attributes' || mutation.attributeName !== 'data-theme') { 55 | continue 56 | } 57 | setRerender((cur) => !cur) 58 | break 59 | } 60 | }) 61 | 62 | observer.observe(html, { attributes: true }) 63 | return () => { 64 | try { 65 | observer.disconnect() 66 | } catch { 67 | // Do nothing 68 | } 69 | } 70 | }, []) 71 | 72 | useEffect(() => { 73 | if (config) { 74 | if (config.mermaid) { 75 | mermaid.initialize({ startOnLoad: true, ...config.mermaid, theme }) 76 | } else { 77 | mermaid.initialize({ startOnLoad: true, theme }) 78 | } 79 | document.querySelectorAll('div.mermaid[data-processed="true"]').forEach((v) => { 80 | v.removeAttribute('data-processed') 81 | v.innerHTML = v.getAttribute('data-mermaid-src') as string 82 | }) 83 | mermaid.contentLoaded() 84 | } 85 | }, [config, theme]) 86 | 87 | useEffect(() => { 88 | setTimeout(mermaid.contentLoaded, 0) 89 | }, [chart]) 90 | 91 | return
{chart}
92 | } 93 | -------------------------------------------------------------------------------- /examples/mdx-example/.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional stylelint cache 59 | .stylelintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variable files 77 | .env 78 | .env.development.local 79 | .env.test.local 80 | .env.production.local 81 | .env.local 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | ### Node Patch ### 133 | # Serverless Webpack directories 134 | .webpack/ 135 | 136 | # Optional stylelint cache 137 | 138 | # SvelteKit build / generate output 139 | .svelte-kit 140 | -------------------------------------------------------------------------------- /lib/src/mdxast-mermaid.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Samuel Wall. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * license file in the root directory of this source tree. 6 | */ 7 | 8 | import { compile } from '@mdx-js/mdx' 9 | import mermaid from './mdxast-mermaid' 10 | 11 | import type { Config } from './config.model' 12 | 13 | describe('mdxast-mermaid', () => { 14 | function compileMdx(mdx: string, config?: Config) { 15 | return compile(mdx, { 16 | outputFormat: 'program', 17 | remarkPlugins: [[mermaid, config]] 18 | }) 19 | } 20 | 21 | test('No mermaid', async () => { 22 | const result = await compileMdx('# Heading 1\n\nNo Mermaid diagram :(') 23 | expect(result.value).toEqual(`/*@jsxRuntime automatic @jsxImportSource react*/ 24 | import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from \"react/jsx-runtime\"; 25 | function _createMdxContent(props) { 26 | const _components = Object.assign({ 27 | h1: \"h1\", 28 | p: \"p\" 29 | }, props.components); 30 | return _jsxs(_Fragment, { 31 | children: [_jsx(_components.h1, { 32 | children: \"Heading 1\" 33 | }), \"\\n\", _jsx(_components.p, { 34 | children: \"No Mermaid diagram :(\" 35 | })] 36 | }); 37 | } 38 | function MDXContent(props = {}) { 39 | const {wrapper: MDXLayout} = props.components || ({}); 40 | return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, { 41 | children: _jsx(_createMdxContent, props) 42 | })) : _createMdxContent(props); 43 | } 44 | export default MDXContent; 45 | `) 46 | }) 47 | 48 | test('ast', async () => { 49 | const result = await compileMdx(`# Heading 1\n 50 | \`\`\`mermaid 51 | graph TD; 52 | A-->B; 53 | A-->C; 54 | B-->D; 55 | C-->D; 56 | \`\`\``, { output: 'ast' }) 57 | expect(result.value).toEqual(`/*@jsxRuntime automatic @jsxImportSource react*/ 58 | import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from \"react/jsx-runtime\"; 59 | function _createMdxContent(props) { 60 | const _components = Object.assign({ 61 | h1: \"h1\", 62 | mermaid: "mermaid" 63 | }, props.components); 64 | return _jsxs(_Fragment, { 65 | children: [_jsx(_components.h1, { 66 | children: \"Heading 1\" 67 | }), "\\n", _jsx(_components.mermaid, { 68 | config: "{\\"output\\":\\"ast\\"}", 69 | chart: "graph TD;\\n A-->B;\\n A-->C;\\n B-->D;\\n C-->D;" 70 | })] 71 | }); 72 | } 73 | function MDXContent(props = {}) { 74 | const {wrapper: MDXLayout} = props.components || ({}); 75 | return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, { 76 | children: _jsx(_createMdxContent, props) 77 | })) : _createMdxContent(props); 78 | } 79 | export default MDXContent; 80 | `) 81 | }) 82 | 83 | test('multiple mermaid instances in svg don\'t clobber previous node', async () => { 84 | const result = await compileMdx( 85 | `## Framework AARRR 86 | 87 | Some content 88 | 89 | \`\`\`mermaid 90 | graph TD; 91 | A-->B; 92 | \`\`\` 93 | 94 | And some more: 95 | 96 | 97 | \`\`\`mermaid 98 | graph TD; 99 | A-->B; 100 | \`\`\``, 101 | { output: 'svg' } 102 | ); 103 | expect(result.value).toContain('And some more'); 104 | }) 105 | }) 106 | 107 | 108 | -------------------------------------------------------------------------------- /doc/docs/examples.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Examples 6 | 7 | The documentation for this library is a working example. 8 | 9 | This file, for example, has a diagram using the component and code block. 10 | 11 | ## Component 12 | 13 | ```jsx title="Component example" 14 | import { Mermaid } from 'mdx-mermaid/Mermaid' 15 | 16 | >John: Hello John, how are you? 20 | loop Healthcheck 21 | John->>John: Fight against hypochondria 22 | end 23 | Note right of John: Rational thoughts
prevail! 24 | John-->>Alice: Great! 25 | John->>Bob: How about you? 26 | Bob-->>John: Jolly good!`} /> 27 | ``` 28 | 29 | >John: Hello John, how are you? 33 | loop Healthcheck 34 | John->>John: Fight against hypochondria 35 | end 36 | Note right of John: Rational thoughts
prevail! 37 | John-->>Alice: Great! 38 | John->>Bob: How about you? 39 | Bob-->>John: Jolly good!`} /> 40 | 41 | ## Code block 42 | 43 | The component doesn't need to be imported as this will be auto inserted. 44 | 45 | ````md title="Code block example" 46 | ```mermaid 47 | gantt 48 | dateFormat YYYY-MM-DD 49 | title Adding GANTT diagram to mermaid 50 | excludes weekdays 2014-01-10 51 | 52 | section A section 53 | Completed task :done, des1, 2014-01-06,2014-01-08 54 | Active task :active, des2, 2014-01-09, 3d 55 | Future task : des3, after des2, 5d 56 | Future task2 : des4, after des3, 5d 57 | ``` 58 | ```` 59 | 60 | ```mermaid 61 | gantt 62 | dateFormat YYYY-MM-DD 63 | title Adding GANTT diagram to mermaid 64 | excludes weekdays 2014-01-10 65 | 66 | section A section 67 | Completed task :done, des1, 2014-01-06,2014-01-08 68 | Active task :active, des2, 2014-01-09, 3d 69 | Future task : des3, after des2, 5d 70 | Future task2 : des4, after des3, 5d 71 | ``` 72 | 73 | ## Mermaid Config 74 | 75 | Mermaid config can be configured through the plugin config: 76 | 77 | ```js title=docusaurus.config.js 78 | { 79 | mermaid: { theme: 'dark' } 80 | } 81 | ``` 82 | 83 | :::caution 84 | 85 | When passing config to the `` component only the first instance should have the config passed to it. 86 | If no config is passed to any component then Mermaid will not initialize. 87 | This is not an issue when using in `.mdx` files as the parser will handle this. 88 | 89 | ::: 90 | 91 | ## SVG Output 92 | 93 | It is possible to render the diagrams to an SVG at build time. 94 | This is useful for server-side rendering (SSR) and to display diagrams to users without JavaScript. 95 | 96 | This mode requires the `optionalDependencies` to be installed. 97 | 98 | ```sh 99 | yarn add puppeteer estree-util-to-js estree-util-visit hast-util-from-html hast-util-to-estree mdast-util-from-markdown mdast-util-mdx micromark-extension-mdxjs 100 | ``` 101 | 102 | Then in your config, add the `output` option: 103 | 104 | ```js title=docusaurus.config.js 105 | { 106 | mermaid: { output: 'svg' } 107 | } 108 | ``` 109 | 110 | :::caution 111 | 112 | This documentation website uses the component mode, so a live example of an SVG rendered diagram cannot be shown. 113 | 114 | ::: 115 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # mdx-mermaid 2 | 3 | Plug and play Mermaid in MDX 4 | 5 | [![npm version](https://badge.fury.io/js/mdx-mermaid.svg)][npm] 6 | [![GitHub license](https://img.shields.io/github/license/sjwall/mdx-mermaid)][license] 7 | [![build](https://github.com/sjwall/mdx-mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/sjwall/mdx-mermaid/actions/workflows/build.yml) 8 | [![codecov](https://codecov.io/gh/sjwall/mdx-mermaid/branch/main/graph/badge.svg?token=OBSGK4GGX8)](https://codecov.io/gh/sjwall/mdx-mermaid) 9 | [![Maintainability](https://api.codeclimate.com/v1/badges/9d89c7483bb1a906ecdf/maintainability)](https://codeclimate.com/github/sjwall/mdx-mermaid/maintainability) 10 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)][pr] 11 | 12 | Use [Mermaid][mermaid] in `.md`, `.mdx`, `.jsx` and `.tsx` files with ease. 13 | 14 | Based off the answer [here][inspire] by unknown. 15 | 16 | More documentation available [here][documentation] 17 | 18 | Use version `^1.3.0` for `@mdxjs/mdx` `v1`. 19 | 20 | Use version `^2.0.0` for `@mdxjs/mdx` `v2`. 21 | 22 | > **Warning**: 23 | > [`rehype-mermaidjs`](https://github.com/remcohaszing/rehype-mermaidjs) and 24 | > [`remark-mermaidjs`](https://github.com/remcohaszing/remark-mermaidjs) 25 | > may better suit your use case. 26 | 27 | ## Quick start 28 | 29 | Install `mdx-mermaid` and `mermaid` 30 | 31 | `mermaid` is a peer dependency so you can specify the version to use 32 | 33 | ```bash 34 | yarn add mdx-mermaid mermaid 35 | ``` 36 | 37 | Configure the plugin: 38 | 39 | ```js 40 | import mdxMermaid from 'mdx-mermaid' 41 | import {Mermaid} from 'mdx-mermaid/lib/Mermaid' 42 | 43 | { 44 | remarkPlugins: [[mdxMermaid.default, {output: 'svg'}]], 45 | components: {mermaid: Mermaid, Mermaid} 46 | } 47 | ``` 48 | 49 | Use code blocks in `.md` or `.mdx` files: 50 | 51 | ````md 52 | ```mermaid 53 | graph TD; 54 | A-->B; 55 | A-->C; 56 | B-->D; 57 | C-->D; 58 | ``` 59 | ```` 60 | 61 | Use the component in `.mdx`, `.jsx` or `.tsx` files: 62 | 63 | ```jsx 64 | import { Mermaid } from 'mdx-mermaid/Mermaid'; 65 | 66 | B; 68 | A-->C; 69 | B-->D; 70 | C-->D; 71 | `} /> 72 | ``` 73 | 74 | There are more examples [here][examples] 75 | 76 | ## SVG output 77 | 78 | For server-side rendering, the output mode can be set to `svg`. 79 | This renders the diagram at build time using `puppeteer`. 80 | This mode requires the `optionalDependencies` to be installed. 81 | 82 | ```sh 83 | yarn add puppeteer estree-util-to-js estree-util-visit hast-util-from-html hast-util-to-estree mdast-util-from-markdown mdast-util-mdx micromark-extension-mdxjs 84 | ``` 85 | 86 | Configure the plugin: 87 | 88 | ```js 89 | import mdxMermaid from 'mdx-mermaid' 90 | 91 | export default { 92 | remarkPlugins: [[mdxMermaid.default, {output: 'svg'}]] 93 | } 94 | ``` 95 | 96 | ## License 97 | 98 | [MIT][license] © [Samuel Wall][author] 99 | 100 | 101 | 102 | [license]: https://github.com/sjwall/mdx-mermaid/blob/main/license 103 | 104 | [author]: https://samuelwall.co.uk 105 | 106 | [npm]: https://www.npmjs.com/package/mdx-mermaid 107 | 108 | [mermaid]: http://mermaid-js.github.io/mermaid/ 109 | 110 | [inspire]: https://github.com/facebook/docusaurus/issues/1258#issuecomment-594393744 111 | 112 | [pr]: http://makeapullrequest.com 113 | 114 | [examples]: https://sjwall.github.io/mdx-mermaid/docs/examples/ 115 | 116 | [documentation]: https://sjwall.github.io/mdx-mermaid/ 117 | -------------------------------------------------------------------------------- /doc/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | 4 | const lightCodeTheme = require('prism-react-renderer/themes/github'); 5 | const darkCodeTheme = require('prism-react-renderer/themes/dracula'); 6 | 7 | async function createConfig() { 8 | /** @type {import('@docusaurus/types').Config} */ 9 | return { 10 | title: 'mdx-mermaid', 11 | tagline: 'Plug and play Mermaid in MDX', 12 | url: 'https://sjwall.github.io', 13 | baseUrl: '/mdx-mermaid/', 14 | onBrokenLinks: 'throw', 15 | onBrokenMarkdownLinks: 'warn', 16 | favicon: 'img/favicon.ico', 17 | trailingSlash: true, 18 | organizationName: 'sjwall', // Usually your GitHub org/user name. 19 | projectName: 'mdx-mermaid', // Usually your repo name. 20 | markdown: { 21 | mermaid: true, 22 | }, 23 | themes: ['@docusaurus/theme-mermaid'], 24 | 25 | presets: [ 26 | [ 27 | 'classic', 28 | /** @type {import('@docusaurus/preset-classic').Options} */ 29 | ({ 30 | docs: { 31 | sidebarPath: require.resolve('./sidebars.js'), 32 | // Please change this to your repo. 33 | editUrl: 34 | 'https://github.com/sjwall/mdx-mermaid/edit/main/doc', 35 | lastVersion: 'current', 36 | versions: { 37 | current: { 38 | label: '2.0.0', 39 | }, 40 | '1.3.0': { 41 | label: '>= 1.3.0', 42 | }, 43 | '1.2.3': { 44 | label: '<= 1.2.3', 45 | }, 46 | }, 47 | }, 48 | theme: { 49 | customCss: require.resolve('./src/css/custom.css'), 50 | }, 51 | }), 52 | ], 53 | ], 54 | 55 | themeConfig: 56 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 57 | ({ 58 | navbar: { 59 | title: 'mdx-mermaid', 60 | logo: { 61 | alt: 'mdx-mermaid', 62 | src: 'img/logo.svg', 63 | }, 64 | items: [ 65 | { 66 | type: 'doc', 67 | docId: 'intro', 68 | position: 'left', 69 | label: 'Tutorial', 70 | }, 71 | { 72 | type: 'docsVersionDropdown', 73 | position: 'right', 74 | dropdownActiveClassDisabled: true, 75 | }, 76 | { 77 | href: 'https://github.com/sjwall/mdx-mermaid', 78 | label: 'GitHub', 79 | position: 'right', 80 | }, 81 | ], 82 | }, 83 | footer: { 84 | style: 'dark', 85 | links: [ 86 | { 87 | title: 'Docs', 88 | items: [ 89 | { 90 | label: 'Tutorial', 91 | to: '/docs/intro', 92 | }, 93 | ], 94 | }, 95 | { 96 | title: 'Community', 97 | items: [ 98 | { 99 | label: 'Stack Overflow', 100 | href: 'https://stackoverflow.com/questions/tagged/mdx-mermaid', 101 | }, 102 | ], 103 | }, 104 | { 105 | title: 'More', 106 | items: [ 107 | { 108 | label: 'GitHub', 109 | href: 'https://github.com/sjwall/mdx-mermaid', 110 | }, 111 | ], 112 | }, 113 | ], 114 | copyright: `Copyright © ${new Date().getFullYear()} Samuel Wall. Built with Docusaurus.`, 115 | }, 116 | prism: { 117 | theme: lightCodeTheme, 118 | darkTheme: darkCodeTheme, 119 | }, 120 | }), 121 | }; 122 | } 123 | 124 | module.exports = createConfig; 125 | -------------------------------------------------------------------------------- /lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdx-mermaid", 3 | "version": "2.0.3", 4 | "description": "Display mermaid diagrams in mdx files.", 5 | "types": "index.d.ts", 6 | "main": "lib/mdxast-mermaid.js", 7 | "exports": { 8 | ".": { 9 | "require": "./lib/mdxast-mermaid.cjs", 10 | "import": "./lib/mdxast-mermaid.mjs", 11 | "types": "./index.d.ts" 12 | }, 13 | "./Mermaid": { 14 | "require": "./lib/Mermaid.cjs", 15 | "import": "./lib/Mermaid.mjs", 16 | "types": "./lib/Mermaid.d.ts" 17 | }, 18 | "./lib/Mermaid": { 19 | "require": "./lib/Mermaid.cjs", 20 | "import": "./lib/Mermaid.mjs", 21 | "types": "./lib/Mermaid.d.ts" 22 | } 23 | }, 24 | "author": "Sam Wall (oss@samuelwall.co.uk)", 25 | "license": "MIT", 26 | "type": "module", 27 | "scripts": { 28 | "build": "rimraf lib && tsc --declarationDir lib --declaration true --outDir temp && rimraf temp && rollup -c", 29 | "test": "node --experimental-vm-modules ../node_modules/jest/bin/jest.js --coverage --config=./jest.config.js", 30 | "dist": "rimraf dist && mkdir dist && mkdir dist/lib && cp -r lib dist/ && cp package.json dist && cp ../readme.md dist && cp ../license dist && cp index.d.ts dist" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/sjwall/mdx-mermaid.git" 35 | }, 36 | "homepage": "https://sjwall.github.io/mdx-mermaid", 37 | "bugs": "https://github.com/sjwall/mdx-mermaid/issues", 38 | "keywords": [ 39 | "mdx", 40 | "markdown", 41 | "mermaid", 42 | "docusaurus", 43 | "mdxast", 44 | "react", 45 | "jsx" 46 | ], 47 | "peerDependencies": { 48 | "mermaid": ">=8.11.0", 49 | "react": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", 50 | "unist-util-visit": "^4.1.0" 51 | }, 52 | "optionalDependencies": { 53 | "estree-util-to-js": "^1.2.0", 54 | "estree-util-visit": "^1.2.1", 55 | "hast-util-from-html": "^1.0.2", 56 | "hast-util-to-estree": "^2.3.3", 57 | "mdast-util-from-markdown": "^1.3.1", 58 | "mdast-util-mdx": "^2.0.1", 59 | "micromark-extension-mdxjs": "^1.0.1", 60 | "puppeteer": "^22.15.0" 61 | }, 62 | "devDependencies": { 63 | "@babel/core": "^7.26.0", 64 | "@babel/preset-env": "^7.26.0", 65 | "@babel/preset-react": "^7.25.9", 66 | "@mdx-js/mdx": "^2.3.0", 67 | "@rollup/plugin-babel": "^6.0.4", 68 | "@rollup/plugin-commonjs": "^24.1.0", 69 | "@rollup/plugin-typescript": "^11.1.6", 70 | "@testing-library/react": "^11.2.7", 71 | "@types/jest": "^28.1.8", 72 | "@types/react": "^17.0.83", 73 | "@types/unist": "^2.0.11", 74 | "@typescript-eslint/eslint-plugin": "^5.62.0", 75 | "@typescript-eslint/parser": "^5.62.0", 76 | "eslint": "^8.57.1", 77 | "eslint-config-standard": "^17.1.0", 78 | "eslint-plugin-import": "^2.31.0", 79 | "eslint-plugin-jsdoc": "^39.9.1", 80 | "eslint-plugin-n": "^15.7.0", 81 | "eslint-plugin-node": "^11.1.0", 82 | "eslint-plugin-promise": "^6.6.0", 83 | "eslint-plugin-react": "^7.37.2", 84 | "hast-util-from-html": "^1.0.2", 85 | "jest": "^28.1.3", 86 | "jest-environment-jsdom": "^28.1.3", 87 | "mermaid": "^9.4.3", 88 | "puppeteer": "^22.15.0", 89 | "react": "^17.0.2", 90 | "react-dom": "^17.0.2", 91 | "rimraf": "^3.0.2", 92 | "rollup": "^3.29.5", 93 | "ts-jest": "^28.0.8", 94 | "typescript": "^4.9.5", 95 | "unist-util-visit": "^4.1.2" 96 | }, 97 | "contributors": [ 98 | "Samuel Wall (https://github.com/sjwall)", 99 | "Craig Andrews (https://github.com/candrews)", 100 | "Dávid Zsámboki (https://github.com/ddyfedd)", 101 | "David Ivanov (https://github.com/DavidIvanov)", 102 | "David Prothero (https://github.com/dprothero)", 103 | "G (https://github.com/genert)", 104 | "johackim (https://github.com/johackim)", 105 | "mzvast (https://github.com/mzvast)", 106 | "Paul Sachs (https://github.com/paul-sachs)", 107 | "Ramy (https://github.com/ramy.loveridge@appshack.se)", 108 | "Taj Pereira (https://github.com/taj-p)" 109 | ] 110 | } 111 | -------------------------------------------------------------------------------- /lib/src/Mermaid.spec.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | /** 5 | * Copyright (c) Samuel Wall. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * license file in the root directory of this source tree. 9 | */ 10 | import React from 'react' 11 | import { act, render, RenderResult } from '@testing-library/react' 12 | import mermaid from 'mermaid' 13 | import { jest } from '@jest/globals' 14 | import { Mermaid } from './Mermaid' 15 | import { 16 | DARK_THEME_KEY, 17 | HTML_THEME_ATTRIBUTE, 18 | LIGHT_THEME_KEY 19 | } from './theme.helper' 20 | 21 | describe('Mermaid', () => { 22 | const spy = { 23 | initialize: jest.spyOn(mermaid, 'initialize').mockImplementation(jest.fn()), 24 | contentLoaded: jest.spyOn(mermaid, 'contentLoaded').mockImplementation(jest.fn()) 25 | } 26 | 27 | const diagram = `graph TD; 28 | A-->B; 29 | A-->C; 30 | B-->D; 31 | C-->D;` 32 | 33 | afterEach(() => { 34 | jest.clearAllMocks() 35 | }) 36 | 37 | const removeUniqueness = (element: Element) => { 38 | element.querySelectorAll('style').forEach((v) => v.remove()) 39 | element.querySelectorAll('svg').forEach((v) => { 40 | v.removeAttribute('id') 41 | v.parentElement!.removeAttribute('id') 42 | }) 43 | } 44 | 45 | const expectMermaidMatch = (result: RenderResult) => { 46 | removeUniqueness(result.baseElement) 47 | expect(result.baseElement.parentElement).toMatchSnapshot() 48 | return result 49 | } 50 | 51 | it('renders without diagram', () => { 52 | expectMermaidMatch(render()) 53 | }) 54 | 55 | it('renders with diagram', () => { 56 | expectMermaidMatch(render()) 57 | }) 58 | 59 | it('renders with diagram change', () => { 60 | const config = {} 61 | jest.useFakeTimers() 62 | const view = expectMermaidMatch(render()) 63 | view.rerender(C; 65 | D-->B; 66 | C-->A; 67 | B-->A;`} config={config} />) 68 | jest.advanceTimersByTime(1000) 69 | expectMermaidMatch(view) 70 | expect(spy.contentLoaded).toBeCalledTimes(3) 71 | expect(spy.initialize).toBeCalledTimes(1) 72 | jest.useRealTimers() 73 | }) 74 | 75 | it('initializes only once', () => { 76 | expectMermaidMatch(render(<> 77 | 78 | 79 | )) 80 | expect(spy.contentLoaded).toBeCalledTimes(1) 81 | expect(spy.initialize).toBeCalledTimes(1) 82 | }) 83 | 84 | it('renders with mermaid config', () => { 85 | expectMermaidMatch(render()) 86 | expect(spy.contentLoaded).toBeCalledTimes(1) 87 | expect(spy.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' }) 88 | }) 89 | 90 | it('renders with mermaid config change', () => { 91 | const view = expectMermaidMatch(render()) 92 | view.baseElement.querySelectorAll('div.mermaid').forEach((v) => { 93 | v.setAttribute('data-processed', 'true') 94 | }) 95 | expect(spy.contentLoaded).toBeCalledTimes(1) 96 | expect(spy.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' }) 97 | view.rerender() 98 | // await waitFor(1000) 99 | expectMermaidMatch(view) 100 | expect(spy.contentLoaded).toBeCalledTimes(2) 101 | expect(spy.initialize).toHaveBeenNthCalledWith(2, { startOnLoad: true, theme: 'forest' }) 102 | }) 103 | 104 | it('renders with string mermaid config', () => { 105 | expectMermaidMatch(render()) 106 | expect(spy.contentLoaded).toBeCalledTimes(1) 107 | expect(spy.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' }) 108 | }) 109 | 110 | it('re-renders mermaid theme on html data-theme attribute change', () => { 111 | const component = render( 112 | ) 113 | 114 | expectMermaidMatch(component) 115 | expect(spy.contentLoaded).toBeCalledTimes(1) 116 | expect(spy.initialize).toBeCalledTimes(1) 117 | 118 | act(() => document.querySelector('html')!.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY)) 119 | 120 | expectMermaidMatch(component) 121 | 122 | expect(spy.contentLoaded).toBeCalledTimes(1) 123 | expect(spy.initialize).toBeCalledTimes(1) 124 | 125 | act(() => document.querySelector('html')!.setAttribute(HTML_THEME_ATTRIBUTE, LIGHT_THEME_KEY)) 126 | 127 | expectMermaidMatch(component) 128 | }) 129 | 130 | it('does not react to non-theme attribute changes of html', () => { 131 | const component = render() 132 | 133 | expectMermaidMatch(component) 134 | expect(spy.contentLoaded).toBeCalledTimes(1) 135 | expect(spy.initialize).toBeCalledTimes(1) 136 | 137 | act(() => document.querySelector('html')!.setAttribute('manifest', 'some-value')) 138 | 139 | expectMermaidMatch(component) 140 | }) 141 | }) 142 | -------------------------------------------------------------------------------- /doc/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/src/__snapshots__/Mermaid.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Mermaid does not react to non-theme attribute changes of html 1`] = ` 4 | 7 | 8 | 9 |
10 |
18 | graph TD; 19 | A-->B; 20 | A-->C; 21 | B-->D; 22 | C-->D; 23 |
24 |
25 | 26 | 27 | `; 28 | 29 | exports[`Mermaid does not react to non-theme attribute changes of html 2`] = ` 30 | 34 | 35 | 36 |
37 |
45 | graph TD; 46 | A-->B; 47 | A-->C; 48 | B-->D; 49 | C-->D; 50 |
51 |
52 | 53 | 54 | `; 55 | 56 | exports[`Mermaid initializes only once 1`] = ` 57 | 58 | 59 | 60 |
61 |
65 | foo 66 |
67 |
71 | bar 72 |
73 |
74 | 75 | 76 | `; 77 | 78 | exports[`Mermaid re-renders mermaid theme on html data-theme attribute change 1`] = ` 79 | 80 | 81 | 82 |
83 |
91 | graph TD; 92 | A-->B; 93 | A-->C; 94 | B-->D; 95 | C-->D; 96 |
97 |
98 | 99 | 100 | `; 101 | 102 | exports[`Mermaid re-renders mermaid theme on html data-theme attribute change 2`] = ` 103 | 106 | 107 | 108 |
109 |
117 | graph TD; 118 | A-->B; 119 | A-->C; 120 | B-->D; 121 | C-->D; 122 |
123 |
124 | 125 | 126 | `; 127 | 128 | exports[`Mermaid re-renders mermaid theme on html data-theme attribute change 3`] = ` 129 | 132 | 133 | 134 |
135 |
143 | graph TD; 144 | A-->B; 145 | A-->C; 146 | B-->D; 147 | C-->D; 148 |
149 |
150 | 151 | 152 | `; 153 | 154 | exports[`Mermaid renders with diagram 1`] = ` 155 | 156 | 157 | 158 |
159 | 160 |
168 | graph TD; 169 | A-->B; 170 | A-->C; 171 | B-->D; 172 | C-->D; 173 |
174 | 175 |
176 | 177 | 178 | `; 179 | 180 | exports[`Mermaid renders with diagram change 1`] = ` 181 | 182 | 183 | 184 |
185 |
193 | graph TD; 194 | A-->B; 195 | A-->C; 196 | B-->D; 197 | C-->D; 198 |
199 |
200 | 201 | 202 | `; 203 | 204 | exports[`Mermaid renders with diagram change 2`] = ` 205 | 206 | 207 | 208 |
209 |
217 | graph TD; 218 | D-->C; 219 | D-->B; 220 | C-->A; 221 | B-->A; 222 |
223 |
224 | 225 | 226 | `; 227 | 228 | exports[`Mermaid renders with mermaid config 1`] = ` 229 | 230 | 231 | 232 |
233 |
241 | graph TD; 242 | A-->B; 243 | A-->C; 244 | B-->D; 245 | C-->D; 246 |
247 |
248 | 249 | 250 | `; 251 | 252 | exports[`Mermaid renders with mermaid config change 1`] = ` 253 | 254 | 255 | 256 |
257 |
265 | graph TD; 266 | A-->B; 267 | A-->C; 268 | B-->D; 269 | C-->D; 270 |
271 |
272 | 273 | 274 | `; 275 | 276 | exports[`Mermaid renders with mermaid config change 2`] = ` 277 | 278 | 279 | 280 |
281 |
289 | graph TD; 290 | A-->B; 291 | A-->C; 292 | B-->D; 293 | C-->D; 294 |
295 |
296 | 297 | 298 | `; 299 | 300 | exports[`Mermaid renders with string mermaid config 1`] = ` 301 | 302 | 303 | 304 |
305 |
313 | graph TD; 314 | A-->B; 315 | A-->C; 316 | B-->D; 317 | C-->D; 318 |
319 |
320 | 321 | 322 | `; 323 | 324 | exports[`Mermaid renders without diagram 1`] = ` 325 | 326 | 327 | 328 |
329 |
333 |
334 | 335 | 336 | `; 337 | -------------------------------------------------------------------------------- /lib/src/mdxast-mermaid.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Samuel Wall. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * license file in the root directory of this source tree. 6 | */ 7 | 8 | import { visit } from 'unist-util-visit' 9 | import type { Literal, Parent, Node, Data } from 'unist' 10 | import type { Parent as MdastParent } from 'mdast' 11 | import type mermaid from 'mermaid' 12 | import type { MermaidConfig } from 'mermaid' 13 | import type { Config } from './config.model' 14 | import type { JSXElement, JSXExpressionContainer, JSXIdentifier } from 'estree-jsx' 15 | 16 | type CodeMermaid = Literal & { 17 | type: 'code' 18 | lang: 'mermaid' 19 | } 20 | 21 | /* istanbul ignore next */ 22 | const renderToSvg = async (id: string, src: string, config: MermaidConfig, url: string): Promise => { 23 | const puppeteer = await import('puppeteer') 24 | let browser = await puppeteer.launch({ args: ["--no-sandbox"] }) 25 | try { 26 | let page = await browser.newPage() 27 | await page.goto( 28 | `data:text/html,` 29 | ) 30 | return await page.evaluate( 31 | (diagramId, mermaidDiagram, config) => { 32 | ((window as any).mermaid as typeof mermaid).initialize({ startOnLoad: false, ...config }) 33 | try { 34 | return ((window as any).mermaid as typeof mermaid).mermaidAPI.render(diagramId, mermaidDiagram) 35 | } catch (error) { 36 | return JSON.stringify(error) 37 | } 38 | }, 39 | id, 40 | src, 41 | config, 42 | ) 43 | } finally { 44 | await browser.close() 45 | } 46 | } 47 | 48 | type OutputResult = (Node | Literal)[] 49 | 50 | const createMermaidNode = (node: CodeMermaid, hName: string, config?: Config): OutputResult => { 51 | return [{ 52 | type: 'mermaidCodeBlock', 53 | data: { 54 | hName, 55 | hProperties: { 56 | config: JSON.stringify(config), 57 | chart: node.value, 58 | }, 59 | }, 60 | }] 61 | } 62 | 63 | const outputAST = (node: CodeMermaid, index: number | null, parent: Parent, Data>, config?: Config): OutputResult => { 64 | return createMermaidNode(node, 'mermaid', config) 65 | } 66 | 67 | /* istanbul ignore next */ 68 | const outputSVG = async (node: CodeMermaid, index: number | null, parent: Parent, Data>, config?: Config): Promise => { 69 | const value = await renderToSvg(`mermaid-svg-${index}`, node.value, config && config.mermaid ? config.mermaid : {}, 70 | config?.svgMermaidSrc ?? 'https://cdn.jsdelivr.net/npm/mermaid@9.3.0/dist/mermaid.min.js') 71 | const { fromHtml } = await import('hast-util-from-html') 72 | const { toEstree } = await import('hast-util-to-estree') 73 | const { toJs, jsx } = await import('estree-util-to-js') 74 | const { fromMarkdown } = await import('mdast-util-from-markdown') 75 | const { mdxjs } = await import('micromark-extension-mdxjs') 76 | const { mdxFromMarkdown } = await import('mdast-util-mdx') 77 | const { visit } = await import('estree-util-visit') 78 | const hast = fromHtml(value, { 79 | fragment: true, 80 | space: 'svg' 81 | }) 82 | const estree = toEstree(hast) 83 | visit(estree, (node, key, index, ancestors) => { 84 | const jsxElement = node as JSXElement 85 | if (node.type === 'JSXElement' && (jsxElement.openingElement.name as JSXIdentifier).name === 'style') { 86 | const styleExpression = jsxElement.children[0] as JSXExpressionContainer 87 | const css = (styleExpression.expression as Literal).value as string 88 | const buffer = Buffer.from(css) 89 | const encoded = buffer.toString('base64') 90 | jsxElement.children = [] 91 | jsxElement.openingElement.attributes.push({ 92 | type: 'JSXAttribute', 93 | name: { type: 'JSXIdentifier', name: 'href' }, 94 | value: { type: 'Literal', value: `data:text/css;base64,${encoded}` } 95 | }, 96 | { 97 | type: 'JSXAttribute', 98 | name: { type: 'JSXIdentifier', name: 'rel' }, 99 | value: { type: 'Literal', value: `stylesheet` } 100 | }, 101 | { 102 | type: 'JSXAttribute', 103 | name: { type: 'JSXIdentifier', name: 'type' }, 104 | value: { type: 'Literal', value: `text/css` } 105 | } 106 | ); 107 | (jsxElement.openingElement.name as JSXIdentifier).name = 'link'; 108 | jsxElement.openingElement.selfClosing = true 109 | jsxElement.closingElement = null 110 | const parent = ancestors[ancestors.length - 1] as any 111 | parent.children.splice(parent.children.indexOf(node), 1) 112 | ; (estree.body[0] as any).expression.children.push(node) 113 | } 114 | }) 115 | const js = toJs(estree, { handlers: jsx }) 116 | const tree = fromMarkdown(js.value.substring(2, js.value.length - 5), { 117 | extensions: [mdxjs()], 118 | mdastExtensions: [mdxFromMarkdown()] 119 | }) 120 | return (tree.children[0] as MdastParent).children 121 | } 122 | 123 | const findInstances = (ast: any) => { 124 | const instances: [Literal, number, Parent | Literal, Data>][] = [] 125 | visit(ast, { type: 'code', lang: 'mermaid' }, (node: CodeMermaid, index, parent) => { 126 | instances.push([node, index!, parent as Parent, Data>]) 127 | }) 128 | return instances 129 | } 130 | 131 | /** 132 | * mdx-mermaid plugin. 133 | * 134 | * @param config Config passed in from parser. 135 | * @returns Function to transform mdxast. 136 | */ 137 | export default function plugin(config?: Config) { 138 | /* istanbul ignore next */ 139 | if (config?.output === 'svg') { 140 | return async function transformer(ast: any): Promise { 141 | // Find all the mermaid diagram code blocks. i.e. ```mermaid 142 | let instances = findInstances(ast); 143 | 144 | // Replace each Mermaid code block with the Mermaid component 145 | // Here we iterate over the instances and replace them with the SVG 146 | // and run findInstances again to get the next set instances. We do this 147 | // because the replacement process can change the AST and cause 148 | // the indexes to be incorrect. 149 | while (instances.length > 0) { 150 | const [node, index, parent] = instances[0]; 151 | const result = await outputSVG(node as any, index, parent, config); 152 | Array.prototype.splice.apply(parent.children, [index, 1, ...result]); 153 | instances = findInstances(ast); 154 | } 155 | return ast 156 | } 157 | } 158 | 159 | return function transformer(ast: any): Parent { 160 | // Find all the mermaid diagram code blocks. i.e. ```mermaid 161 | const instances = findInstances(ast) 162 | 163 | // Replace each Mermaid code block with the Mermaid component 164 | for (let i = 0; i < instances.length; i++) { 165 | const [node, index, parent] = instances[i] 166 | /* istanbul ignore next */ 167 | const passConfig = i == 0 ? config : undefined 168 | const result = outputAST(node as any, index, parent, passConfig); 169 | Array.prototype.splice.apply(parent.children, [index, 1, ...result]) 170 | } 171 | return ast 172 | } 173 | } 174 | --------------------------------------------------------------------------------