Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

esbuild for v11 #4109

Closed
wants to merge 49 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9763c03
ESM only
sidharthv96 Feb 19, 2023
ab8fb60
Merge branch 'release/10.0.0' into sidv/esbuildV10
sidharthv96 Feb 19, 2023
1f5e676
Split builds
sidharthv96 Feb 19, 2023
f48e48d
Merge branch 'release/10.0.0' into sidv/esbuildV10
sidharthv96 Feb 19, 2023
a0d9069
feat: replace vite with esbuild
sidharthv96 Feb 19, 2023
dd2cace
Merge branch 'release/10.0.0' into sidv/esbuildV10
sidharthv96 Feb 19, 2023
e095115
Cleanup
sidharthv96 Feb 19, 2023
ac91b9d
Cleanup whitespaces
sidharthv96 Feb 19, 2023
2239996
Cleanup whitespaces
sidharthv96 Feb 19, 2023
fa5459d
Build Visualization
sidharthv96 Feb 19, 2023
a88789f
Build Visualization
sidharthv96 Feb 19, 2023
284ef24
fix viewer.js
sidharthv96 Feb 19, 2023
192aac3
Fix external diagram test
sidharthv96 Feb 19, 2023
220a9dc
Merge branch 'release/10.0.0' into sidv/esbuildV10
sidharthv96 Feb 19, 2023
ed04d73
remove WebpackUsage test
sidharthv96 Feb 19, 2023
6cc34dc
fix xss test
sidharthv96 Feb 19, 2023
63f4c5e
10.0.0-rc.4
sidharthv96 Feb 19, 2023
908479a
Merge branch 'release/10.0.0' into sidv/esbuildV10
sidharthv96 Feb 19, 2023
986162d
Merge branch 'release/10.0.0' into sidv/esbuildV10
sidharthv96 Feb 19, 2023
0f5e66e
Merge branch 'release/10.0.0' into sidv/esbuildV10
sidharthv96 Feb 19, 2023
f2d7e69
Merge branch 'develop' into sidv/esbuildV10
sidharthv96 Feb 24, 2023
ce5a1e7
Fix filenames
sidharthv96 Feb 24, 2023
91e0b22
fix: Concurrent rendering issue
sidharthv96 Feb 24, 2023
665ef41
Merge branch 'sidv/fixRunAsync' into sidv/esbuildV10
sidharthv96 Feb 24, 2023
fe83a78
Merge branch 'sidv/fixRunAsync' into sidv/esbuildV10
sidharthv96 Feb 24, 2023
32c45dd
Merge branch 'develop' into sidv/esbuildV10
sidharthv96 Mar 4, 2023
26877c3
Merge branch 'develop' into sidv/esbuildV10
sidharthv96 Aug 11, 2023
18ce8ea
Add zenuml
sidharthv96 Aug 11, 2023
df3de3d
Fix tests
sidharthv96 Aug 11, 2023
150d459
Fix zenuml
sidharthv96 Aug 11, 2023
edfc393
chore: Refactor build to simplify vite and esbuild
sidharthv96 Aug 11, 2023
26001d6
chore: Refactor build to simplify vite and esbuild
sidharthv96 Aug 11, 2023
92378b5
Fix package.json
sidharthv96 Aug 11, 2023
871b4ba
Cleanup package.json
sidharthv96 Aug 11, 2023
32abb54
Add vite server back
sidharthv96 Aug 11, 2023
ac1ff75
feat: Add IIFE
sidharthv96 Aug 12, 2023
9eba560
Add error log with breaking change
sidharthv96 Aug 12, 2023
698395e
Update version
sidharthv96 Aug 12, 2023
0644fdf
Match any function name in error
sidharthv96 Aug 12, 2023
d70de89
Update docs
sidharthv96 Aug 12, 2023
91658a0
Fix iife test
sidharthv96 Aug 12, 2023
d9faa69
Merge branch 'sidv/esbuildV10' of https://github.com/mermaid-js/merma…
sidharthv96 Aug 12, 2023
dce65c6
Merge branch 'next' into sidv/esbuildV10
sidharthv96 Aug 12, 2023
978d017
Update .build/common.ts
sidharthv96 Aug 12, 2023
d8b91db
chore: Remove `mermaid.default`
sidharthv96 Aug 12, 2023
1e652e7
Merge branch 'sidv/esbuildV10' of https://github.com/mermaid-js/merma…
sidharthv96 Aug 12, 2023
a39122b
v11.0.0-alpha.2
sidharthv96 Aug 12, 2023
f640b71
chore(live-reload): Minor cleanup
sidharthv96 Aug 13, 2023
3989691
chore: Add clean to build:esbuild
sidharthv96 Aug 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .build/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Shared common options for both ESBuild and Vite
*/
export const packageOptions = {
sidharthv96 marked this conversation as resolved.
Show resolved Hide resolved
mermaid: {
name: 'mermaid',
packageName: 'mermaid',
file: 'mermaid.ts',
},
'mermaid-example-diagram': {
name: 'mermaid-example-diagram',
packageName: 'mermaid-example-diagram',
file: 'detector.ts',
},
'mermaid-zenuml': {
name: 'mermaid-zenuml',
packageName: 'mermaid-zenuml',
file: 'detector.ts',
},
} as const;
File renamed without changes.
122 changes: 122 additions & 0 deletions .build/jsonSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { load, JSON_SCHEMA } from 'js-yaml';
import assert from 'node:assert';
import Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';

