Skip to content

Commit a2fc072

Browse files
authored
GLSP-1693: Switch bundling to esbuild (#142)
* GLSP-1693: Switch bundling to esbuild Part of: eclipse-glsp/glsp#1693 * Update esbuild to ~0.28.0 in workflow example packages Address review feedback on outdated esbuild versions.
1 parent bd32db3 commit a2fc072

13 files changed

Lines changed: 435 additions & 601 deletions

File tree

.vscode/launch.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,6 @@
7777
"${workspaceRoot}/examples/workflow-server/*/lib/**/*.js",
7878
"${workspaceRoot}/packages/**/*/lib/**/*.js"
7979
],
80-
"sourceMapPathOverrides": {
81-
"webpack://@eclipse-glsp-examples/workflow-server/(.+)": "${workspaceFolder}/examples/workflow-server/$1"
82-
},
8380
"smartStep": true,
8481
"internalConsoleOptions": "openOnSessionStart",
8582
"outputCapture": "std"
@@ -100,9 +97,6 @@
10097
"${workspaceRoot}/examples/workflow-server/*/lib/**/*.js",
10198
"${workspaceRoot}/packages/**/*/lib/**/*.js"
10299
],
103-
"sourceMapPathOverrides": {
104-
"webpack://@eclipse-glsp-examples/workflow-server/(.+)": "${workspaceFolder}/examples/workflow-server/$1"
105-
},
106100
"smartStep": true,
107101
"internalConsoleOptions": "openOnSessionStart",
108102
"outputCapture": "std"

.vscode/tasks.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@
2525
},
2626
"problemMatcher": ["$tsc-watch"]
2727
},
28+
{
29+
"label": "Start Example GLSP server (dev, hot reload)",
30+
"type": "shell",
31+
"group": "none",
32+
"command": "yarn dev",
33+
"presentation": {
34+
"reveal": "always",
35+
"panel": "dedicated"
36+
},
37+
"problemMatcher": []
38+
},
2839
{
2940
"label": "Execute all glsp-server tests",
3041
"type": "shell",

CLAUDE.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ Eclipse GLSP Server Node monorepo. Provides the TypeScript-based server framewor
1010
- **Build**: `yarn` from root installs and compiles everything
1111
- **Compile**: `yarn compile` (runs `tsc -b` with project references)
1212
- **Clean**: `yarn clean`
13-
- **Watch**: `yarn watch` (parallel TypeScript + bundle watch)
14-
- **Start example server**: `yarn start` (TCP on 5007) or `yarn start:websocket` (WebSocket on 8081)
13+
- **Watch**: `yarn watch` (parallel TypeScript + esbuild bundle watch)
14+
- **Bundle**: `yarn bundle` (esbuild bundles the workflow example, node + webworker targets)
15+
- **Start example server**: `yarn start` (TCP on 5007) or `yarn start:websocket` (WebSocket on 8081) — runs the pre-built bundle
16+
- **Dev server (hot reload)**: `yarn dev` (TCP on 5007) or `yarn dev:ws` (WebSocket on 8081) — esbuild watches `src` and restarts the server on change
1517

1618
## Validation
1719

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
# Populated by `yarn start` (verbatim assets + synced worker bundle + webpack output)
1+
# Populated by `yarn start` (verbatim assets + synced worker bundle + esbuild output)
22
dist/
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/********************************************************************************
2+
* Copyright (c) 2026 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
// @ts-check
17+
const { resolve } = require('node:path');
18+
const esbuild = require('esbuild');
19+
20+
const watch = process.argv.slice(2).includes('--watch');
21+
22+
/**
23+
* Reports the build progress and surfaces errors/warnings in a format that
24+
* VS Code's `$esbuild-watch` problem matcher can pick up.
25+
* @type {import('esbuild').Plugin}
26+
*/
27+
const esbuildProblemMatcherPlugin = {
28+
name: 'esbuild-problem-matcher',
29+
setup(build) {
30+
build.onStart(() => {
31+
console.log(`${watch ? '[watch] ' : ''}build started`);
32+
});
33+
build.onEnd(result => {
34+
result.errors.forEach(({ text, location }) => {
35+
console.error(`✘ [ERROR] ${text}`);
36+
if (location) {
37+
console.error(` ${location.file}:${location.line}:${location.column}:`);
38+
}
39+
});
40+
console.log(`${watch ? '[watch] ' : ''}build finished`);
41+
});
42+
}
43+
};
44+
45+
// Bundle `vscode-jsonrpc/browser` plus the page-side script into a single file emitted into
46+
// `dist/`, alongside the verbatim assets copied from `public/` and the worker bundle synced
47+
// from `@eclipse-glsp-examples/workflow-server-bundled-web` by `scripts/prepare-dist.mjs`.
48+
/** @type {import('esbuild').BuildOptions} */
49+
const config = {
50+
entryPoints: [resolve(__dirname, 'src/index.js')],
51+
outfile: resolve(__dirname, 'dist/index.bundle.js'),
52+
bundle: true,
53+
sourcemap: true,
54+
platform: 'browser',
55+
format: 'iife',
56+
target: 'es2019',
57+
mainFields: ['browser', 'module', 'main'],
58+
conditions: ['browser'],
59+
logLevel: 'silent',
60+
plugins: [esbuildProblemMatcherPlugin]
61+
};
62+
63+
async function main() {
64+
if (watch) {
65+
const ctx = await esbuild.context(config);
66+
await ctx.watch();
67+
} else {
68+
await esbuild.build(config);
69+
}
70+
}
71+
72+
main().catch(e => {
73+
console.error(e);
74+
process.exit(1);
75+
});

examples/workflow-server-mcp-demo/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"name": "Eclipse GLSP"
1515
},
1616
"scripts": {
17-
"build": "webpack",
17+
"build": "node esbuild.js",
1818
"clean": "rimraf dist",
1919
"prestart": "node ./scripts/prepare-dist.mjs && yarn build",
2020
"start": "npx -y serve -l 8000 ./dist"
@@ -23,8 +23,7 @@
2323
"@eclipse-glsp-examples/workflow-server-bundled-web": "2.8.0-next"
2424
},
2525
"devDependencies": {
26-
"vscode-jsonrpc": "8.2.0",
27-
"webpack": "^5.75.0",
28-
"webpack-cli": "^5.0.1"
26+
"esbuild": "~0.28.0",
27+
"vscode-jsonrpc": "8.2.0"
2928
}
3029
}

