standalone.ts 11.8 KB
Newer Older
A
Alex Dima 已提交
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as ts from 'typescript';
import * as fs from 'fs';
import * as path from 'path';
9
import * as tss from './treeshaking';
A
Alex Dima 已提交
10 11 12 13

const REPO_ROOT = path.join(__dirname, '../../');
const SRC_DIR = path.join(REPO_ROOT, 'src');

14 15
let dirCache: { [dir: string]: boolean; } = {};

A
Alex Dima 已提交
16
function writeFile(filePath: string, contents: Buffer | string): void {
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
	function ensureDirs(dirPath: string): void {
		if (dirCache[dirPath]) {
			return;
		}
		dirCache[dirPath] = true;

		ensureDirs(path.dirname(dirPath));
		if (fs.existsSync(dirPath)) {
			return;
		}
		fs.mkdirSync(dirPath);
	}
	ensureDirs(path.dirname(filePath));
	fs.writeFileSync(filePath, contents);
}

export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string }): void {
J
Johannes Rieken 已提交
34
	const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString());
M
Matt Bierner 已提交
35 36 37
	let compilerOptions: { [key: string]: any };
	if (tsConfig.extends) {
		compilerOptions = Object.assign({}, require(path.join(options.sourcesRoot, tsConfig.extends)).compilerOptions, tsConfig.compilerOptions);
A
Alex Dima 已提交
38
		delete tsConfig.extends;
M
Matt Bierner 已提交
39 40 41 42 43
	} else {
		compilerOptions = tsConfig.compilerOptions;
	}
	tsConfig.compilerOptions = compilerOptions;

J
Johannes Rieken 已提交
44
	compilerOptions.noEmit = false;
M
Matt Bierner 已提交
45 46 47 48 49
	compilerOptions.noUnusedLocals = false;
	compilerOptions.preserveConstEnums = false;
	compilerOptions.declaration = false;
	compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic;

A
Alex Dima 已提交
50

M
Matt Bierner 已提交
51
	options.compilerOptions = compilerOptions;
A
Alex Dima 已提交
52