import type { MermaidConfig, BaseDiagramConfig } from '../packages/mermaid/src/config.type.js';

/**
* All of the keys in the mermaid config that have a mermaid diagram config.
*/
const MERMAID_CONFIG_DIAGRAM_KEYS = [
'flowchart',
'sequence',
'gantt',
'journey',
'class',
'state',
'er',
'pie',
'quadrantChart',
'requirement',
'mindmap',
'timeline',
'gitGraph',
'c4',
'sankey',
] as const;

/**
* Generate default values from the JSON Schema.
*
* AJV does not support nested default values yet (or default values with $ref),
* so we need to manually find them (this may be fixed in ajv v9).
*
* @param mermaidConfigSchema - The Mermaid JSON Schema to use.
* @returns The default mermaid config object.
*/
export function generateDefaults(mermaidConfigSchema: JSONSchemaType<MermaidConfig>) {
const ajv = new Ajv2019({
useDefaults: true,
allowUnionTypes: true,
strict: true,
});

ajv.addKeyword({
keyword: 'meta:enum', // used by jsonschema2md
errors: false,
});
ajv.addKeyword({
keyword: 'tsType', // used by json-schema-to-typescript
errors: false,
});

// ajv currently doesn't support nested default values, see https://github.com/ajv-validator/ajv/issues/1718
// (may be fixed in v9) so we need to manually use sub-schemas
const mermaidDefaultConfig = {};

assert.ok(mermaidConfigSchema.$defs);
const baseDiagramConfig = mermaidConfigSchema.$defs.BaseDiagramConfig;

for (const key of MERMAID_CONFIG_DIAGRAM_KEYS) {
const subSchemaRef = mermaidConfigSchema.properties[key].$ref;
const [root, defs, defName] = subSchemaRef.split('/');
assert.strictEqual(root, '#');
assert.strictEqual(defs, '$defs');
const subSchema = {
$schema: mermaidConfigSchema.$schema,
$defs: mermaidConfigSchema.$defs,
...mermaidConfigSchema.$defs[defName],
} as JSONSchemaType<BaseDiagramConfig>;

const validate = ajv.compile(subSchema);

mermaidDefaultConfig[key] = {};

for (const required of subSchema.required ?? []) {
if (subSchema.properties[required] === undefined && baseDiagramConfig.properties[required]) {
mermaidDefaultConfig[key][required] = baseDiagramConfig.properties[required].default;
}
}
if (!validate(mermaidDefaultConfig[key])) {
throw new Error(
`schema for subconfig ${key} does not have valid defaults! Errors were ${JSON.stringify(
validate.errors,
undefined,
2
)}`
);
}
}

const validate = ajv.compile(mermaidConfigSchema);

if (!validate(mermaidDefaultConfig)) {
throw new Error(
`Mermaid config JSON Schema does not have valid defaults! Errors were ${JSON.stringify(
validate.errors,
undefined,
2
)}`
);
}

return mermaidDefaultConfig;
}

