Refactor build process: remove unused entry points and update resource patterns for desktop build

This commit is contained in:
Johannes
2026-02-09 18:00:57 +01:00
parent 450ee67c02
commit 6d6e9a4f3e
3 changed files with 283 additions and 18 deletions

View File

@@ -0,0 +1,207 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const fs = require('fs');
const path = require('path');
function walk(dir, base) {
let results = [];
try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const e of entries) {
const rel = path.join(base, e.name);
if (e.isDirectory()) {
results = results.concat(walk(path.join(dir, e.name), rel));
} else {
results.push(rel);
}
}
} catch (err) { /* skip */ }
return results;
}
const oldRoot = '/Users/jrieken/Code/vscode/Visual Studio Code - Insiders-OLD.app/Contents/Resources/app';
const newRoot = '/Users/jrieken/Code/vscode/Visual Studio Code - Insiders-NEW.app/Contents/Resources/app';
const oldFiles = new Set(walk(oldRoot, ''));
const newFiles = new Set(walk(newRoot, ''));
const onlyOld = [...oldFiles].filter(f => !newFiles.has(f)).sort();
const onlyNew = [...newFiles].filter(f => !oldFiles.has(f)).sort();
// Group by top-level directory
function groupByTopDir(files) {
const groups = {};
for (const f of files) {
const parts = f.split(path.sep);
const topDir = parts.length > 1 ? parts[0] : '(root)';
if (!groups[topDir]) { groups[topDir] = []; }
groups[topDir].push(f);
}
return groups;
}
console.log('OLD total files:', oldFiles.size);
console.log('NEW total files:', newFiles.size);
console.log('');
console.log('============================================');
console.log('FILES ONLY IN OLD (missing from NEW):', onlyOld.length);
console.log('============================================');
const oldGroups = groupByTopDir(onlyOld);
for (const [dir, files] of Object.entries(oldGroups).sort()) {
console.log(`\n [${dir}] (${files.length} files)`);
for (const f of files.slice(0, 50)) {
console.log(` - ${f}`);
}
if (files.length > 50) { console.log(` ... and ${files.length - 50} more`); }
}
console.log('');
console.log('============================================');
console.log('FILES ONLY IN NEW (extra in NEW):', onlyNew.length);
console.log('============================================');
const newGroups = groupByTopDir(onlyNew);
for (const [dir, files] of Object.entries(newGroups).sort()) {
console.log(`\n [${dir}] (${files.length} files)`);
for (const f of files.slice(0, 50)) {
console.log(` - ${f}`);
}
if (files.length > 50) { console.log(` ... and ${files.length - 50} more`); }
}
// Check JS files with significant size differences
console.log('');
console.log('============================================');
console.log('.js files with >2x size difference');
console.log('============================================');
const common = [...oldFiles].filter(f => newFiles.has(f) && f.endsWith('.js'));
const sizeDiffs = [];
for (const f of common) {
try {
const oldSize = fs.statSync(path.join(oldRoot, f)).size;
const newSize = fs.statSync(path.join(newRoot, f)).size;
if (oldSize === 0 || newSize === 0) { continue; }
const ratio = newSize / oldSize;
if (ratio < 0.5 || ratio > 2.0) {
sizeDiffs.push({ file: f, oldSize, newSize, ratio });
}
} catch (e) { /* skip */ }
}
sizeDiffs.sort((a, b) => a.ratio - b.ratio);
if (sizeDiffs.length === 0) {
console.log(' None found.');
} else {
for (const d of sizeDiffs) {
const arrow = d.ratio < 1 ? 'SMALLER' : 'LARGER';
console.log(` ${d.file}: OLD=${d.oldSize} NEW=${d.newSize} (${d.ratio.toFixed(2)}x, ${arrow})`);
}
}
// Check for empty JS files in NEW that aren't empty in OLD
console.log('');
console.log('============================================');
console.log('.js files that became empty in NEW');
console.log('============================================');
let emptyCount = 0;
for (const f of common) {
if (!f.endsWith('.js')) { continue; }
try {
const oldSize = fs.statSync(path.join(oldRoot, f)).size;
const newSize = fs.statSync(path.join(newRoot, f)).size;
if (oldSize > 0 && newSize === 0) {
console.log(` ${f}: was ${oldSize} bytes, now 0`);
emptyCount++;
}
} catch (e) { /* skip */ }
}
if (emptyCount === 0) { console.log(' None found.'); }
// Size comparison by top-level directory within out/
console.log('');
console.log('============================================');
console.log('Size comparison by area (out/vs/*)');
console.log('============================================');
function dirSize(dir) {
let total = 0;
try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const e of entries) {
const full = path.join(dir, e.name);
if (e.isDirectory()) {
total += dirSize(full);
} else {
total += fs.statSync(full).size;
}
}
} catch (e) { /* skip */ }
return total;
}
const oldOut = path.join(oldRoot, 'out');
const newOut = path.join(newRoot, 'out');
// Top-level out/ size
const oldTotal = dirSize(oldOut);
const newTotal = dirSize(newOut);
console.log(` TOTAL out/: OLD=${(oldTotal / 1024 / 1024).toFixed(1)}MB NEW=${(newTotal / 1024 / 1024).toFixed(1)}MB diff=+${((newTotal - oldTotal) / 1024 / 1024).toFixed(1)}MB`);
const areas = ['vs/base', 'vs/code', 'vs/editor', 'vs/platform', 'vs/workbench'];
for (const area of areas) {
const oldSize = dirSize(path.join(oldOut, area));
const newSize = dirSize(path.join(newOut, area));
const diff = newSize - oldSize;
const sign = diff >= 0 ? '+' : '';
console.log(` ${area}: OLD=${(oldSize / 1024 / 1024).toFixed(1)}MB NEW=${(newSize / 1024 / 1024).toFixed(1)}MB diff=${sign}${(diff / 1024 / 1024).toFixed(1)}MB`);
}
// Detailed breakdown of extra files in NEW
console.log('');
console.log('============================================');
console.log('EXTRA FILES IN NEW - DETAILED BREAKDOWN');
console.log('============================================');
const extra = [...newFiles].filter(f => !oldFiles.has(f)).sort();
const byExt = {};
for (const f of extra) {
const ext = path.extname(f) || '(no ext)';
if (!byExt[ext]) { byExt[ext] = []; }
byExt[ext].push(f);
}
console.log('\nBy extension:');
for (const [ext, files] of Object.entries(byExt).sort((a, b) => b[1].length - a[1].length)) {
console.log(` ${ext}: ${files.length} files`);
}
// List all JS files
console.log('\n--- Extra .js files ---');
(byExt['.js'] || []).forEach(f => console.log(` ${f}`));
// List all HTML files
console.log('\n--- Extra .html files ---');
(byExt['.html'] || []).forEach(f => console.log(` ${f}`));
// List other non-CSS files
console.log('\n--- Extra non-CSS/non-JS/non-HTML files ---');
for (const f of extra) {
const ext = path.extname(f);
if (ext !== '.css' && ext !== '.js' && ext !== '.html') {
console.log(` ${f}`);
}
}
// List CSS files grouped by area
console.log('\n--- Extra .css files by area ---');
const cssFiles = byExt['.css'] || [];
const cssAreas = {};
for (const f of cssFiles) {
const parts = f.split(path.sep);
const area = parts.length > 3 ? parts.slice(0, 3).join('/') : parts.slice(0, 2).join('/');
if (!cssAreas[area]) { cssAreas[area] = []; }
cssAreas[area].push(f);
}
for (const [area, files] of Object.entries(cssAreas).sort()) {
console.log(` [${area}] (${files.length} files)`);
}