53
	console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`);
A
Alex Dima 已提交
54

55 56 57
	// Take the extra included .d.ts files from `tsconfig.monaco.json`
	options.typings = (<string[]>tsConfig.include).filter(includedFile => /\.d\.ts$/.test(includedFile));

J
Johannes Rieken 已提交
58 59 60 61 62 63 64
	// Add extra .d.ts files from `node_modules/@types/`
	if (Array.isArray(options.compilerOptions?.types)) {
		options.compilerOptions.types.forEach((type: string) => {
			options.typings.push(`../node_modules/@types/${type}/index.d.ts`);
		});
	}

65 66 67 68 69 70
	let result = tss.shake(options);
	for (let fileName in result) {
		if (result.hasOwnProperty(fileName)) {
			writeFile(path.join(options.destRoot, fileName), result[fileName]);
		}
	}
71
	let copied: { [fileName: string]: boolean; } = {};
72 73 74 75 76 77 78
	const copyFile = (fileName: string) => {
		if (copied[fileName]) {
			return;
		}
		copied[fileName] = true;
		const srcPath = path.join(options.sourcesRoot, fileName);
		const dstPath = path.join(options.destRoot, fileName);
A
Alex Dima 已提交
79
		writeFile(dstPath, fs.readFileSync(srcPath));
80
	};
M
Matt Bierner 已提交
81
	const writeOutputFile = (fileName: string, contents: string | Buffer) => {
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
		writeFile(path.join(options.destRoot, fileName), contents);
	};
	for (let fileName in result) {
		if (result.hasOwnProperty(fileName)) {
			const fileContents = result[fileName];
			const info = ts.preProcessFile(fileContents);

			for (let i = info.importedFiles.length - 1; i >= 0; i--) {
				const importedFileName = info.importedFiles[i].fileName;

				let importedFilePath: string;
				if (/^vs\/css!/.test(importedFileName)) {
					importedFilePath = importedFileName.substr('vs/css!'.length) + '.css';
				} else {
					importedFilePath = importedFileName;
				}
				if (/(^\.\/)|(^\.\.\/)/.test(importedFilePath)) {
					importedFilePath = path.join(path.dirname(fileName), importedFilePath);
				}

				if (/\.css$/.test(importedFilePath)) {
					transportCSS(importedFilePath, copyFile, writeOutputFile);
				} else {
					if (fs.existsSync(path.join(options.sourcesRoot, importedFilePath + '.js'))) {
						copyFile(importedFilePath + '.js');
					}
				}
			}
		}
	}

113
	delete tsConfig.compilerOptions.moduleResolution;
A
Alex Dima 已提交
114 115
	writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t'));

116 117 118 119 120 121 122 123 124 125 126 127
	[
		'vs/css.build.js',
		'vs/css.d.ts',
		'vs/css.js',
		'vs/loader.js',
		'vs/nls.build.js',
		'vs/nls.d.ts',
		'vs/nls.js',
		'vs/nls.mock.ts',
	].forEach(copyFile);
}

128 129
export interface IOptions2 {
	srcFolder: string;
A
Alex Dima 已提交
130 131
	outFolder: string;
	outResourcesFolder: string;
132 133
	ignores: string[];
	renames: { [filename: string]: string; };
A
Alex Dima 已提交
134 135
}

136 137
export function createESMSourcesAndResources2(options: IOptions2): void {
	const SRC_FOLDER = path.join(REPO_ROOT, options.srcFolder);
A
Alex Dima 已提交
138 139 140
	const OUT_FOLDER = path.join(REPO_ROOT, options.outFolder);
	const OUT_RESOURCES_FOLDER = path.join(REPO_ROOT, options.outResourcesFolder);

141 142 143
	const getDestAbsoluteFilePath = (file: string): string => {
		let dest = options.renames[file.replace(/\\/g, '/')] || file;
		if (dest === 'tsconfig.json') {
A
Alex Dima 已提交
144
			return path.join(OUT_FOLDER, `tsconfig.json`);
145 146 147
		}
		if (/\.ts$/.test(dest)) {
			return path.join(OUT_FOLDER, dest);
A
Alex Dima 已提交
148
		}
149
		return path.join(OUT_RESOURCES_FOLDER, dest);
A
Alex Dima 已提交
150 151
	};

152
	const allFiles = walkDirRecursive(SRC_FOLDER);
153
	for (const file of allFiles) {
154 155 156

		if (options.ignores.indexOf(file.replace(/\\/g, '/')) >= 0) {
			continue;
A
Alex Dima 已提交
157 158
		}

159 160 161
		if (file === 'tsconfig.json') {
			const tsConfig = JSON.parse(fs.readFileSync(path.join(SRC_FOLDER, file)).toString());
			tsConfig.compilerOptions.module = 'es6';
162
			tsConfig.compilerOptions.outDir = path.join(path.relative(OUT_FOLDER, OUT_RESOURCES_FOLDER), 'vs').replace(/\\/g, '/');
163 164
			write(getDestAbsoluteFilePath(file), JSON.stringify(tsConfig, null, '\t'));
			continue;
A
Alex Dima 已提交
165
		}
166

167
		if (/\.d\.ts$/.test(file) || /\.css$/.test(file) || /\.js$/.test(file) || /\.ttf$/.test(file)) {
168 169 170
			// Transport the files directly
			write(getDestAbsoluteFilePath(file), fs.readFileSync(path.join(SRC_FOLDER, file)));
			continue;
A
Alex Dima 已提交
171 172
		}

173 174 175
		if (/\.ts$/.test(file)) {
			// Transform the .ts file
			let fileContents = fs.readFileSync(path.join(SRC_FOLDER, file)).toString();
A
Alex Dima 已提交
176

177
			const info = ts.preProcessFile(fileContents);
A
Alex Dima 已提交
178

179 180 181 182 183 184 185 186 187 188
			for (let i = info.importedFiles.length - 1; i >= 0; i--) {
				const importedFilename = info.importedFiles[i].fileName;
				const pos = info.importedFiles[i].pos;
				const end = info.importedFiles[i].end;

				let importedFilepath: string;
				if (/^vs\/css!/.test(importedFilename)) {
					importedFilepath = importedFilename.substr('vs/css!'.length) + '.css';
				} else {
					importedFilepath = importedFilename;
A
Alex Dima 已提交
189
				}
190 191
				if (/(^\.\/)|(^\.\.\/)/.test(importedFilepath)) {
					importedFilepath = path.join(path.dirname(file), importedFilepath);
A
Alex Dima 已提交
192 193
				}

194
				let relativePath: string;
195
				if (importedFilepath === path.dirname(file).replace(/\\/g, '/')) {
196
					relativePath = '../' + path.basename(path.dirname(file));
197
				} else if (importedFilepath === path.dirname(path.dirname(file)).replace(/\\/g, '/')) {
198 199 200
					relativePath = '../../' + path.basename(path.dirname(path.dirname(file)));
				} else {
					relativePath = path.relative(path.dirname(file), importedFilepath);
A
Alex Dima 已提交
201
				}
202
				relativePath = relativePath.replace(/\\/g, '/');
203 204
				if (!/(^\.\/)|(^\.\.\/)/.test(relativePath)) {
					relativePath = './' + relativePath;
A
Alex Dima 已提交
205
				}
206 207 208 209 210
				fileContents = (
					fileContents.substring(0, pos + 1)
					+ relativePath
					+ fileContents.substring(end + 1)
				);
A
Alex Dima 已提交
211 212
			}

213 214 215
			fileContents = fileContents.replace(/import ([a-zA-z0-9]+) = require\(('[^']+')\);/g, function (_, m1, m2) {
				return `import * as ${m1} from ${m2};`;
			});
A
Alex Dima 已提交
216

217
			write(getDestAbsoluteFilePath(file), fileContents);
A
Alex Dima 已提交
218 219 220
			continue;
		}

221 222
		console.log(`UNKNOWN FILE: ${file}`);
	}
A
Alex Dima 已提交
223 224


225 226 227 228 229 230 231 232
	function walkDirRecursive(dir: string): string[] {
		if (dir.charAt(dir.length - 1) !== '/' || dir.charAt(dir.length - 1) !== '\\') {
			dir += '/';
		}
		let result: string[] = [];
		_walkDirRecursive(dir, result, dir.length);
		return result;
	}
A
Alex Dima 已提交
233

234 235 236 237 238 239
	function _walkDirRecursive(dir: string, result: string[], trimPos: number): void {
		const files = fs.readdirSync(dir);
		for (let i = 0; i < files.length; i++) {
			const file = path.join(dir, files[i]);
			if (fs.statSync(file).isDirectory()) {
				_walkDirRecursive(file, result, trimPos);
A
Alex Dima 已提交
240
			} else {
241
				result.push(file.substr(trimPos));
A
Alex Dima 已提交
242 243 244 245
			}
		}
	}

246 247 248
	function write(absoluteFilePath: string, contents: string | Buffer): void {
		if (/(\.ts$)|(\.js$)/.test(absoluteFilePath)) {
			contents = toggleComments(contents.toString());
A
Alex Dima 已提交
249
		}
250 251 252 253 254
		writeFile(absoluteFilePath, contents);

		function toggleComments(fileContents: string): string {
			let lines = fileContents.split(/\r\n|\r|\n/);
			let mode = 0;
255 256
			for (let i = 0; i < lines.length; i++) {
				const line = lines[i];
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
				if (mode === 0) {
					if (/\/\/ ESM-comment-begin/.test(line)) {
						mode = 1;
						continue;
					}
					if (/\/\/ ESM-uncomment-begin/.test(line)) {
						mode = 2;
						continue;
					}
					continue;
				}

				if (mode === 1) {
					if (/\/\/ ESM-comment-end/.test(line)) {
						mode = 0;
						continue;
					}
					lines[i] = '// ' + line;
					continue;
				}
A
Alex Dima 已提交
277

278 279 280 281 282 283 284 285 286 287
				if (mode === 2) {
					if (/\/\/ ESM-uncomment-end/.test(line)) {
						mode = 0;
						continue;
					}
					lines[i] = line.replace(/^(\s*)\/\/ ?/, function (_, indent) {
						return indent;
					});
				}
			}
A
Alex Dima 已提交
288

289 290 291
			return lines.join('\n');
		}
	}
A
Alex Dima 已提交
292 293
}

294
function transportCSS(module: string, enqueue: (module: string) => void, write: (path: string, contents: string | Buffer) => void): boolean {
A
Alex Dima 已提交
295 296 297 298 299 300 301

	if (!/\.css/.test(module)) {
		return false;
	}

	const filename = path.join(SRC_DIR, module);
	const fileContents = fs.readFileSync(filename).toString();
C
ChaseKnowlden 已提交
302
	const inlineResources = 'base64'; // see https://github.com/microsoft/monaco-editor/issues/148
A
Alex Dima 已提交
303

304
	const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64');
A
Alex Dima 已提交
305 306 307
	write(module, newContents);
	return true;

308
	function _rewriteOrInlineUrls(contents: string, forceBase64: boolean): string {
A
Alex Dima 已提交
309
		return _replaceURL(contents, (url) => {
310 311 312 313 314 315
			const fontMatch = url.match(/^(.*).ttf\?(.*)$/);
			if (fontMatch) {
				const relativeFontPath = `${fontMatch[1]}.ttf`; // trim the query parameter
				const fontPath = path.join(path.dirname(module), relativeFontPath);
				enqueue(fontPath);
				return relativeFontPath;
A
Alex Dima 已提交
316 317
			}

318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
			const imagePath = path.join(path.dirname(module), url);
			const fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath));
			const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png';
			let DATA = ';base64,' + fileContents.toString('base64');

			if (!forceBase64 && /\.svg$/.test(url)) {
				// .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris
				let newText = fileContents.toString()
					.replace(/"/g, '\'')
					.replace(/</g, '%3C')
					.replace(/>/g, '%3E')
					.replace(/&/g, '%26')
					.replace(/#/g, '%23')
					.replace(/\s+/g, ' ');
				let encodedData = ',' + newText;
				if (encodedData.length < DATA.length) {
					DATA = encodedData;
				}
			}
			return '"data:' + MIME + DATA + '"';
A
Alex Dima 已提交
338 339 340 341 342 343
		});
	}

	function _replaceURL(contents: string, replacer: (url: string) => string): string {
		// Use ")" as the terminator as quotes are oftentimes not used at all
		return contents.replace(/url\(\s*([^\)]+)\s*\)?/g, (_: string, ...matches: string[]) => {
M
Matt Bierner 已提交
344
			let url = matches[0];
A
Alex Dima 已提交
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
			// Eliminate starting quotes (the initial whitespace is not captured)
			if (url.charAt(0) === '"' || url.charAt(0) === '\'') {
				url = url.substring(1);
			}
			// The ending whitespace is captured
			while (url.length > 0 && (url.charAt(url.length - 1) === ' ' || url.charAt(url.length - 1) === '\t')) {
				url = url.substring(0, url.length - 1);
			}
			// Eliminate ending quotes
			if (url.charAt(url.length - 1) === '"' || url.charAt(url.length - 1) === '\'') {
				url = url.substring(0, url.length - 1);
			}

			if (!_startsWith(url, 'data:') && !_startsWith(url, 'http://') && !_startsWith(url, 'https://')) {
				url = replacer(url);
			}

			return 'url(' + url + ')';
		});
	}

	function _startsWith(haystack: string, needle: string): boolean {
		return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle;
	}
}