export const loadSchema = (src: string, filename: string): JSONSchemaType<MermaidConfig> => {
const jsonSchema = load(src, {
filename,
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
schema: JSON_SCHEMA,
}) as JSONSchemaType<MermaidConfig>;
return jsonSchema;
};

export const getDefaults = (schema: JSONSchemaType<MermaidConfig>) => {
return `export default ${JSON.stringify(generateDefaults(schema), undefined, 2)};`;
};

export const getSchema = (schema: JSONSchemaType<MermaidConfig>) => {
return `export default ${JSON.stringify(schema, undefined, 2)};`;
};
34 changes: 34 additions & 0 deletions .esbuild/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { build } from 'esbuild';
import { mkdir, writeFile } from 'node:fs/promises';
import { getBuildConfig } from './util.js';
import { packageOptions } from '../.build/common.js';

const shouldVisualize = process.argv.includes('--visualize');

const buildPackage = async (entryName: keyof typeof packageOptions) => {
await build(getBuildConfig({ entryName, minify: false }));
const { metafile } = await build(
getBuildConfig({ entryName, minify: true, metafile: shouldVisualize })
);
if (metafile) {
// Upload metafile into https://esbuild.github.io/analyze/
await writeFile(`stats/meta-${entryName}.json`, JSON.stringify(metafile));
}
await build(getBuildConfig({ entryName, minify: false, core: true }));
await build(getBuildConfig({ entryName, minify: true, format: 'iife' }));
};

const handler = (e) => {
console.error(e);
process.exit(1);
};

const main = async () => {
await mkdir('stats').catch(() => {});
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
for (const pkg of packageNames) {
await buildPackage(pkg).catch(handler);
}
};

void main();
15 changes: 15 additions & 0 deletions .esbuild/jisonPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { readFile } from 'node:fs/promises';
import { transformJison } from '../.build/jisonTransformer.js';
import { Plugin } from 'esbuild';

export const jisonPlugin: Plugin = {
name: 'jison',
setup(build) {
build.onLoad({ filter: /\.jison$/ }, async (args) => {
// Load the file from the file system
const source = await readFile(args.path, 'utf8');
const contents = transformJison(source);
return { contents, warnings: [] };
});
},
};
35 changes: 35 additions & 0 deletions .esbuild/jsonSchemaPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { JSONSchemaType } from 'ajv/dist/2019.js';
import type { MermaidConfig } from '../packages/mermaid/src/config.type.js';
import { readFile } from 'node:fs/promises';
import { getDefaults, getSchema, loadSchema } from '../.build/jsonSchema.js';

/**
* ESBuild plugin that handles JSON Schemas saved as a `.schema.yaml` file.
*
* Use `my-example.schema.yaml?only-defaults=true` to only load the default values.
*/

export const jsonSchemaPlugin = {
name: 'json-schema-plugin',
setup(build) {
let schema: JSONSchemaType<MermaidConfig> | undefined = undefined;
let content = '';

build.onLoad({ filter: /config\.schema\.yaml$/ }, async (args) => {
// Load the file from the file system
const source = await readFile(args.path, 'utf8');
const resolvedSchema: JSONSchemaType<MermaidConfig> =
content === source && schema ? schema : loadSchema(source, args.path);
if (content !== source) {
content = source;
schema = resolvedSchema;
}
const contents = args.suffix.includes('only-defaults')
? getDefaults(resolvedSchema)
: getSchema(resolvedSchema);
return { contents, warnings: [] };
});
},
};

