optimize.ts 9.0 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 25
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 i18n from './i18n';
import * as gulpUtil from 'gulp-util';
import * as flatmap from 'gulp-flatmap';
import * as pump from 'pump';
import * as sm from 'source-map';
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 74 75 76 77 78 79 80 81 82 83 84 85 86
			.src(sources, { base: 'out-build' })
			.pipe(es.through(function (data) {
				if (isFirst) {
					isFirst = false;
					this.emit('data', new VinylFile({
						path: 'fake',
						base: '',
						contents: new Buffer(bundledFileHeader)
					}));
					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 118 119 120 121 122 123 124 125 126
			path: source.path ? root + '/' + source.path.replace(/\\/g, '/') : 'fake',
			base: base,
			contents: new Buffer(source.contents)
		});
	});

	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
	/**
	 * (languages to process)
	 */
D
Dirk Baeumer 已提交
166
	languages: i18n.Language[];
167
}
168
export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream {
J
Joao Moreno 已提交
169 170 171 172 173
	const entryPoints = opts.entryPoints;
	const otherSources = opts.otherSources;
	const resources = opts.resources;
	const loaderConfig = opts.loaderConfig;
	const bundledFileHeader = opts.header;
A
Alex Dima 已提交
174
	const bundleLoader = (typeof opts.bundleLoader === 'undefined' ? true : opts.bundleLoader);
J
Joao Moreno 已提交
175
	const out = opts.out;
I
isidor 已提交
176

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

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

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

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

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

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

		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 已提交
222
		const result = es.merge(
A
Alex Dima 已提交
223
			loader(bundledFileHeader, bundleLoader),
I
isidor 已提交
224 225
			bundlesStream,
			otherSourcesStream,
226 227
			resourcesStream,
			bundleInfoStream
I
isidor 已提交
228 229 230 231 232 233 234 235
		);

		return result
			.pipe(sourcemaps.write('./', {
				sourceRoot: null,
				addComment: true,
				includeContent: true
			}))
A
Alex Dima 已提交
236
			.pipe(i18n.processNlsFiles({
237 238
				fileHeader: bundledFileHeader,
				languages: opts.languages
A
Alex Dima 已提交
239
			}))
I
isidor 已提交
240 241
			.pipe(gulp.dest(out));
	};
D
Dirk Baeumer 已提交
242
}
I
isidor 已提交
243

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

257 258 259
			if (/@minifier_do_not_preserve/.test(text)) {
				return false;
			}
I
isidor 已提交
260

261
			const isOurCopyright = IS_OUR_COPYRIGHT_REGEXP.test(text);
I
isidor 已提交
262

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

271 272 273 274 275 276 277 278
			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 已提交
279 280
	};

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

293
	return es.duplex(input, output);
I
isidor 已提交
294 295
}

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

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

303 304 305 306 307 308 309 310 311 312
		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 已提交
313
				sourceMappingURL,
I
isidor 已提交
314 315
				sourceRoot: null,
				includeContent: true,
J
Joao Moreno 已提交
316
				addComment: true
317 318
			}),
			gulp.dest(src + '-min')
319 320 321 322
			, (err: any) => {
				if (err instanceof uglify.GulpUglifyError) {
					console.error(`Uglify error in '${err.cause && err.cause.filename}'`);
				}
323

324 325
				cb(err);
			});
I
isidor 已提交
326
	};
D
Dirk Baeumer 已提交
327
}