optimize.ts 8.9 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 9 10 11 12 13
import * as path from 'path';
import * as gulp from 'gulp';
import * as sourcemaps from 'gulp-sourcemaps';
import * as filter from 'gulp-filter';
import * as minifyCSS from 'gulp-cssnano';
import * as uglify from 'gulp-uglify';
J
Joao Moreno 已提交
14 15
import * as composer from 'gulp-uglify/composer';
import * as uglifyes from 'uglify-es';
16 17 18 19 20 21 22 23 24
import * as es from 'event-stream';
import * as concat from 'gulp-concat';
import * as VinylFile from 'vinyl';
import * as bundle from './bundle';
import * as util from './util';
import * as gulpUtil from 'gulp-util';
import * as flatmap from 'gulp-flatmap';
import * as pump from 'pump';
import * as sm from 'source-map';
25
import { Language } from './i18n';
26

27 28
const REPO_ROOT_PATH = path.join(__dirname, '../..');

29
function log(prefix: string, message: string): void {
30 31
	gulpUtil.log(gulpUtil.colors.cyan('[' + prefix + ']'), message);
}
D
Dirk Baeumer 已提交
32

E
Erich Gamma 已提交
33
export function loaderConfig(emptyPaths?: string[]) {
J
Joao Moreno 已提交
34
	const result = {
I
isidor 已提交
35 36
		paths: {
			'vs': 'out-build/vs',
J
Joao Moreno 已提交
37
			'vscode': 'empty:'
I
isidor 已提交
38
		},
39
		nodeModules: emptyPaths || []
I
isidor 已提交
40 41
	};

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

I
isidor 已提交
44
	return result;
45
}
I
isidor 已提交
46

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

49 50 51 52
declare class FileSourceMap extends VinylFile {
	public sourceMap: sm.RawSourceMap;
}

53
function loader(bundledFileHeader: string, bundleLoader: boolean): NodeJS.ReadWriteStream {
A
Alex Dima 已提交
54 55 56 57 58 59 60 61 62 63
	let sources = [
		'out-build/vs/loader.js'
	];
	if (bundleLoader) {
		sources = sources.concat([
			'out-build/vs/css.js',
			'out-build/vs/nls.js'
		]);
	}

J
Joao Moreno 已提交
64
	let isFirst = true;
A
Alex Dima 已提交
65 66
	return (
		gulp
67 68 69 70 71 72 73
			.src(sources, { base: 'out-build' })
			.pipe(es.through(function (data) {
				if (isFirst) {
					isFirst = false;
					this.emit('data', new VinylFile({
						path: 'fake',
						base: '',
74
						contents: Buffer.from(bundledFileHeader)
75 76 77 78 79 80 81 82 83 84 85 86
					}));
					this.emit('data', data);
				} else {
					this.emit('data', data);
				}
			}))
			.pipe(util.loadSourcemaps())
			.pipe(concat('vs/loader.js'))
			.pipe(es.mapSync<FileSourceMap, FileSourceMap>(function (f) {
				f.sourceMap.sourceRoot = util.toFileUri(path.join(REPO_ROOT_PATH, 'src'));
				return f;
			}))
A
Alex Dima 已提交
87
	);
I
isidor 已提交
88 89
}

90
function toConcatStream(bundledFileHeader: string, sources: bundle.IFile[], dest: string): NodeJS.ReadWriteStream {
J
Joao Moreno 已提交
91
	const useSourcemaps = /\.js$/.test(dest) && !/\.nls\.js$/.test(dest);
I
isidor 已提交
92 93 94

	// 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 已提交
95 96 97
	let containsOurCopyright = false;
	for (let i = 0, len = sources.length; i < len; i++) {
		const fileContents = sources[i].contents;
I
isidor 已提交
98 99 100 101 102 103 104 105 106 107 108 109 110
		if (IS_OUR_COPYRIGHT_REGEXP.test(fileContents)) {
			containsOurCopyright = true;
			break;
		}
	}

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

111
	const treatedSources = sources.map(function (source) {
112
		const root = source.path ? REPO_ROOT_PATH.replace(/\\/g, '/') : '';
J
Joao Moreno 已提交
113
		const base = source.path ? root + '/out-build' : '';
I
isidor 已提交
114

115
		return new VinylFile({
I
isidor 已提交
116 117
			path: source.path ? root + '/' + source.path.replace(/\\/g, '/') : 'fake',
			base: base,
118
			contents: Buffer.from(source.contents)
I
isidor 已提交
119 120 121 122 123 124 125 126
		});
	});

	return es.readArray(treatedSources)
		.pipe(useSourcemaps ? util.loadSourcemaps() : es.through())
		.pipe(concat(dest));
}

127 128
function toBundleStream(bundledFileHeader: string, bundles: bundle.IConcatFile[]): NodeJS.ReadWriteStream {
	return es.merge(bundles.map(function (bundle) {
I
isidor 已提交
129 130 131 132
		return toConcatStream(bundledFileHeader, bundle.sources, bundle.dest);
	}));
}

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
export interface IOptimizeTaskOpts {
	/**
	 * (for AMD files, will get bundled and get Copyright treatment)
	 */
	entryPoints: bundle.IEntryPoint[];
	/**
	 * (for non-AMD files that should get Copyright treatment)
	 */
	otherSources: string[];
	/**
	 * (svg, etc.)
	 */
	resources: string[];
	loaderConfig: any;
	/**
	 * (true by default - append css and nls to loader)
	 */
	bundleLoader?: boolean;
	/**
	 * (basically the Copyright treatment)
	 */
	header: string;
	/**
	 * (emit bundleInfo.json file)
	 */
	bundleInfo: boolean;
	/**
	 * (out folder name)
	 */
	out: string;
163 164 165 166
	/**
	 * (out folder name)
	 */
	languages?: Language[];
167
}
168

169
export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream {
J
Joao Moreno 已提交
170 171 172 173 174
	const entryPoints = opts.entryPoints;
	const otherSources = opts.otherSources;
	const resources = opts.resources;
	const loaderConfig = opts.loaderConfig;
	const bundledFileHeader = opts.header;
A
Alex Dima 已提交
175
	const bundleLoader = (typeof opts.bundleLoader === 'undefined' ? true : opts.bundleLoader);
J
Joao Moreno 已提交
176
	const out = opts.out;
I
isidor 已提交
177

178
	return function () {
J
Joao Moreno 已提交
179 180 181
		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 已提交
182

183
		bundle.bundle(entryPoints, loaderConfig, function (err, result) {
I
isidor 已提交
184 185
			if (err) { return bundlesStream.emit('error', JSON.stringify(err)); }

186 187 188
			toBundleStream(bundledFileHeader, result.files).pipe(bundlesStream);

			// Remove css inlined resources
J
Joao Moreno 已提交
189
			const filteredResources = resources.slice();
190
			result.cssInlinedResources.forEach(function (resource) {
J
Joao Moreno 已提交
191 192 193
				if (process.env['VSCODE_BUILD_VERBOSE']) {
					log('optimizer', 'excluding inlined: ' + resource);
				}
194 195 196
				filteredResources.push('!' + resource);
			});
			gulp.src(filteredResources, { base: 'out-build' }).pipe(resourcesStream);
197

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

J
Joao Moreno 已提交
209
		const otherSourcesStream = es.through();
210
		const otherSourcesStreamArr: NodeJS.ReadWriteStream[] = [];
I
isidor 已提交
211 212 213 214 215 216 217 218 219 220 221 222

		gulp.src(otherSources, { base: 'out-build' })
			.pipe(es.through(function (data) {
				otherSourcesStreamArr.push(toConcatStream(bundledFileHeader, [data], data.relative));
			}, function () {
				if (!otherSourcesStreamArr.length) {
					setTimeout(function () { otherSourcesStream.emit('end'); }, 0);
				} else {
					es.merge(otherSourcesStreamArr).pipe(otherSourcesStream);
				}
			}));

J
Joao Moreno 已提交
223
		const result = es.merge(
A
Alex Dima 已提交
224
			loader(bundledFileHeader, bundleLoader),
I
isidor 已提交
225 226
			bundlesStream,
			otherSourcesStream,
227 228
			resourcesStream,
			bundleInfoStream
I
isidor 已提交
229 230 231 232 233 234 235 236 237 238
		);

		return result
			.pipe(sourcemaps.write('./', {
				sourceRoot: null,
				addComment: true,
				includeContent: true
			}))
			.pipe(gulp.dest(out));
	};
D
Dirk Baeumer 已提交
239
}
I
isidor 已提交
240

241 242 243
declare class FileWithCopyright extends VinylFile {
	public __hasOurCopyright: boolean;
}
I
isidor 已提交
244
/**
J
Joao Moreno 已提交
245 246
 * Wrap around uglify and allow the preserveComments function
 * to have a file "context" to include our copyright only once per file.
I
isidor 已提交
247
 */
248 249 250
function uglifyWithCopyrights(): NodeJS.ReadWriteStream {
	const preserveComments = (f: FileWithCopyright) => {
		return (node, comment: { value: string; type: string; }) => {
251 252
			const text = comment.value;
			const type = comment.type;
I
isidor 已提交
253

254 255 256
			if (/@minifier_do_not_preserve/.test(text)) {
				return false;
			}
I
isidor 已提交
257

258
			const isOurCopyright = IS_OUR_COPYRIGHT_REGEXP.test(text);
I
isidor 已提交
259

260 261 262 263 264 265
			if (isOurCopyright) {
				if (f.__hasOurCopyright) {
					return false;
				}
				f.__hasOurCopyright = true;
				return true;
I
isidor 已提交
266 267
			}

268 269 270 271 272 273 274 275
			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 已提交
276 277
	};

J
Joao Moreno 已提交
278
	const minify = composer(uglifyes);
279 280 281
	const input = es.through();
	const output = input
		.pipe(flatmap((stream, f) => {
J
Joao Moreno 已提交
282
			return stream.pipe(minify({
J
Joao Moreno 已提交
283
				output: {
J
Joao Moreno 已提交
284
					comments: preserveComments(<FileWithCopyright>f),
285
					max_line_len: 1024
J
Joao Moreno 已提交
286
				}
287
			}));
288
		}));
I
isidor 已提交
289

290
	return es.duplex(input, output);
I
isidor 已提交
291 292
}

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

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

300 301 302 303 304 305 306 307 308 309
		pump(
			gulp.src([src + '/**', '!' + src + '/**/*.map']),
			jsFilter,
			sourcemaps.init({ loadMaps: true }),
			uglifyWithCopyrights(),
			jsFilter.restore,
			cssFilter,
			minifyCSS({ reduceIdents: false }),
			cssFilter.restore,
			sourcemaps.write('./', {
J
Joao Moreno 已提交
310
				sourceMappingURL,
I
isidor 已提交
311 312
				sourceRoot: null,
				includeContent: true,
J
Joao Moreno 已提交
313
				addComment: true
314 315
			}),
			gulp.dest(src + '-min')
316 317 318 319
			, (err: any) => {
				if (err instanceof uglify.GulpUglifyError) {
					console.error(`Uglify error in '${err.cause && err.cause.filename}'`);
				}
320

321 322
				cb(err);
			});
I
isidor 已提交
323
	};
D
Dirk Baeumer 已提交
324
}