examples/workflow-server-mcp-demo/scripts/prepare-dist.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
1515
********************************************************************************/
1616

17-
// Populates `dist/` with everything `serve` needs that webpack doesn't emit:
17+
// Populates `dist/` with everything `serve` needs that esbuild doesn't emit:
1818
// - verbatim files from `public/` (index.html, mcp-service-worker.js)
1919
// - the worker bundle from `@eclipse-glsp-examples/workflow-server-bundled-web`
20-
// Webpack writes `index.bundle.js` into the same dir as a separate step.
20+
// esbuild writes `index.bundle.js` into the same dir as a separate step.
2121

2222
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
2323
import { createRequire } from 'node:module';

examples/workflow-server-mcp-demo/webpack.config.js

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/********************************************************************************
2+
* Copyright (c) 2022-2026 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
// @ts-check
17+
const { spawn } = require('node:child_process');
18+
const { resolve } = require('node:path');
19+
const esbuild = require('esbuild');
20+
21+
const argv = process.argv.slice(2);
22+
const watch = argv.includes('--watch');
23+
// Dev mode: build only the node target and (re)start the server after each successful build.
24+
const runNode = argv.includes('--run-node');
25+
// Everything after `--` is forwarded verbatim to the spawned node server (e.g. `--port 5007`).
26+
const dashDash = argv.indexOf('--');
27+
const serverArgs = dashDash >= 0 ? argv.slice(dashDash + 1) : [];
28+
29+
const bundledDir = resolve(__dirname, '..', 'workflow-server-bundled');
30+
const bundledWebDir = resolve(__dirname, '..', 'workflow-server-bundled-web');
31+
32+
/**
33+
* Reports the build progress and surfaces errors/warnings in a format that
34+
* VS Code's `$esbuild-watch` problem matcher can pick up.
35+
* @type {import('esbuild').Plugin}
36+
*/
37+
const esbuildProblemMatcherPlugin = {
38+
name: 'esbuild-problem-matcher',
39+
setup(build) {
40+
build.onStart(() => {
41+
console.log(`${watch ? '[watch] ' : ''}build started`);
42+
});
43+
build.onEnd(result => {
44+
result.errors.forEach(({ text, location }) => {
45+
console.error(`✘ [ERROR] ${text}`);
46+
if (location) {
47+
console.error(` ${location.file}:${location.line}:${location.column}:`);
48+
}
49+
});
50+
console.log(`${watch ? '[watch] ' : ''}build finished`);
51+
});
52+
}
53+
};
54+
55+
/** @type {import('esbuild').BuildOptions} */
56+
const common = {
57+
bundle: true,
58+
// Minify one-shot production builds; keep watch/dev output readable and fast to rebuild.
59+
// `sourcemap` still maps the (minified) output back to the original TypeScript sources.
60+
minify: !watch,
61+
// Preserve original class/function names through minification — GLSP's logger and error
62+
// messages use `constructor.name` for labels, which would otherwise be mangled.
63+
keepNames: true,
64+
sourcemap: true,
65+
logLevel: 'silent',
66+
tsconfig: resolve(__dirname, 'tsconfig.json'),
67+
plugins: [esbuildProblemMatcherPlugin]
68+
};
69+
70+
/** @type {import('esbuild').BuildOptions} */
71+
const nodeConfig = {
72+
...common,
73+
entryPoints: [resolve(__dirname, 'src/node/app.ts')],
74+
outfile: resolve(bundledDir, 'wf-glsp-server-node.js'),
75+
platform: 'node',
76+
format: 'cjs',
77+
target: 'node22',
78+
// `ws` requires these optional native deps in a try/catch; keep them external so the
79+
// graceful-fallback path works and esbuild does not error on the missing modules.
80+
external: ['bufferutil', 'utf-8-validate']
81+
};
82+
83+
/** @type {import('esbuild').BuildOptions} */
84+
const webworkerConfig = {
85+
...common,
86+
entryPoints: [resolve(__dirname, 'src/browser/app.ts')],
87+
outfile: resolve(bundledWebDir, 'wf-glsp-server-webworker.js'),
88+
platform: 'browser',
89+
format: 'iife',
90+
target: 'es2019',
91+
// Honor the legacy `browser` package field + browser condition for transitive deps.
92+
mainFields: ['browser', 'module', 'main'],
93+
conditions: ['browser']
94+
};
95+
96+
/** Restart the bundled node server after every successful build. */
97+
function runNodePlugin() {
98+
/** @type {import('node:child_process').ChildProcess | undefined} */
99+
let child;
100+
const stop = () => child?.kill('SIGTERM');
101+
process.on('SIGINT', () => {
102+
stop();
103+
process.exit(0);
104+
});
105+
return {
106+
name: 'run-node-server',
107+
setup(build) {
108+
build.onEnd(result => {
109+
if (result.errors.length > 0) {
110+
// Keep the last good server running so a broken build does not crash the loop.
111+
return;
112+
}
113+
stop();
114+
child = spawn('node', ['--enable-source-maps', nodeConfig.outfile, ...serverArgs], { stdio: 'inherit' });
115+
});
116+
}
117+
};
118+
}
119+
120+
async function run(config) {
121+
if (watch) {
122+
const ctx = await esbuild.context(config);
123+
await ctx.watch();
124+
} else {
125+
await esbuild.build(config);
126+
}
127+
}
128+
129+
async function main() {
130+
if (runNode) {
131+
await run({ ...nodeConfig, plugins: [esbuildProblemMatcherPlugin, runNodePlugin()] });
132+
} else {
133+
await Promise.all([run(nodeConfig), run(webworkerConfig)]);
134+
}
135+
}
136+
137+
main().catch(e => {
138+
console.error(e);
139+
process.exit(1);
140+
});

