Skip to content

Commit 61da059

Browse files
committed
[build] Replace tsc watch with esbuild watch mode for faster development
- Split build scripts: esbuild.extension.cjs for extension, esbuild.webviews.cjs for webviews - Add watch mode support to extension builds using esbuild.context() - Update tasks.json with proper dependency chain (compile → watch-all → parallel watchers) - Skip type-checking during watch mode (VS Code editor handles this) - Add error handling to keep watchers running after build errors - Reduces CPU usage from 80% to ~5-10% during development Signed-off-by: Victor Rubezhny <vrubezhny@redhat.com> Assisted-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent dbcb8e0 commit 61da059

5 files changed

Lines changed: 207 additions & 118 deletions

File tree

.vscode/tasks.json

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,17 @@
88
"type": "npm",
99
"script": "watch:extension",
1010
"isBackground": true,
11-
"problemMatcher": "$tsc-watch"
11+
"problemMatcher": {
12+
"owner": "custom",
13+
"pattern": {
14+
"regexp": "."
15+
},
16+
"background": {
17+
"activeOnStart": true,
18+
"beginsPattern": ".*Building the extension.*",
19+
"endsPattern": ".*Watching the extension\\.\\.\\."
20+
}
21+
}
1222
},
1323
{
1424
"label": "watch:webviews",
@@ -28,13 +38,21 @@
2838
}
2939
},
3040
{
31-
"label": "watch",
41+
"label": "watch-all",
3242
"dependsOn": [
3343
"watch:extension",
3444
"watch:webviews"
3545
],
3646
"dependsOrder": "parallel"
3747
},
48+
{
49+
"label": "watch",
50+
"dependsOn": [
51+
"compile",
52+
"watch-all"
53+
],
54+
"dependsOrder": "sequence"
55+
},
3856
{
3957
"label": "compile",
4058
"type": "npm",

build/esbuild.build.cjs

Lines changed: 3 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -3,97 +3,14 @@
33
* Licensed under the MIT License. See LICENSE file in the project root for license information.
44
*-----------------------------------------------------------------------------------------------*/
55

6-
const { execSync } = require('child_process');
7-
const esbuild = require('esbuild');
8-
const { esmAliasPlugin, esbuildProblemMatcherPlugin, nativeNodeModulesPlugin, svgrPlugin, verbosePlugin } = require('./esbuild.plugins.cjs');
9-
const { webviews, srcDir, outDir } = require('./esbuild.settings.cjs');
10-
const { sassPlugin } = require('esbuild-sass-plugin');
11-
const { cp, mkdir, stat } = require('node:fs/promises');
12-
const path = require('path');
13-
const { sync } = require('fast-glob');
6+
const { buildExtension } = require('./esbuild.extension.cjs');
147
const { buildWebviews } = require('./esbuild.webviews.cjs');
158

16-
const production = process.argv.includes('--production');
17-
189
// eslint-disable no-console
1910

20-
// Run type-checking
21-
22-
/// Verify the extension
23-
try {
24-
// execSync('tsc --noEmit', { stdio: 'inherit' });
25-
execSync('tsc --noEmit -p tsconfig.json', { stdio: 'inherit' });
26-
} catch (err) {
27-
console.error('❌ TypeScript type-checking failed.');
28-
process.exit(1);
29-
}
30-
31-
console.log(`esbuild: building for production: ${production ? 'Yes' : 'No'}`);
32-
33-
const baseConfig = {
34-
bundle: true,
35-
target: 'chrome108',
36-
minify: production,
37-
sourcemap: !production,
38-
logLevel: 'warning',
39-
logOverride: {
40-
'equals-negative-zero': 'silent'
41-
}
42-
};
43-
44-
async function buildExtension() {
45-
console.log(`📦 Building the extension for ${ production ? 'Production' : 'Development'}...`);
46-
47-
if (production) {
48-
// Build the extension.js
49-
const extConfig = {
50-
...baseConfig,
51-
platform: 'node',
52-
format: 'cjs',
53-
entryPoints: [`./${srcDir}/extension.ts`],
54-
outfile: `${outDir}/${srcDir}/extension.js`,
55-
external: [ 'vscode', 'shelljs', 'jsonc-parser' ],
56-
plugins: [
57-
nativeNodeModulesPlugin(),
58-
esbuildProblemMatcherPlugin(production) // this one is to be added to the end of plugins array
59-
]
60-
};
61-
await esbuild.build(extConfig);
62-
console.log('✅ Extension build completed');
63-
} else {
64-
// Build the Extension for development
65-
const srcFiles = sync(`${srcDir}/**/*.{js,ts}`, { absolute: false });
66-
const devExtConfig = {
67-
...baseConfig,
68-
platform: 'node',
69-
format: 'cjs',
70-
entryPoints: srcFiles.map(f => `./${f}`),
71-
outbase: srcDir,
72-
outdir: `${outDir}/${srcDir}`,
73-
external: [ 'vscode', 'shelljs', 'jsonc-parser', '@aws-sdk/client-s3' ],
74-
plugins: [
75-
// verbosePlugin(),
76-
esmAliasPlugin(),
77-
nativeNodeModulesPlugin(),
78-
esbuildProblemMatcherPlugin(production) // this one is to be added to the end of plugins array
79-
]
80-
};
81-
82-
await esbuild.build(devExtConfig);
83-
84-
const jsonFiles = sync('src/**/*.json', { absolute: false });
85-
for (const file of jsonFiles) {
86-
const dest = path.join('out', file);
87-
await cp(file, dest, { recursive: false, force: true });
88-
}
89-
await cp('package.json', 'out/package.json');
90-
console.log('✅ Extension build completed');
91-
}
92-
}
93-
9411
async function buildAll() {
95-
await buildExtension();
96-
await buildWebviews();
12+
await buildExtension();
13+
await buildWebviews();
9714
}
9815

9916
buildAll().catch(err => {

build/esbuild.extension.cjs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*-----------------------------------------------------------------------------------------------
2+
* Copyright (c) Red Hat, Inc. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE file in the project root for license information.
4+
*-----------------------------------------------------------------------------------------------*/
5+
6+
const { execSync } = require('child_process');
7+
const esbuild = require('esbuild');
8+
const { esmAliasPlugin, esbuildProblemMatcherPlugin, nativeNodeModulesPlugin } = require('./esbuild.plugins.cjs');
9+
const { srcDir, outDir } = require('./esbuild.settings.cjs');
10+
const { cp } = require('node:fs/promises');
11+
const path = require('path');
12+
const { sync } = require('fast-glob');
13+
14+
const production = process.argv.includes('--production');
15+
const isWatch = process.argv.includes('--watch');
16+
17+
// eslint-disable no-console
18+
19+
const baseConfig = {
20+
bundle: true,
21+
target: 'chrome108',
22+
minify: production,
23+
sourcemap: !production,
24+
logLevel: 'warning',
25+
logOverride: {
26+
'equals-negative-zero': 'silent'
27+
}
28+
};
29+
30+
async function copyJsonFiles() {
31+
const jsonFiles = sync('src/**/*.json', { absolute: false });
32+
for (const file of jsonFiles) {
33+
const dest = path.join('out', file);
34+
await cp(file, dest, { recursive: false, force: true });
35+
}
36+
await cp('package.json', 'out/package.json');
37+
}
38+
39+
async function buildExtension() {
40+
// Only run type-checking on non-watch builds
41+
if (!isWatch) {
42+
console.log('🔍 Running TypeScript type-checking...');
43+
try {
44+
execSync('tsc --noEmit -p tsconfig.json', { stdio: 'inherit' });
45+
} catch (err) {
46+
console.error('❌ TypeScript type-checking failed.');
47+
process.exit(1);
48+
}
49+
}
50+
51+
console.log(`📦 Building the extension for ${production ? 'Production' : 'Development'}...`);
52+
53+
if (production) {
54+
// Build the extension.js (single bundle for production)
55+
const extConfig = {
56+
...baseConfig,
57+
platform: 'node',
58+
format: 'cjs',
59+
entryPoints: [`./${srcDir}/extension.ts`],
60+
outfile: `${outDir}/${srcDir}/extension.js`,
61+
external: ['vscode', 'shelljs', 'jsonc-parser'],
62+
plugins: [
63+
nativeNodeModulesPlugin(),
64+
esbuildProblemMatcherPlugin(production)
65+
]
66+
};
67+
await esbuild.build(extConfig);
68+
await copyJsonFiles();
69+
console.log('✅ Extension build completed');
70+
} else {
71+
// Build the Extension for development (individual files)
72+
const srcFiles = sync(`${srcDir}/**/*.{js,ts}`, { absolute: false });
73+
const devExtConfig = {
74+
...baseConfig,
75+
platform: 'node',
76+
format: 'cjs',
77+
entryPoints: srcFiles.map(f => `./${f}`),
78+
outbase: srcDir,
79+
outdir: `${outDir}/${srcDir}`,
80+
external: ['vscode', 'shelljs', 'jsonc-parser', '@aws-sdk/client-s3'],
81+
plugins: [
82+
esmAliasPlugin(),
83+
nativeNodeModulesPlugin(),
84+
esbuildProblemMatcherPlugin(production)
85+
]
86+
};
87+
88+
if (isWatch) {
89+
try {
90+
const ctx = await esbuild.context({
91+
...devExtConfig,
92+
plugins: [
93+
...devExtConfig.plugins,
94+
{
95+
name: 'rebuild-hook',
96+
setup(build) {
97+
build.onEnd(result => {
98+
if (result.errors.length === 0) {
99+
console.log('🔁 Extension rebuild succeeded');
100+
copyJsonFiles().catch(err =>
101+
console.error('❌ Failed to copy JSON files after rebuild:', err)
102+
);
103+
} else {
104+
console.error('❌ Extension rebuild errors:', result.errors);
105+
}
106+
});
107+
}
108+
}
109+
]
110+
});
111+
await ctx.watch();
112+
await copyJsonFiles().catch(err => {
113+
console.error('❌ Failed to copy JSON files on initial watch setup:', err);
114+
});
115+
console.log('👀 Watching the extension...');
116+
117+
// Keep the process alive even if there are errors
118+
// esbuild will continue watching and rebuild on changes
119+
await new Promise(() => {}); // Never resolves - keeps watch running
120+
} catch (err) {
121+
console.error('❌ Failed to start extension watcher:', err);
122+
console.error('⚠️ Watch mode failed to start. Please fix errors and restart.');
123+
process.exit(1);
124+
}
125+
} else {
126+
await esbuild.build(devExtConfig);
127+
await copyJsonFiles();
128+
console.log('✅ Extension build completed');
129+
}
130+
}
131+
}
132+
133+
if (require.main === module) {
134+
buildExtension().catch(err => {
135+
console.error('❌ Extension build failed:', err);
136+
process.exit(1);
137+
});
138+
}
139+
140+
module.exports = { buildExtension };

build/esbuild.webviews.cjs

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ const isWatch = process.argv.includes('--watch');
1717

1818
// eslint-disable no-console
1919

20-
// Verify the WebViews
21-
try {
22-
execSync('tsc --noEmit -p ./src/webview/tsconfig.json', { stdio: 'inherit' });
23-
} catch (err) {
24-
console.error('❌ TypeScript type-checking failed.');
25-
process.exit(1);
20+
// Verify the WebViews (skip in watch mode - VS Code editor handles this)
21+
if (!isWatch) {
22+
try {
23+
execSync('tsc --noEmit -p ./src/webview/tsconfig.json', { stdio: 'inherit' });
24+
} catch (err) {
25+
console.error('❌ TypeScript type-checking failed.');
26+
process.exit(1);
27+
}
2628
}
2729

2830
const baseConfig = {
@@ -53,30 +55,42 @@ async function buildWebviews() {
5355
};
5456

5557
if (isWatch) {
56-
const ctx = await esbuild.context({
57-
...devWebViewConfig,
58-
plugins: [
59-
...devWebViewConfig.plugins,
60-
{
61-
name: 'rebuild-hook',
62-
setup(build) {
63-
build.onEnd(result => {
64-
if (result.errors.length === 0) {
65-
console.log('🔁 Rebuild succeeded');
66-
copyHtmlFiles().catch(err =>
67-
console.error('❌ Failed to copy HTML files after rebuild:', err)
68-
);
69-
} else {
70-
console.error('❌ Rebuild errors:', result.errors);
71-
}
72-
});
58+
try {
59+
const ctx = await esbuild.context({
60+
...devWebViewConfig,
61+
plugins: [
62+
...devWebViewConfig.plugins,
63+
{
64+
name: 'rebuild-hook',
65+
setup(build) {
66+
build.onEnd(result => {
67+
if (result.errors.length === 0) {
68+
console.log('🔁 Rebuild succeeded');
69+
copyHtmlFiles().catch(err =>
70+
console.error('❌ Failed to copy HTML files after rebuild:', err)
71+
);
72+
} else {
73+
console.error('❌ Rebuild errors:', result.errors);
74+
}
75+
});
76+
}
7377
}
74-
}
75-
]
76-
});
77-
await ctx.watch();
78-
await copyHtmlFiles();
79-
console.log('👀 Watching the webviews...');
78+
]
79+
});
80+
await ctx.watch();
81+
await copyHtmlFiles().catch(err => {
82+
console.error('❌ Failed to copy HTML files on initial watch setup:', err);
83+
});
84+
console.log('👀 Watching the webviews...');
85+
86+
// Keep the process alive even if there are errors
87+
// esbuild will continue watching and rebuild on changes
88+
await new Promise(() => {}); // Never resolves - keeps watch running
89+
} catch (err) {
90+
console.error('❌ Failed to start webviews watcher:', err);
91+
console.error('⚠️ Watch mode failed to start. Please fix errors and restart.');
92+
process.exit(1);
93+
}
8094
} else {
8195
await esbuild.build(devWebViewConfig);
8296
await copyHtmlFiles();

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"build": "npm run prebuild-esm && node ./build/esbuild.build.cjs && npm run bundle-tools",
5252
"vscode:prepublish": "npm install --ignore-scripts && npm run clean && npm run lint && npm run prebuild-esm && node ./build/esbuild.build.cjs --production && npm run bundle-tools && npm prune --omit=dev",
5353
"prebuild-esm": "node ./build/esbuild.transform-esm.cjs",
54-
"watch:extension": "tsc -p tsconfig.json --watch",
54+
"watch:extension": "node ./build/esbuild.extension.cjs --watch",
5555
"watch:webviews": "node ./build/esbuild.webviews.cjs --watch",
5656
"bundle-tools": "node ./build/bundle-tools.cjs --platform",
5757
"verify-tools": "node ./build/verify-tools.cjs",

0 commit comments

Comments
 (0)