/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. 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'; const REPO_ROOT = path.join(__dirname, '../../'); const SRC_DIR = path.join(REPO_ROOT, 'src'); const OUT_EDITOR = path.join(REPO_ROOT, 'out-editor'); export interface IOptions { entryPoints: string[]; outFolder: string; outResourcesFolder: string; redirects: { [module: string]: string; }; } export function createESMSourcesAndResources(options: IOptions): void { const OUT_FOLDER = path.join(REPO_ROOT, options.outFolder); const OUT_RESOURCES_FOLDER = path.join(REPO_ROOT, options.outResourcesFolder); let in_queue: { [module: string]: boolean; } = Object.create(null); let queue: string[] = []; const enqueue = (module: string) => { if (in_queue[module]) { return; } in_queue[module] = true; queue.push(module); }; const seenDir: { [key: string]: boolean; } = {}; const createDirectoryRecursive = (dir: string) => { if (seenDir[dir]) { return; } let lastSlash = dir.lastIndexOf('/'); if (lastSlash === -1) { lastSlash = dir.lastIndexOf('\\'); } if (lastSlash !== -1) { createDirectoryRecursive(dir.substring(0, lastSlash)); } seenDir[dir] = true; try { fs.mkdirSync(dir); } catch (err) { } }; seenDir[REPO_ROOT] = true; const toggleComments = (fileContents: string) => { let lines = fileContents.split(/\r\n|\r|\n/); let mode = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; 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; } if (mode === 2) { if (/\/\/ ESM-uncomment-end/.test(line)) { mode = 0; continue; } lines[i] = line.replace(/^(\s*)\/\/ ?/, function (_, indent) { return indent; }); } } return lines.join('\n'); }; const write = (filePath: string, contents: string | Buffer) => { let absoluteFilePath: string; if (/\.ts$/.test(filePath)) { absoluteFilePath = path.join(OUT_FOLDER, filePath); } else { absoluteFilePath = path.join(OUT_RESOURCES_FOLDER, filePath); } createDirectoryRecursive(path.dirname(absoluteFilePath)); if (/(\.ts$)|(\.js$)/.test(filePath)) { contents = toggleComments(contents.toString()); } fs.writeFileSync(absoluteFilePath, contents); }; options.entryPoints.forEach((entryPoint) => enqueue(entryPoint)); while (queue.length > 0) { const module = queue.shift(); if (transportCSS(options, module, enqueue, write)) { continue; } if (transportResource(options, module, enqueue, write)) { continue; } if (transportDTS(options, module, enqueue, write)) { continue; } let filename: string; if (options.redirects[module]) { filename = path.join(SRC_DIR, options.redirects[module] + '.ts'); } else { filename = path.join(SRC_DIR, module + '.ts'); } let fileContents = fs.readFileSync(filename).toString(); const info = ts.preProcessFile(fileContents); 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; } if (/(^\.\/)|(^\.\.\/)/.test(importedFilepath)) { importedFilepath = path.join(path.dirname(module), importedFilepath); } enqueue(importedFilepath); let relativePath: string; if (importedFilepath === path.dirname(module)) { relativePath = '../' + path.basename(path.dirname(module)); } else if (importedFilepath === path.dirname(path.dirname(module))) { relativePath = '../../' + path.basename(path.dirname(path.dirname(module))); } else { relativePath = path.relative(path.dirname(module), importedFilepath); } if (!/(^\.\/)|(^\.\.\/)/.test(relativePath)) { relativePath = './' + relativePath; } fileContents = ( fileContents.substring(0, pos + 1) + relativePath + fileContents.substring(end + 1) ); } fileContents = fileContents.replace(/import ([a-zA-z0-9]+) = require\(('[^']+')\);/g, function (_, m1, m2) { return `import * as ${m1} from ${m2};`; }); fileContents = fileContents.replace(/Thenable/g, 'PromiseLike'); write(module + '.ts', fileContents); } const esm_opts = { "compilerOptions": { "outDir": path.relative(path.dirname(OUT_FOLDER), OUT_RESOURCES_FOLDER), "rootDir": "src", "module": "es6", "target": "es5", "experimentalDecorators": true, "lib": [ "dom", "es5", "es2015.collection", "es2015.promise" ], "types": [ ] } }; fs.writeFileSync(path.join(path.dirname(OUT_FOLDER), 'tsconfig.json'), JSON.stringify(esm_opts, null, '\t')); const monacodts = fs.readFileSync(path.join(SRC_DIR, 'vs/monaco.d.ts')).toString(); fs.writeFileSync(path.join(OUT_FOLDER, 'vs/monaco.d.ts'), monacodts); } function transportCSS(options: IOptions, module: string, enqueue: (module: string) => void, write: (path: string, contents: string | Buffer) => void): boolean { if (!/\.css/.test(module)) { return false; } const filename = path.join(SRC_DIR, module); const fileContents = fs.readFileSync(filename).toString(); const inlineResources = 'base64'; // see https://github.com/Microsoft/monaco-editor/issues/148 const inlineResourcesLimit = 300000;//3000; // see https://github.com/Microsoft/monaco-editor/issues/336 const newContents = _rewriteOrInlineUrls(filename, fileContents, inlineResources === 'base64', inlineResourcesLimit); write(module, newContents); return true; function _rewriteOrInlineUrls(originalFileFSPath: string, contents: string, forceBase64: boolean, inlineByteLimit: number): string { return _replaceURL(contents, (url) => { let imagePath = path.join(path.dirname(module), url); let fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath)); if (fileContents.length < inlineByteLimit) { 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, '%3E') .replace(/&/g, '%26') .replace(/#/g, '%23') .replace(/\s+/g, ' '); let encodedData = ',' + newText; if (encodedData.length < DATA.length) { DATA = encodedData; } } return '"data:' + MIME + DATA + '"'; } enqueue(imagePath); return url; }); } 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[]) => { var url = matches[0]; // 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; } } function transportResource(options: IOptions, module: string, enqueue: (module: string) => void, write: (path: string, contents: string | Buffer) => void): boolean { if (!/\.svg/.test(module)) { return false; } write(module, fs.readFileSync(path.join(SRC_DIR, module))); return true; } function transportDTS(options: IOptions, module: string, enqueue: (module: string) => void, write: (path: string, contents: string | Buffer) => void): boolean { if (options.redirects[module] && fs.existsSync(path.join(SRC_DIR, options.redirects[module] + '.ts'))) { return false; } if (!fs.existsSync(path.join(SRC_DIR, module + '.d.ts'))) { return false; } write(module + '.d.ts', fs.readFileSync(path.join(SRC_DIR, module + '.d.ts'))); let filename: string; if (options.redirects[module]) { write(module + '.js', fs.readFileSync(path.join(SRC_DIR, options.redirects[module] + '.js'))); } else { write(module + '.js', fs.readFileSync(path.join(SRC_DIR, module + '.js'))); } return true; }