View File

@@ -134,7 +134,6 @@ function getEntryPointsForTarget(target: BuildTarget): string[] {
...desktopWorkerEntryPoints,
...desktopEntryPoints,
...codeEntryPoints,
...webEntryPoints, // Desktop also includes web for remote development
...keyboardMapEntryPoints,
];
case 'server':
@@ -184,9 +183,7 @@ function getCssBundleEntryPointsForTarget(target: BuildTarget): Set<string> {
case 'desktop':
return new Set([
'vs/workbench/workbench.desktop.main',
'vs/workbench/workbench.web.main.internal',
'vs/code/electron-browser/workbench/workbench',
'vs/code/browser/workbench/workbench',
]);
case 'server':
return new Set(); // Server has no UI
@@ -230,9 +227,6 @@ const desktopResourcePatterns = [
// HTML
'vs/code/electron-browser/workbench/workbench.html',
'vs/code/electron-browser/workbench/workbench-dev.html',
'vs/code/browser/workbench/workbench.html',
'vs/code/browser/workbench/workbench-dev.html',
'vs/code/browser/workbench/callback.html',
'vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html',
'vs/workbench/contrib/webview/browser/pre/*.html',
@@ -241,11 +235,11 @@ const desktopResourcePatterns = [
// Shell scripts
'vs/base/node/*.sh',
'vs/workbench/contrib/terminal/common/scripts/**/*.sh',
'vs/workbench/contrib/terminal/common/scripts/**/*.ps1',
'vs/workbench/contrib/terminal/common/scripts/**/*.psm1',
'vs/workbench/contrib/terminal/common/scripts/**/*.fish',
'vs/workbench/contrib/terminal/common/scripts/**/*.zsh',
'vs/workbench/contrib/terminal/common/scripts/*.sh',
'vs/workbench/contrib/terminal/common/scripts/*.ps1',
'vs/workbench/contrib/terminal/common/scripts/*.psm1',
'vs/workbench/contrib/terminal/common/scripts/*.fish',
'vs/workbench/contrib/terminal/common/scripts/*.zsh',
'vs/workbench/contrib/externalTerminal/**/*.scpt',
// Media - audio
@@ -254,8 +248,7 @@ const desktopResourcePatterns = [
// Media - images
'vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.svg',
'vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.png',
'vs/workbench/contrib/extensions/browser/media/*.svg',
'vs/workbench/contrib/extensions/browser/media/*.png',
'vs/workbench/contrib/extensions/browser/media/{theme-icon.png,language-icon.svg}',
'vs/workbench/services/extensionManagement/common/media/*.svg',
'vs/workbench/services/extensionManagement/common/media/*.png',
'vs/workbench/browser/parts/editor/media/*.png',
@@ -460,6 +453,7 @@ async function copyFile(srcPath: string, destPath: string): Promise<void> {
const desktopStandaloneFiles = [
'vs/base/parts/sandbox/electron-browser/preload.ts',
'vs/base/parts/sandbox/electron-browser/preload-aux.ts',
'vs/platform/browserView/electron-browser/preload-browserView.ts',
];
async function compileStandaloneFiles(outDir: string, doMinify: boolean, target: BuildTarget): Promise<void> {
@@ -558,11 +552,15 @@ async function copyResources(outDir: string, target: BuildTarget, excludeDevFile
}
}
// Copy CSS files
// Copy CSS files (only for development/transpile builds, not production bundles
// where CSS is already bundled into combined files like workbench.desktop.main.css)
if (!excludeDevFiles) {
const cssCount = await copyCssFiles(outDir, excludeTests);
copied += cssCount;
console.log(`[resources] Copied ${copied} files (${cssCount} CSS)`);
} else {
console.log(`[resources] Copied ${copied} files (CSS skipped - bundled)`);
}
}
// ============================================================================

View File

@@ -108,6 +108,66 @@ npm run gulp vscode-reh-web-darwin-arm64-min
---
## Build Comparison: OLD (gulp-tsb) vs NEW (esbuild) — Desktop Build
### Summary
| Metric | OLD | NEW | Delta |
|--------|-----|-----|-------|
| Total files in `out/` | 3993 | 4301 | +309 extra, 1 missing |
| Total size of `out/` | 25.8 MB | 64.6 MB | +38.8 MB (2.5×) |
| `workbench.desktop.main.js` | 13.0 MB | 15.5 MB | +2.5 MB |
### 1 Missing File (in OLD, not in NEW)
| File | Why Missing | Fix |
|------|-------------|-----|
| `out/vs/platform/browserView/electron-browser/preload-browserView.js` | Not listed in `desktopStandaloneFiles` in index.ts. Only `preload.ts` and `preload-aux.ts` are compiled as standalone files. | **Add** `'vs/platform/browserView/electron-browser/preload-browserView.ts'` to the `desktopStandaloneFiles` array in `index.ts`. |
### 309 Extra Files (in NEW, not in OLD) — Breakdown
| Category | Count | Explanation |
|----------|-------|-------------|
| **CSS files** | 291 | `copyCssFiles()` copies ALL `.css` from `src/` to the output. The old bundler inlines CSS into the main `.css` bundle (e.g., `workbench.desktop.main.css`) and never ships individual CSS files. These individual files ARE needed at runtime because the new ESM system uses `import './foo.css'` resolved by an import map. |
| **Vendor JS files** | 3 | `dompurify.js`, `marked.js`, `semver.js` — listed in `commonResourcePatterns`. The old bundler inlines these into the main bundle. The new system keeps them as separate files because they're plain JS (not TS). They're needed. |
| **Web workbench bundle** | 1 | `vs/code/browser/workbench/workbench.js` (15.4 MB). This is the web workbench entry point bundle. It should NOT be in a desktop build — the old build explicitly excludes `out-build/vs/code/browser/**`. The `desktopResourcePatterns` in index.ts includes `vs/code/browser/workbench/*.html` and `callback.html` which is correct, but the actual bundle gets written by the esbuild desktop bundle step because the desktop entry points include web entry points. |
| **Web workbench internal** | 1 | `vs/workbench/workbench.web.main.internal.js` (15.4 MB). Similar: shouldn't ship in a desktop build. It's output by the esbuild bundler. |
| **Keyboard layout contributions** | 3 | `layout.contribution.{darwin,linux,win}.js` — the old bundler inlines these into the main bundle. These are new separate files from the esbuild bundler. |
| **NLS files** | 2 | `nls.messages.js` (new) and `nls.metadata.json` (new). The old build has `nls.messages.json` and `nls.keys.json` but not a `.js` version or metadata. The `.js` version is produced by the NLS plugin. |
| **HTML files** | 2 | `vs/code/browser/workbench/workbench.html` and `callback.html` — correctly listed in `desktopResourcePatterns` (these are needed for desktop's built-in web server). |
| **SVG loading spinners** | 3 | `loading-dark.svg`, `loading-hc.svg`, `loading.svg` in `vs/workbench/contrib/extensions/browser/media/`. The old build only copies `theme-icon.png` and `language-icon.svg` from that folder; the new build's `desktopResourcePatterns` uses `*.svg` which is broader. |
| **codicon.ttf (duplicate)** | 1 | At `vs/base/browser/ui/codicons/codicon/codicon.ttf`. The old build copies this to `out/media/codicon.ttf` only. The new build has BOTH: the copy in `out/media/` (from esbuild's `file` loader) AND the original path (from `commonResourcePatterns`). Duplicate. |
| **PSReadLine.psm1** | 1 | `vs/workbench/contrib/terminal/common/scripts/psreadline/PSReadLine.psm1` — the old build uses `*.psm1` in `terminal/common/scripts/` (non-recursive?). The new build uses `**/*.psm1` (recursive), picking up this subdirectory file. Check if it's needed. |
| **date file** | 1 | `out/date` — build timestamp, produced by the new build's `bundle()` function. The old build doesn't write this; it reads `package.json.date` instead. |
### Size Increase Breakdown by Area
| Area | OLD | NEW | Delta | Why |
|------|-----|-----|-------|-----|
| `vs/code` | 1.5 MB | 17.4 MB | +15.9 MB | Web workbench bundle (15.4 MB) shouldn't be in desktop build |
| `vs/workbench` | 18.9 MB | 38.7 MB | +19.8 MB | `workbench.web.main.internal.js` (15.4 MB) + unmangled desktop bundle (+2.5 MB) + individual CSS files (~1 MB) |
| `vs/base` | 0 MB | 0.4 MB | +0.4 MB | Individual CSS files + vendor JS |
| `vs/editor` | 0.3 MB | 0.5 MB | +0.1 MB | Individual CSS files |
| `vs/platform` | 1.7 MB | 1.9 MB | +0.2 MB | Individual CSS files |
### JS Files with >2× Size Change
| File | OLD | NEW | Ratio | Reason |
|------|-----|-----|-------|--------|
| `vs/workbench/contrib/webview/browser/pre/service-worker.js` | 7 KB | 15 KB | 2.2× | Not minified / includes more inlined code |
| `vs/code/electron-browser/workbench/workbench.js` | 10 KB | 28 KB | 2.75× | OLD is minified to 6 lines; NEW is 380 lines (not compressed, includes tslib banner) |
### Action Items
1. **[CRITICAL] Missing `preload-browserView.ts`** — Add to `desktopStandaloneFiles` in index.ts. Without it, BrowserView (used for Simple Browser) may fail.
2. **[SIZE] Web bundles in desktop build** — `workbench.web.main.internal.js` and `vs/code/browser/workbench/workbench.js` together add ~31 MB. These are written by the esbuild bundler and not filtered out. Consider: either don't bundle web entry points for the desktop target, or ensure the packaging step excludes them (currently `packageTask` takes `out-vscode-min/**` without filtering).
3. **[SIZE] No mangling** — The desktop main bundle is 2.5 MB larger due to no property mangling. Known open item.
4. **[MINOR] Duplicate codicon.ttf** — Exists at both `out/media/codicon.ttf` (from esbuild `file` loader) and `out/vs/base/browser/ui/codicons/codicon/codicon.ttf` (from `commonResourcePatterns`). Consider removing from `commonResourcePatterns` if it's already handled by the loader.
5. **[MINOR] Extra SVGs** — `desktopResourcePatterns` uses `*.svg` for extensions media but old build only ships `language-icon.svg`. The loading spinners may be unused in the desktop build.
6. **[MINOR] Extra PSReadLine.psm1** from recursive glob — verify if needed.
---
## Self-hosting Setup
The default `VS Code - Build` task now runs three parallel watchers: