diff --git a/.esbuild/esbuild.cjs b/.esbuild/esbuild.cjs deleted file mode 100644 index 393dadcf31..0000000000 --- a/.esbuild/esbuild.cjs +++ /dev/null @@ -1,20 +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'); - -// mermaid.js -build(iifeBuild({ minify: false, watch })).catch(handler); -// mermaid.esm.mjs -build(esmBuild({ minify: false, watch })).catch(handler); - -// mermaid.min.js -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/serve.cjs b/.esbuild/serve.cjs deleted file mode 100644 index 4c06572f3e..0000000000 --- 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 4dbcd01c37..0000000000 --- a/.esbuild/util.cjs +++ /dev/null @@ -1,94 +0,0 @@ -const { transformJison } = require('./jisonTransformer.cjs'); -const fs = require('fs'); -const { dependencies } = require('../package.json'); - -/** @typedef {import('esbuild').BuildOptions} Options */ - -/** - * @param {Options} override - * @returns {Options} - */ -const buildOptions = (override = {}) => { - return { - bundle: true, - minify: true, - keepNames: true, - banner: { js: '"use strict";' }, - globalName: 'mermaid', - platform: 'browser', - tsconfig: 'tsconfig.json', - resolveExtensions: ['.ts', '.js', '.mjs', '.json', '.jison'], - external: ['require', 'fs', 'path'], - entryPoints: ['src/mermaid.ts'], - outfile: 'dist/mermaid.min.js', - plugins: [jisonPlugin], - sourcemap: 'external', - ...override, - }; -}; - -/** - * Build options for mermaid.esm.* build. - * - * For ESM browser use. - * - * @param {Options} override - Override options. - * @returns {Options} ESBuild build options. - */ -exports.esmBuild = (override = { minify: true }) => { - return buildOptions({ - format: 'esm', - outfile: `dist/mermaid.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 {Options} override - Override options. - * @returns {Options} ESBuild build options. - */ -exports.esmCoreBuild = (override) => { - return buildOptions({ - format: 'esm', - outfile: `dist/mermaid.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 {Options} override - Override options. - * @returns {Options} ESBuild build options. - */ -exports.iifeBuild = (override = { minify: true }) => { - return buildOptions({ - outfile: `dist/mermaid${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/.vite/build.ts b/.vite/build.ts new file mode 100644 index 0000000000..c4df49ea24 --- /dev/null +++ b/.vite/build.ts @@ -0,0 +1,89 @@ +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' }; +type OutputOptions = Exclude< + Exclude['rollupOptions'], + undefined +>['output']; + +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 = [ + { + 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) { + // Core build is used to generate file without bundled dependencies. + // This is used by downstream projects to bundle dependencies themselves. + external.push(...Object.keys(dependencies)); + // This needs to be an array. Otherwise vite will build esm & umd with same name and overwrite esm with umd. + output = [ + { + 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: false, core: true })); +} diff --git a/.vite/jisonPlugin.ts b/.vite/jisonPlugin.ts new file mode 100644 index 0000000000..c211907841 --- /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 5f89c6647b..a5734e1259 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 0000000000..4cadc07cb7 --- /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/.vite/tsconfig.json b/.vite/tsconfig.json new file mode 100644 index 0000000000..915436da1e --- /dev/null +++ b/.vite/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "module": "ES2022" + } +} diff --git a/cypress/helpers/util.js b/cypress/helpers/util.js index d138dfcfef..bee9a59f0b 100644 --- a/cypress/helpers/util.js +++ b/cypress/helpers/util.js @@ -1,4 +1,6 @@ -import { Base64 } from 'js-base64'; +const utf8ToB64 = (str) => { + return window.btoa(unescape(encodeURIComponent(str))); +}; export const mermaidUrl = (graphStr, options, api) => { const obj = { @@ -6,7 +8,7 @@ export const mermaidUrl = (graphStr, options, api) => { mermaid: options, }; const objStr = JSON.stringify(obj); - let url = 'http://localhost:9000/e2e.html?graph=' + Base64.encodeURI(objStr); + let url = 'http://localhost:9000/e2e.html?graph=' + utf8ToB64(objStr); if (api) { url = 'http://localhost:9000/xss.html?graph=' + graphStr; } diff --git a/cypress/integration/other/xss.spec.js b/cypress/integration/other/xss.spec.js index 4911dd8df5..76b2c47f2c 100644 --- a/cypress/integration/other/xss.spec.js +++ b/cypress/integration/other/xss.spec.js @@ -15,7 +15,7 @@ describe('XSS', () => { it('should not allow tags in the css', () => { const str = - 'eyJjb2RlIjoiJSV7aW5pdDogeyAnZm9udEZhbWlseSc6ICdcXFwiPjwvc3R5bGU-PGltZyBzcmM9eCBvbmVycm9yPXhzc0F0dGFjaygpPid9IH0lJVxuZ3JhcGggTFJcbiAgICAgQSAtLT4gQiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0IiwiZmxvd2NoYXJ0Ijp7Imh0bWxMYWJlbHMiOmZhbHNlfX0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9'; + 'eyJjb2RlIjoiJSV7aW5pdDogeyAnZm9udEZhbWlseSc6ICdcXFwiPjwvc3R5bGU+PGltZyBzcmM9eCBvbmVycm9yPXhzc0F0dGFjaygpPid9IH0lJVxuZ3JhcGggTFJcbiAgICAgQSAtLT4gQiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0IiwiZmxvd2NoYXJ0Ijp7Imh0bWxMYWJlbHMiOmZhbHNlfX0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9'; const url = mermaidUrl( str, diff --git a/cypress/platform/bundle-test.js b/cypress/platform/bundle-test.js index 373f8741a0..074dd8c635 100644 --- a/cypress/platform/bundle-test.js +++ b/cypress/platform/bundle-test.js @@ -1,4 +1,4 @@ -import mermaid from '../../dist/mermaid.core'; +import mermaid from '../../src/mermaid'; let code = `flowchart LR Power_Supply --> Transmitter_A diff --git a/cypress/platform/e2e.html b/cypress/platform/e2e.html index a239113345..c686afc886 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 2d3661a4e8..39b985ccc1 100644 --- a/cypress/platform/xss.html +++ b/cypress/platform/xss.html @@ -1,6 +1,6 @@ - +