diff --git a/.esbuild/esbuild.cjs b/.esbuild/esbuild.cjs deleted file mode 100644 index ed2069b02..000000000 --- a/.esbuild/esbuild.cjs +++ /dev/null @@ -1,21 +0,0 @@ -const { esmBuild, esmCoreBuild, iifeBuild } = require('./util.cjs'); -const { build } = require('esbuild'); - -const handler = (e) => { - console.error(e); - process.exit(1); -}; -const watch = process.argv.includes('--watch'); -const pkg = 'mermaid'; - -// mermaid.js -build(iifeBuild(pkg, { minify: false, watch })).catch(handler); -// mermaid.esm.mjs -build(esmBuild(pkg, { minify: false, watch })).catch(handler); - -// mermaid.min.js -build(iifeBuild(pkg)).catch(handler); -// mermaid.esm.min.mjs -build(esmBuild(pkg)).catch(handler); -// mermaid.core.mjs (node_modules unbundled) -build(esmCoreBuild(pkg)).catch(handler); diff --git a/.esbuild/serve.cjs b/.esbuild/serve.cjs deleted file mode 100644 index 374ddd8ce..000000000 --- a/.esbuild/serve.cjs +++ /dev/null @@ -1,79 +0,0 @@ -const esbuild = require('esbuild'); -const http = require('http'); -const { iifeBuild, esmBuild } = require('./util.cjs'); -const express = require('express'); - -// 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', - }, - outExtension: { '.js': format === 'iife' ? '.js' : '.esm.mjs' }, - }; -}; - -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; - } - } - // 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 }); - }; -}; - -(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(); - - app.use(express.static('demos')); - app.use(express.static('cypress/platform')); - app.all('/mermaid.js', generateHandler(iifeServer)); - app.all('/mermaid.esm.mjs', generateHandler(esmServer)); - - 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 deleted file mode 100644 index cae261639..000000000 --- a/.esbuild/util.cjs +++ /dev/null @@ -1,108 +0,0 @@ -const { transformJison } = require('./jisonTransformer.cjs'); -const fs = require('fs'); -const { dependencies } = require('../package.json'); - -/** @typedef {import('esbuild').BuildOptions} Options */ - -const packageOptionsMap = { - mermaid: { - globalName: 'mermaid', - entryPoints: ['packages/mermaid/src/mermaid.ts'], - }, -}; - -/** - * @param {string} pkg - Package name. - * @param {Options} override - * @returns {Options} - */ -const buildOptions = (pkg, override = {}) => { - const packageOptions = packageOptionsMap[pkg]; - if (!packageOptions) { - throw new Error(`Unknown package: ${pkg}`); - } - return { - bundle: true, - minify: true, - keepNames: true, - banner: { js: '"use strict";' }, - platform: 'browser', - tsconfig: 'tsconfig.json', - resolveExtensions: ['.ts', '.js', '.mjs', '.json', '.jison'], - external: ['require', 'fs', 'path'], - plugins: [jisonPlugin], - sourcemap: 'external', - outdir: 'dist', - ...packageOptions, - ...override, - }; -}; - -/** - * Build options for mermaid.esm.* build. - * - * For ESM browser use. - * - * @param {string} pkg - Package name. - * @param {Options} override - Override options. - * @returns {Options} ESBuild build options. - */ -exports.esmBuild = (pkg, override = { minify: true }) => { - return buildOptions(pkg, { - format: 'esm', - outExtension: { '.js': `.esm${override.minify ? '.min' : ''}.mjs` }, - ...override, - }); -}; - -/** - * Build options for mermaid.core.* build. - * - * This build does not bundle `./node_modules/`, as it is designed to be used with - * Webpack/ESBuild/Vite to use mermaid inside an app/website. - * - * @param {string} pkg - Package name. - * @param {Options} override - Override options. - * @returns {Options} ESBuild build options. - */ -exports.esmCoreBuild = (pkg, override) => { - return buildOptions(pkg, { - format: 'esm', - outExtension: { '.js': '.core.mjs' }, - external: ['require', 'fs', 'path', ...Object.keys(dependencies)], - platform: 'neutral', - ...override, - }); -}; - -/** - * Build options for mermaid.js build. - * - * For IIFE browser use (where ESM is not yet supported). - * - * @param {string} pkg - Package name. - * @param {Options} override - Override options. - * @returns {Options} ESBuild build options. - */ -exports.iifeBuild = (pkg, override = { minify: true }) => { - return buildOptions(pkg, { - outExtension: { '.js': `${override.minify ? '.min' : ''}.js` }, - format: 'iife', - footer: { - js: 'mermaid = mermaid.default;', - }, - ...override, - }); -}; - -const jisonPlugin = { - name: 'jison', - setup(build) { - build.onLoad({ filter: /\.jison$/ }, async (args) => { - // Load the file from the file system - const source = await fs.promises.readFile(args.path, 'utf8'); - const contents = transformJison(source); - return { contents, warnings: [] }; - }); - }, -}; diff --git a/.gitignore b/.gitignore index 9641d2574..6e4fe723a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,11 @@ token package-lock.json -.vscode/ +# ignore files in /.vscode/ except for launch.json and extensions.json +/.vscode/** +!/.vscode/launch.json +!/.vscode/extensions.json + cypress/platform/current.html cypress/platform/experimental.html local/ diff --git a/.vite/build.ts b/.vite/build.ts new file mode 100644 index 000000000..327be2dcb --- /dev/null +++ b/.vite/build.ts @@ -0,0 +1,81 @@ +import { build, InlineConfig } from 'vite'; +import { resolve } from 'path'; +import { fileURLToPath } from 'url'; +import jisonPlugin from './jisonPlugin.js'; +import pkg from '../package.json' assert { type: 'json' }; +import { OutputOptions } from 'vite/node_modules/rollup'; +const { dependencies } = pkg; +const watch = process.argv.includes('--watch'); +const __dirname = fileURLToPath(new URL('.', import.meta.url)); + +interface BuildOptions { + minify: boolean | 'esbuild'; + core?: boolean; + watch?: boolean; +} + +export const getBuildConfig = ({ minify, core, watch }: BuildOptions): InlineConfig => { + const external = ['require', 'fs', 'path']; + let output: OutputOptions | OutputOptions[] = [ + { + name: 'mermaid', + format: 'esm', + sourcemap: true, + entryFileNames: `[name].esm${minify ? '.min' : ''}.mjs`, + }, + { + name: 'mermaid', + format: 'umd', + sourcemap: true, + entryFileNames: `[name]${minify ? '.min' : ''}.js`, + }, + ]; + + if (core) { + external.push(...Object.keys(dependencies)); + output = { + name: 'mermaid', + format: 'esm', + sourcemap: true, + entryFileNames: `[name].core.mjs`, + }; + } + + const config: InlineConfig = { + configFile: false, + build: { + emptyOutDir: false, + lib: { + entry: resolve(__dirname, '../src/mermaid.ts'), + name: 'mermaid', + // the proper extensions will be added + fileName: 'mermaid', + }, + minify, + rollupOptions: { + external, + output, + }, + }, + resolve: { + extensions: ['.jison', '.js', '.ts', '.json'], + }, + plugins: [jisonPlugin()], + }; + + if (watch && config.build) { + config.build.watch = { + include: 'src/**', + }; + } + + return config; +}; + +if (watch) { + build(getBuildConfig({ minify: false, watch })); +} else { + build(getBuildConfig({ minify: false })); + build(getBuildConfig({ minify: 'esbuild' })); + build(getBuildConfig({ minify: true, core: true })); +} diff --git a/.vite/jisonPlugin.ts b/.vite/jisonPlugin.ts new file mode 100644 index 000000000..c21190784 --- /dev/null +++ b/.vite/jisonPlugin.ts @@ -0,0 +1,17 @@ +import { transformJison } from './jisonTransformer.js'; +const fileRegex = /\.(jison)$/; + +export default function jison() { + return { + name: 'jison', + + transform(src: string, id: string) { + if (fileRegex.test(id)) { + return { + code: transformJison(src), + map: null, // provide source map if available + }; + } + }, + }; +} diff --git a/.esbuild/jisonTransformer.cjs b/.vite/jisonTransformer.ts similarity index 55% rename from .esbuild/jisonTransformer.cjs rename to .vite/jisonTransformer.ts index 5f89c6647..a5734e125 100644 --- a/.esbuild/jisonTransformer.cjs +++ b/.vite/jisonTransformer.ts @@ -1,6 +1,9 @@ -const { Generator } = require('jison'); -exports.transformJison = (src) => { - const parser = new Generator(src, { +// @ts-ignore No typings for jison +import jison from 'jison'; + +export const transformJison = (src: string): string => { + // @ts-ignore No typings for jison + const parser = new jison.Generator(src, { moduleType: 'js', 'token-stack': true, }); diff --git a/.vite/server.ts b/.vite/server.ts new file mode 100644 index 000000000..4cadc07cb --- /dev/null +++ b/.vite/server.ts @@ -0,0 +1,26 @@ +import express from 'express'; +import { createServer as createViteServer } from 'vite'; +// import { getBuildConfig } from './build'; + +async function createServer() { + const app = express(); + + // Create Vite server in middleware mode + const vite = await createViteServer({ + configFile: './vite.config.ts', + server: { middlewareMode: true }, + appType: 'custom', // don't include Vite's default HTML handling middlewares + }); + + app.use(vite.middlewares); + app.use(express.static('dist')); + app.use(express.static('demos')); + app.use(express.static('cypress/platform')); + + app.listen(9000, () => { + console.log(`Listening on http://localhost:9000`); + }); +} + +// build(getBuildConfig({ minify: false, watch: true })); +createServer(); diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..9633bed66 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "zixuanchen.vitest-explorer", + "luniclynx.bison" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..92df7056e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug Current Test File", + "autoAttachChildProcesses": true, + "skipFiles": ["/**", "**/node_modules/**"], + "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", + "args": ["run", "${relativeFile}"], + "smartStep": true, + "console": "integratedTerminal" + } + ] +} diff --git a/cypress/platform/bundle-test.js b/cypress/platform/bundle-test.js index 4b1f446a1..a991918c4 100644 --- a/cypress/platform/bundle-test.js +++ b/cypress/platform/bundle-test.js @@ -1,4 +1,4 @@ -import mermaid from '../../packages/mermaid/dist/mermaid.core'; +import mermaid from '../../packages/mermaid/src/mermaid'; let code = `flowchart LR Power_Supply --> Transmitter_A diff --git a/cypress/platform/e2e.html b/cypress/platform/e2e.html index a23911334..c686afc88 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 2d3661a4e..39b985ccc 100644 --- a/cypress/platform/xss.html +++ b/cypress/platform/xss.html @@ -1,6 +1,6 @@ - +