Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/fixtures/edit/issue-8129/optimize.ts
13405 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 * as ansiColors from 'ansi-colors';
7
import * as esbuild from 'esbuild';
8
import * as es from 'event-stream';
9
import * as fancyLog from 'fancy-log';
10
import * as fs from 'fs';
11
import * as gulp from 'gulp';
12
import * as concat from 'gulp-concat';
13
import * as filter from 'gulp-filter';
14
import * as sourcemaps from 'gulp-sourcemaps';
15
import * as path from 'path';
16
import * as pump from 'pump';
17
import * as VinylFile from 'vinyl';
18
import { isAMD } from './amd';
19
import * as bundle from './bundle';
20
import { Language, processNlsFiles } from './i18n';
21
import { gulpPostcss } from './postcss';
22
import { createStatsStream } from './stats';
23
import * as util from './util';
24
25
const REPO_ROOT_PATH = path.join(__dirname, '../..');
26
27
function log(prefix: string, message: string): void {
28
fancyLog(ansiColors.cyan('[' + prefix + ']'), message);
29
}
30
31
export function loaderConfig() {
32
const result: any = {
33
paths: {
34
'vs': 'out-build/vs',
35
'vscode': 'empty:'
36
},
37
amdModulesPattern: /^vs\//
38
};
39
40
result['vs/css'] = { inlineResources: true };
41
42
return result;
43
}
44
45
const IS_OUR_COPYRIGHT_REGEXP = /Copyright \(C\) Microsoft Corporation/i;
46
47
function loaderPlugin(src: string, base: string, amdModuleId: string | undefined): NodeJS.ReadWriteStream {
48
return (
49
gulp
50
.src(src, { base })
51
.pipe(es.through(function (data: VinylFile) {
52
if (amdModuleId) {
53
let contents = data.contents.toString('utf8');
54
contents = contents.replace(/^define\(/m, `define("${amdModuleId}",`);
55
data.contents = Buffer.from(contents);
56
}
57
this.emit('data', data);
58
}))
59
);
60
}
61
62
function loader(src: string, bundledFileHeader: string, bundleLoader: boolean, externalLoaderInfo?: util.IExternalLoaderInfo): NodeJS.ReadWriteStream {
63
let loaderStream = gulp.src(`${src}/vs/loader.js`, { base: `${src}` });
64
if (bundleLoader) {
65
loaderStream = es.merge(
66
loaderStream,
67
loaderPlugin(`${src}/vs/css.js`, `${src}`, 'vs/css')
68
);
69
}
70
71
const files: VinylFile[] = [];
72
const order = (f: VinylFile) => {
73
if (f.path.endsWith('loader.js')) {
74
return 0;
75
}
76
if (f.path.endsWith('css.js')) {
77
return 1;
78
}
79
return 2;
80
};
81
82
return (
83
loaderStream
84
.pipe(es.through(function (data) {
85
files.push(data);
86
}, function () {
87
files.sort((a, b) => {
88
return order(a) - order(b);
89
});
90
files.unshift(new VinylFile({
91
path: 'fake',
92
base: '.',
93
contents: Buffer.from(bundledFileHeader)
94
}));
95
if (externalLoaderInfo !== undefined) {
96
files.push(new VinylFile({
97
path: 'fake2',
98
base: '.',
99
contents: Buffer.from(emitExternalLoaderInfo(externalLoaderInfo))
100
}));
101
}
102
for (const file of files) {
103
this.emit('data', file);
104
}
105
this.emit('end');
106
}))
107
.pipe(concat('vs/loader.js'))
108
);
109
}
110
111
function emitExternalLoaderInfo(externalLoaderInfo: util.IExternalLoaderInfo): string {
112
const externalBaseUrl = externalLoaderInfo.baseUrl;
113
externalLoaderInfo.baseUrl = '$BASE_URL';
114
115
// If defined, use the runtime configured baseUrl.
116
const code = `
117
(function() {
118
const baseUrl = require.getConfig().baseUrl || ${JSON.stringify(externalBaseUrl)};
119
require.config(${JSON.stringify(externalLoaderInfo, undefined, 2)});
120
})();`;
121
return code.replace('"$BASE_URL"', 'baseUrl');
122
}
123
124
function toConcatStream(src: string, bundledFileHeader: string, sources: bundle.IFile[], dest: string, fileContentMapper: (contents: string, path: string) => string): NodeJS.ReadWriteStream {
125
const useSourcemaps = /\.js$/.test(dest) && !/\.nls\.js$/.test(dest);
126
127
// If a bundle ends up including in any of the sources our copyright, then
128
// insert a fake source at the beginning of each bundle with our copyright
129
let containsOurCopyright = false;
130
for (let i = 0, len = sources.length; i < len; i++) {
131
const fileContents = sources[i].contents;
132
if (IS_OUR_COPYRIGHT_REGEXP.test(fileContents)) {
133
containsOurCopyright = true;
134
break;
135
}
136
}
137
138
if (containsOurCopyright) {
139
sources.unshift({
140
path: null,
141
contents: bundledFileHeader
142
});
143
}
144
145
const treatedSources = sources.map(function (source) {
146
const root = source.path ? REPO_ROOT_PATH.replace(/\\/g, '/') : '';
147
const base = source.path ? root + `/${src}` : '.';
148
const path = source.path ? root + '/' + source.path.replace(/\\/g, '/') : 'fake';
149
const contents = source.path ? fileContentMapper(source.contents, path) : source.contents;
150
151
return new VinylFile({
152
path: path,
153
base: base,
154
contents: Buffer.from(contents)
155
});
156
});
157
158
return es.readArray(treatedSources)
159
.pipe(useSourcemaps ? util.loadSourcemaps() : es.through())
160
.pipe(concat(dest))
161
.pipe(createStatsStream(dest));
162
}
163
164
function toBundleStream(src: string, bundledFileHeader: string, bundles: bundle.IConcatFile[], fileContentMapper: (contents: string, path: string) => string): NodeJS.ReadWriteStream {
165
return es.merge(bundles.map(function (bundle) {
166
return toConcatStream(src, bundledFileHeader, bundle.sources, bundle.dest, fileContentMapper);
167
}));
168
}
169
170
export interface IOptimizeAMDTaskOpts {
171
/**
172
* The folder to read files from.
173
*/
174
src: string;
175
/**
176
* (for AMD files, will get bundled and get Copyright treatment)
177
*/
178
entryPoints: bundle.IEntryPoint[];
179
/**
180
* (svg, etc.)
181
*/
182
resources: string[];
183
loaderConfig: any;
184
/**
185
* Additional info we append to the end of the loader
186
*/
187
externalLoaderInfo?: util.IExternalLoaderInfo;
188
/**
189
* (true by default - append css and nls to loader)
190
*/
191
bundleLoader?: boolean;
192
/**
193
* (basically the Copyright treatment)
194
*/
195
header?: string;
196
/**
197
* (emit bundleInfo.json file)
198
*/
199
bundleInfo: boolean;
200
/**
201
* Language configuration.
202
*/
203
languages?: Language[];
204
/**
205
* File contents interceptor
206
* @param contents The contents of the file
207
* @param path The absolute file path, always using `/`, even on Windows
208
*/
209
fileContentMapper?: (contents: string, path: string) => string;
210
}
211
212
const DEFAULT_FILE_HEADER = [
213
'/*!--------------------------------------------------------',
214
' * Copyright (C) Microsoft Corporation. All rights reserved.',
215
' *--------------------------------------------------------*/'
216
].join('\n');
217
218
function optimizeAMDTask(opts: IOptimizeAMDTaskOpts): NodeJS.ReadWriteStream {
219
const src = opts.src;
220
const entryPoints = opts.entryPoints.filter(d => d.target !== 'esm');
221
const resources = opts.resources;
222
const loaderConfig = opts.loaderConfig;
223
const bundledFileHeader = opts.header || DEFAULT_FILE_HEADER;
224
const fileContentMapper = opts.fileContentMapper || ((contents: string, _path: string) => contents);
225
226
const bundlesStream = es.through(); // this stream will contain the bundled files
227
const resourcesStream = es.through(); // this stream will contain the resources
228
const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json
229
230
bundle.bundle(entryPoints, loaderConfig, function (err, result) {
231
if (err || !result) { return bundlesStream.emit('error', JSON.stringify(err)); }
232
233
toBundleStream(src, bundledFileHeader, result.files, fileContentMapper).pipe(bundlesStream);
234
235
// Remove css inlined resources
236
const filteredResources = resources.slice();
237
result.cssInlinedResources.forEach(function (resource) {
238
if (process.env['VSCODE_BUILD_VERBOSE']) {
239
log('optimizer', 'excluding inlined: ' + resource);
240
}
241
filteredResources.push('!' + resource);
242
});
243
gulp.src(filteredResources, { base: `${src}`, allowEmpty: true }).pipe(resourcesStream);
244
245
const bundleInfoArray: VinylFile[] = [];
246
if (opts.bundleInfo) {
247
bundleInfoArray.push(new VinylFile({
248
path: 'bundleInfo.json',
249
base: '.',
250
contents: Buffer.from(JSON.stringify(result.bundleData, null, '\t'))
251
}));
252
}
253
es.readArray(bundleInfoArray).pipe(bundleInfoStream);
254
});
255
256
const result = es.merge(
257
loader(src, bundledFileHeader, false, opts.externalLoaderInfo),
258
bundlesStream,
259
resourcesStream,
260
bundleInfoStream
261
);
262
263
return result
264
.pipe(sourcemaps.write('./', {
265
sourceRoot: undefined,
266
addComment: true,
267
includeContent: true
268
}))
269
.pipe(opts.languages && opts.languages.length ? processNlsFiles({
270
out: opts.src,
271
fileHeader: bundledFileHeader,
272
languages: opts.languages
273
}) : es.through());
274
}
275
276
function optimizeESMTask(opts: IOptimizeAMDTaskOpts, cjsOpts?: IOptimizeCommonJSTaskOpts): NodeJS.ReadWriteStream {
277
const resourcesStream = es.through(); // this stream will contain the resources
278
const bundlesStream = es.through(); // this stream will contain the bundled files
279
280
const entryPoints = opts.entryPoints.filter(d => d.target !== 'amd');
281
if (cjsOpts) {
282
cjsOpts.entryPoints.forEach(entryPoint => entryPoints.push({ name: path.parse(entryPoint).name }));
283
}
284
285
const allMentionedModules = new Set<string>();
286
for (const entryPoint of entryPoints) {
287
allMentionedModules.add(entryPoint.name);
288
entryPoint.include?.forEach(allMentionedModules.add, allMentionedModules);
289
entryPoint.exclude?.forEach(allMentionedModules.add, allMentionedModules);
290
}
291
292
allMentionedModules.delete('vs/css'); // TODO@esm remove this when vs/css is removed
293
294
const bundleAsync = async () => {
295
296
const files: VinylFile[] = [];
297
const tasks: Promise<any>[] = [];
298
299
for (const entryPoint of entryPoints) {
300
301
console.log(`[bundle] '${entryPoint.name}'`);
302
303
// support for 'dest' via esbuild#in/out
304
const dest = entryPoint.dest?.replace(/\.[^/.]+$/, '') ?? entryPoint.name;
305
306
// boilerplate massage
307
const banner = { js: '' };
308
const tslibPath = path.join(require.resolve('tslib'), '../tslib.es6.js');
309
banner.js += await fs.promises.readFile(tslibPath, 'utf-8');
310
311
const boilerplateTrimmer: esbuild.Plugin = {
312
name: 'boilerplate-trimmer',
313
setup(build) {
314
build.onLoad({ filter: /\.js$/ }, async args => {
315
const contents = await fs.promises.readFile(args.path, 'utf-8');
316
const newContents = bundle.removeAllTSBoilerplate(contents);
317
return { contents: newContents };
318
});
319
}
320
};
321
322
// support for 'preprend' via the esbuild#banner
323
if (entryPoint.prepend?.length) {
324
for (const item of entryPoint.prepend) {
325
const fullpath = path.join(REPO_ROOT_PATH, opts.src, item.path);
326
const source = await fs.promises.readFile(fullpath, 'utf8');
327
banner.js += source + '\n';
328
}
329
}
330
331
const task = esbuild.build({
332
bundle: true,
333
external: entryPoint.exclude,
334
packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages
335
platform: 'neutral', // makes esm
336
format: 'esm',
337
sourcemap: 'external',
338
plugins: [boilerplateTrimmer],
339
target: ['es2022'],
340
loader: {
341
'.ttf': 'file',
342
'.svg': 'file',
343
'.png': 'file',
344
'.sh': 'file',
345
},
346
assetNames: 'media/[name]', // moves media assets into a sub-folder "media"
347
banner: entryPoint.name === 'vs/workbench/workbench.web.main' ? undefined : banner, // TODO@esm remove line when we stop supporting web-amd-esm-bridge
348
entryPoints: [
349
{
350
in: path.join(REPO_ROOT_PATH, opts.src, `${entryPoint.name}.js`),
351
out: dest,
352
}
353
],
354
outdir: path.join(REPO_ROOT_PATH, opts.src),
355
write: false, // enables res.outputFiles
356
metafile: true, // enables res.metafile
357
358
}).then(res => {
359
for (const file of res.outputFiles) {
360
361
let contents = file.contents;
362
let sourceMapFile: esbuild.OutputFile | undefined = undefined;
363
364
if (file.path.endsWith('.js')) {
365
366
if (opts.fileContentMapper) {
367
// UGLY the fileContentMapper is per file but at this point we have all files
368
// bundled already. So, we call the mapper for the same contents but each file
369
// that has been included in the bundle...
370
let newText = file.text;
371
for (const input of Object.keys(res.metafile.inputs)) {
372
newText = opts.fileContentMapper(newText, input);
373
}
374
contents = Buffer.from(newText);
375
}
376
377
sourceMapFile = res.outputFiles.find(f => f.path === `${file.path}.map`);
378
}
379
380
const fileProps = {
381
contents: Buffer.from(contents),
382
sourceMap: sourceMapFile ? JSON.parse(sourceMapFile.text) : undefined, // support gulp-sourcemaps
383
path: file.path,
384
base: path.join(REPO_ROOT_PATH, opts.src)
385
};
386
files.push(new VinylFile(fileProps));
387
}
388
});
389
390
// await task; // FORCE serial bundling (makes debugging easier)
391
tasks.push(task);
392
}
393
394
await Promise.all(tasks);
395
return { files };
396
};
397
398
bundleAsync().then((output) => {
399
400
// bundle output (JS, CSS, SVG...)
401
es.readArray(output.files).pipe(bundlesStream);
402
403
// forward all resources
404
gulp.src(opts.resources, { base: `${opts.src}`, allowEmpty: true }).pipe(resourcesStream);
405
});
406
407
const result = es.merge(
408
bundlesStream,
409
resourcesStream
410
);
411
412
return result
413
.pipe(sourcemaps.write('./', {
414
sourceRoot: undefined,
415
addComment: true,
416
includeContent: true
417
}))
418
.pipe(opts.languages && opts.languages.length ? processNlsFiles({
419
out: opts.src,
420
fileHeader: opts.header || DEFAULT_FILE_HEADER,
421
languages: opts.languages
422
}) : es.through());
423
}
424
425
export interface IOptimizeCommonJSTaskOpts {
426
/**
427
* The paths to consider for optimizing.
428
*/
429
entryPoints: string[];
430
/**
431
* The folder to read files from.
432
*/
433
src: string;
434
/**
435
* ESBuild `platform` option: https://esbuild.github.io/api/#platform
436
*/
437
platform: 'browser' | 'node' | 'neutral';
438
/**
439
* ESBuild `external` option: https://esbuild.github.io/api/#external
440
*/
441
external: string[];
442
}
443
444
function optimizeCommonJSTask(opts: IOptimizeCommonJSTaskOpts): NodeJS.ReadWriteStream {
445
const src = opts.src;
446
const entryPoints = opts.entryPoints;
447
448
return gulp.src(entryPoints, { base: `${src}`, allowEmpty: true })
449
.pipe(es.map((f: any, cb) => {
450
esbuild.build({
451
entryPoints: [f.path],
452
bundle: true,
453
platform: opts.platform,
454
write: false,
455
external: opts.external
456
}).then(res => {
457
const jsFile = res.outputFiles[0];
458
f.contents = Buffer.from(jsFile.contents);
459
460
cb(undefined, f);
461
});
462
}));
463
}
464
465
export interface IOptimizeManualTaskOpts {
466
/**
467
* The paths to consider for concatenation. The entries
468
* will be concatenated in the order they are provided.
469
*/
470
src: string[];
471
/**
472
* Destination target to concatenate the entryPoints into.
473
*/
474
out: string;
475
}
476
477
function optimizeManualTask(options: IOptimizeManualTaskOpts[]): NodeJS.ReadWriteStream {
478
const concatenations = options.map(opt => {
479
return gulp
480
.src(opt.src)
481
.pipe(concat(opt.out));
482
});
483
484
return es.merge(...concatenations);
485
}
486
487
export function optimizeLoaderTask(src: string, out: string, bundleLoader: boolean, bundledFileHeader = '', externalLoaderInfo?: util.IExternalLoaderInfo): () => NodeJS.ReadWriteStream {
488
return () => loader(src, bundledFileHeader, bundleLoader, externalLoaderInfo).pipe(gulp.dest(out));
489
}
490
491
export interface IOptimizeTaskOpts {
492
/**
493
* Destination folder for the optimized files.
494
*/
495
out: string;
496
/**
497
* Optimize AMD modules (using our AMD loader).
498
*/
499
amd: IOptimizeAMDTaskOpts;
500
/**
501
* Optimize CommonJS modules (using esbuild).
502
*/
503
commonJS?: IOptimizeCommonJSTaskOpts;
504
/**
505
* Optimize manually by concatenating files.
506
*/
507
manual?: IOptimizeManualTaskOpts[];
508
}
509
510
export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream {
511
return function () {
512
const optimizers: NodeJS.ReadWriteStream[] = [];
513
if (!isAMD()) {
514
optimizers.push(optimizeESMTask(opts.amd, opts.commonJS));
515
} else {
516
optimizers.push(optimizeAMDTask(opts.amd));
517
518
if (opts.commonJS) {
519
optimizers.push(optimizeCommonJSTask(opts.commonJS));
520
}
521
}
522
523
if (opts.manual) {
524
optimizers.push(optimizeManualTask(opts.manual));
525
}
526
527
return es.merge(...optimizers).pipe(gulp.dest(opts.out));
528
};
529
}
530
531
export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => void {
532
const sourceMappingURL = sourceMapBaseUrl ? ((f: any) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined;
533
534
return cb => {
535
const cssnano = require('cssnano') as typeof import('cssnano');
536
const svgmin = require('gulp-svgmin') as typeof import('gulp-svgmin');
537
538
const jsFilter = filter('**/*.js', { restore: true });
539
const cssFilter = filter('**/*.css', { restore: true });
540
const svgFilter = filter('**/*.svg', { restore: true });
541
542
pump(
543
gulp.src([src + '/**', '!' + src + '/**/*.map']),
544
jsFilter,
545
sourcemaps.init({ loadMaps: true }),
546
es.map((f: any, cb) => {
547
esbuild.build({
548
entryPoints: [f.path],
549
minify: true,
550
sourcemap: 'external',
551
outdir: '.',
552
platform: 'node',
553
target: ['es2022'],
554
write: false
555
}).then(res => {
556
const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path))!;
557
const sourceMapFile = res.outputFiles.find(f => /\.js\.map$/.test(f.path))!;
558
559
const contents = Buffer.from(jsFile.contents);
560
const unicodeMatch = contents.toString().match(/[^\x00-\xFF]+/g);
561
if (unicodeMatch) {
562
cb(new Error(`Found non-ascii character ${unicodeMatch[0]} in the minified output of ${f.path}. Non-ASCII characters in the output can cause performance problems when loading. Please review if you have introduced a regular expression that esbuild is not automatically converting and convert it to using unicode escape sequences.`));
563
} else {
564
f.contents = contents;
565
f.sourceMap = JSON.parse(sourceMapFile.text);
566
567
cb(undefined, f);
568
}
569
}, cb);
570
}),
571
jsFilter.restore,
572
cssFilter,
573
gulpPostcss([cssnano({ preset: 'default' })]),
574
cssFilter.restore,
575
svgFilter,
576
svgmin(),
577
svgFilter.restore,
578
sourcemaps.write('./', {
579
sourceMappingURL,
580
sourceRoot: undefined,
581
includeContent: true,
582
addComment: true
583
} as any),
584
gulp.dest(src + '-min'),
585
(err: any) => cb(err));
586
};
587
}
588
589