diff --git a/build/lib/compilation.js b/build/lib/compilation.js index a2de56aa0ce..14c777078c4 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -11,11 +11,13 @@ const bom = require("gulp-bom"); const sourcemaps = require("gulp-sourcemaps"); const tsb = require("gulp-tsb"); const path = require("path"); +const ts = require("typescript"); const _ = require("underscore"); const monacodts = require("../monaco/api"); const nls = require("./nls"); const reporter_1 = require("./reporter"); const util = require("./util"); +const util2 = require("gulp-util"); const watch = require('./watch'); const reporter = reporter_1.createReporter(); function getTypeScriptCompilerOptions(src) { @@ -79,14 +81,15 @@ function compileTask(src, out, build) { return function () { const compile = createCompile(src, build, true); const srcPipe = es.merge(gulp.src(`${src}/**`, { base: `${src}` }), gulp.src(typesDts)); - // Do not write .d.ts files to disk, as they are not needed there. - const dtsFilter = util.filter(data => !/\.d\.ts$/.test(data.path)); + let generator = new MonacoGenerator(false); + if (src === 'src') { + generator.execute(); + } return srcPipe + .pipe(generator.stream) .pipe(compile()) - .pipe(dtsFilter) - .pipe(gulp.dest(out)) - .pipe(dtsFilter.restore) - .pipe(src !== 'src' ? es.through() : monacodtsTask(out, false)); + .pipe(gulp.dest(out)); + // .pipe(src !== 'src' ? es.through() : monacodtsTask(out, false)); }; } exports.compileTask = compileTask; @@ -95,67 +98,116 @@ function watchTask(out, build) { const compile = createCompile('src', build); const src = es.merge(gulp.src('src/**', { base: 'src' }), gulp.src(typesDts)); const watchSrc = watch('src/**', { base: 'src' }); - // Do not write .d.ts files to disk, as they are not needed there. - const dtsFilter = util.filter(data => !/\.d\.ts$/.test(data.path)); + let generator = new MonacoGenerator(true); + generator.execute(); return watchSrc + .pipe(generator.stream) .pipe(util.incremental(compile, src, true)) - .pipe(dtsFilter) - .pipe(gulp.dest(out)) - .pipe(dtsFilter.restore) - .pipe(monacodtsTask(out, true)); + .pipe(gulp.dest(out)); }; } exports.watchTask = watchTask; -function monacodtsTask(out, isWatch) { - const basePath = path.resolve(process.cwd(), out); - const neededFiles = {}; - monacodts.getFilesToWatch(out).forEach(function (filePath) { - filePath = path.normalize(filePath); - neededFiles[filePath] = true; - }); - const inputFiles = {}; - for (const filePath in neededFiles) { - if (/\bsrc(\/|\\)vs\b/.test(filePath)) { - // This file is needed from source => simply read it now - inputFiles[filePath] = fs.readFileSync(filePath).toString(); - } - } - const setInputFile = (filePath, contents) => { - if (inputFiles[filePath] === contents) { - // no change - return; - } - inputFiles[filePath] = contents; - const neededInputFilesCount = Object.keys(neededFiles).length; - const availableInputFilesCount = Object.keys(inputFiles).length; - if (neededInputFilesCount === availableInputFilesCount) { - run(); - } - }; - const run = () => { - const result = monacodts.run(out, inputFiles); - if (!result.isTheSame) { - if (isWatch) { - fs.writeFileSync(result.filePath, result.content); +const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); +class MonacoGenerator { + constructor(isWatch) { + this._isWatch = isWatch; + this.stream = es.through(); + this._inputFiles = monacodts.getIncludesInRecipe().map((moduleId) => { + if (/\.d\.ts$/.test(moduleId)) { + // This source file is already in .d.ts form + return path.join(REPO_SRC_FOLDER, moduleId); } else { - fs.writeFileSync(result.filePath, result.content); - resultStream.emit('error', 'monaco.d.ts is no longer up to date. Please run gulp watch and commit the new file.'); + return path.join(REPO_SRC_FOLDER, `${moduleId}.ts`); } + }); + // Install watchers + this._watchers = []; + if (this._isWatch) { + this._inputFiles.forEach((filePath) => { + const watcher = fs.watch(filePath); + watcher.addListener('change', () => { + this._inputFileChanged[filePath] = true; + // Avoid hitting empty files... :/ + setTimeout(() => this.execute(), 10); + }); + this._watchers.push(watcher); + }); + const recipeWatcher = fs.watch(monacodts.RECIPE_PATH); + recipeWatcher.addListener('change', () => { + this._recipeFileChanged = true; + // Avoid hitting empty files... :/ + setTimeout(() => this.execute(), 10); + }); + this._watchers.push(recipeWatcher); } - }; - let resultStream; - if (isWatch) { - watch('build/monaco/*').pipe(es.through(function () { - run(); - })); + this._inputFileChanged = {}; + this._inputFiles.forEach(file => this._inputFileChanged[file] = true); + this._recipeFileChanged = true; + this._dtsFilesContents = {}; } - resultStream = es.through(function (data) { - const filePath = path.normalize(path.resolve(basePath, data.relative)); - if (neededFiles[filePath]) { - setInputFile(filePath, data.contents.toString()); + dispose() { + this._watchers.forEach(watcher => watcher.close()); + } + _run() { + let somethingChanged = false; + const setDTSFileContent = (file, contents) => { + if (this._dtsFilesContents[file] === contents) { + return; + } + this._dtsFilesContents[file] = contents; + somethingChanged = true; + }; + const fileMap = {}; + this._inputFiles.forEach((inputFile) => { + if (!this._inputFileChanged[inputFile]) { + return; + } + this._inputFileChanged[inputFile] = false; + const inputFileContents = fs.readFileSync(inputFile).toString(); + if (/\.d\.ts$/.test(inputFile)) { + // This is a .d.ts file + setDTSFileContent(inputFile, inputFileContents); + return; + } + fileMap[inputFile] = inputFileContents; + }); + if (Object.keys(fileMap).length > 0) { + const service = ts.createLanguageService(new monacodts.TypeScriptLanguageServiceHost({}, fileMap, {})); + Object.keys(fileMap).forEach((fileName) => { + const output = service.getEmitOutput(fileName, true).outputFiles[0].text; + const destFileName = fileName.replace(/\.ts$/, '.d.ts'); + setDTSFileContent(destFileName, output); + }); } - this.emit('data', data); - }); - return resultStream; + if (this._recipeFileChanged) { + this._recipeFileChanged = false; + somethingChanged = true; + } + if (!somethingChanged) { + // Nothing changed + return null; + } + return monacodts.run('src', this._dtsFilesContents); + } + _log(message, ...rest) { + util2.log(util2.colors.cyan('[monaco.d.ts]'), message, ...rest); + } + execute() { + const startTime = Date.now(); + const result = this._run(); + if (!result) { + // nothing really changed + return; + } + if (result.isTheSame) { + this._log(`monaco.d.ts is unchanged - total time took ${Date.now() - startTime} ms`); + return; + } + fs.writeFileSync(result.filePath, result.content); + this._log(`monaco.d.ts is changed - total time took ${Date.now() - startTime} ms`); + if (!this._isWatch) { + this.stream.emit('error', 'monaco.d.ts is no longer up to date. Please run gulp watch and commit the new file.'); + } + } } diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 725e36a92eb..78fb02c7cbb 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -12,11 +12,13 @@ import * as bom from 'gulp-bom'; import * as sourcemaps from 'gulp-sourcemaps'; import * as tsb from 'gulp-tsb'; import * as path from 'path'; +import * as ts from 'typescript'; import * as _ from 'underscore'; import * as monacodts from '../monaco/api'; import * as nls from './nls'; import { createReporter } from './reporter'; import * as util from './util'; +import * as util2 from 'gulp-util'; const watch = require('./watch'); const reporter = createReporter(); @@ -95,15 +97,16 @@ export function compileTask(src: string, out: string, build: boolean): () => Nod gulp.src(typesDts), ); - // Do not write .d.ts files to disk, as they are not needed there. - const dtsFilter = util.filter(data => !/\.d\.ts$/.test(data.path)); + let generator = new MonacoGenerator(false); + if (src === 'src') { + generator.execute(); + } return srcPipe + .pipe(generator.stream) .pipe(compile()) - .pipe(dtsFilter) - .pipe(gulp.dest(out)) - .pipe(dtsFilter.restore) - .pipe(src !== 'src' ? es.through() : monacodtsTask(out, false)); + .pipe(gulp.dest(out)); + // .pipe(src !== 'src' ? es.through() : monacodtsTask(out, false)); }; } @@ -118,76 +121,148 @@ export function watchTask(out: string, build: boolean): () => NodeJS.ReadWriteSt ); const watchSrc = watch('src/**', { base: 'src' }); - // Do not write .d.ts files to disk, as they are not needed there. - const dtsFilter = util.filter(data => !/\.d\.ts$/.test(data.path)); + let generator = new MonacoGenerator(true); + generator.execute(); return watchSrc + .pipe(generator.stream) .pipe(util.incremental(compile, src, true)) - .pipe(dtsFilter) - .pipe(gulp.dest(out)) - .pipe(dtsFilter.restore) - .pipe(monacodtsTask(out, true)); + .pipe(gulp.dest(out)); }; } -function monacodtsTask(out: string, isWatch: boolean): NodeJS.ReadWriteStream { +const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); - const basePath = path.resolve(process.cwd(), out); +class MonacoGenerator { + private readonly _isWatch: boolean; + public readonly stream: NodeJS.ReadWriteStream; + /** + * This list is never changed for the lifetime of this object. + */ + private readonly _inputFiles: string[]; + private readonly _watchers: fs.FSWatcher[]; - const neededFiles: { [file: string]: boolean; } = {}; - monacodts.getFilesToWatch(out).forEach(function (filePath) { - filePath = path.normalize(filePath); - neededFiles[filePath] = true; - }); + private _inputFileChanged: { [filePath: string]: boolean; }; + private _recipeFileChanged: boolean; - const inputFiles: { [file: string]: string; } = {}; - for (const filePath in neededFiles) { - if (/\bsrc(\/|\\)vs\b/.test(filePath)) { - // This file is needed from source => simply read it now - inputFiles[filePath] = fs.readFileSync(filePath).toString(); + private _dtsFilesContents: { [filePath: string]: string; }; + + constructor(isWatch: boolean) { + this._isWatch = isWatch; + this.stream = es.through(); + this._inputFiles = monacodts.getIncludesInRecipe().map((moduleId) => { + if (/\.d\.ts$/.test(moduleId)) { + // This source file is already in .d.ts form + return path.join(REPO_SRC_FOLDER, moduleId); + } else { + return path.join(REPO_SRC_FOLDER, `${moduleId}.ts`); + } + }); + + // Install watchers + this._watchers = []; + if (this._isWatch) { + this._inputFiles.forEach((filePath) => { + const watcher = fs.watch(filePath); + watcher.addListener('change', () => { + this._inputFileChanged[filePath] = true; + // Avoid hitting empty files... :/ + setTimeout(() => this.execute(), 10); + }); + this._watchers.push(watcher); + }); + + const recipeWatcher = fs.watch(monacodts.RECIPE_PATH); + recipeWatcher.addListener('change', () => { + this._recipeFileChanged = true; + // Avoid hitting empty files... :/ + setTimeout(() => this.execute(), 10); + }); + this._watchers.push(recipeWatcher); } + + this._inputFileChanged = {}; + this._inputFiles.forEach(file => this._inputFileChanged[file] = true); + this._recipeFileChanged = true; + this._dtsFilesContents = {}; } - const setInputFile = (filePath: string, contents: string) => { - if (inputFiles[filePath] === contents) { - // no change + public dispose(): void { + this._watchers.forEach(watcher => watcher.close()); + } + + private _run(): monacodts.IMonacoDeclarationResult | null { + let somethingChanged = false; + + const setDTSFileContent = (file: string, contents: string): void => { + if (this._dtsFilesContents[file] === contents) { + return; + } + this._dtsFilesContents[file] = contents; + somethingChanged = true; + }; + + const fileMap: { [fileName: string]: string; } = {}; + + this._inputFiles.forEach((inputFile) => { + if (!this._inputFileChanged[inputFile]) { + return; + } + this._inputFileChanged[inputFile] = false; + + const inputFileContents = fs.readFileSync(inputFile).toString(); + if (/\.d\.ts$/.test(inputFile)) { + // This is a .d.ts file + setDTSFileContent(inputFile, inputFileContents); + return; + } + + fileMap[inputFile] = inputFileContents; + }); + + if (Object.keys(fileMap).length > 0) { + const service = ts.createLanguageService(new monacodts.TypeScriptLanguageServiceHost({}, fileMap, {})); + + Object.keys(fileMap).forEach((fileName) => { + const output = service.getEmitOutput(fileName, true).outputFiles[0].text; + const destFileName = fileName.replace(/\.ts$/, '.d.ts'); + setDTSFileContent(destFileName, output); + }); + } + + if (this._recipeFileChanged) { + this._recipeFileChanged = false; + somethingChanged = true; + } + + if (!somethingChanged) { + // Nothing changed + return null; + } + + return monacodts.run('src', this._dtsFilesContents); + } + + private _log(message: any, ...rest: any[]): void { + util2.log(util2.colors.cyan('[monaco.d.ts]'), message, ...rest); + } + + public execute(): void { + const startTime = Date.now(); + const result = this._run(); + if (!result) { + // nothing really changed return; } - inputFiles[filePath] = contents; - const neededInputFilesCount = Object.keys(neededFiles).length; - const availableInputFilesCount = Object.keys(inputFiles).length; - if (neededInputFilesCount === availableInputFilesCount) { - run(); + if (result.isTheSame) { + this._log(`monaco.d.ts is unchanged - total time took ${Date.now() - startTime} ms`); + return; } - }; - const run = () => { - const result = monacodts.run(out, inputFiles); - if (!result.isTheSame) { - if (isWatch) { - fs.writeFileSync(result.filePath, result.content); - } else { - fs.writeFileSync(result.filePath, result.content); - resultStream.emit('error', 'monaco.d.ts is no longer up to date. Please run gulp watch and commit the new file.'); - } + fs.writeFileSync(result.filePath, result.content); + this._log(`monaco.d.ts is changed - total time took ${Date.now() - startTime} ms`); + if (!this._isWatch) { + this.stream.emit('error', 'monaco.d.ts is no longer up to date. Please run gulp watch and commit the new file.'); } - }; - - let resultStream: NodeJS.ReadWriteStream; - - if (isWatch) { - watch('build/monaco/*').pipe(es.through(function () { - run(); - })); } - - resultStream = es.through(function (data) { - const filePath = path.normalize(path.resolve(basePath, data.relative)); - if (neededFiles[filePath]) { - setInputFile(filePath, data.contents.toString()); - } - this.emit('data', data); - }); - - return resultStream; } diff --git a/build/monaco/api.js b/build/monaco/api.js index ec041b12c0b..e1fa18442f2 100644 --- a/build/monaco/api.js +++ b/build/monaco/api.js @@ -14,7 +14,7 @@ function log(message, ...rest) { } const SRC = path.join(__dirname, '../../src'); const OUT_ROOT = path.join(__dirname, '../../'); -const RECIPE_PATH = path.join(__dirname, './monaco.d.ts.recipe'); +exports.RECIPE_PATH = path.join(__dirname, './monaco.d.ts.recipe'); const DECLARATION_PATH = path.join(__dirname, '../../src/vs/monaco.d.ts'); var CURRENT_PROCESSING_RULE = ''; function logErr(message, ...rest) { @@ -314,7 +314,7 @@ function generateDeclarationFile(out, inputFiles, recipe) { ]; } function getIncludesInRecipe() { - let recipe = fs.readFileSync(RECIPE_PATH).toString(); + let recipe = fs.readFileSync(exports.RECIPE_PATH).toString(); let lines = recipe.split(/\r\n|\n|\r/); let result = []; lines.forEach(line => { @@ -333,6 +333,7 @@ function getIncludesInRecipe() { }); return result; } +exports.getIncludesInRecipe = getIncludesInRecipe; function getFilesToWatch(out) { return getIncludesInRecipe().map((moduleId) => moduleIdToPath(out, moduleId)); } @@ -340,7 +341,7 @@ exports.getFilesToWatch = getFilesToWatch; function run(out, inputFiles) { log('Starting monaco.d.ts generation'); SOURCE_FILE_MAP = {}; - let recipe = fs.readFileSync(RECIPE_PATH).toString(); + let recipe = fs.readFileSync(exports.RECIPE_PATH).toString(); let [result, usageContent] = generateDeclarationFile(out, inputFiles, recipe); let currentContent = fs.readFileSync(DECLARATION_PATH).toString(); log('Finished monaco.d.ts generation'); @@ -404,6 +405,7 @@ class TypeScriptLanguageServiceHost { return fileName === this.getDefaultLibFileName(this._compilerOptions); } } +exports.TypeScriptLanguageServiceHost = TypeScriptLanguageServiceHost; function execute() { const OUTPUT_FILES = {}; const SRC_FILES = {}; diff --git a/build/monaco/api.ts b/build/monaco/api.ts index ec30cc00c48..8e7eead0590 100644 --- a/build/monaco/api.ts +++ b/build/monaco/api.ts @@ -16,7 +16,7 @@ function log(message: any, ...rest: any[]): void { const SRC = path.join(__dirname, '../../src'); const OUT_ROOT = path.join(__dirname, '../../'); -const RECIPE_PATH = path.join(__dirname, './monaco.d.ts.recipe'); +export const RECIPE_PATH = path.join(__dirname, './monaco.d.ts.recipe'); const DECLARATION_PATH = path.join(__dirname, '../../src/vs/monaco.d.ts'); var CURRENT_PROCESSING_RULE = ''; @@ -375,7 +375,7 @@ function generateDeclarationFile(out: string, inputFiles: { [file: string]: stri ]; } -function getIncludesInRecipe(): string[] { +export function getIncludesInRecipe(): string[] { let recipe = fs.readFileSync(RECIPE_PATH).toString(); let lines = recipe.split(/\r\n|\n|\r/); let result: string[] = []; @@ -442,7 +442,7 @@ export function complainErrors() { interface ILibMap { [libName: string]: string; } interface IFileMap { [fileName: string]: string; } -class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { +export class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { private readonly _libs: ILibMap; private readonly _files: IFileMap; diff --git a/src/tsconfig.json b/src/tsconfig.json index 6f552e5ca05..4b9f4170455 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -4,7 +4,6 @@ "removeComments": false, "preserveConstEnums": true, "sourceMap": false, - "declaration": true, "outDir": "../out" }, "include": [