examples/workflow-server/package.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@
4646
"browser.js"
4747
],
4848
"scripts": {
49-
"build": "tsc -b && yarn bundle && yarn bundle:browser",
50-
"bundle": "webpack",
51-
"bundle:browser": "webpack --env target=webworker ",
49+
"build": "tsc -b && yarn bundle",
50+
"bundle": "node esbuild.js",
5251
"clean": "rimraf lib *.tsbuildinfo",
52+
"dev": "node esbuild.js --watch --run-node -- --port 5007",
53+
"dev:ws": "node esbuild.js --watch --run-node -- -w --port 8081",
5354
"generate:index": "glsp generateIndex src/browser src/common src/node -s -f",
5455
"lint": "eslint ./src",
5556
"watch": "tsc -w",
56-
"watch:bundle": "webpack -w"
57+
"watch:bundle": "node esbuild.js --watch"
5758
},
5859
"dependencies": {
5960
"@eclipse-glsp/layout-elk": "2.8.0-next",
@@ -62,9 +63,7 @@
6263
"inversify": "^6.1.3"
6364
},
6465
"devDependencies": {
65-
"source-map-loader": "^4.0.1",
66-
"webpack": "^5.75.0",
67-
"webpack-cli": "^5.0.1"
66+
"esbuild": "~0.28.0"
6867
},
6968
"publishConfig": {
7069
"access": "public"

0 commit comments

Comments
 (0)