diff --git a/.esbuild/esbuild.cjs b/.esbuild/esbuild.cjs index e38951bdb..393dadcf3 100644 --- a/.esbuild/esbuild.cjs +++ b/.esbuild/esbuild.cjs @@ -13,8 +13,8 @@ build(iifeBuild({ minify: false, watch })).catch(handler); build(esmBuild({ minify: false, watch })).catch(handler); // mermaid.min.js -build(esmBuild()).catch(handler); -// mermaid.esm.min.mjs build(iifeBuild()).catch(handler); +// mermaid.esm.min.mjs +build(esmBuild()).catch(handler); // mermaid.core.mjs (node_modules unbundled) build(esmCoreBuild()).catch(handler); diff --git a/.esbuild/jisonTransformer.cjs b/.esbuild/jisonTransformer.cjs new file mode 100644 index 000000000..5f89c6647 --- /dev/null +++ b/.esbuild/jisonTransformer.cjs @@ -0,0 +1,14 @@ +const { Generator } = require('jison'); +exports.transformJison = (src) => { + const parser = new Generator(src, { + moduleType: 'js', + 'token-stack': true, + }); + const source = parser.generate({ moduleMain: '() => {}' }); + const exporter = ` + parser.parser = parser; + export { parser }; + export default parser; + `; + return `${source} ${exporter}`; +}; diff --git a/.esbuild/serve.cjs b/.esbuild/serve.cjs index c54ff1e9f..4c06572f3 100644 --- a/.esbuild/serve.cjs +++ b/.esbuild/serve.cjs @@ -1,53 +1,79 @@ const esbuild = require('esbuild'); const http = require('http'); -const path = require('path'); -const { iifeBuild } = require('./util.cjs'); +const { iifeBuild, esmBuild } = require('./util.cjs'); +const express = require('express'); -// Start esbuild's server on a random local port -esbuild - .serve( - { - servedir: path.join(__dirname, '..'), +// Start 2 esbuild servers. One for IIFE and one for ESM +// Serve 2 static directories: demo & cypress/platform +// Have 3 entry points: +// mermaid: './src/mermaid', +// e2e: './cypress/platform/viewer.js', +// 'bundle-test': './cypress/platform/bundle-test.js', + +const getEntryPointsAndExtensions = (format) => { + return { + entryPoints: { + mermaid: './src/mermaid', + e2e: 'cypress/platform/viewer.js', + 'bundle-test': 'cypress/platform/bundle-test.js', }, - iifeBuild({ minify: false }) - ) - .then((result) => { - // The result tells us where esbuild's local server is - const { host, port } = result; + outExtension: { '.js': format === 'iife' ? '.js' : '.esm.mjs' }, + }; +}; - // Then start a proxy server on port 3000 - http - .createServer((req, res) => { - if (req.url.includes('mermaid.js')) { - req.url = '/dist/mermaid.js'; +const generateHandler = (server) => { + return (req, res) => { + const options = { + hostname: server.host, + port: server.port, + path: req.url, + method: req.method, + headers: req.headers, + }; + // Forward each incoming request to esbuild + const proxyReq = http.request(options, (proxyRes) => { + // If esbuild returns "not found", send a custom 404 page + if (proxyRes.statusCode === 404) { + if (!req.url.endsWith('.html')) { + res.writeHead(404, { 'Content-Type': 'text/html' }); + res.end('

A custom 404 page

'); + return; } - const options = { - hostname: host, - port: port, - path: req.url, - method: req.method, - headers: req.headers, - }; + } + // Otherwise, forward the response from esbuild to the client + res.writeHead(proxyRes.statusCode, proxyRes.headers); + proxyRes.pipe(res, { end: true }); + }); + // Forward the body of the request to esbuild + req.pipe(proxyReq, { end: true }); + }; +}; - // Forward each incoming request to esbuild - const proxyReq = http.request(options, (proxyRes) => { - // If esbuild returns "not found", send a custom 404 page - console.error('pp', req.url); - if (proxyRes.statusCode === 404) { - if (!req.url.endsWith('.html')) { - res.writeHead(404, { 'Content-Type': 'text/html' }); - res.end('

A custom 404 page

'); - return; - } - } +(async () => { + const iifeServer = await esbuild.serve( + {}, + { + ...iifeBuild({ minify: false, outfile: undefined, outdir: 'dist' }), + ...getEntryPointsAndExtensions('iife'), + } + ); + const esmServer = await esbuild.serve( + {}, + { + ...esmBuild({ minify: false, outfile: undefined, outdir: 'dist' }), + ...getEntryPointsAndExtensions('esm'), + } + ); + const app = express(); - // Otherwise, forward the response from esbuild to the client - res.writeHead(proxyRes.statusCode, proxyRes.headers); - proxyRes.pipe(res, { end: true }); - }); + app.use(express.static('demos')); + app.use(express.static('cypress/platform')); + app.all('/mermaid.js', generateHandler(iifeServer)); + app.all('/mermaid.esm.mjs', generateHandler(esmServer)); - // Forward the body of the request to esbuild - req.pipe(proxyReq, { end: true }); - }) - .listen(3000); + app.all('/e2e.esm.mjs', generateHandler(esmServer)); + app.all('/bundle-test.esm.mjs', generateHandler(esmServer)); + app.listen(9000, () => { + console.log(`Listening on http://localhost:9000`); }); +})(); diff --git a/.esbuild/util.cjs b/.esbuild/util.cjs index 7c2d37a50..4dbcd01c3 100644 --- a/.esbuild/util.cjs +++ b/.esbuild/util.cjs @@ -1,4 +1,4 @@ -const { transformJison } = require('./jisonTransformer'); +const { transformJison } = require('./jisonTransformer.cjs'); const fs = require('fs'); const { dependencies } = require('../package.json'); @@ -17,21 +17,16 @@ const buildOptions = (override = {}) => { globalName: 'mermaid', platform: 'browser', tsconfig: 'tsconfig.json', - resolveExtensions: ['.ts', '.js', '.json', '.jison'], + resolveExtensions: ['.ts', '.js', '.mjs', '.json', '.jison'], external: ['require', 'fs', 'path'], - outdir: 'dist', + entryPoints: ['src/mermaid.ts'], + outfile: 'dist/mermaid.min.js', plugins: [jisonPlugin], sourcemap: 'external', ...override, }; }; -const getOutFiles = (extension) => { - return { - [`mermaid${extension}`]: 'src/mermaid.ts', - [`diagramAPI${extension}`]: 'src/diagram-api/diagramAPI.ts', - }; -}; /** * Build options for mermaid.esm.* build. * @@ -43,8 +38,7 @@ const getOutFiles = (extension) => { exports.esmBuild = (override = { minify: true }) => { return buildOptions({ format: 'esm', - entryPoints: getOutFiles(`.esm${override.minify ? '.min' : ''}`), - outExtension: { '.js': '.mjs' }, + outfile: `dist/mermaid.esm${override.minify ? '.min' : ''}.mjs`, ...override, }); }; @@ -61,8 +55,7 @@ exports.esmBuild = (override = { minify: true }) => { exports.esmCoreBuild = (override) => { return buildOptions({ format: 'esm', - entryPoints: getOutFiles(`.core`), - outExtension: { '.js': '.mjs' }, + outfile: `dist/mermaid.core.mjs`, external: ['require', 'fs', 'path', ...Object.keys(dependencies)], platform: 'neutral', ...override, @@ -79,8 +72,11 @@ exports.esmCoreBuild = (override) => { */ exports.iifeBuild = (override = { minify: true }) => { return buildOptions({ - entryPoints: getOutFiles(override.minify ? '.min' : ''), + outfile: `dist/mermaid${override.minify ? '.min' : ''}.js`, format: 'iife', + footer: { + js: 'mermaid = mermaid.default;', + }, ...override, }); }; diff --git a/.eslintignore b/.eslintignore index 60c278861..e1957aef9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,4 @@ dist/** docs/Setup.md cypress.config.js cypress/plugins/index.js +coverage \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e567aba89..44e2f4cb1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -40,18 +40,3 @@ jobs: - name: Verify Docs run: yarn docs:verify - - - name: Check no `console.log()` in .jison files - # ESLint can't parse .jison files directly - # In the future, it might be worth making a `eslint-plugin-jison`, so - # that this will be built into the `yarn lint` command. - run: | - shopt -s globstar - mkdir -p tmp/ - for jison_file in src/**/*.jison; do - outfile="tmp/$(basename -- "$jison_file" .jison)-jison.js" - echo "Converting $jison_file to $outfile" - # default module-type (CJS) always adds a console.log() - yarn jison "$jison_file" --outfile "$outfile" --module-type "amd" - done - yarn eslint --no-eslintrc --rule no-console:error --parser "@babel/eslint-parser" "./tmp/*-jison.js" diff --git a/.lintstagedrc.json b/.lintstagedrc.json index b88abda4b..1e7c61dd8 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,5 +1,6 @@ { "src/docs/**": ["yarn docs:build --git"], "src/docs.mts": ["yarn docs:build --git"], - "*.{ts,js,json,html,md,mts}": ["eslint --fix", "prettier --write"] + "*.{ts,js,json,html,md,mts}": ["eslint --fix", "prettier --write"], + "*.jison": ["yarn lint:jison"] } diff --git a/.prettierignore b/.prettierignore index 2ce673fe9..877e20f81 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ dist cypress/platform/xss3.html -.cache \ No newline at end of file +.cache +coverage \ No newline at end of file diff --git a/.webpack/loaders/jison.js b/.webpack/loaders/jison.js deleted file mode 100644 index 0d5ebc7e5..000000000 --- a/.webpack/loaders/jison.js +++ /dev/null @@ -1,25 +0,0 @@ -const { Generator } = require('jison'); -const validate = require('schema-utils'); - -const schema = { - title: 'Jison Parser options', - type: 'object', - properties: { - 'token-stack': { - type: 'boolean', - }, - debug: { - type: 'boolean', - }, - }, - additionalProperties: false, -}; - -module.exports = function jisonLoader(source) { - const options = this.getOptions(); - (validate.validate || validate)(schema, options, { - name: 'Jison Loader', - baseDataPath: 'options', - }); - return new Generator(source, options).generate(); -}; diff --git a/.webpack/webpack.config.babel.js b/.webpack/webpack.config.babel.js deleted file mode 100644 index 15760b19b..000000000 --- a/.webpack/webpack.config.babel.js +++ /dev/null @@ -1,46 +0,0 @@ -import { merge, mergeWithCustomize, customizeObject } from 'webpack-merge'; -import nodeExternals from 'webpack-node-externals'; -import baseConfig from './webpack.config.base'; - -export default (_env, args) => { - return [ - // non-minified - merge(baseConfig, { - optimization: { - minimize: false, - }, - }), - // core [To be used by webpack/esbuild/vite etc to bundle mermaid] - merge(baseConfig, { - externals: [nodeExternals()], - output: { - filename: '[name].core.js', - }, - optimization: { - minimize: false, - }, - }), - // umd - merge(baseConfig, { - output: { - filename: '[name].min.js', - }, - }), - // esm - mergeWithCustomize({ - customizeObject: customizeObject({ - 'output.library': 'replace', - }), - })(baseConfig, { - experiments: { - outputModule: true, - }, - output: { - library: { - type: 'module', - }, - filename: '[name].esm.min.mjs', - }, - }), - ]; -}; diff --git a/.webpack/webpack.config.base.js b/.webpack/webpack.config.base.js deleted file mode 100644 index 3ebd1863d..000000000 --- a/.webpack/webpack.config.base.js +++ /dev/null @@ -1,71 +0,0 @@ -import path from 'path'; -const esbuild = require('esbuild'); -const { ESBuildMinifyPlugin } = require('esbuild-loader'); -export const resolveRoot = (...relativePath) => path.resolve(__dirname, '..', ...relativePath); - -export default { - amd: false, // https://github.com/lodash/lodash/issues/3052 - target: 'web', - entry: { - mermaid: './src/mermaid', - }, - resolve: { - extensions: ['.wasm', '.mjs', '.js', '.ts', '.json', '.jison'], - fallback: { - fs: false, // jison generated code requires 'fs' - path: require.resolve('path-browserify'), - }, - }, - output: { - path: resolveRoot('./dist'), - filename: '[name].js', - library: { - name: 'mermaid', - type: 'umd', - export: 'default', - }, - globalObject: 'typeof self !== "undefined" ? self : this', - }, - module: { - rules: [ - { - test: /\.ts$/, - use: 'ts-loader', - exclude: /node_modules/, - }, - { - test: /\.js$/, - include: [resolveRoot('./src'), resolveRoot('./node_modules/dagre-d3-renderer/lib')], - use: { - loader: 'esbuild-loader', - options: { - implementation: esbuild, - target: 'es2015', - }, - }, - }, - { - // load scss to string - test: /\.scss$/, - use: ['css-to-string-loader', 'css-loader', 'sass-loader'], - }, - { - test: /\.jison$/, - use: { - loader: path.resolve(__dirname, './loaders/jison.js'), - options: { - 'token-stack': true, - }, - }, - }, - ], - }, - devtool: 'source-map', - optimization: { - minimizer: [ - new ESBuildMinifyPlugin({ - target: 'es2015', - }), - ], - }, -}; diff --git a/.webpack/webpack.config.e2e.babel.js b/.webpack/webpack.config.e2e.babel.js deleted file mode 100644 index 7f26b8aed..000000000 --- a/.webpack/webpack.config.e2e.babel.js +++ /dev/null @@ -1,26 +0,0 @@ -import baseConfig, { resolveRoot } from './webpack.config.base'; -import { merge } from 'webpack-merge'; - -export default merge(baseConfig, { - mode: 'development', - entry: { - mermaid: './src/mermaid', - e2e: './cypress/platform/viewer.js', - 'bundle-test': './cypress/platform/bundle-test.js', - }, - output: { - globalObject: 'window', - }, - devServer: { - compress: true, - port: 9000, - static: [ - { directory: resolveRoot('cypress', 'platform') }, - { directory: resolveRoot('dist') }, - { directory: resolveRoot('demos') }, - ], - }, - externals: { - mermaid: 'mermaid', - }, -}); diff --git a/__mocks__/MERMAID.js b/__mocks__/MERMAID.js deleted file mode 100644 index 58e31037a..000000000 --- a/__mocks__/MERMAID.js +++ /dev/null @@ -1,3 +0,0 @@ -export const curveBasis = 'basis'; -export const curveLinear = 'linear'; -export const curveCardinal = 'cardinal'; diff --git a/__mocks__/d3.js b/__mocks__/d3.ts similarity index 82% rename from __mocks__/d3.js rename to __mocks__/d3.ts index 268aadf24..67f09b6f4 100644 --- a/__mocks__/d3.js +++ b/__mocks__/d3.ts @@ -1,4 +1,7 @@ -let NewD3 = function () { +// @ts-nocheck TODO: Fix TS +import { vi } from 'vitest'; + +const NewD3 = function () { /** * */ @@ -56,9 +59,9 @@ export const MockD3 = (name, parent) => { children.push(mockElem); return mockElem; }; - elem.lower = jest.fn(() => elem); - elem.attr = jest.fn(() => elem); - elem.text = jest.fn(() => elem); - elem.style = jest.fn(() => elem); + elem.lower = vi.fn(() => elem); + elem.attr = vi.fn(() => elem); + elem.text = vi.fn(() => elem); + elem.style = vi.fn(() => elem); return elem; }; diff --git a/__mocks__/dagre-d3.ts b/__mocks__/dagre-d3.ts new file mode 100644 index 000000000..a1a677591 --- /dev/null +++ b/__mocks__/dagre-d3.ts @@ -0,0 +1,3 @@ +import { vi } from 'vitest'; + +// export const render = vi.fn(); diff --git a/__mocks__/entity-decode/browser.ts b/__mocks__/entity-decode/browser.ts new file mode 100644 index 000000000..bd82d79fd --- /dev/null +++ b/__mocks__/entity-decode/browser.ts @@ -0,0 +1,3 @@ +module.exports = function (txt: string) { + return txt; +}; diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 1779657a2..000000000 --- a/babel.config.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: 'defaults, ie >= 11, current node', - }, - ], - ], -}; diff --git a/cypress.config.js b/cypress.config.cjs similarity index 100% rename from cypress.config.js rename to cypress.config.cjs diff --git a/cypress/platform/e2e.html b/cypress/platform/e2e.html index 021e2ad58..a23911334 100644 --- a/cypress/platform/e2e.html +++ b/cypress/platform/e2e.html @@ -2,7 +2,7 @@ - + - + + diff --git a/cypress/platform/xss.html b/cypress/platform/xss.html index 4c0fc91ea..2d3661a4e 100644 --- a/cypress/platform/xss.html +++ b/cypress/platform/xss.html @@ -1,6 +1,6 @@ - +