export default jsonSchemaPlugin;
38 changes: 38 additions & 0 deletions .esbuild/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import express from 'express';
import cors from 'cors';
import { getBuildConfig } from './util.js';
import { context } from 'esbuild';

async function createServer() {
const app = express();
const mermaidCtx = await context(
getBuildConfig({ minify: false, core: false, entryName: 'mermaid' })
);
const mermaidIIFECtx = await context(
getBuildConfig({ minify: false, core: false, entryName: 'mermaid', format: 'iife' })
);
const externalCtx = await context(
getBuildConfig({ minify: false, core: false, entryName: 'mermaid-example-diagram' })
);
const zenuml = await context(
getBuildConfig({ minify: false, core: false, entryName: 'mermaid-zenuml' })
);

mermaidCtx.watch();
mermaidIIFECtx.watch();
externalCtx.watch();
zenuml.watch();

app.use(cors());
app.use(express.static('./packages/mermaid/dist'));
app.use(express.static('./packages/mermaid-zenuml/dist'));
app.use(express.static('./packages/mermaid-example-diagram/dist'));
app.use(express.static('demos'));
app.use(express.static('cypress/platform'));

app.listen(9000, () => {
console.log(`Listening on http://localhost:9000`);
});
}

createServer();
81 changes: 81 additions & 0 deletions .esbuild/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { resolve } from 'path';
import { fileURLToPath } from 'url';
import type { BuildOptions } from 'esbuild';
import { readFileSync } from 'fs';
import jsonSchemaPlugin from './jsonSchemaPlugin.js';
import { packageOptions } from '../.build/common.js';
import { jisonPlugin } from './jisonPlugin.js';

const __dirname = fileURLToPath(new URL('.', import.meta.url));

interface MermaidBuildOptions {
minify: boolean;
core?: boolean;
metafile?: boolean;
format?: 'esm' | 'iife';
entryName: keyof typeof packageOptions;
}

const buildOptions = (override: BuildOptions): BuildOptions => {
return {
bundle: true,
minify: true,
keepNames: true,
platform: 'browser',
tsconfig: 'tsconfig.json',
resolveExtensions: ['.ts', '.js', '.json', '.jison', '.yaml'],
external: ['require', 'fs', 'path'],
outdir: 'dist',
plugins: [jisonPlugin, jsonSchemaPlugin],
sourcemap: 'external',
...override,
};
};

export const getBuildConfig = ({
minify,
core,
entryName,
metafile,
format,
}: MermaidBuildOptions): BuildOptions => {
const external: string[] = ['require', 'fs', 'path'];
const { name, file, packageName } = packageOptions[entryName];
let output: BuildOptions = buildOptions({
absWorkingDir: resolve(__dirname, `../packages/${packageName}`),
entryPoints: {
[`${name}${core ? '.core' : format === 'iife' ? '' : '.esm'}${
minify ? '.min' : ''
}`]: `src/${file}`,
},
metafile,
logLevel: 'info',
});

if (core) {
const { dependencies } = JSON.parse(
readFileSync(resolve(__dirname, `../packages/${packageName}/package.json`), 'utf-8')
);
// Core build is used to generate file without bundled dependencies.
// This is used by downstream projects to bundle dependencies themselves.
// Ignore dependencies and any dependencies of dependencies
external.push(...Object.keys(dependencies));
output.external = external;
}

if (format === 'iife') {
output.format = 'iife';
output.splitting = false;
output.globalName = '__esbuild_esm_mermaid';
output.footer = {
js: 'globalThis.mermaid = globalThis.__esbuild_esm_mermaid.default;',
};
output.outExtension = { '.js': '.js' };
} else {
output.format = 'esm';
output.splitting = true;
output.outExtension = { '.js': '.mjs' };
}

return output;
};
Loading