3.5 KB125 lines
Blame
1/* eslint-disable no-console */
2import chokidar from 'chokidar';
3import cors from 'cors';
4import { context } from 'esbuild';
5import type { Request, Response } from 'express';
6import express from 'express';
7import { packageOptions } from '../.build/common.js';
8import { generateLangium } from '../.build/generateLangium.js';
9import { defaultOptions, getBuildConfig } from './util.js';
10import 'dotenv/config';
11
12const configs = Object.values(packageOptions).map(({ packageName }) =>
13 getBuildConfig({
14 ...defaultOptions,
15 minify: false,
16 core: false,
17 options: packageOptions[packageName],
18 })
19);
20const mermaidIIFEConfig = getBuildConfig({
21 ...defaultOptions,
22 minify: false,
23 core: false,
24 options: packageOptions.mermaid,
25 format: 'iife',
26});
27configs.push(mermaidIIFEConfig);
28
29const contexts = await Promise.all(
30 configs.map(async (config) => ({ config, context: await context(config) }))
31);
32
33let rebuildCounter = 1;
34const rebuildAll = async () => {
35 const buildNumber = rebuildCounter++;
36 const timeLabel = `Rebuild ${buildNumber} Time (total)`;
37 console.time(timeLabel);
38 await Promise.all(
39 contexts.map(async ({ config, context }) => {
40 const buildVariant = `Rebuild ${buildNumber} Time (${Object.keys(config.entryPoints!)[0]} ${config.format})`;
41 console.time(buildVariant);
42 await context.rebuild();
43 console.timeEnd(buildVariant);
44 })
45 ).catch((e) => console.error(e));
46 console.timeEnd(timeLabel);
47};
48
49let clients: { id: number; response: Response }[] = [];
50function eventsHandler(request: Request, response: Response) {
51 const headers = {
52 'Content-Type': 'text/event-stream',
53 Connection: 'keep-alive',
54 'Cache-Control': 'no-cache',
55 };
56 response.writeHead(200, headers);
57 const clientId = Date.now();
58 clients.push({
59 id: clientId,
60 response,
61 });
62 request.on('close', () => {
63 clients = clients.filter((client) => client.id !== clientId);
64 });
65}
66
67let timeoutID: NodeJS.Timeout | undefined = undefined;
68
69/**
70 * Debounce file change events to avoid rebuilding multiple times.
71 */
72function handleFileChange() {
73 if (timeoutID !== undefined) {
74 clearTimeout(timeoutID);
75 }
76 // eslint-disable-next-line @typescript-eslint/no-misused-promises
77 timeoutID = setTimeout(async () => {
78 await rebuildAll();
79 sendEventsToAll();
80 timeoutID = undefined;
81 }, 100);
82}
83
84function sendEventsToAll() {
85 clients.forEach(({ response }) => response.write(`data: ${Date.now()}\n\n`));
86}
87
88async function createServer() {
89 await generateLangium();
90 handleFileChange();
91 const app = express();
92 const port = process.env.MERMAID_PORT ?? 9000;
93 chokidar
94 .watch('**/src/**/*.{js,ts,langium,yaml,json}', {
95 ignoreInitial: true,
96 ignored: [/node_modules/, /dist/, /docs/, /coverage/],
97 })
98 // eslint-disable-next-line @typescript-eslint/no-misused-promises
99 .on('all', async (event, path) => {
100 // Ignore other events.
101 if (!['add', 'change'].includes(event)) {
102 return;
103 }
104 console.log(`${path} changed. Rebuilding...`);
105 if (path.endsWith('.langium')) {
106 await generateLangium();
107 }
108 handleFileChange();
109 });
110
111 app.use(cors());
112 app.get('/events', eventsHandler);
113 for (const { packageName } of Object.values(packageOptions)) {
114 app.use(express.static(`./packages/${packageName}/dist`));
115 }
116 app.use(express.static('demos'));
117 app.use(express.static('cypress/platform'));
118
119 app.listen(port, () => {
120 console.log(`Listening on http://localhost:${port}`);
121 });
122}
123
124void createServer();
125