Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/lib/compilation.ts
5221 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import es from 'event-stream';
7
import fs from 'fs';
8
import gulp from 'gulp';
9
import path from 'path';
10
import * as monacodts from './monaco-api.ts';
11
import * as nls from './nls.ts';
12
import { createReporter } from './reporter.ts';
13
import * as util from './util.ts';
14
import fancyLog from 'fancy-log';
15
import ansiColors from 'ansi-colors';
16
import os from 'os';
17
import File from 'vinyl';
18
import * as task from './task.ts';
19
import { Mangler } from './mangle/index.ts';
20
import type { RawSourceMap } from 'source-map';
21
import ts from 'typescript';
22
import watch from './watch/index.ts';
23
import bom from 'gulp-bom';
24
import * as tsb from './tsb/index.ts';
25
import sourcemaps from 'gulp-sourcemaps';
26
27
28
// --- gulp-tsb: compile and transpile --------------------------------
29
30
const reporter = createReporter();
31
32
function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions {
33
const rootDir = path.join(import.meta.dirname, `../../${src}`);
34
const options: ts.CompilerOptions = {};
35
options.verbose = false;
36
options.sourceMap = true;
37
if (process.env['VSCODE_NO_SOURCEMAP']) { // To be used by developers in a hurry
38
options.sourceMap = false;
39
}
40
options.rootDir = rootDir;
41
options.baseUrl = rootDir;
42
options.sourceRoot = util.toFileUri(rootDir);
43
options.newLine = /\r\n/.test(fs.readFileSync(import.meta.filename, 'utf8')) ? 0 : 1;
44
return options;
45
}
46
47
interface ICompileTaskOptions {
48
readonly build: boolean;
49
readonly emitError: boolean;
50
readonly transpileOnly: boolean | { esbuild: boolean };
51
readonly preserveEnglish: boolean;
52
}
53
54
export function createCompile(src: string, { build, emitError, transpileOnly, preserveEnglish }: ICompileTaskOptions) {
55
const projectPath = path.join(import.meta.dirname, '../../', src, 'tsconfig.json');
56
const overrideOptions = { ...getTypeScriptCompilerOptions(src), inlineSources: Boolean(build) };
57
if (!build) {
58
overrideOptions.inlineSourceMap = true;
59
}
60
61
const compilation = tsb.create(projectPath, overrideOptions, {
62
verbose: false,
63
transpileOnly: Boolean(transpileOnly),
64
transpileWithEsbuild: typeof transpileOnly !== 'boolean' && transpileOnly.esbuild
65
}, err => reporter(err));
66
67
function pipeline(token?: util.ICancellationToken) {
68
69
const tsFilter = util.filter(data => /\.ts$/.test(data.path));
70
const isUtf8Test = (f: File) => /(\/|\\)test(\/|\\).*utf8/.test(f.path);
71
const isRuntimeJs = (f: File) => f.path.endsWith('.js') && !f.path.includes('fixtures');
72
const noDeclarationsFilter = util.filter(data => !(/\.d\.ts$/.test(data.path)));
73
74
const input = es.through();
75
const output = input
76
.pipe(util.$if(isUtf8Test, bom())) // this is required to preserve BOM in test files that loose it otherwise
77
.pipe(util.$if(!build && isRuntimeJs, util.appendOwnPathSourceURL()))
78
.pipe(tsFilter)
79
.pipe(util.loadSourcemaps())
80
.pipe(compilation(token))
81
.pipe(noDeclarationsFilter)
82
.pipe(util.$if(build, nls.nls({ preserveEnglish })))
83
.pipe(noDeclarationsFilter.restore)
84
.pipe(util.$if(!transpileOnly, sourcemaps.write('.', {
85
addComment: false,
86
includeContent: !!build,
87
sourceRoot: overrideOptions.sourceRoot
88
})))
89
.pipe(tsFilter.restore)
90
.pipe(reporter.end(!!emitError));
91
92
return es.duplex(input, output);
93
}
94
pipeline.tsProjectSrc = () => {
95
return compilation.src({ base: src });
96
};
97
pipeline.projectPath = projectPath;
98
return pipeline;
99
}
100
101
export function transpileTask(src: string, out: string, esbuild?: boolean): task.StreamTask {
102
103
const task = () => {
104
105
const transpile = createCompile(src, { build: false, emitError: true, transpileOnly: { esbuild: !!esbuild }, preserveEnglish: false });
106
const srcPipe = gulp.src(`${src}/**`, { base: `${src}` });
107
108
return srcPipe
109
.pipe(transpile())
110
.pipe(gulp.dest(out));
111
};
112
113
task.taskName = `transpile-${path.basename(src)}`;
114
return task;
115
}
116
117
export function compileTask(src: string, out: string, build: boolean, options: { disableMangle?: boolean; preserveEnglish?: boolean } = {}): task.StreamTask {
118
119
const task = () => {
120
121
if (os.totalmem() < 4_000_000_000) {
122
throw new Error('compilation requires 4GB of RAM');
123
}
124
125
const compile = createCompile(src, { build, emitError: true, transpileOnly: false, preserveEnglish: !!options.preserveEnglish });
126
const srcPipe = gulp.src(`${src}/**`, { base: `${src}` });
127
const generator = new MonacoGenerator(false);
128
if (src === 'src') {
129
generator.execute();
130
}
131
132
// mangle: TypeScript to TypeScript
133
let mangleStream = es.through();
134
if (build && !options.disableMangle) {
135
let ts2tsMangler: Mangler | undefined = new Mangler(compile.projectPath, (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data), { mangleExports: true, manglePrivateFields: true });
136
const newContentsByFileName = ts2tsMangler.computeNewFileContents(new Set(['saveState']));
137
mangleStream = es.through(async function write(data: File & { sourceMap?: RawSourceMap }) {
138
type TypeScriptExt = typeof ts & { normalizePath(path: string): string };
139
const tsNormalPath = (ts as TypeScriptExt).normalizePath(data.path);
140
const newContents = (await newContentsByFileName).get(tsNormalPath);
141
if (newContents !== undefined) {
142
data.contents = Buffer.from(newContents.out);
143
data.sourceMap = newContents.sourceMap && JSON.parse(newContents.sourceMap);
144
}
145
this.push(data);
146
}, async function end() {
147
// free resources
148
(await newContentsByFileName).clear();
149
150
this.push(null);
151
ts2tsMangler = undefined;
152
});
153
}
154
155
return srcPipe
156
.pipe(mangleStream)
157
.pipe(generator.stream)
158
.pipe(compile())
159
.pipe(gulp.dest(out));
160
};
161
162
task.taskName = `compile-${path.basename(src)}`;
163
return task;
164
}
165
166
export function watchTask(out: string, build: boolean, srcPath: string = 'src'): task.StreamTask {
167
168
const task = () => {
169
const compile = createCompile(srcPath, { build, emitError: false, transpileOnly: false, preserveEnglish: false });
170
171
const src = gulp.src(`${srcPath}/**`, { base: srcPath });
172
const watchSrc = watch(`${srcPath}/**`, { base: srcPath, readDelay: 200 });
173
174
const generator = new MonacoGenerator(true);
175
generator.execute();
176
177
return watchSrc
178
.pipe(generator.stream)
179
.pipe(util.incremental(compile, src, true))
180
.pipe(gulp.dest(out));
181
};
182
task.taskName = `watch-${path.basename(out)}`;
183
return task;
184
}
185
186
const REPO_SRC_FOLDER = path.join(import.meta.dirname, '../../src');
187
188
class MonacoGenerator {
189
private readonly _isWatch: boolean;
190
public readonly stream: NodeJS.ReadWriteStream;
191
192
private readonly _watchedFiles: { [filePath: string]: boolean };
193
private readonly _fsProvider: monacodts.FSProvider;
194
private readonly _declarationResolver: monacodts.DeclarationResolver;
195
196
constructor(isWatch: boolean) {
197
this._isWatch = isWatch;
198
this.stream = es.through();
199
this._watchedFiles = {};
200
const onWillReadFile = (moduleId: string, filePath: string) => {
201
if (!this._isWatch) {
202
return;
203
}
204
if (this._watchedFiles[filePath]) {
205
return;
206
}
207
this._watchedFiles[filePath] = true;
208
209
fs.watchFile(filePath, () => {
210
this._declarationResolver.invalidateCache(moduleId);
211
this._executeSoon();
212
});
213
};
214
this._fsProvider = new class extends monacodts.FSProvider {
215
public readFileSync(moduleId: string, filePath: string): Buffer {
216
onWillReadFile(moduleId, filePath);
217
return super.readFileSync(moduleId, filePath);
218
}
219
};
220
this._declarationResolver = new monacodts.DeclarationResolver(this._fsProvider);
221
222
if (this._isWatch) {
223
fs.watchFile(monacodts.RECIPE_PATH, () => {
224
this._executeSoon();
225
});
226
}
227
}
228
229
private _executeSoonTimer: NodeJS.Timeout | null = null;
230
private _executeSoon(): void {
231
if (this._executeSoonTimer !== null) {
232
clearTimeout(this._executeSoonTimer);
233
this._executeSoonTimer = null;
234
}
235
this._executeSoonTimer = setTimeout(() => {
236
this._executeSoonTimer = null;
237
this.execute();
238
}, 20);
239
}
240
241
private _run(): monacodts.IMonacoDeclarationResult | null {
242
const r = monacodts.run3(this._declarationResolver);
243
if (!r && !this._isWatch) {
244
// The build must always be able to generate the monaco.d.ts
245
throw new Error(`monaco.d.ts generation error - Cannot continue`);
246
}
247
return r;
248
}
249
250
private _log(message: string, ...rest: unknown[]): void {
251
fancyLog(ansiColors.cyan('[monaco.d.ts]'), message, ...rest);
252
}
253
254
public execute(): void {
255
const startTime = Date.now();
256
const result = this._run();
257
if (!result) {
258
// nothing really changed
259
return;
260
}
261
if (result.isTheSame) {
262
return;
263
}
264
265
fs.writeFileSync(result.filePath, result.content);
266
fs.writeFileSync(path.join(REPO_SRC_FOLDER, 'vs/editor/common/standalone/standaloneEnums.ts'), result.enums);
267
this._log(`monaco.d.ts is changed - total time took ${Date.now() - startTime} ms`);
268
if (!this._isWatch) {
269
this.stream.emit('error', 'monaco.d.ts is no longer up to date. Please run gulp watch and commit the new file.');
270
}
271
}
272
}
273
274
function generateApiProposalNames() {
275
let eol: string;
276
277
try {
278
const src = fs.readFileSync('src/vs/platform/extensions/common/extensionsApiProposals.ts', 'utf-8');
279
const match = /\r?\n/m.exec(src);
280
eol = match ? match[0] : os.EOL;
281
} catch {
282
eol = os.EOL;
283
}
284
285
const pattern = /vscode\.proposed\.([a-zA-Z\d]+)\.d\.ts$/;
286
const versionPattern = /^\s*\/\/\s*version\s*:\s*(\d+)\s*$/mi;
287
const proposals = new Map<string, { proposal: string; version?: number }>();
288
289
const input = es.through();
290
const output = input
291
.pipe(util.filter((f: File) => pattern.test(f.path)))
292
.pipe(es.through((f: File) => {
293
const name = path.basename(f.path);
294
const match = pattern.exec(name);
295
296
if (!match) {
297
return;
298
}
299
300
const proposalName = match[1];
301
302
const contents = f.contents!.toString('utf8');
303
const versionMatch = versionPattern.exec(contents);
304
const version = versionMatch ? versionMatch[1] : undefined;
305
306
proposals.set(proposalName, {
307
proposal: `https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${proposalName}.d.ts`,
308
version: version ? parseInt(version) : undefined
309
});
310
}, function () {
311
const names = [...proposals.keys()].sort();
312
const contents = [
313
'/*---------------------------------------------------------------------------------------------',
314
' * Copyright (c) Microsoft Corporation. All rights reserved.',
315
' * Licensed under the MIT License. See License.txt in the project root for license information.',
316
' *--------------------------------------------------------------------------------------------*/',
317
'',
318
'// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.',
319
'',
320
'const _allApiProposals = {',
321
`${names.map(proposalName => {
322
const proposal = proposals.get(proposalName)!;
323
return `\t${proposalName}: {${eol}\t\tproposal: '${proposal.proposal}',${eol}${proposal.version ? `\t\tversion: ${proposal.version}${eol}` : ''}\t}`;
324
}).join(`,${eol}`)}`,
325
'};',
326
'export const allApiProposals = Object.freeze<{ [proposalName: string]: Readonly<{ proposal: string; version?: number }> }>(_allApiProposals);',
327
'export type ApiProposalName = keyof typeof _allApiProposals;',
328
'',
329
].join(eol);
330
331
this.emit('data', new File({
332
path: 'vs/platform/extensions/common/extensionsApiProposals.ts',
333
contents: Buffer.from(contents)
334
}));
335
this.emit('end');
336
}));
337
338
return es.duplex(input, output);
339
}
340
341
const apiProposalNamesReporter = createReporter('api-proposal-names');
342
343
export const compileApiProposalNamesTask = task.define('compile-api-proposal-names', () => {
344
return gulp.src('src/vscode-dts/**')
345
.pipe(generateApiProposalNames())
346
.pipe(gulp.dest('src'))
347
.pipe(apiProposalNamesReporter.end(true));
348
});
349
350
export const watchApiProposalNamesTask = task.define('watch-api-proposal-names', () => {
351
const task = () => gulp.src('src/vscode-dts/**')
352
.pipe(generateApiProposalNames())
353
.pipe(apiProposalNamesReporter.end(true));
354
355
return watch('src/vscode-dts/**', { readDelay: 200 })
356
.pipe(util.debounce(task))
357
.pipe(gulp.dest('src'));
358
});
359
360
// Codicons
361
const root = path.dirname(path.dirname(import.meta.dirname));
362
const codiconSource = path.join(root, 'node_modules', '@vscode', 'codicons', 'dist', 'codicon.ttf');
363
const codiconDest = path.join(root, 'src', 'vs', 'base', 'browser', 'ui', 'codicons', 'codicon', 'codicon.ttf');
364
365
function copyCodiconsImpl() {
366
try {
367
if (fs.existsSync(codiconSource)) {
368
fs.mkdirSync(path.dirname(codiconDest), { recursive: true });
369
fs.copyFileSync(codiconSource, codiconDest);
370
} else {
371
fancyLog(ansiColors.red('[codicons]'), `codicon.ttf not found in node_modules. Please run 'npm install' to install dependencies.`);
372
}
373
} catch (e) {
374
fancyLog(ansiColors.red('[codicons]'), `Error copying codicon.ttf: ${e}`);
375
}
376
}
377
378
export const copyCodiconsTask = task.define('copy-codicons', () => {
379
copyCodiconsImpl();
380
return Promise.resolve();
381
});
382
gulp.task(copyCodiconsTask);
383
384
export const watchCodiconsTask = task.define('watch-codicons', () => {
385
copyCodiconsImpl();
386
return watch('node_modules/@vscode/codicons/dist/**', { readDelay: 200 })
387
.on('data', () => copyCodiconsImpl());
388
});
389
gulp.task(watchCodiconsTask);
390
391