Compare commits

...

30 Commits

Author SHA1 Message Date
Per Brolin
9bb0ed2040 Added registerExternalDiagram for Mindmap 2022-11-09 15:54:39 +01:00
Knut Sveidqvist
2f9d6e0aff Merge pull request #3774 from mermaid-js/sidv/fixLL
Fix lazy loading in webpack
2022-11-09 08:27:55 +01:00
Sidharth Vinod
ecc51d7cb8 fix: Remove registerDiagram export 2022-11-09 11:27:36 +05:30
Sidharth Vinod
c309e3e3d6 Merge branch 'sidv/fixLL' of https://github.com/mermaid-js/mermaid into sidv/fixLL
* 'sidv/fixLL' of https://github.com/mermaid-js/mermaid:
  Apply suggestions from code review
2022-11-09 09:55:09 +05:30
Sidharth Vinod
f52df3037f fix versions 2022-11-09 09:55:05 +05:30
Sidharth Vinod
649ab17806 feat: Add config validator MVP 2022-11-09 09:54:52 +05:30
Sidharth Vinod
89da6ea31a Apply suggestions from code review
Co-authored-by: Alois Klink <alois@aloisklink.com>
2022-11-09 09:54:06 +05:30
Sidharth Vinod
c7f7ff39ce fix: Import path in viewer.js 2022-11-09 00:43:19 +05:30
Sidharth Vinod
8e63a072e4 Cleanup package.json 2022-11-09 00:39:54 +05:30
Sidharth Vinod
b03ac389fa Restore package and lock from master 2022-11-09 00:36:38 +05:30
Sidharth Vinod
d2511f6a8c fix pnpm lock 2022-11-08 20:21:37 +05:30
Sidharth Vinod
5b53cee673 Fix pnpm-lock 2022-11-08 20:16:17 +05:30
Sidharth Vinod
1b2dce99c9 Merge branch 'release/9.2.1' of https://github.com/mermaid-js/mermaid into sidv/fixLL
* 'release/9.2.1' of https://github.com/mermaid-js/mermaid:
  Fixing applitools batches
2022-11-08 20:15:13 +05:30
Sidharth Vinod
745abb81dc Fix pnpm 2022-11-08 19:52:24 +05:30
Sidharth Vinod
e64e98fbfc Bump pnpm 2022-11-08 19:48:26 +05:30
Sidharth Vinod
605f288554 fix Lint 2022-11-08 19:33:50 +05:30
Sidharth Vinod
6d2552ea6e fix: Filenames 2022-11-08 19:26:02 +05:30
Sidharth Vinod
20b4358c0e fix: Make options in registerExternalDiagrams optional 2022-11-08 19:21:49 +05:30
Sidharth Vinod
7ca525622b fix #3757 : Remove dynamic imports for lazy load. 2022-11-08 19:12:37 +05:30
Sidharth Vinod
aab8f9273f Merge branch 'feat/3701-expose-registerDiagram' into sidv/fixLL
* feat/3701-expose-registerDiagram:
  feat: add `mermaidAPI.registerDiagram()`
  refactor(mermaid): remove registerDiagram cb func
  fix(mermaid): fix DiagramDefinition types
2022-11-08 13:50:54 +05:30
Sidharth Vinod
166dca4924 webpack test 2022-11-08 12:51:59 +05:30
Sidharth Vinod
75d276e19e Merge branch 'release_9.2.1_bugfixes' into sidv/webpackTest
* release_9.2.1_bugfixes:
  fix(mermaid): default mermaid back to CommonJS
  fix(mermaid): fix mermaid.render types
2022-11-07 14:32:35 +05:30
Sidharth Vinod
2243af1871 Merge pull request #3767 from aloisklink/fix/convert-package-back-to-commonjs
[9.2] fix(mermaid): default mermaid back to CommonJS
2022-11-07 14:31:45 +05:30
Sidharth Vinod
41dbf0fa96 Merge pull request #3768 from aloisklink/fix/fix-mermaid.render-types
[9.2] fix(mermaid): fix `mermaid.render` types
2022-11-07 14:01:37 +05:30
Alois Klink
1a5e7315c0 fix(mermaid): default mermaid back to CommonJS
Default mermaid back to being a CommonJS module.

Improrting Mermaid as CommonJS (e.g. using `require("mermaid")`)
is normally broken (since v8), due to it's dependency on d3,
which is now ESM only.

However, it looks like some software
(e.g. TypeScript, in the docusaurus project)
could still handle the CommonJS version of Mermaid.

This commit now means that older versions of Node/build-tools
should now default to using the CommonJS version of Mermaid.

Newer tools should still see that the `"module"` field points to ESM,
or use the `exports["."]["import"]` field to load ESM.
2022-11-06 23:41:09 +00:00
Alois Klink
da6bb9498a fix(mermaid): fix mermaid.render types
The cb param of mermaid.render should be optional,
and mermaid.render returns an SVG string instead of `void`.
2022-11-06 22:46:57 +00:00
Sidharth Vinod
1e417833f4 Basic webpack 2022-11-04 11:29:24 +05:30
Alois Klink
f41e34e61a feat: add mermaidAPI.registerDiagram()
Exposes the registerDiagram() function publically as
`mermaid.mermaidAPI.registerDiagram` so that users can add their
own diagrams at bundle-time.

This is instead of using the lazyLoadedDiagrams config setting.
2022-10-23 16:53:25 +01:00
Alois Klink
89d3d297b7 refactor(mermaid): remove registerDiagram cb func
Remove the callback function parameter from registerDiagram.
Instead, we can just load the callback function from the `injectUtils`
diagram definition, if it exists.
2022-10-23 16:34:18 +01:00
Alois Klink
41249fd064 fix(mermaid): fix DiagramDefinition types
The `injectUtils` function takes the utils as multiple parameters,
not an object.
2022-10-23 14:23:30 +01:00
28 changed files with 392 additions and 160 deletions

1
.npmrc
View File

@@ -1 +1,2 @@
auto-install-peers=true auto-install-peers=true
strict-peer-dependencies=false

View File

@@ -23,23 +23,13 @@ const packageOptions = {
'mermaid-mindmap': { 'mermaid-mindmap': {
name: 'mermaid-mindmap', name: 'mermaid-mindmap',
packageName: 'mermaid-mindmap', packageName: 'mermaid-mindmap',
file: 'diagram-definition.ts',
},
'mermaid-mindmap-detector': {
name: 'mermaid-mindmap-detector',
packageName: 'mermaid-mindmap',
file: 'detector.ts',
},
'mermaid-example-diagram': {
name: 'mermaid-example-diagram',
packageName: 'mermaid-example-diagram',
file: 'diagram-definition.ts',
},
'mermaid-example-diagram-detector': {
name: 'mermaid-example-diagram-detector',
packageName: 'mermaid-example-diagram',
file: 'detector.ts', file: 'detector.ts',
}, },
// 'mermaid-example-diagram-detector': {
// name: 'mermaid-example-diagram-detector',
// packageName: 'mermaid-example-diagram',
// file: 'detector.ts',
// },
}; };
interface BuildOptions { interface BuildOptions {
@@ -111,7 +101,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
include: [ include: [
'packages/mermaid-mindmap/src/**', 'packages/mermaid-mindmap/src/**',
'packages/mermaid/src/**', 'packages/mermaid/src/**',
'packages/mermaid-example-diagram/src/**', // 'packages/mermaid-example-diagram/src/**',
], ],
}; };
} }
@@ -141,7 +131,7 @@ if (watch) {
build(getBuildConfig({ minify: false, watch, core: true, entryName: 'mermaid' })); build(getBuildConfig({ minify: false, watch, core: true, entryName: 'mermaid' }));
if (!mermaidOnly) { if (!mermaidOnly) {
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-mindmap' })); build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-mindmap' }));
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' })); // build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
} }
} else { } else {
void main(); void main();

View File

@@ -0,0 +1,13 @@
describe('mermaid', () => {
describe('registerDiagram', () => {
it('should work on @mermaid-js/mermaid-mindmap and mermaid-example-diagram', () => {
const url = 'http://localhost:9000/external-diagrams-mindmap.html';
cy.visit(url);
cy.get('svg', {
// may be a bit slower than normal, since vite might need to re-compile mermaid/mermaid-mindmap/mermaid-example-diagram
timeout: 10000,
}).matchImageSnapshot();
});
});
});

View File

@@ -0,0 +1,49 @@
<html>
<body>
<h1>Should correctly load a third-party diagram using registerDiagram</h1>
<pre id="diagram" class="mermaid">
mindmap
root
A
B
C
D
E
A2
B2
C2
D2
E2
child1((Circle))
grandchild 1
grandchild 2
child2(Round rectangle)
grandchild 3
grandchild 4
child3[Square]
grandchild 5
::icon(mdi mdi-fire)
gc6((grand<br/>child 6))
::icon(mdi mdi-fire)
gc7((grand<br/>grand<br/>child 8))
</pre>
<!-- <pre id="diagram" class="mermaid2">
example-diagram
</pre> -->
<!-- <div id="cy"></div> -->
<!-- <script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script> -->
<!-- <script src="./mermaid-example-diagram-detector.js"></script> -->
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> -->
<!-- <script type="module" src="./external-diagrams-mindmap.mjs" /> -->
<script type="module">
import mindmap from '../../packages/mermaid-mindmap/src/detector';
// import example from '../../packages/mermaid-example-diagram/src/detector';
import mermaid from '../../packages/mermaid/src/mermaid';
await mermaid.registerExternalDiagrams([mindmap]);
await mermaid.initialize({ logLevel: 0 });
await mermaid.initThrowsErrorsAsync();
</script>
</body>
</html>

View File

@@ -1,4 +1,5 @@
import mermaid2 from '../../packages/mermaid/src/mermaid'; import mermaid2 from '../../packages/mermaid/src/mermaid';
import mindmap from '../../packages/mermaid-mindmap/src/detector';
function b64ToUtf8(str) { function b64ToUtf8(str) {
return decodeURIComponent(escape(window.atob(str))); return decodeURIComponent(escape(window.atob(str)));
@@ -9,7 +10,7 @@ function b64ToUtf8(str) {
* configuration for mermaid rendering and calls init for rendering the mermaid diagrams on the * configuration for mermaid rendering and calls init for rendering the mermaid diagrams on the
* page. * page.
*/ */
const contentLoaded = function () { const contentLoaded = async function () {
let pos = document.location.href.indexOf('?graph='); let pos = document.location.href.indexOf('?graph=');
if (pos > 0) { if (pos > 0) {
pos = pos + 7; pos = pos + 7;
@@ -36,8 +37,7 @@ const contentLoaded = function () {
document.getElementsByTagName('body')[0].appendChild(div); document.getElementsByTagName('body')[0].appendChild(div);
} }
graphObj.mermaid.lazyLoadedDiagrams = ['/mermaid-mindmap-detector.esm.mjs']; await mermaid2.registerExternalDiagrams([mindmap]);
mermaid2.initialize(graphObj.mermaid); mermaid2.initialize(graphObj.mermaid);
mermaid2.init(); mermaid2.init();
} }

View File

@@ -49,10 +49,10 @@
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module"> <script type="module">
// import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.esm.min.mjs'; import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.esm.min.mjs';
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9.2.0/dist/mermaid.esm.min.mjs'; import mindmap from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-mindmap@9/dist/mermaid-mindmap.esm.mjs';
// import mermaid from 'http://localhost:9000/mermaid.esm.mjs'; await mermaid.registerExternalDiagrams([mindmap]);
console.log(mermaid); // eslint-disable-line
window.mermaid = mermaid; window.mermaid = mermaid;
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
@@ -60,20 +60,18 @@
logLevel: 4, logLevel: 4,
startOnLoad: true, startOnLoad: true,
themeCSS: '.label { font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; }', themeCSS: '.label { font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; }',
lazyLoadedDiagrams: [
'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-mindmap@9.2.0/dist/mermaid-mindmap-detector.esm.mjs',
// 'http://localhost:9000/mermaid-mindmap-detector.esm.mjs',
],
}; };
if (isDarkMode) conf.theme = 'dark'; if (isDarkMode) conf.theme = 'dark';
async function loadMermaid() { async function loadMermaid() {
await mermaid.initialize(conf); mermaid.parseError = (e) => {
console.log('parse error', e); // eslint-disable-line
};
await mermaid.registerExternalDiagrams([mindmap]);
mermaid.initialize(conf);
console.log('mermaid initialized'); // eslint-disable-line console.log('mermaid initialized'); // eslint-disable-line
} }
mermaid.parseError = (e) => {
console.log('parse error', e); // eslint-disable-line
};
await loadMermaid(); await loadMermaid();
</script> </script>
<script> <script>

View File

@@ -3,19 +3,8 @@
"private": true, "private": true,
"version": "9.2.0-rc4", "version": "9.2.0-rc4",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid.core.mjs",
"module": "dist/mermaid.core.mjs",
"types": "dist/mermaid.d.ts",
"type": "module", "type": "module",
"packageManager": "pnpm@7.13.2", "packageManager": "pnpm@7.13.2",
"exports": {
".": {
"require": "./dist/mermaid.min.js",
"import": "./dist/mermaid.core.mjs",
"types": "./dist/mermaid.d.ts"
},
"./*": "./*"
},
"keywords": [ "keywords": [
"diagram", "diagram",
"markdown", "markdown",
@@ -147,9 +136,6 @@
"resolutions": { "resolutions": {
"d3": "^7.0.0" "d3": "^7.0.0"
}, },
"files": [
"dist"
],
"sideEffects": [ "sideEffects": [
"**/*.css", "**/*.css",
"**/*.scss" "**/*.scss"

View File

@@ -0,0 +1,3 @@
### Do not refer this package. It is not ready.
### Refer mermaid-mindmap instead.

View File

@@ -12,3 +12,5 @@ export const diagram = {
styles, styles,
injectUtils, injectUtils,
}; };
export { detector, id } from './detector';

View File

@@ -1,14 +1,14 @@
{ {
"name": "@mermaid-js/mermaid-mindmap", "name": "@mermaid-js/mermaid-mindmap",
"version": "9.2.0", "version": "9.2.2-rc.2",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid-mindmap.core.mjs",
"module": "dist/mermaid-mindmap.core.mjs", "module": "dist/mermaid-mindmap.core.mjs",
"types": "dist/detector.d.ts",
"type": "module", "type": "module",
"exports": { "exports": {
".": { ".": {
"require": "./dist/mermaid-mindmap.min.js", "import": "./dist/mermaid-mindmap.core.mjs",
"import": "./dist/mermaid-mindmap.core.mjs" "types": "./dist/detector.d.ts"
}, },
"./*": "./*" "./*": "./*"
}, },

View File

@@ -1,10 +1,20 @@
export const id = 'mindmap'; import type { ExternalDiagramDefinition } from 'mermaid';
export const detector = (txt: string) => { const id = 'mindmap';
const detector = (txt: string) => {
return txt.match(/^\s*mindmap/) !== null; return txt.match(/^\s*mindmap/) !== null;
}; };
export const loadDiagram = async () => { const loader = async () => {
const { diagram } = await import('./diagram-definition'); const { diagram } = await import('./diagram-definition');
return { id, diagram }; return { id, diagram };
}; };
const plugin: ExternalDiagramDefinition = {
id,
detector,
loader,
};
export default plugin;

1
packages/mermaid/README.md Symbolic link
View File

@@ -0,0 +1 @@
../../README.md

View File

@@ -1,11 +1,11 @@
{ {
"name": "mermaid", "name": "mermaid",
"version": "9.2.0", "version": "9.2.2-rc.2",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "./dist/mermaid.core.mjs", "main": "./dist/mermaid.min.js",
"module": "./dist/mermaid.core.mjs", "module": "./dist/mermaid.core.mjs",
"types": "./dist/mermaid.d.ts", "types": "./dist/mermaid.d.ts",
"type": "module", "type": "commonjs",
"exports": { "exports": {
".": { ".": {
"require": "./dist/mermaid.min.js", "require": "./dist/mermaid.min.js",
@@ -127,7 +127,8 @@
"d3": "^7.0.0" "d3": "^7.0.0"
}, },
"files": [ "files": [
"dist" "dist",
"README.md"
], ],
"sideEffects": [ "sideEffects": [
"**/*.css", "**/*.css",

View File

@@ -106,11 +106,10 @@ export const getDiagramFromText = (
// registerDiagram(type, diagram, undefined, diagram.injectUtils); // registerDiagram(type, diagram, undefined, diagram.injectUtils);
// // new diagram will try getDiagram again and if fails then it is a valid throw // // new diagram will try getDiagram again and if fails then it is a valid throw
return loader().then(({ diagram }) => { return loader().then(({ diagram }) => {
registerDiagram(type, diagram, undefined, diagram.injectUtils); registerDiagram(type, diagram, undefined);
return new Diagram(txt, parseError); return new Diagram(txt, parseError);
}); });
} }
// return new Diagram(txt, parseError);
}; };
export default Diagram; export default Diagram;

View File

@@ -40,7 +40,8 @@ export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: any[])
} }
currentConfig = cfg; currentConfig = cfg;
return cfg; checkConfig(currentConfig);
return currentConfig;
}; };
/** /**
@@ -68,7 +69,7 @@ export const setSiteConfig = (conf: MermaidConfig): MermaidConfig => {
siteConfig.themeVariables = theme[conf.theme].getThemeVariables(conf.themeVariables); siteConfig.themeVariables = theme[conf.theme].getThemeVariables(conf.themeVariables);
} }
currentConfig = updateCurrentConfig(siteConfig, directives); updateCurrentConfig(siteConfig, directives);
return siteConfig; return siteConfig;
}; };
@@ -117,6 +118,7 @@ export const setConfig = (conf: MermaidConfig): MermaidConfig => {
// conf[key] = manipulator ? manipulator(conf[key]) : conf[key]; // conf[key] = manipulator ? manipulator(conf[key]) : conf[key];
// }); // });
checkConfig(conf);
assignWithDepth(currentConfig, conf); assignWithDepth(currentConfig, conf);
return getConfig(); return getConfig();
@@ -224,3 +226,25 @@ export const reset = (config = siteConfig): void => {
directives = []; directives = [];
updateCurrentConfig(config, directives); updateCurrentConfig(config, directives);
}; };
enum ConfigWarning {
'LAZY_LOAD_DEPRECATED' = 'The configuration options lazyLoadedDiagrams and loadExternalDiagramsAtStartup are deprecated. Please use registerExternalDiagrams instead.',
}
type ConfigWarningStrings = keyof typeof ConfigWarning;
const issuedWarnings: { [key in ConfigWarningStrings]?: boolean } = {};
const issueWarning = (warning: ConfigWarningStrings) => {
if (issuedWarnings[warning]) {
return;
}
log.warn(ConfigWarning[warning]);
issuedWarnings[warning] = true;
};
const checkConfig = (config: MermaidConfig) => {
if (!config) {
return;
}
if (config.lazyLoadedDiagrams || config.loadExternalDiagramsAtStartup) {
issueWarning('LAZY_LOAD_DEPRECATED');
}
};

View File

@@ -3,7 +3,9 @@
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
export interface MermaidConfig { export interface MermaidConfig {
/** @deprecated use mermaid.registerLazyDiagrams instead */
lazyLoadedDiagrams?: string[]; lazyLoadedDiagrams?: string[];
/** @deprecated use mermaid.registerLazyDiagrams instead */
loadExternalDiagramsAtStartup?: boolean; loadExternalDiagramsAtStartup?: boolean;
theme?: string; theme?: string;
themeVariables?: any; themeVariables?: any;

View File

@@ -115,7 +115,6 @@ const config: Partial<MermaidConfig> = {
* Default value: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize'] * Default value: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize']
*/ */
secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize'], secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize'],
lazyLoadedDiagrams: [],
/** /**
* This option controls if the generated ids of nodes in the SVG are generated randomly or based * This option controls if the generated ids of nodes in the SVG are generated randomly or based
* on a seed. If set to false, the IDs are generated based on the current date and thus are not * on a seed. If set to false, the IDs are generated based on the current date and thus are not

View File

@@ -22,17 +22,19 @@ export interface Detectors {
[key: string]: DiagramDetector; [key: string]: DiagramDetector;
} }
/**
* Registers the given diagram with Mermaid.
*
* Can be used for third-party custom diagrams.
*
* @param id - A unique ID for the given diagram.
* @param diagram - The diagram definition.
* @param detector - Function that returns `true` if a given mermaid text is this diagram definition.
*/
export const registerDiagram = ( export const registerDiagram = (
id: string, id: string,
diagram: DiagramDefinition, diagram: DiagramDefinition,
detector?: DiagramDetector, detector?: DiagramDetector
callback?: (
_log: any,
_setLogLevel: any,
_getConfig: any,
_sanitizeText: any,
_setupGraphViewbox: any
) => void
) => { ) => {
log.debug(`Registering diagram ${id}`); log.debug(`Registering diagram ${id}`);
if (diagrams[id]) { if (diagrams[id]) {
@@ -48,8 +50,9 @@ export const registerDiagram = (
addDetector(id, detector); addDetector(id, detector);
} }
addStylesForDiagram(id, diagram.styles); addStylesForDiagram(id, diagram.styles);
if (typeof callback !== 'undefined') {
callback(log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox); if (diagram.injectUtils) {
diagram.injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox);
} }
log.debug(`Registered diagram ${id}. ${Object.keys(diagrams).join(', ')} diagrams registered.`); log.debug(`Registered diagram ${id}. ${Object.keys(diagrams).join(', ')} diagrams registered.`);
}; };

View File

@@ -14,7 +14,13 @@ export interface DiagramDefinition {
parser: any; parser: any;
styles: any; styles: any;
init?: (config: MermaidConfig) => void; init?: (config: MermaidConfig) => void;
injectUtils?: (utils: InjectUtils) => void; injectUtils?: (
_log: InjectUtils['_log'],
_setLogLevel: InjectUtils['_setLogLevel'],
_getConfig: InjectUtils['_getConfig'],
_sanitizeText: InjectUtils['_sanitizeText'],
_setupGraphViewbox: InjectUtils['_setupGraphViewbox']
) => void;
} }
export interface DetectorRecord { export interface DetectorRecord {
@@ -22,5 +28,11 @@ export interface DetectorRecord {
loader?: DiagramLoader; loader?: DiagramLoader;
} }
export interface ExternalDiagramDefinition {
id: string;
detector: DiagramDetector;
loader: DiagramLoader;
}
export type DiagramDetector = (text: string, config?: MermaidConfig) => boolean; export type DiagramDetector = (text: string, config?: MermaidConfig) => boolean;
export type DiagramLoader = (() => Promise<{ id: string; diagram: DiagramDefinition }>) | null; export type DiagramLoader = () => Promise<{ id: string; diagram: DiagramDefinition }>;

View File

@@ -49,10 +49,10 @@
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module"> <script type="module">
// import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@<MERMAID_VERSION>/dist/mermaid.esm.min.mjs'; import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@<MERMAID_VERSION>/dist/mermaid.esm.min.mjs';
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9.2.0-rc6/dist/mermaid.esm.min.mjs'; import mindmap from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-mindmap@<MERMAID_VERSION>/dist/mermaid-mindmap.esm.mjs';
// import mermaid from 'http://localhost:9000/mermaid.esm.mjs'; await mermaid.registerExternalDiagrams([mindmap]);
console.log(mermaid); // eslint-disable-line
window.mermaid = mermaid; window.mermaid = mermaid;
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
@@ -60,20 +60,18 @@
logLevel: 4, logLevel: 4,
startOnLoad: true, startOnLoad: true,
themeCSS: '.label { font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; }', themeCSS: '.label { font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; }',
lazyLoadedDiagrams: [
'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-mindmap@9.2.0-rc3/dist/mermaid-mindmap-detector.esm.mjs',
// 'http://localhost:9000/mermaid-mindmap-detector.esm.mjs',
],
}; };
if (isDarkMode) conf.theme = 'dark'; if (isDarkMode) conf.theme = 'dark';
async function loadMermaid() { async function loadMermaid() {
await mermaid.initialize(conf); mermaid.parseError = (e) => {
console.log('parse error', e); // eslint-disable-line
};
await mermaid.registerExternalDiagrams([mindmap]);
mermaid.initialize(conf);
console.log('mermaid initialized'); // eslint-disable-line console.log('mermaid initialized'); // eslint-disable-line
} }
mermaid.parseError = (e) => {
console.log('parse error', e); // eslint-disable-line
};
await loadMermaid(); await loadMermaid();
</script> </script>
<script> <script>

View File

@@ -54,24 +54,83 @@ describe('when using mermaid and ', function () {
expect(mermaidAPI.render).toHaveBeenCalled(); expect(mermaidAPI.render).toHaveBeenCalled();
}); });
}); });
describe('when using #initThrowsErrorsAsync', function () { describe('when using #registerExternalDiagrams', function () {
it('should throw error (but still render) if lazyLoadedDiagram fails', async () => { it('should throw error (but still render) if registerExternalDiagrams fails', async () => {
const node = document.createElement('div'); const node = document.createElement('div');
node.appendChild(document.createTextNode('graph TD;\na;')); node.appendChild(document.createTextNode('graph TD;\na;'));
mermaidAPI.setConfig({ await expect(
lazyLoadedDiagrams: ['this-file-does-not-exist.mjs'], mermaid.registerExternalDiagrams(
}); [
await expect(mermaid.initThrowsErrorsAsync(undefined, node)).rejects.toThrowError( {
// this error message is probably different on every platform id: 'dummy',
// this one is just for vite-note (node/jest/browser may be different) detector: (text) => /dummy/.test(text),
'Failed to load this-file-does-not-exist.mjs' loader: () => Promise.reject('error'),
); },
],
{ lazyLoad: false }
)
).rejects.toThrow('Failed to load 1 external diagrams');
expect(() => mermaid.initThrowsErrorsAsync(undefined, node)).not.toThrow();
// should still render, even if lazyLoadedDiagrams fails // should still render, even if lazyLoadedDiagrams fails
expect(mermaidAPI.renderAsync).toHaveBeenCalled(); expect(mermaidAPI.renderAsync).toHaveBeenCalled();
}); });
it('should defer diagram load based on parameter', async () => {
let loaded = false;
const dummyDiagram = {
db: {},
renderer: () => {
// do nothing
},
parser: () => {
// do nothing
},
styles: () => {
// do nothing
},
};
await expect(
mermaid.registerExternalDiagrams(
[
{
id: 'dummy',
detector: (text) => /dummy/.test(text),
loader: () => {
loaded = true;
return Promise.resolve({
id: 'dummy',
diagram: dummyDiagram,
});
},
},
],
{ lazyLoad: true }
)
).resolves.toBe(undefined);
expect(loaded).toBe(false);
await expect(
mermaid.registerExternalDiagrams(
[
{
id: 'dummy2',
detector: (text) => /dummy2/.test(text),
loader: () => {
loaded = true;
return Promise.resolve({
id: 'dummy2',
diagram: dummyDiagram,
});
},
},
],
{ lazyLoad: false }
)
).resolves.toBe(undefined);
expect(loaded).toBe(true);
});
afterEach(() => { afterEach(() => {
// we modify mermaid config in some tests, so we need to make sure to reset them // we modify mermaid config in some tests, so we need to make sure to reset them
mermaidAPI.reset(); mermaidAPI.reset();

View File

@@ -9,8 +9,11 @@ import { mermaidAPI } from './mermaidAPI';
import { addDetector } from './diagram-api/detectType'; import { addDetector } from './diagram-api/detectType';
import { isDetailedError, type DetailedError } from './utils'; import { isDetailedError, type DetailedError } from './utils';
import { registerDiagram } from './diagram-api/diagramAPI'; import { registerDiagram } from './diagram-api/diagramAPI';
import { ExternalDiagramDefinition } from './diagram-api/types';
export type { MermaidConfig, DetailedError }; export type { MermaidConfig, DetailedError, ExternalDiagramDefinition };
let externalDiagramsRegistered = false;
/** /**
* ## init * ## init
* *
@@ -47,8 +50,8 @@ const init = async function (
callback?: Function callback?: Function
) { ) {
try { try {
const conf = mermaidAPI.getConfig(); // Not really sure if we need to check this, or simply call initThrowsErrorsAsync directly.
if (conf?.lazyLoadedDiagrams && conf.lazyLoadedDiagrams.length > 0) { if (externalDiagramsRegistered) {
await initThrowsErrorsAsync(config, nodes, callback); await initThrowsErrorsAsync(config, nodes, callback);
} else { } else {
initThrowsErrors(config, nodes, callback); initThrowsErrors(config, nodes, callback);
@@ -89,6 +92,7 @@ const handleError = (error: unknown, errors: DetailedError[], parseError?: Funct
} }
} }
}; };
const initThrowsErrors = function ( const initThrowsErrors = function (
config?: MermaidConfig, config?: MermaidConfig,
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
@@ -177,45 +181,39 @@ const initThrowsErrors = function (
} }
}; };
let lazyLoadingPromise: Promise<PromiseSettledResult<void>[]> | undefined = undefined;
/** /**
* This is an internal function and should not be made public, as it will likely change. * This is an internal function and should not be made public, as it will likely change.
* @internal * @internal
* @param conf - Mermaid config. * @param diagrams - Array of {@link ExternalDiagramDefinition}.
* @returns An array of {@link PromiseSettledResult}, showing the status of imports.
*/ */
const registerLazyLoadedDiagrams = async (conf: MermaidConfig) => { const registerLazyLoadedDiagrams = (diagrams: ExternalDiagramDefinition[]) => {
// Only lazy load once for (const { id, detector, loader } of diagrams) {
// TODO: This is a hack. We should either throw error when new diagrams are added, or load them anyway. addDetector(id, detector, loader);
if (lazyLoadingPromise === undefined) {
// Load all lazy loaded diagrams in parallel
lazyLoadingPromise = Promise.allSettled(
(conf?.lazyLoadedDiagrams ?? []).map(async (diagram: string) => {
const { id, detector, loadDiagram } = await import(diagram);
addDetector(id, detector, loadDiagram);
})
);
} }
return await lazyLoadingPromise;
}; };
let loadingPromise: Promise<unknown> | undefined = undefined; /**
* This is an internal function and should not be made public, as it will likely change.
const loadExternalDiagrams = async (conf: MermaidConfig) => { * @internal
// Only lazy load once * @param diagrams - Array of {@link ExternalDiagramDefinition}.
// TODO: This is a hack. We should either throw error when new diagrams are added, or load them anyway. */
if (loadingPromise === undefined) { const loadExternalDiagrams = async (diagrams: ExternalDiagramDefinition[]) => {
log.debug(`Loading ${conf?.lazyLoadedDiagrams?.length} external diagrams`); log.debug(`Loading ${diagrams.length} external diagrams`);
// Load all lazy loaded diagrams in parallel // Load all lazy loaded diagrams in parallel
loadingPromise = Promise.allSettled( const results = await Promise.allSettled(
(conf?.lazyLoadedDiagrams ?? []).map(async (url: string) => { diagrams.map(async ({ id, detector, loader }) => {
const { id, detector, loadDiagram } = await import(url); const { diagram } = await loader();
const { diagram } = await loadDiagram(); registerDiagram(id, diagram, detector);
registerDiagram(id, diagram, detector, diagram.injectUtils); })
}) );
); const failed = results.filter((result) => result.status === 'rejected');
if (failed.length > 0) {
log.error(`Failed to load ${failed.length} external diagrams`);
for (const res of failed) {
log.error(res);
}
throw new Error(`Failed to load ${failed.length} external diagrams`);
} }
await loadingPromise;
}; };
/** /**
@@ -242,13 +240,6 @@ const initThrowsErrorsAsync = async function (
) { ) {
const conf = mermaidAPI.getConfig(); const conf = mermaidAPI.getConfig();
const registerLazyLoadedDiagramsErrors: Error[] = [];
for (const registerResult of await registerLazyLoadedDiagrams(conf)) {
if (registerResult.status == 'rejected') {
registerLazyLoadedDiagramsErrors.push(registerResult.reason);
}
}
if (config) { if (config) {
// This is a legacy way of setting config. It is not documented and should be removed in the future. // This is a legacy way of setting config. It is not documented and should be removed in the future.
// @ts-ignore: TODO Fix ts errors // @ts-ignore: TODO Fix ts errors
@@ -323,10 +314,9 @@ const initThrowsErrorsAsync = async function (
handleError(error, errors, mermaid.parseError); handleError(error, errors, mermaid.parseError);
} }
} }
const allErrors = [...registerLazyLoadedDiagramsErrors, ...errors]; if (errors.length > 0) {
if (allErrors.length > 0) {
// TODO: We should be throwing an error object. // TODO: We should be throwing an error object.
throw allErrors[0]; throw errors[0];
} }
}; };
@@ -335,16 +325,25 @@ const initialize = function (config: MermaidConfig) {
}; };
/** /**
* @param config * Used to register external diagram types.
* @deprecated This is an internal function and should not be used. Will be removed in v10. * @param diagrams - Array of {@link ExternalDiagramDefinition}.
* @param opts
* @param opts.lazyLoad - If true, the diagram will be loaded on demand.
*/ */
const initializeAsync = async function (config: MermaidConfig) { const registerExternalDiagrams = async (
if (config.loadExternalDiagramsAtStartup) { diagrams: ExternalDiagramDefinition[],
await loadExternalDiagrams(config); {
lazyLoad = true,
}: {
lazyLoad?: boolean;
} = {}
) => {
if (lazyLoad) {
registerLazyLoadedDiagrams(diagrams);
} else { } else {
await registerLazyLoadedDiagrams(config); await loadExternalDiagrams(diagrams);
} }
mermaidAPI.initialize(config); externalDiagramsRegistered = true;
}; };
/** /**
@@ -414,7 +413,7 @@ const executeQueue = async () => {
* @param txt * @param txt
* @deprecated This is an internal function and should not be used. Will be removed in v10. * @deprecated This is an internal function and should not be used. Will be removed in v10.
*/ */
const parseAsync = (txt: string) => { const parseAsync = (txt: string): Promise<boolean> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// This promise will resolve when the mermaidAPI.render call is done. // This promise will resolve when the mermaidAPI.render call is done.
// It will be queued first and will be executed when it is first in line // It will be queued first and will be executed when it is first in line
@@ -424,7 +423,7 @@ const parseAsync = (txt: string) => {
(r) => { (r) => {
// This resolves for the promise for the queue handling // This resolves for the promise for the queue handling
res(r); res(r);
// This fullfills the promise sent to the value back to the original caller // This fulfills the promise sent to the value back to the original caller
resolve(r); resolve(r);
}, },
(e) => { (e) => {
@@ -482,9 +481,9 @@ const parseAsync = (txt: string) => {
const renderAsync = ( const renderAsync = (
id: string, id: string,
text: string, text: string,
cb: (svgCode: string, bindFunctions?: (element: Element) => void) => void, cb?: (svgCode: string, bindFunctions?: (element: Element) => void) => void,
container?: Element container?: Element
): Promise<void> => { ): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// This promise will resolve when the mermaidAPI.render call is done. // This promise will resolve when the mermaidAPI.render call is done.
// It will be queued first and will be executed when it is first in line // It will be queued first and will be executed when it is first in line
@@ -522,8 +521,8 @@ const mermaid: {
init: typeof init; init: typeof init;
initThrowsErrors: typeof initThrowsErrors; initThrowsErrors: typeof initThrowsErrors;
initThrowsErrorsAsync: typeof initThrowsErrorsAsync; initThrowsErrorsAsync: typeof initThrowsErrorsAsync;
registerExternalDiagrams: typeof registerExternalDiagrams;
initialize: typeof initialize; initialize: typeof initialize;
initializeAsync: typeof initializeAsync;
contentLoaded: typeof contentLoaded; contentLoaded: typeof contentLoaded;
setParseErrorHandler: typeof setParseErrorHandler; setParseErrorHandler: typeof setParseErrorHandler;
} = { } = {
@@ -537,8 +536,8 @@ const mermaid: {
init, init,
initThrowsErrors, initThrowsErrors,
initThrowsErrorsAsync, initThrowsErrorsAsync,
registerExternalDiagrams,
initialize, initialize,
initializeAsync,
parseError: undefined, parseError: undefined,
contentLoaded, contentLoaded,
setParseErrorHandler, setParseErrorHandler,

View File

@@ -115,19 +115,19 @@ export const decodeEntities = function (text: string): string {
* *
* @param {string} id The id of the element to be rendered * @param {string} id The id of the element to be rendered
* @param {string} text The graph definition * @param {string} text The graph definition
* @param {(svgCode: string, bindFunctions?: (element: Element) => void) => void} cb Callback which * @param cb - Optional callback which
* is called after rendering is finished with the svg code as inparam. * is called after rendering is finished with the svg code as inparam.
* @param {Element} container Selector to element in which a div with the graph temporarily will be * @param {Element} container Selector to element in which a div with the graph temporarily will be
* inserted. If one is provided a hidden div will be inserted in the body of the page instead. The * inserted. If one is provided a hidden div will be inserted in the body of the page instead. The
* element will be removed when rendering is completed. * element will be removed when rendering is completed.
* @returns {void} * @returns Returns the rendered element as a string containing the SVG definition.
*/ */
const render = function ( const render = function (
id: string, id: string,
text: string, text: string,
cb: (svgCode: string, bindFunctions?: (element: Element) => void) => void, cb?: (svgCode: string, bindFunctions?: (element: Element) => void) => void,
container?: Element container?: Element
): void { ): string {
addDiagrams(); addDiagrams();
configApi.reset(); configApi.reset();
text = text.replace(/\r\n?/g, '\n'); // parser problems on CRLF ignore all CR and leave LF;; text = text.replace(/\r\n?/g, '\n'); // parser problems on CRLF ignore all CR and leave LF;;
@@ -401,9 +401,9 @@ const render = function (
const renderAsync = async function ( const renderAsync = async function (
id: string, id: string,
text: string, text: string,
cb: (svgCode: string, bindFunctions?: (element: Element) => void) => void, cb?: (svgCode: string, bindFunctions?: (element: Element) => void) => void,
container?: Element container?: Element
): Promise<void> { ): Promise<string> {
addDiagrams(); addDiagrams();
configApi.reset(); configApi.reset();
text = text.replace(/\r\n?/g, '\n'); // parser problems on CRLF ignore all CR and leave LF;; text = text.replace(/\r\n?/g, '\n'); // parser problems on CRLF ignore all CR and leave LF;;

View File

@@ -1,3 +1,4 @@
packages: packages:
# all packages in direct subdirs of packages/ # all packages in direct subdirs of packages/
- 'packages/*' - 'packages/*'
# - 'tests/*'

View File

@@ -0,0 +1,23 @@
{
"name": "webpack",
"version": "1.0.0",
"description": "",
"private": true,
"module": "commonjs",
"scripts": {
"build": "webpack",
"serve": "webpack serve"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"
},
"dependencies": {
"mermaid": "workspace:*",
"@mermaid-js/mermaid-mindmap": "workspace:*"
}
}

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Getting Started</title>
</head>
<body>
<div id="graphDiv"></div>
<script src="./main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,38 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable no-console */
const mermaid = require('mermaid');
import mindmap from '@mermaid-js/mermaid-mindmap';
const render = async (graph) => {
const svg = await mermaid.renderAsync('dummy', graph);
console.log(svg);
document.getElementById('graphDiv').innerHTML = svg;
};
const load = async () => {
await mermaid.registerExternalDiagrams([mindmap]);
await render('info');
setTimeout(async () => {
await render(`mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectivness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
Pen and paper
Mermaid
`);
}, 2500);
};
window.addEventListener('load', load, false);

View File

@@ -0,0 +1,10 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
};