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

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

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

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

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

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

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

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

52
function loader(bundledFileHeader: string, bundleLoader: boolean): NodeJS.ReadWriteStream {
A
Alex Dima 已提交
53 54 55 56 57 58 59 60 61 62
	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 已提交
63
	let isFirst = true;
A
Alex Dima 已提交
64 65
	return (
		gulp
66 67 68 69 70 71 72
			.src(sources, { base: 'out-build' })
			.pipe(es.through(function (data) {
				if (isFirst) {
					isFirst = false;
					this.emit('data', new VinylFile({
						path: 'fake',
						base: '',
73
						contents: Buffer.from(bundledFileHeader)
74 75 76 77 78 79 80 81 82 83 84 85
					}));
					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 已提交
86
	);
I
isidor 已提交
87 88
}

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

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

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

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

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

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

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

132 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
export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream {
J
Joao Moreno 已提交
165 166 167 168 169
	const entryPoints = opts.entryPoints;
	const otherSources = opts.otherSources;
	const resources = opts.resources;
	const loaderConfig = opts.loaderConfig;
	const bundledFileHeader = opts.header;
A
Alex Dima 已提交
170
	const bundleLoader = (typeof opts.bundleLoader === 'undefined' ? true : opts.bundleLoader);
J
Joao Moreno 已提交
171
	const out = opts.out;
I
isidor 已提交
172

173
	return function () {
J
Joao Moreno 已提交
174 175 176
		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 已提交
177

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

181 182 183
			toBundleStream(bundledFileHeader, result.files).pipe(bundlesStream);

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

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

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

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

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

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

249 250 251
			if (/@minifier_do_not_preserve/.test(text)) {
				return false;
			}
I
isidor 已提交
252

253
			const isOurCopyright = IS_OUR_COPYRIGHT_REGEXP.test(text);
I
isidor 已提交
254

255 256 257 258 259 260
			if (isOurCopyright) {
				if (f.__hasOurCopyright) {
					return false;
				}
				f.__hasOurCopyright = true;
				return true;
I
isidor 已提交
261 262
			}

263 264 265 266 267 268 269 270
			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 已提交
271 272
	};

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

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

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

291
	return cb => {
J
Joao Moreno 已提交
292 293
		const jsFilter = filter('**/*.js', { restore: true });
		const cssFilter = filter('**/*.css', { restore: true });
I
isidor 已提交
294

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

316 317
				cb(err);
			});
I
isidor 已提交
318
	};
D
Dirk Baeumer 已提交
319
}