From 6d6e9a4f3eb81bb181eb8d0078d3ed87f482ca04 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 9 Feb 2026 18:00:57 +0100 Subject: [PATCH] Refactor build process: remove unused entry points and update resource patterns for desktop build --- build/next/compare-builds.cjs | 207 ++++++++++++++++++++++++++++++++++ build/next/index.ts | 34 +++--- build/next/working.md | 60 ++++++++++ 3 files changed, 283 insertions(+), 18 deletions(-) create mode 100644 build/next/compare-builds.cjs diff --git a/build/next/compare-builds.cjs b/build/next/compare-builds.cjs new file mode 100644 index 00000000000..520bcb40bcc --- /dev/null +++ b/build/next/compare-builds.cjs @@ -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)`); +} diff --git a/build/next/index.ts b/build/next/index.ts index e7978d726cc..29528a79cd0 100644 --- a/build/next/index.ts +++ b/build/next/index.ts @@ -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 { 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 { 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 { @@ -558,11 +552,15 @@ async function copyResources(outDir: string, target: BuildTarget, excludeDevFile } } - // Copy CSS files - const cssCount = await copyCssFiles(outDir, excludeTests); - copied += cssCount; - - console.log(`[resources] Copied ${copied} files (${cssCount} CSS)`); + // 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)`); + } } // ============================================================================ diff --git a/build/next/working.md b/build/next/working.md index f9ebdd40d04..56a6aafbad0 100644 --- a/build/next/working.md +++ b/build/next/working.md @@ -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: