optimize.ts 9.5 KB
Newer Older
I
isidor 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

J
Joao Moreno 已提交
6 7
'use strict';

8
import * as es from 'event-stream';
9
import * as gulp from 'gulp';
10 11 12
import * as concat from 'gulp-concat';
import * as filter from 'gulp-filter';
import * as flatmap from 'gulp-flatmap';
A
Alex Dima 已提交
13 14
import * as fancyLog from 'fancy-log';
import * as ansiColors from 'ansi-colors';
15 16
import * as path from 'path';
import * as pump from 'pump';
17 18
import * as VinylFile from 'vinyl';
import * as bundle from './bundle';
19 20
import { Language, processNlsFiles } from './i18n';
import { createStatsStream } from './stats';
21
import * as util from './util';
22

23 24
const REPO_ROOT_PATH = path.join(__dirname, '../..');

25
function log(prefix: string, message: string): void {
A
Alex Dima 已提交
26
	fancyLog(ansiColors.cyan('[' + prefix + ']'), message);
27
}
D
Dirk Baeumer 已提交
28

29
export function loaderConfig() {
M
Matt Bierner 已提交
30
	const result: any = {
I
isidor 已提交
31 32
		paths: {
			'vs': 'out-build/vs',
J
Joao Moreno 已提交
33
			'vscode': 'empty:'
I
isidor 已提交
34
		},
35
		amdModulesPattern: /^vs\//
I
isidor 已提交
36 37
	};

B
Benjamin Pasero 已提交
38 39
	result['vs/css'] = { inlineResources: true };

I
isidor 已提交
40
	return result;
41
}
I
isidor 已提交
42

J
Joao Moreno 已提交
43
const IS_OUR_COPYRIGHT_REGEXP = /Copyright \(C\) Microsoft Corporation/i;
I
isidor 已提交
44

45
function loader(src: string, bundledFileHeader: string, bundleLoader: boolean): NodeJS.ReadWriteStream {
A
Alex Dima 已提交
46
	let sources = [
47
		`${src}/vs/loader.js`
A
Alex Dima 已提交
48 49 50
	];
	if (bundleLoader) {
		sources = sources.concat([
51 52
			`${src}/vs/css.js`,
			`${src}/vs/nls.js`
A
Alex Dima 已提交
53 54 55
		]);
	}

J
Joao Moreno 已提交
56
	let isFirst = true;
A
Alex Dima 已提交
57 58
	return (
		gulp
59
			.src(sources, { base: `${src}` })
60 61 62 63 64
			.pipe(es.through(function (data) {
				if (isFirst) {
					isFirst = false;
					this.emit('data', new VinylFile({
						path: 'fake',
65
						base: '.',
66
						contents: Buffer.from(bundledFileHeader)
67 68 69 70 71 72 73
					}));
					this.emit('data', data);
				} else {
					this.emit('data', data);
				}
			}))
			.pipe(concat('vs/loader.js'))
A
Alex Dima 已提交
74
	);
I
isidor 已提交
75 76
}

77
function toConcatStream(src: string, bundledFileHeader: string, sources: bundle.IFile[], dest: string, fileContentMapper: (contents: string, path: string) => string): NodeJS.ReadWriteStream {
J
Joao Moreno 已提交
78
	const useSourcemaps = /\.js$/.test(dest) && !/\.nls\.js$/.test(dest);
I
isidor 已提交
79 80 81

	// If a bundle ends up including in any of the sources our copyright, then
	// insert a fake source at the beginning of each bundle with our copyright
J
Joao Moreno 已提交
82 83 84
	let containsOurCopyright = false;
	for (let i = 0, len = sources.length; i < len; i++) {
		const fileContents = sources[i].contents;
I
isidor 已提交
85 86 87 88 89 90 91 92 93 94 95 96 97
		if (IS_OUR_COPYRIGHT_REGEXP.test(fileContents)) {
			containsOurCopyright = true;
			break;
		}
	}

	if (containsOurCopyright) {
		sources.unshift({
			path: null,
			contents: bundledFileHeader
		});
	}

98
	const treatedSources = sources.map(function (source) {
99
		const root = source.path ? REPO_ROOT_PATH.replace(/\\/g, '/') : '';
100
		const base = source.path ? root + `/${src}` : '.';
101 102
		const path = source.path ? root + '/' + source.path.replace(/\\/g, '/') : 'fake';
		const contents = source.path ? fileContentMapper(source.contents, path) : source.contents;
I
isidor 已提交
103

104
		return new VinylFile({
105
			path: path,
I
isidor 已提交
106
			base: base,
107
			contents: Buffer.from(contents)
I
isidor 已提交
108 109 110 111 112
		});
	});

	return es.readArray(treatedSources)
		.pipe(useSourcemaps ? util.loadSourcemaps() : es.through())
113
		.pipe(concat(dest))
114
		.pipe(createStatsStream(dest));
I
isidor 已提交
115 116
}

117
function toBundleStream(src: string, bundledFileHeader: string, bundles: bundle.IConcatFile[], fileContentMapper: (contents: string, path: string) => string): NodeJS.ReadWriteStream {
118
	return es.merge(bundles.map(function (bundle) {
119
		return toConcatStream(src, bundledFileHeader, bundle.sources, bundle.dest, fileContentMapper);
I
isidor 已提交
120 121 122
	}));
}

123
export interface IOptimizeTaskOpts {
124 125 126 127
	/**
	 * The folder to read files from.
	 */
	src: string;
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
	/**
	 * (for AMD files, will get bundled and get Copyright treatment)
	 */
	entryPoints: bundle.IEntryPoint[];
	/**
	 * (svg, etc.)
	 */
	resources: string[];
	loaderConfig: any;
	/**
	 * (true by default - append css and nls to loader)
	 */
	bundleLoader?: boolean;
	/**
	 * (basically the Copyright treatment)
	 */
B
Benjamin Pasero 已提交
144
	header?: string;
145 146 147 148 149 150 151 152
	/**
	 * (emit bundleInfo.json file)
	 */
	bundleInfo: boolean;
	/**
	 * (out folder name)
	 */
	out: string;
153 154 155 156
	/**
	 * (out folder name)
	 */
	languages?: Language[];
157 158 159 160 161 162
	/**
	 * File contents interceptor
	 * @param contents The contens of the file
	 * @param path The absolute file path, always using `/`, even on Windows
	 */
	fileContentMapper?: (contents: string, path: string) => string;
163
}
164

B
Benjamin Pasero 已提交
165 166 167 168 169 170
const DEFAULT_FILE_HEADER = [
	'/*!--------------------------------------------------------',
	' * Copyright (C) Microsoft Corporation. All rights reserved.',
	' *--------------------------------------------------------*/'
].join('\n');

171
export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream {
172
	const src = opts.src;
J
Joao Moreno 已提交
173 174 175
	const entryPoints = opts.entryPoints;
	const resources = opts.resources;
	const loaderConfig = opts.loaderConfig;
B
Benjamin Pasero 已提交
176
	const bundledFileHeader = opts.header || DEFAULT_FILE_HEADER;
A
Alex Dima 已提交
177
	const bundleLoader = (typeof opts.bundleLoader === 'undefined' ? true : opts.bundleLoader);
J
Joao Moreno 已提交
178
	const out = opts.out;
179
	const fileContentMapper = opts.fileContentMapper || ((contents: string, _path: string) => contents);
I
isidor 已提交
180

181
	return function () {
J
João Moreno 已提交
182
		const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps');
183

J
Joao Moreno 已提交
184 185 186
		const bundlesStream = es.through(); // this stream will contain the bundled files
		const resourcesStream = es.through(); // this stream will contain the resources
		const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json
I
isidor 已提交
187

188
		bundle.bundle(entryPoints, loaderConfig, function (err, result) {
189
			if (err || !result) { return bundlesStream.emit('error', JSON.stringify(err)); }
I
isidor 已提交
190

191
			toBundleStream(src, bundledFileHeader, result.files, fileContentMapper).pipe(bundlesStream);
192 193

			// Remove css inlined resources
J
Joao Moreno 已提交
194
			const filteredResources = resources.slice();
195
			result.cssInlinedResources.forEach(function (resource) {
J
Joao Moreno 已提交
196 197 198
				if (process.env['VSCODE_BUILD_VERBOSE']) {
					log('optimizer', 'excluding inlined: ' + resource);
				}
199 200
				filteredResources.push('!' + resource);
			});
A
Alex Dima 已提交
201
			gulp.src(filteredResources, { base: `${src}`, allowEmpty: true }).pipe(resourcesStream);
202

203
			const bundleInfoArray: VinylFile[] = [];
204
			if (opts.bundleInfo) {
205
				bundleInfoArray.push(new VinylFile({
206 207
					path: 'bundleInfo.json',
					base: '.',
208
					contents: Buffer.from(JSON.stringify(result.bundleData, null, '\t'))
209 210 211
				}));
			}
			es.readArray(bundleInfoArray).pipe(bundleInfoStream);
I
isidor 已提交
212 213
		});

J
Joao Moreno 已提交
214
		const result = es.merge(
215
			loader(src, bundledFileHeader, bundleLoader),
I
isidor 已提交
216
			bundlesStream,
217 218
			resourcesStream,
			bundleInfoStream
I
isidor 已提交
219 220 221 222
		);

		return result
			.pipe(sourcemaps.write('./', {
223
				sourceRoot: undefined,
I
isidor 已提交
224 225 226
				addComment: true,
				includeContent: true
			}))
227 228 229 230
			.pipe(opts.languages && opts.languages.length ? processNlsFiles({
				fileHeader: bundledFileHeader,
				languages: opts.languages
			}) : es.through())
I
isidor 已提交
231 232
			.pipe(gulp.dest(out));
	};
D
Dirk Baeumer 已提交
233
}
I
isidor 已提交
234

J
João Moreno 已提交
235 236 237
// declare class FileWithCopyright extends VinylFile {
// 	public __hasOurCopyright: boolean;
// }
I
isidor 已提交
238
/**
J
Joao Moreno 已提交
239 240
 * Wrap around uglify and allow the preserveComments function
 * to have a file "context" to include our copyright only once per file.
I
isidor 已提交
241
 */
242
function uglifyWithCopyrights(): NodeJS.ReadWriteStream {
J
João Moreno 已提交
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
	const esbuild = require('gulp-esbuild') as typeof import('gulp-esbuild');

	// const preserveComments = (f: FileWithCopyright) => {
	// 	return (_node: any, comment: { value: string; type: string; }) => {
	// 		const text = comment.value;
	// 		const type = comment.type;

	// 		if (/@minifier_do_not_preserve/.test(text)) {
	// 			return false;
	// 		}

	// 		const isOurCopyright = IS_OUR_COPYRIGHT_REGEXP.test(text);

	// 		if (isOurCopyright) {
	// 			if (f.__hasOurCopyright) {
	// 				return false;
	// 			}
	// 			f.__hasOurCopyright = true;
	// 			return true;
	// 		}

	// 		if ('comment2' === type) {
	// 			// check for /*!. Note that text doesn't contain leading /*
	// 			return (text.length > 0 && text[0] === '!') || /@preserve|license|@cc_on|copyright/i.test(text);
	// 		} else if ('comment1' === type) {
	// 			return /license|copyright/i.test(text);
	// 		}
	// 		return false;
	// 	};
	// };
I
isidor 已提交
273

274 275 276
	const input = es.through();
	const output = input
		.pipe(flatmap((stream, f) => {
J
João Moreno 已提交
277 278 279 280
			return stream.pipe(esbuild({
				outfile: f.relative,
				sourcemap: true,
				minify: true,
J
João Moreno 已提交
281 282
				platform: 'node',
				target: ['node12.18']
283
			}));
284
		}));
I
isidor 已提交
285

286
	return es.duplex(input, output);
I
isidor 已提交
287 288
}

E
Erich Gamma 已提交
289
export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => void {
290
	const sourceMappingURL = sourceMapBaseUrl ? ((f: any) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined;
J
Joao Moreno 已提交
291

292
	return cb => {
J
João Moreno 已提交
293 294
		const minifyCSS = require('gulp-cssnano') as typeof import('gulp-cssnano');
		const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps');
295

J
Joao Moreno 已提交
296 297
		const jsFilter = filter('**/*.js', { restore: true });
		const cssFilter = filter('**/*.css', { restore: true });
I
isidor 已提交
298

299 300 301 302 303 304 305 306 307
		pump(
			gulp.src([src + '/**', '!' + src + '/**/*.map']),
			jsFilter,
			sourcemaps.init({ loadMaps: true }),
			uglifyWithCopyrights(),
			jsFilter.restore,
			cssFilter,
			minifyCSS({ reduceIdents: false }),
			cssFilter.restore,
R
Rob Lourens 已提交
308 309 310 311 312 313 314
			(<any>sourcemaps).mapSources((sourcePath: string) => {
				if (sourcePath === 'bootstrap-fork.js') {
					return 'bootstrap-fork.orig.js';
				}

				return sourcePath;
			}),
315
			sourcemaps.write('./', {
J
Joao Moreno 已提交
316
				sourceMappingURL,
317
				sourceRoot: undefined,
I
isidor 已提交
318
				includeContent: true,
J
Joao Moreno 已提交
319
				addComment: true
320
			} as any),
J
João Moreno 已提交
321 322
			gulp.dest(src + '-min'),
			(err: any) => cb(err));
I
isidor 已提交
323
	};
D
Dirk Baeumer 已提交
324
}