Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 0 additions & 35 deletions .eslint-plugin-local/code-no-native-private.ts

This file was deleted.

1 change: 1 addition & 0 deletions build/gulpfile.vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ function runEsbuildBundle(outDir: string, minify: boolean, nls: boolean, target:
const args = [scriptPath, 'bundle', '--out', outDir, '--target', target];
if (minify) {
args.push('--minify');
args.push('--mangle-privates');
}
if (nls) {
args.push('--nls');
Expand Down
1 change: 1 addition & 0 deletions build/gulpfile.vscode.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function runEsbuildBundle(outDir: string, minify: boolean, nls: boolean): Promis
const args = [scriptPath, 'bundle', '--out', outDir, '--target', 'web'];
if (minify) {
args.push('--minify');
args.push('--mangle-privates');
}
if (nls) {
args.push('--nls');
Expand Down
2 changes: 2 additions & 0 deletions build/linux/rpm/dep-lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const referenceGeneratedDepsByArch = {
'libc.so.6(GLIBC_2.3.3)(64bit)',
'libc.so.6(GLIBC_2.3.4)(64bit)',
'libc.so.6(GLIBC_2.4)(64bit)',
'libc.so.6(GLIBC_2.5)(64bit)',
'libc.so.6(GLIBC_2.6)(64bit)',
'libc.so.6(GLIBC_2.7)(64bit)',
'libc.so.6(GLIBC_2.8)(64bit)',
Expand Down Expand Up @@ -144,6 +145,7 @@ export const referenceGeneratedDepsByArch = {
'libc.so.6(GLIBC_2.27)',
'libc.so.6(GLIBC_2.28)',
'libc.so.6(GLIBC_2.4)',
'libc.so.6(GLIBC_2.5)',
'libc.so.6(GLIBC_2.6)',
'libc.so.6(GLIBC_2.7)',
'libc.so.6(GLIBC_2.8)',
Expand Down
190 changes: 88 additions & 102 deletions build/next/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { promisify } from 'util';
import glob from 'glob';
import gulpWatch from '../lib/watch/index.ts';
import { nlsPlugin, createNLSCollector, finalizeNLS, postProcessNLS } from './nls-plugin.ts';
import { convertPrivateFields, type ConvertPrivateFieldsResult } from './private-to-property.ts';
import { getVersion } from '../lib/getVersion.ts';
import product from '../../product.json' with { type: 'json' };
import packageJson from '../../package.json' with { type: 'json' };
Expand Down Expand Up @@ -41,6 +42,7 @@ const options = {
watch: process.argv.includes('--watch'),
minify: process.argv.includes('--minify'),
nls: process.argv.includes('--nls'),
manglePrivates: process.argv.includes('--mangle-privates'),
excludeTests: process.argv.includes('--exclude-tests'),
out: getArgValue('--out'),
target: getArgValue('--target') ?? 'desktop', // 'desktop' | 'server' | 'server-web' | 'web'
Expand All @@ -61,6 +63,17 @@ const UTF8_BOM = Buffer.from([0xef, 0xbb, 0xbf]);
// Entry Points (from build/buildfile.ts)
// ============================================================================

// Extension host bundles are excluded from private field mangling because they
// expose API surface to extensions where encapsulation matters.
const extensionHostEntryPoints = [
'vs/workbench/api/node/extensionHostProcess',
'vs/workbench/api/worker/extensionHostWorkerMain',
];

function isExtensionHostBundle(filePath: string): boolean {
return extensionHostEntryPoints.some(ep => filePath.endsWith(`${ep}.js`));
}

// Workers - shared between targets
const workerEntryPoints = [
'vs/editor/common/services/editorWebWorkerMain',
Expand Down Expand Up @@ -216,18 +229,6 @@ const commonResourcePatterns = [
'vs/workbench/browser/parts/editor/media/letterpress*.svg',
];

// Resources only needed for dev/transpile builds (these get bundled into the main
// JS/CSS bundles for production, so separate copies are redundant)
const devOnlyResourcePatterns = [
// Fonts (esbuild file loader copies to media/codicon.ttf for production)
'vs/base/browser/ui/codicons/codicon/codicon.ttf',

// Vendor JavaScript libraries (bundled into workbench main JS for production)
'vs/base/common/marked/marked.js',
'vs/base/common/semver/semver.js',
'vs/base/browser/dompurify/dompurify.js',
];

// Resources for desktop target
const desktopResourcePatterns = [
...commonResourcePatterns,
Expand Down Expand Up @@ -355,28 +356,6 @@ function getResourcePatternsForTarget(target: BuildTarget): string[] {
}
}

// Test fixtures (only copied for development builds, not production)
const testFixturePatterns = [
'**/test/**/*.json',
'**/test/**/*.txt',
'**/test/**/*.snap',
'**/test/**/*.tst',
'**/test/**/*.html',
'**/test/**/*.js',
'**/test/**/*.jxs',
'**/test/**/*.tsx',
'**/test/**/*.css',
'**/test/**/*.png',
'**/test/**/*.md',
'**/test/**/*.zip',
'**/test/**/*.pdf',
'**/test/**/*.qwoff',
'**/test/**/*.wuff',
'**/test/**/*.less',
// Files without extensions (executables, etc.)
'**/test/**/fixtures/executable/*',
];

// ============================================================================
// Utilities
// ============================================================================
Expand Down Expand Up @@ -498,34 +477,57 @@ async function compileStandaloneFiles(outDir: string, doMinify: boolean, target:
console.log(`[standalone] Done`);
}

async function copyCssFiles(outDir: string, excludeTests = false): Promise<number> {
// Copy all CSS files from src to output (they're imported by JS)
const cssFiles = await globAsync('**/*.css', {
/**
* Copy ALL non-TypeScript files from src/ to the output directory.
* This matches the old gulp build behavior where `gulp.src('src/**')` streams
* every file and non-TS files bypass the compiler via tsFilter.restore.
* Used for development/transpile builds only - production bundles use
* copyResources() with curated per-target patterns instead.
*/
async function copyAllNonTsFiles(outDir: string, excludeTests: boolean): Promise<void> {
console.log(`[resources] Copying all non-TS files to ${outDir}...`);

const ignorePatterns = [
// Exclude .ts files but keep .d.ts files (they're needed at runtime for type references)
'**/*.ts',
];
if (excludeTests) {
ignorePatterns.push('**/test/**');
}

const files = await globAsync('**/*', {
cwd: path.join(REPO_ROOT, SRC_DIR),
nodir: true,
ignore: ignorePatterns,
});

// Re-include .d.ts files that were excluded by the *.ts ignore
const dtsFiles = await globAsync('**/*.d.ts', {
cwd: path.join(REPO_ROOT, SRC_DIR),
ignore: excludeTests ? ['**/test/**'] : [],
});

for (const file of cssFiles) {
const allFiles = [...new Set([...files, ...dtsFiles])];

await Promise.all(allFiles.map(file => {
const srcPath = path.join(REPO_ROOT, SRC_DIR, file);
const destPath = path.join(REPO_ROOT, outDir, file);
return copyFile(srcPath, destPath);
}));

await copyFile(srcPath, destPath);
}

return cssFiles.length;
console.log(`[resources] Copied ${allFiles.length} files`);
}

async function copyResources(outDir: string, target: BuildTarget, excludeDevFiles = false, excludeTests = false): Promise<void> {
/**
* Copy curated resource files for production bundles.
* Uses specific per-target patterns matching the old build's vscodeResourceIncludes,
* serverResourceIncludes, etc. Only called by bundle() - transpile uses copyAllNonTsFiles().
*/
async function copyResources(outDir: string, target: BuildTarget): Promise<void> {
console.log(`[resources] Copying to ${outDir} for target '${target}'...`);
let copied = 0;

const ignorePatterns: string[] = [];
if (excludeTests) {
ignorePatterns.push('**/test/**');
}
if (excludeDevFiles) {
ignorePatterns.push('**/*-dev.html');
}
const ignorePatterns = ['**/test/**', '**/*-dev.html'];

const resourcePatterns = getResourcePatternsForTarget(target);
for (const pattern of resourcePatterns) {
Expand All @@ -543,47 +545,7 @@ async function copyResources(outDir: string, target: BuildTarget, excludeDevFile
}
}

// Copy test fixtures (only for development builds)
if (!excludeTests) {
for (const pattern of testFixturePatterns) {
const files = await globAsync(pattern, {
cwd: path.join(REPO_ROOT, SRC_DIR),
});

for (const file of files) {
const srcPath = path.join(REPO_ROOT, SRC_DIR, file);
const destPath = path.join(REPO_ROOT, outDir, file);

await copyFile(srcPath, destPath);
copied++;
}
}
}

// Copy dev-only resources (vendor JS, codicon font) - only for development/transpile
// builds. In production bundles these are inlined by esbuild.
if (!excludeDevFiles) {
for (const pattern of devOnlyResourcePatterns) {
const files = await globAsync(pattern, {
cwd: path.join(REPO_ROOT, SRC_DIR),
ignore: ignorePatterns,
});
for (const file of files) {
await copyFile(path.join(REPO_ROOT, SRC_DIR, file), path.join(REPO_ROOT, outDir, file));
copied++;
}
}
}

// 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)`);
}
console.log(`[resources] Copied ${copied} files`);
}

// ============================================================================
Expand Down Expand Up @@ -740,15 +702,15 @@ async function transpile(outDir: string, excludeTests: boolean): Promise<void> {
// Bundle (Goal 2: JS → bundled JS)
// ============================================================================

async function bundle(outDir: string, doMinify: boolean, doNls: boolean, target: BuildTarget, sourceMapBaseUrl?: string): Promise<void> {
async function bundle(outDir: string, doMinify: boolean, doNls: boolean, doManglePrivates: boolean, target: BuildTarget, sourceMapBaseUrl?: string): Promise<void> {
await cleanDir(outDir);

// Write build date file (used by packaging to embed in product.json)
const outDirPath = path.join(REPO_ROOT, outDir);
await fs.promises.mkdir(outDirPath, { recursive: true });
await fs.promises.writeFile(path.join(outDirPath, 'date'), new Date().toISOString(), 'utf8');

console.log(`[bundle] ${SRC_DIR} → ${outDir} (target: ${target})${doMinify ? ' (minify)' : ''}${doNls ? ' (nls)' : ''}`);
console.log(`[bundle] ${SRC_DIR} → ${outDir} (target: ${target})${doMinify ? ' (minify)' : ''}${doNls ? ' (nls)' : ''}${doManglePrivates ? ' (mangle-privates)' : ''}`);
const t1 = Date.now();

// Read TSLib for banner
Expand Down Expand Up @@ -902,6 +864,7 @@ ${tslib}`,

// Post-process and write all output files
let bundled = 0;
const mangleStats: { file: string; result: ConvertPrivateFieldsResult }[] = [];
for (const { result } of buildResults) {
if (!result.outputFiles) {
continue;
Expand All @@ -918,6 +881,17 @@ ${tslib}`,
content = postProcessNLS(content, indexMap, preserveEnglish);
}

// Convert native #private fields to regular properties.
// Skip extension host bundles - they expose API surface to extensions
// where true encapsulation matters more than the perf gain.
if (file.path.endsWith('.js') && doManglePrivates && !isExtensionHostBundle(file.path)) {
const mangleResult = convertPrivateFields(content, file.path);
content = mangleResult.code;
if (mangleResult.editCount > 0) {
mangleStats.push({ file: path.relative(path.join(REPO_ROOT, outDir), file.path), result: mangleResult });
}
}

// Rewrite sourceMappingURL to CDN URL if configured
if (sourceMapBaseUrl) {
const relativePath = path.relative(path.join(REPO_ROOT, outDir), file.path);
Expand All @@ -940,8 +914,21 @@ ${tslib}`,
bundled++;
}

// Copy resources (exclude dev files and tests for production)
await copyResources(outDir, target, true, true);
// Log mangle-privates stats
if (doManglePrivates && mangleStats.length > 0) {
let totalClasses = 0, totalFields = 0, totalEdits = 0, totalElapsed = 0;
for (const { file, result } of mangleStats) {
console.log(`[mangle-privates] ${file}: ${result.classCount} classes, ${result.fieldCount} fields, ${result.editCount} edits, ${result.elapsed}ms`);
totalClasses += result.classCount;
totalFields += result.fieldCount;
totalEdits += result.editCount;
totalElapsed += result.elapsed;
}
console.log(`[mangle-privates] Total: ${totalClasses} classes, ${totalFields} fields, ${totalEdits} edits, ${totalElapsed}ms`);
}

// Copy resources (curated per-target patterns for production)
await copyResources(outDir, target);

// Compile standalone TypeScript files (like Electron preload scripts) that cannot be bundled
await compileStandaloneFiles(outDir, doMinify, target);
Expand Down Expand Up @@ -974,7 +961,7 @@ async function watch(): Promise<void> {
const t1 = Date.now();
try {
await transpile(outDir, false);
await copyResources(outDir, 'desktop', false, false);
await copyAllNonTsFiles(outDir, false);
console.log(`Finished transpilation with 0 errors after ${Date.now() - t1} ms`);
} catch (err) {
console.error('[watch] Initial build failed:', err);
Expand Down Expand Up @@ -1025,9 +1012,6 @@ async function watch(): Promise<void> {
}
};

// Extensions to watch and copy (non-TypeScript resources)
const copyExtensions = ['.css', '.html', '.js', '.json', '.ttf', '.svg', '.png', '.mp3', '.scm', '.sh', '.ps1', '.psm1', '.fish', '.zsh', '.scpt'];

// Watch src directory using existing gulp-watch based watcher
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
const srcDir = path.join(REPO_ROOT, SRC_DIR);
Expand All @@ -1036,7 +1020,8 @@ async function watch(): Promise<void> {
watchStream.on('data', (file: { path: string }) => {
if (file.path.endsWith('.ts') && !file.path.endsWith('.d.ts')) {
pendingTsFiles.add(file.path);
} else if (copyExtensions.some(ext => file.path.endsWith(ext))) {
} else {
// Copy any non-TS file (matches old gulp build's `src/**` behavior)
pendingCopyFiles.add(file.path);
}

Expand Down Expand Up @@ -1075,6 +1060,7 @@ Options for 'transpile':
Options for 'bundle':
--minify Minify the output bundles
--nls Process NLS (localization) strings
--mangle-privates Convert native #private fields to regular properties
--out <dir> Output directory (default: out-vscode)
--target <target> Build target: desktop (default), server, server-web, web
--source-map-base-url <url> Rewrite sourceMappingURL to CDN URL
Expand Down Expand Up @@ -1112,13 +1098,13 @@ async function main(): Promise<void> {
console.log(`[transpile] ${SRC_DIR} → ${outDir}${options.excludeTests ? ' (excluding tests)' : ''}`);
const t1 = Date.now();
await transpile(outDir, options.excludeTests);
await copyResources(outDir, 'desktop', false, options.excludeTests);
await copyAllNonTsFiles(outDir, options.excludeTests);
console.log(`[transpile] Done in ${Date.now() - t1}ms`);
}
break;

case 'bundle':
await bundle(options.out ?? OUT_VSCODE_DIR, options.minify, options.nls, options.target as BuildTarget, options.sourceMapBaseUrl);
await bundle(options.out ?? OUT_VSCODE_DIR, options.minify, options.nls, options.manglePrivates, options.target as BuildTarget, options.sourceMapBaseUrl);
break;

default:
Expand Down
Loading
Loading