diff --git a/.build/common.ts b/.build/common.ts index e9d0ebfa6..274977fa2 100644 --- a/.build/common.ts +++ b/.build/common.ts @@ -22,4 +22,9 @@ export const packageOptions = { packageName: 'mermaid-zenuml', file: 'detector.ts', }, + 'mermaid-flowchart-elk': { + name: 'mermaid-flowchart-elk', + packageName: 'mermaid-flowchart-elk', + file: 'detector.ts', + }, } as const; diff --git a/.build/types.ts b/.build/types.ts new file mode 100644 index 000000000..d9f10d21a --- /dev/null +++ b/.build/types.ts @@ -0,0 +1,16 @@ +import { packageOptions } from './common.js'; +import { execSync } from 'child_process'; + +const buildType = (packageName: string) => { + console.log(`Building types for ${packageName}`); + try { + const out = execSync(`tsc -p ./packages/${packageName}/tsconfig.json --emitDeclarationOnly`); + out.length > 0 && console.log(out.toString()); + } catch (e) { + console.error(e); + } +}; + +for (const { packageName } of Object.values(packageOptions)) { + buildType(packageName); +} diff --git a/.esbuild/server.ts b/.esbuild/server.ts index 63d981b99..9102c7de8 100644 --- a/.esbuild/server.ts +++ b/.esbuild/server.ts @@ -5,38 +5,25 @@ import { getBuildConfig, defaultOptions } from './util.js'; import { context } from 'esbuild'; import chokidar from 'chokidar'; import { generateLangium } from '../.build/generateLangium.js'; +import { packageOptions } from '../.build/common.js'; -const parserCtx = await context( - getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'parser' }) +const configs = Object.values(packageOptions).map(({ packageName }) => + getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: packageName }) ); -const mermaidCtx = await context( - getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid' }) -); -const mermaidIIFECtx = await context( - getBuildConfig({ - ...defaultOptions, - minify: false, - core: false, - entryName: 'mermaid', - format: 'iife', - }) -); -const externalCtx = await context( - getBuildConfig({ - ...defaultOptions, - minify: false, - core: false, - entryName: 'mermaid-example-diagram', - }) -); -const zenumlCtx = await context( - getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid-zenuml' }) -); -const contexts = [parserCtx, mermaidCtx, mermaidIIFECtx, externalCtx, zenumlCtx]; +const mermaidIIFEConfig = getBuildConfig({ + ...defaultOptions, + minify: false, + core: false, + entryName: 'mermaid', + format: 'iife', +}); +configs.push(mermaidIIFEConfig); + +const contexts = await Promise.all(configs.map((config) => context(config))); const rebuildAll = async () => { console.time('Rebuild time'); - await Promise.all(contexts.map((ctx) => ctx.rebuild())); + await Promise.all(contexts.map((ctx) => ctx.rebuild())).catch((e) => console.error(e)); console.timeEnd('Rebuild time'); }; @@ -101,10 +88,9 @@ async function createServer() { app.use(cors()); app.get('/events', eventsHandler); - app.use(express.static('./packages/parser/dist')); - app.use(express.static('./packages/mermaid/dist')); - app.use(express.static('./packages/mermaid-zenuml/dist')); - app.use(express.static('./packages/mermaid-example-diagram/dist')); + for (const { packageName } of Object.values(packageOptions)) { + app.use(express.static(`./packages/${packageName}/dist`)); + } app.use(express.static('demos')); app.use(express.static('cypress/platform')); diff --git a/.github/codecov.yaml b/.github/codecov.yaml index 950edb6a9..945043085 100644 --- a/.github/codecov.yaml +++ b/.github/codecov.yaml @@ -15,3 +15,4 @@ coverage: # Turing off for now as code coverage isn't stable and causes unnecessary build failures. # default: # threshold: 2% + patch: off diff --git a/.vite/server.ts b/.vite/server.ts index 838d71191..99d16f6f2 100644 --- a/.vite/server.ts +++ b/.vite/server.ts @@ -1,6 +1,7 @@ import express from 'express'; import cors from 'cors'; import { createServer as createViteServer } from 'vite'; +import { packageOptions } from '../.build/common.js'; async function createServer() { const app = express(); @@ -14,10 +15,9 @@ async function createServer() { }); app.use(cors()); - app.use(express.static('./packages/parser/dist')); - app.use(express.static('./packages/mermaid/dist')); - app.use(express.static('./packages/mermaid-zenuml/dist')); - app.use(express.static('./packages/mermaid-example-diagram/dist')); + for (const { packageName } of Object.values(packageOptions)) { + app.use(express.static(`./packages/${packageName}/dist`)); + } app.use(vite.middlewares); app.use(express.static('demos')); app.use(express.static('cypress/platform')); diff --git a/cypress/integration/other/flowchart-elk.spec.js b/cypress/integration/other/flowchart-elk.spec.js new file mode 100644 index 000000000..22a6efc0f --- /dev/null +++ b/cypress/integration/other/flowchart-elk.spec.js @@ -0,0 +1,14 @@ +import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.ts'; + +describe('Flowchart elk', () => { + it('should use dagre as fallback', () => { + urlSnapshotTest('http://localhost:9000/flow-elk.html', { + name: 'flow-elk fallback to dagre', + }); + }); + it('should allow overriding with external package', () => { + urlSnapshotTest('http://localhost:9000/flow-elk.html?elk=true', { + name: 'flow-elk overriding dagre with elk', + }); + }); +}); diff --git a/cypress/platform/flow-elk.html b/cypress/platform/flow-elk.html new file mode 100644 index 000000000..f319f62e2 --- /dev/null +++ b/cypress/platform/flow-elk.html @@ -0,0 +1,28 @@ + +
++ flowchart-elk + a[hello] --> b[world] + b --> c{test} + c --> one + c --> two + c --> three ++ + + + diff --git a/demos/flowchart-elk.html b/demos/flowchart-elk.html new file mode 100644 index 000000000..69ac2d2bc --- /dev/null +++ b/demos/flowchart-elk.html @@ -0,0 +1,35 @@ + + + + + +
+ flowchart-elk TD + A([Start]) ==> B[Step 1] + B ==> C{Flow 1} + C -- Choice 1.1 --> D[Step 2.1] + C -- Choice 1.3 --> I[Step 2.3] + C == Choice 1.2 ==> E[Step 2.2] + D --> F{Flow 2} + E ==> F{Flow 2} + F{Flow 2} == Choice 2.1 ==> H[Feedback node] + H[Feedback node] ==> B[Step 1] + F{Flow 2} == Choice 2.2 ==> G((Finish)) + ++ + + + diff --git a/package.json b/package.json index c899c7dfa..2ca8d8d9a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "build:esbuild": "pnpm run -r clean && ts-node-esm --transpileOnly .esbuild/build.ts", "build:mermaid": "pnpm build:esbuild --mermaid", "build:viz": "pnpm build:esbuild --visualize", - "build:types": "tsc -p ./packages/parser/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-zenuml/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly", + "build:types": "ts-node-esm --transpileOnly .build/types.ts", "build:types:watch": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly --watch", "dev": "ts-node-esm --transpileOnly .esbuild/server.ts", "dev:vite": "ts-node-esm --transpileOnly .vite/server.ts", diff --git a/packages/mermaid-flowchart-elk/package.json b/packages/mermaid-flowchart-elk/package.json new file mode 100644 index 000000000..e02dba897 --- /dev/null +++ b/packages/mermaid-flowchart-elk/package.json @@ -0,0 +1,45 @@ +{ + "name": "@mermaid-js/flowchart-elk", + "version": "1.0.0", + "description": "Flowchart plugin for mermaid with ELK layout", + "module": "dist/mermaid-flowchart-elk.core.mjs", + "types": "dist/packages/mermaid-flowchart-elk/src/detector.d.ts", + "type": "module", + "exports": { + ".": { + "import": "./dist/mermaid-flowchart-elk.core.mjs", + "types": "./dist/packages/mermaid-flowchart-elk/src/detector.d.ts" + }, + "./*": "./*" + }, + "keywords": [ + "diagram", + "markdown", + "flowchart", + "elk", + "mermaid" + ], + "scripts": { + "prepublishOnly": "pnpm -w run build" + }, + "repository": { + "type": "git", + "url": "https://github.com/mermaid-js/mermaid" + }, + "author": "Knut Sveidqvist", + "license": "MIT", + "dependencies": { + "d3": "^7.4.0", + "dagre-d3-es": "7.0.10", + "khroma": "^2.0.0", + "elkjs": "^0.8.2" + }, + "devDependencies": { + "concurrently": "^8.0.0", + "rimraf": "^5.0.0", + "mermaid": "workspace:^" + }, + "files": [ + "dist" + ] +} diff --git a/packages/mermaid/src/diagrams/flowchart/elk/detector.spec.ts b/packages/mermaid-flowchart-elk/src/detector.spec.ts similarity index 100% rename from packages/mermaid/src/diagrams/flowchart/elk/detector.spec.ts rename to packages/mermaid-flowchart-elk/src/detector.spec.ts diff --git a/packages/mermaid-flowchart-elk/src/detector.ts b/packages/mermaid-flowchart-elk/src/detector.ts new file mode 100644 index 000000000..52fb355a5 --- /dev/null +++ b/packages/mermaid-flowchart-elk/src/detector.ts @@ -0,0 +1,32 @@ +import type { + ExternalDiagramDefinition, + DiagramDetector, + DiagramLoader, +} from '../../mermaid/src/diagram-api/types.js'; + +const id = 'flowchart-elk'; + +const detector: DiagramDetector = (txt, config): boolean => { + if ( + // If diagram explicitly states flowchart-elk + /^\s*flowchart-elk/.test(txt) || + // If a flowchart/graph diagram has their default renderer set to elk + (/^\s*flowchart|graph/.test(txt) && config?.flowchart?.defaultRenderer === 'elk') + ) { + return true; + } + return false; +}; + +const loader: DiagramLoader = async () => { + const { diagram } = await import('./diagram-definition.js'); + return { id, diagram }; +}; + +const plugin: ExternalDiagramDefinition = { + id, + detector, + loader, +}; + +export default plugin; diff --git a/packages/mermaid-flowchart-elk/src/diagram-definition.ts b/packages/mermaid-flowchart-elk/src/diagram-definition.ts new file mode 100644 index 000000000..a4e678dcc --- /dev/null +++ b/packages/mermaid-flowchart-elk/src/diagram-definition.ts @@ -0,0 +1,12 @@ +// @ts-ignore: JISON typing missing +import parser from '../../mermaid/src/diagrams/flowchart/parser/flow.jison'; +import * as db from '../../mermaid/src/diagrams/flowchart/flowDb.js'; +import styles from '../../mermaid/src/diagrams/flowchart/styles.js'; +import renderer from './flowRenderer-elk.js'; + +export const diagram = { + db, + renderer, + parser, + styles, +}; diff --git a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js b/packages/mermaid-flowchart-elk/src/flowRenderer-elk.js similarity index 97% rename from packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js rename to packages/mermaid-flowchart-elk/src/flowRenderer-elk.js index e45739524..da4f339d5 100644 --- a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js +++ b/packages/mermaid-flowchart-elk/src/flowRenderer-elk.js @@ -1,16 +1,16 @@ import { select, line, curveLinear } from 'd3'; -import { insertNode } from '../../../dagre-wrapper/nodes.js'; -import insertMarkers from '../../../dagre-wrapper/markers.js'; -import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js'; +import { insertNode } from '../../mermaid/src/dagre-wrapper/nodes.js'; +import insertMarkers from '../../mermaid/src/dagre-wrapper/markers.js'; +import { insertEdgeLabel } from '../../mermaid/src/dagre-wrapper/edges.js'; import { findCommonAncestor } from './render-utils.js'; -import { labelHelper } from '../../../dagre-wrapper/shapes/util.js'; -import { getConfig } from '../../../config.js'; -import { log } from '../../../logger.js'; -import { setupGraphViewbox } from '../../../setupGraphViewbox.js'; -import common from '../../common/common.js'; -import { interpolateToCurve, getStylesFromArray } from '../../../utils.js'; +import { labelHelper } from '../../mermaid/src/dagre-wrapper/shapes/util.js'; +import { getConfig } from '../../mermaid/src/config.js'; +import { log } from '../../mermaid/src/logger.js'; +import { setupGraphViewbox } from '../../mermaid/src/setupGraphViewbox.js'; +import common from '../../mermaid/src/diagrams/common/common.js'; +import { interpolateToCurve, getStylesFromArray } from '../../mermaid/src/utils.js'; import ELK from 'elkjs/lib/elk.bundled.js'; -import { getLineFunctionsWithOffset } from '../../../utils/lineWithOffset.js'; +import { getLineFunctionsWithOffset } from '../../mermaid/src/utils/lineWithOffset.js'; const elk = new ELK(); @@ -695,7 +695,7 @@ const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAb * * @param text * @param diagObj - * @returns {Record