From cff0f831a66ee601ef9117dc264236b04694057d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 26 Jun 2018 15:06:59 -0700 Subject: [PATCH] Report errors in corrrect process for gulp-typescript-oop --- Gulpfile.js | 67 +++-- package-lock.json | 6 +- scripts/build/gulp-typescript-oop.js | 91 ------ scripts/build/gulp-typescript-oop/index.js | 145 +++++++++ scripts/build/gulp-typescript-oop/protocol.js | 279 ++++++++++++++++++ scripts/build/gulp-typescript-oop/worker.js | 79 +++++ scripts/build/main.js | 91 ------ scripts/build/project.js | 50 ++-- 8 files changed, 586 insertions(+), 222 deletions(-) delete mode 100644 scripts/build/gulp-typescript-oop.js create mode 100644 scripts/build/gulp-typescript-oop/index.js create mode 100644 scripts/build/gulp-typescript-oop/protocol.js create mode 100644 scripts/build/gulp-typescript-oop/worker.js delete mode 100644 scripts/build/main.js diff --git a/Gulpfile.js b/Gulpfile.js index b1109a3e340..e202b2d983c 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -46,7 +46,7 @@ const produceLKGJs = "scripts/produceLKG.js"; const word2mdJs = "scripts/word2md.js"; gulp.task("scripts", /*help*/ false, () => project.compile(scriptsProject), { aliases: [ - configurePrereleaseJs, + configurePrereleaseJs, processDiagnosticMessagesJs, generateLocalizedDiagnosticMessagesJs, produceLKGJs, @@ -134,10 +134,11 @@ gulp.task(generatedLCGFile, /*help*/ false, [generateLocalizedDiagnosticMessages gulp.task("localize", /*help*/ false, [generatedLCGFile]); +const servicesProject = "src/services/tsconfig.json"; const typescriptServicesProject = "built/local/typescriptServices.tsconfig.json"; gulp.task(typescriptServicesProject, /*help*/ false, () => { // NOTE: flatten services so that we can properly strip @internal - project.flatten("src/services/tsconfig.json", typescriptServicesProject, { + project.flatten(servicesProject, typescriptServicesProject, { compilerOptions: { "removeComments": true, "stripInternal": true, @@ -148,7 +149,7 @@ gulp.task(typescriptServicesProject, /*help*/ false, () => { const typescriptServicesJs = "built/local/typescriptServices.js"; const typescriptServicesDts = "built/local/typescriptServices.d.ts"; -gulp.task(typescriptServicesJs, /*help*/ false, ["lib", "generate-diagnostics", typescriptServicesProject], () => +gulp.task(typescriptServicesJs, /*help*/ false, ["lib", "generate-diagnostics", typescriptServicesProject], () => project.compile(typescriptServicesProject, { dts: files => files.pipe(convertConstEnums()) }), { aliases: [typescriptServicesDts] }); @@ -225,7 +226,7 @@ gulp.task(tsserverlibraryProject, /*help*/ false, () => { const tsserverlibraryJs = "built/local/tsserverlibrary.js"; const tsserverlibraryDts = "built/local/tsserverlibrary.d.ts"; gulp.task(tsserverlibraryJs, /*help*/ false, [typescriptServicesJs, tsserverlibraryProject], () => - project.compile(tsserverlibraryProject, { + project.compile(tsserverlibraryProject, { dts: files => files .pipe(convertConstEnums()) .pipe(append("\nexport = ts;\nexport as namespace ts;")), @@ -253,21 +254,21 @@ gulp.task(specMd, /*help*/ false, [word2mdJs], () => exec("cscript", ["//nologo", word2mdJs, path.resolve(specMd), path.resolve("doc/TypeScript Language Specification.docx")])); gulp.task( - "generate-spec", - "Generates a Markdown version of the Language Specification", + "generate-spec", + "Generates a Markdown version of the Language Specification", [specMd]); gulp.task("produce-LKG", /*help*/ false, ["scripts", "local", cancellationTokenJs, typingsInstallerJs, watchGuardJs, tscReleaseJs], () => { const expectedFiles = [ - tscReleaseJs, - typescriptServicesJs, - tsserverJs, - typescriptJs, - typescriptDts, - typescriptServicesDts, - tsserverlibraryDts, - tsserverlibraryDts, - typingsInstallerJs, + tscReleaseJs, + typescriptServicesJs, + tsserverJs, + typescriptJs, + typescriptDts, + typescriptServicesDts, + tsserverlibraryDts, + tsserverlibraryDts, + typingsInstallerJs, cancellationTokenJs ].concat(libraryTargets); const missingFiles = expectedFiles @@ -286,8 +287,8 @@ gulp.task("produce-LKG", /*help*/ false, ["scripts", "local", cancellationTokenJ }); gulp.task( - "LKG", - "Makes a new LKG out of the built js files", + "LKG", + "Makes a new LKG out of the built js files", () => runSequence("clean-built", "produce-LKG")); // Task to build the tests infrastructure using the built compiler @@ -464,12 +465,40 @@ gulp.task( "Runs 'local'", ["local"]); +gulp.task( + "watch-diagnostics", + /*help*/ false, + [processDiagnosticMessagesJs], + () => gulp.watch([diagnosticMessagesJson], [diagnosticInformationMapTs, builtGeneratedDiagnosticMessagesJson])); + +gulp.task( + "watch-lib", + /*help*/ false, + () => gulp.watch(["src/lib/**/*"], ["lib"])); + gulp.task( "watch-tsc", - "Watches for changes to the build inputs for built/local/tsc.js", - [typescriptServicesJs], + /*help*/ false, + ["watch-diagnostics", "watch-lib", typescriptServicesJs], () => project.watch(tscProject, { typescript: "built" })); +gulp.task( + "watch-services", + /*help*/ false, + ["watch-diagnostics", "watch-lib", typescriptServicesJs], + () => project.watch(servicesProject, { typescript: "built" })); + +gulp.task( + "watch-server", + /*help*/ false, + ["watch-diagnostics", "watch-lib", typescriptServicesJs], + () => project.watch(tsserverProject, { typescript: "built" })); + +gulp.task( + "watch-local", + /*help*/ false, + ["watch-lib", "watch-tsc", "watch-services", "watch-server"]); + gulp.task( "watch", "Watches for changes to the build inputs for built/local/run.js executes runtests-parallel.", diff --git a/package-lock.json b/package-lock.json index 5b8e90db339..638adaf2e0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5801,9 +5801,9 @@ "dev": true }, "typescript": { - "version": "3.0.0-dev.20180609", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.0-dev.20180609.tgz", - "integrity": "sha512-bqO5mSGbxZoiY/9Y1bnnU36dC5CfnrA9I9WKf3QB0qMuJakoofO2DNTDRwsypyhCThlNtF2Ls/OMj3Txglu4Xg==", + "version": "3.0.0-dev.20180626", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.0-dev.20180626.tgz", + "integrity": "sha512-OQH9osIC4CdsVzVvsb2RenRTVPRKwSIMIpRy2J42XNOEUP+vhX56BX1Z47K3l//LEGY0xG7zF7qVKCDlUhhrlg==", "dev": true }, "uglify-js": { diff --git a/scripts/build/gulp-typescript-oop.js b/scripts/build/gulp-typescript-oop.js deleted file mode 100644 index cf1252681a6..00000000000 --- a/scripts/build/gulp-typescript-oop.js +++ /dev/null @@ -1,91 +0,0 @@ -// @ts-check -const path = require("path"); -const child_process = require("child_process"); -const tsc = require("gulp-typescript"); -const Vinyl = require("vinyl"); -const { Duplex, Readable } = require("stream"); - -/** - * @param {string | undefined} tsConfigFileName - * @param {tsc.Settings} settings - * @param {Object} options - * @param {string} [options.typescript] - */ -function createProject(tsConfigFileName, settings, options) { - settings = Object.assign({}, settings); - options = Object.assign({}, options); - if (settings.typescript) throw new Error(); - - const localSettings = Object.assign({}, settings); - if (options.typescript) { - options.typescript = path.resolve(options.typescript); - localSettings.typescript = require(options.typescript); - } - - const project = tsConfigFileName === undefined ? tsc.createProject(localSettings) : tsc.createProject(tsConfigFileName, localSettings); - const wrappedProject = /** @type {tsc.Project} */(() => { - const proc = child_process.fork(require.resolve("./main.js"), [], { - // Prevent errors when debugging gulpfile due to the same debug port being passed to forked children. - execArgv: [] - }); - /** @type {Duplex & { js?: Readable, dts?: Readable }} */ - const compileStream = new Duplex({ - objectMode: true, - read() {}, - /** @param {*} file */ - write(file, _encoding, callback) { - proc.send({ method: "write", params: { path: file.path, cwd: file.cwd, base: file.base, sourceMap: file.sourceMap }}); - callback(); - }, - final(callback) { - proc.send({ method: "final" }); - callback(); - } - }); - const jsStream = compileStream.js = new Readable({ - objectMode: true, - read() {} - }); - const dtsStream = compileStream.dts = new Readable({ - objectMode: true, - read() {} - }); - proc.send({ method: "createProject", params: { tsConfigFileName, settings, options } }); - proc.on("message", ({ method, params }) => { - if (method === "write") { - const file = new Vinyl({ - path: params.path, - cwd: params.cwd, - base: params.base, - contents: Buffer.from(params.contents, "utf8") - }); - if (params.sourceMap) file.sourceMap = params.sourceMap - compileStream.push(file);; - if (file.path.endsWith(".d.ts")) { - dtsStream.push(file); - } - else { - jsStream.push(file); - } - } - else if (method === "final") { - compileStream.push(null); - jsStream.push(null); - dtsStream.push(null); - proc.kill(); - } - else if (method === "error") { - const error = new Error(); - error.name = params.name; - error.message = params.message; - error.stack = params.stack; - compileStream.emit("error", error); - proc.kill(); - } - }); - return /** @type {*} */(compileStream); - }); - return Object.assign(wrappedProject, project); -} - -exports.createProject = createProject; \ No newline at end of file diff --git a/scripts/build/gulp-typescript-oop/index.js b/scripts/build/gulp-typescript-oop/index.js new file mode 100644 index 00000000000..3ef5effaf5a --- /dev/null +++ b/scripts/build/gulp-typescript-oop/index.js @@ -0,0 +1,145 @@ +// @ts-check +const path = require("path"); +const child_process = require("child_process"); +const tsc = require("gulp-typescript"); +const Vinyl = require("vinyl"); +const { Duplex, Readable } = require("stream"); +const protocol = require("./protocol"); + +/** + * @param {string | undefined} tsConfigFileName + * @param {tsc.Settings} settings + * @param {CreateProjectOptions} options + * + * @typedef CreateProjectOptions + * @property {string} [typescript] + * @property {boolean} [parse] + */ +function createProject(tsConfigFileName, settings, options) { + settings = Object.assign({}, settings); + options = Object.assign({}, options); + if (settings.typescript) throw new Error(); + + const localSettings = Object.assign({}, settings); + if (options.typescript) { + options.typescript = path.resolve(options.typescript); + localSettings.typescript = require(options.typescript); + } + + const project = tsConfigFileName === undefined ? tsc.createProject(localSettings) : tsc.createProject(tsConfigFileName, localSettings); + const wrappedProject = /** @type {tsc.Project} */((reporter = tsc.reporter.defaultReporter()) => { + const ts = project.typescript; + const proc = child_process.fork(require.resolve("./worker.js"), [], { + // Prevent errors when debugging gulpfile due to the same debug port being passed to forked children. + execArgv: [] + }); + /** @type {Map} */ + const inputs = new Map(); + /** @type {Map} */ + const sourceFiles = new Map(); + /** @type {protocol.SourceFileHost & protocol.VinylHost} */ + const host = { + getVinyl(path) { return inputs.get(path); }, + getSourceFile(fileName) { return sourceFiles.get(fileName); }, + createSourceFile(fileName, text, languageVersion) { + if (text === undefined) throw new Error("File not cached."); + /** @type {protocol.SourceFile} */ + let file; + if (options.parse) { + file = ts.createSourceFile(fileName, text, languageVersion, /*setParentNodes*/ true); + } + else { + // NOTE: the built-in reporters in gulp-typescript don't actually need a full + // source file, so save time by faking one unless requested. + file = /**@type {protocol.SourceFile}*/({ + pos: 0, + end: text.length, + kind: ts.SyntaxKind.SourceFile, + fileName, + text, + languageVersion, + statements: /**@type {*} */([]), + endOfFileToken: { pos: text.length, end: text.length, kind: ts.SyntaxKind.EndOfFileToken }, + amdDependencies: /**@type {*} */([]), + referencedFiles: /**@type {*} */([]), + typeReferenceDirectives: /**@type {*} */([]), + libReferenceDirectives: /**@type {*} */([]), + languageVariant: ts.LanguageVariant.Standard, + isDeclarationFile: /\.d\.ts$/.test(fileName), + hasNoDefaultLib: /[\\/]lib\.[^\\/]+\.d\.ts$/.test(fileName) + }); + } + sourceFiles.set(fileName, file); + return file; + } + }; + /** @type {Duplex & { js?: Readable, dts?: Readable }} */ + const compileStream = new Duplex({ + objectMode: true, + read() {}, + /** @param {*} file */ + write(file, _encoding, callback) { + inputs.set(file.path, file); + proc.send(protocol.message.write(file)); + callback(); + }, + final(callback) { + proc.send(protocol.message.final()); + callback(); + } + }); + const jsStream = compileStream.js = new Readable({ + objectMode: true, + read() {} + }); + const dtsStream = compileStream.dts = new Readable({ + objectMode: true, + read() {} + }); + proc.send(protocol.message.createProject(tsConfigFileName, settings, options)); + proc.on("message", (/**@type {protocol.WorkerMessage}*/ message) => { + switch (message.method) { + case "write": { + const file = protocol.vinylFromJson(message.params); + compileStream.push(file); + if (file.path.endsWith(".d.ts")) { + dtsStream.push(file); + } + else { + jsStream.push(file); + } + break; + } + case "final": { + compileStream.push(null); + jsStream.push(null); + dtsStream.push(null); + proc.kill(); // TODO(rbuckton): pool workers? may not be feasible due to gulp-typescript holding onto memory + break; + } + case "error": { + const error = protocol.errorFromJson(message.params); + compileStream.emit("error", error); + proc.kill(); // TODO(rbuckton): pool workers? may not be feasible due to gulp-typescript holding onto memory + break; + } + case "reporter.error": { + if (reporter.error) { + const error = protocol.typeScriptErrorFromJson(message.params, host); + reporter.error(error, project.typescript); + } + break; + } + case "reporter.finish": { + if (reporter.finish) { + reporter.finish(message.params); + } + } + } + }); + return /** @type {*} */(compileStream); + }); + return Object.assign(wrappedProject, project); +} + +exports.createProject = createProject; \ No newline at end of file diff --git a/scripts/build/gulp-typescript-oop/protocol.js b/scripts/build/gulp-typescript-oop/protocol.js new file mode 100644 index 00000000000..58e60b058d4 --- /dev/null +++ b/scripts/build/gulp-typescript-oop/protocol.js @@ -0,0 +1,279 @@ +// @ts-check +const Vinyl = require("vinyl"); + +/** + * @param {File} file + * @returns {*} + */ +function vinylToJson(file) { + if (file.isStream()) throw new TypeError("Streams not supported."); + return { + path: file.path, + cwd: file.cwd, + base: file.base, + contents: file.isBuffer() ? file.contents.toString("utf8") : undefined, + sourceMap: file.sourceMap + }; +} +exports.vinylToJson = vinylToJson; + +/** + * @param {*} json + * @returns {File} + */ +function vinylFromJson(json) { + return new Vinyl({ + path: json.path, + cwd: json.cwd, + base: json.base, + contents: typeof json.contents === "string" ? Buffer.from(json.contents, "utf8") : undefined, + sourceMap: json.sourceMap + }); +} +exports.vinylFromJson = vinylFromJson; + +/** + * @param {Error} error + * @returns {*} + */ +function errorToJson(error) { + return { + name: error.name, + message: error.message, + stack: error.stack + }; +} +exports.errorToJson = errorToJson; + +/** + * @param {*} json + * @returns {Error} + */ +function errorFromJson(json) { + const error = new Error(); + error.name = json.name; + error.message = json.message; + error.stack = json.stack; + return error; +} +exports.errorFromJson = errorFromJson; + +/** + * @param {TypeScriptError} error + * @returns {*} + */ +function typeScriptErrorToJson(error) { + return Object.assign({}, errorToJson(error), { + fullFilename: error.fullFilename, + relativeFilename: error.relativeFilename, + file: error.file && { path: error.file.path }, + tsFile: error.tsFile && sourceFileToJson(error.tsFile), + diagnostic: diagnosticToJson(error.diagnostic), + startPosition: error.startPosition, + endPosition: error.endPosition + }); +} +exports.typeScriptErrorToJson = typeScriptErrorToJson; + +/** + * @param {*} json + * @param {SourceFileHost & VinylHost} host + * @returns {TypeScriptError} + */ +function typeScriptErrorFromJson(json, host) { + const error = /**@type {TypeScriptError}*/(errorFromJson(json)); + error.fullFilename = json.fullFilename; + error.relativeFilename = json.relativeFilename; + error.file = json.file && host.getVinyl(json.file.path); + error.tsFile = json.tsFile && sourceFileFromJson(json.tsFile, host); + error.diagnostic = diagnosticFromJson(json.diagnostic, host); + error.startPosition = json.startPosition; + error.endPosition = json.endPosition; + return error; +} +exports.typeScriptErrorFromJson = typeScriptErrorFromJson; + +/** + * @param {SourceFile} file + * @returns {*} + */ +function sourceFileToJson(file) { + return { + fileName: file.fileName, + text: file.text, + languageVersion: file.languageVersion + }; +} +exports.sourceFileToJson = sourceFileToJson; + +/** + * @param {*} json + * @param {SourceFileHost} host + */ +function sourceFileFromJson(json, host) { + return host.getSourceFile(json.fileName) + || host.createSourceFile(json.fileName, json.text, json.languageVersion); +} +exports.sourceFileFromJson = sourceFileFromJson; + +/** + * @param {Diagnostic} diagnostic + * @returns {*} + */ +function diagnosticToJson(diagnostic) { + return Object.assign({}, diagnosticRelatedInformationToJson(diagnostic), { + category: diagnostic.category, + code: diagnostic.code, + source: diagnostic.source, + relatedInformation: diagnostic.relatedInformation && diagnostic.relatedInformation.map(diagnosticRelatedInformationToJson) + }); +} +exports.diagnosticToJson = diagnosticToJson; + +/** + * @param {*} json + * @param {SourceFileHost} host + * @returns {Diagnostic} + */ +function diagnosticFromJson(json, host) { + return Object.assign({}, diagnosticRelatedInformationFromJson(json, host), { + category: json.category, + code: json.code, + source: json.source, + relatedInformation: json.relatedInformation && json.relatedInformation.map(diagnosticRelatedInformationFromJson, host) + }); +} +exports.diagnosticFromJson = diagnosticFromJson; + +/** + * @param {DiagnosticRelatedInformation} diagnostic + * @returns {*} + */ +function diagnosticRelatedInformationToJson(diagnostic) { + return { + file: diagnostic.file && { fileName: diagnostic.file.fileName }, + start: diagnostic.start, + length: diagnostic.length, + messageText: diagnostic.messageText + }; +} +exports.diagnosticRelatedInformationToJson = diagnosticRelatedInformationToJson; + +/** + * @param {*} json + * @param {SourceFileHost} host + * @returns {DiagnosticRelatedInformation} + */ +function diagnosticRelatedInformationFromJson(json, host) { + return { + file: json.file && sourceFileFromJson(json.file, host), + start: json.start, + length: json.length, + messageText: json.messageText + }; +} +exports.diagnosticRelatedInformationFromJson = diagnosticRelatedInformationFromJson; + +exports.message = {}; + +/** + * @param {string | undefined} tsConfigFileName + * @param {import("gulp-typescript").Settings} settings + * @param {Object} options + * @param {string} [options.typescript] + * @returns {CreateProjectMessage} + * + * @typedef CreateProjectMessage + * @property {"createProject"} method + * @property {CreateProjectParams} params + * + * @typedef CreateProjectParams + * @property {string | undefined} tsConfigFileName + * @property {import("gulp-typescript").Settings} settings + * @property {CreateProjectOptions} options + * + * @typedef CreateProjectOptions + * @property {string} [typescript] + */ +exports.message.createProject = function(tsConfigFileName, settings, options) { + return { method: "createProject", params: { tsConfigFileName, settings, options } }; +}; + +/** + * @param {File} file + * @returns {WriteMessage} + * + * @typedef WriteMessage + * @property {"write"} method + * @property {*} params + */ +exports.message.write = function(file) { + return { method: "write", params: vinylToJson(file) }; +}; + +/** + * @returns {FinalMessage} + * + * @typedef FinalMessage + * @property {"final"} method + */ +exports.message.final = function() { + return { method: "final" }; +}; + +/** + * @param {Error} error + * @returns {ErrorMessage} + * + * @typedef ErrorMessage + * @property {"error"} method + * @property {*} params + */ +exports.message.error = function(error) { + return { method: "error", params: errorToJson(error) }; +}; + +exports.message.reporter = {}; + +/** + * @param {TypeScriptError} error + * @returns {reporter.ErrorMessage} + * + * @typedef reporter.ErrorMessage + * @property {"reporter.error"} method + * @property {*} params + */ +exports.message.reporter.error = function(error) { + return { method: "reporter.error", params: typeScriptErrorToJson(error) }; +}; + +/** + * @param {*} results + * @returns {reporter.FinishMessage} + * + * @typedef reporter.FinishMessage + * @property {"reporter.finish"} method + * @property {*} params + */ +exports.message.reporter.finish = function(results) { + return { method: "reporter.finish", params: results }; +}; + +/** + * @typedef {import("vinyl")} File + * @typedef {typeof import("typescript")} TypeScriptModule + * @typedef {import("typescript").SourceFile} SourceFile + * @typedef {import("typescript").Diagnostic} Diagnostic + * @typedef {import("typescript").DiagnosticRelatedInformation} DiagnosticRelatedInformation + * @typedef {import("gulp-typescript").reporter.TypeScriptError} TypeScriptError + * @typedef {WriteMessage | FinalMessage | CreateProjectMessage} HostMessage + * @typedef {WriteMessage | FinalMessage | ErrorMessage | reporter.ErrorMessage | reporter.FinishMessage} WorkerMessage + * + * @typedef SourceFileHost + * @property {(fileName: string) => SourceFile | undefined} getSourceFile + * @property {(fileName: string, text: string, languageVersion: number) => SourceFile} createSourceFile + * + * @typedef VinylHost + * @property {(path: string) => File | undefined} getVinyl + */ +void 0; \ No newline at end of file diff --git a/scripts/build/gulp-typescript-oop/worker.js b/scripts/build/gulp-typescript-oop/worker.js new file mode 100644 index 00000000000..5d68bc591e8 --- /dev/null +++ b/scripts/build/gulp-typescript-oop/worker.js @@ -0,0 +1,79 @@ +// @ts-check +const fs = require("fs"); +const tsc = require("gulp-typescript"); +const { Readable, Writable } = require("stream"); +const protocol = require("./protocol"); + +/** @type {tsc.Project} */ +let project; + +/** @type {Readable} */ +let inputStream; + +/** @type {Writable} */ +let outputStream; + +/** @type {tsc.CompileStream} */ +let compileStream; + +process.on("message", (/**@type {protocol.HostMessage}*/ message) => { + try { + switch (message.method) { + case "createProject": { + const { tsConfigFileName, settings, options } = message.params; + if (options.typescript) { + settings.typescript = require(options.typescript); + } + + project = tsConfigFileName === undefined + ? tsc.createProject(settings) + : tsc.createProject(tsConfigFileName, settings); + + inputStream = new Readable({ + objectMode: true, + read() {} + }); + + outputStream = new Writable({ + objectMode: true, + /** + * @param {*} file + */ + write(file, _, callback) { + process.send(protocol.message.write(file)); + callback(); + }, + final(callback) { + process.send(protocol.message.final()); + callback(); + } + }); + compileStream = project({ + error(error) { process.send(protocol.message.reporter.error(error)); }, + finish(results) { process.send(protocol.message.reporter.finish(results)); } + }); + compileStream.on("error", error => { + process.send(protocol.message.error(error)); + }); + outputStream.on("error", () => { + /* do nothing */ + }); + inputStream.pipe(compileStream).pipe(outputStream); + break; + } + case "write": { + const file = protocol.vinylFromJson(message.params); + if (!file.isBuffer()) file.contents = fs.readFileSync(file.path); + inputStream.push(file); + break; + } + case "final": { + inputStream.push(null); + break; + } + } + } + catch (e) { + process.send(protocol.message.error(e)); + } +}); \ No newline at end of file diff --git a/scripts/build/main.js b/scripts/build/main.js deleted file mode 100644 index f3ded5d0c42..00000000000 --- a/scripts/build/main.js +++ /dev/null @@ -1,91 +0,0 @@ -// @ts-check -const fs = require("fs"); -const tsc = require("gulp-typescript"); -const Vinyl = require("vinyl"); -const { Readable, Writable } = require("stream"); - -/** @type {tsc.Project} */ -let project; - -/** @type {Readable} */ -let inputStream; - -/** @type {Writable} */ -let outputStream; - -/** @type {tsc.CompileStream} */ -let compileStream; - -process.on("message", ({ method, params }) => { - try { - if (method === "createProject") { - const { tsConfigFileName, settings, options } = params; - if (options.typescript) { - settings.typescript = require(options.typescript); - } - project = tsConfigFileName === undefined ? tsc.createProject(settings) : tsc.createProject(tsConfigFileName, settings); - inputStream = new Readable({ - objectMode: true, - read() {} - }); - outputStream = new Writable({ - objectMode: true, - /** - * @param {*} file - */ - write(file, _, callback) { - process.send({ - method: "write", - params: { - path: file.path, - cwd: file.cwd, - base: file.base, - contents: file.contents.toString(), - sourceMap: file.sourceMap - } - }); - callback(); - }, - final(callback) { - process.send({ method: "final" }); - callback(); - } - }); - outputStream.on("error", error => { - process.send({ - method: "error", - params: { - name: error.name, - message: error.message, - stack: error.stack - } - }); - }); - compileStream = project(); - inputStream.pipe(compileStream).pipe(outputStream); - } - else if (method === "write") { - const file = new Vinyl({ - path: params.path, - cwd: params.cwd, - base: params.base - }); - file.contents = fs.readFileSync(file.path); - if (params.sourceMap) file.sourceMap = params.sourceMap; - inputStream.push(/** @type {*} */(file)); - } - else if (method === "final") { - inputStream.push(null); - } - } - catch (e) { - process.send({ - method: "error", - params: { - name: e.name, - message: e.message, - stack: e.stack - } - }); - } -}); \ No newline at end of file diff --git a/scripts/build/project.js b/scripts/build/project.js index badeb606d3b..35b500d749a 100644 --- a/scripts/build/project.js +++ b/scripts/build/project.js @@ -137,6 +137,7 @@ function watch(projectSpec, options, tasks, callback) { if (typeof options === "function") callback = options, tasks = /**@type {string[] | undefined}*/(undefined), options = /**@type {CompileOptions | undefined}*/(undefined); if (Array.isArray(options)) tasks = options, options = /**@type {CompileOptions | undefined}*/(undefined); const resolvedOptions = resolveCompileOptions(options); + resolvedOptions.watch = true; const resolvedProjectSpec = resolveProjectSpec(projectSpec, resolvedOptions.paths, /*referrer*/ undefined); const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, resolvedOptions.paths); projectGraph.isRoot = true; @@ -284,6 +285,7 @@ function resolvePathOptions(options) { * @property {boolean} [verbose] Indicates whether verbose logging is enabled. * @property {boolean} [force] Force recompilation (no up-to-date check). * @property {boolean} [inProcess] Indicates whether to run gulp-typescript in-process or out-of-process (default). + * @property {boolean} [watch] Indicates the project was created in watch mode */ function resolveCompileOptions(options = {}) { const paths = resolvePathOptions(options); @@ -305,7 +307,7 @@ function resolveCompileOptions(options = {}) { * @returns {ResolvedCompileOptions} */ function mergeCompileOptions(left, right) { - if (left.typescript !== right.typescript) throw new Error("Cannot merge project options targeting different TypeScript packages"); + if (left.typescript.typescript !== right.typescript.typescript) throw new Error("Cannot merge project options targeting different TypeScript packages"); if (tryReuseCompileOptions(left, right)) return left; return { paths: left.paths, @@ -314,7 +316,8 @@ function mergeCompileOptions(left, right) { dts: right.dts || left.dts, verbose: right.verbose || left.verbose, force: right.force || left.force, - inProcess: right.inProcess || left.inProcess + inProcess: right.inProcess || left.inProcess, + watch: right.watch || left.watch }; } @@ -599,30 +602,40 @@ function resolveDestPath(projectGraph, paths) { */ function ensureCompileTask(projectGraph, options) { const projectGraphConfig = getOrCreateProjectGraphConfiguration(projectGraph, options); - projectGraphConfig.resolvedOptions = options = mergeCompileOptions(options, options); - if (!projectGraphConfig.compileTaskCreated) { - const deps = makeProjectReferenceCompileTasks(projectGraph, options.typescript, options.paths); - compilationGulp.task(compileTaskName(projectGraph, options.typescript), deps, () => { - const destPath = resolveDestPath(projectGraph, options.paths); + projectGraphConfig.resolvedOptions = mergeCompileOptions(projectGraphConfig.resolvedOptions, options); + const hasCompileTask = projectGraphConfig.compileTaskCreated; + projectGraphConfig.compileTaskCreated = true; + const deps = makeProjectReferenceCompileTasks(projectGraph, projectGraphConfig.resolvedOptions.typescript, projectGraphConfig.resolvedOptions.paths, projectGraphConfig.resolvedOptions.watch); + if (!hasCompileTask) { + compilationGulp.task(compileTaskName(projectGraph, projectGraphConfig.resolvedOptions.typescript), deps, () => { + const destPath = resolveDestPath(projectGraph, projectGraphConfig.resolvedOptions.paths); const { sourceMap, inlineSourceMap, inlineSources = false, sourceRoot, declarationMap } = projectGraph.project.options; const configFilePath = projectGraph.project.options.configFilePath; const sourceMapPath = inlineSourceMap ? undefined : "."; const sourceMapOptions = { includeContent: inlineSources, sourceRoot, destPath }; - const project = options.inProcess - ? tsc.createProject(configFilePath, { typescript: require(options.typescript.typescript) }) - : tsc_oop.createProject(configFilePath, {}, { typescript: options.typescript.typescript }); + const project = projectGraphConfig.resolvedOptions.inProcess + ? tsc.createProject(configFilePath, { typescript: require(projectGraphConfig.resolvedOptions.typescript.typescript) }) + : tsc_oop.createProject(configFilePath, {}, { typescript: projectGraphConfig.resolvedOptions.typescript.typescript }); const stream = project.src() - .pipe(gulpif(!options.force, upToDate(projectGraph.project, { verbose: options.verbose, parseProject: createParseProject(options.paths) }))) + .pipe(gulpif(!projectGraphConfig.resolvedOptions.force, upToDate(projectGraph.project, { verbose: projectGraphConfig.resolvedOptions.verbose, parseProject: createParseProject(projectGraphConfig.resolvedOptions.paths) }))) .pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.init())) .pipe(project()); - const js = (options.js ? options.js(stream.js) : stream.js) + if (projectGraphConfig.resolvedOptions.watch) { + stream.on("error", error => { + if (error.message === "TypeScript: Compilation failed") { + stream.emit("end"); + stream.js.emit("end"); + stream.dts.emit("end"); + } + }); + } + const js = (projectGraphConfig.resolvedOptions.js ? projectGraphConfig.resolvedOptions.js(stream.js) : stream.js) .pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.write(sourceMapPath, sourceMapOptions))); - const dts = (options.dts ? options.dts(stream.dts) : stream.dts) + const dts = (projectGraphConfig.resolvedOptions.dts ? projectGraphConfig.resolvedOptions.dts(stream.dts) : stream.dts) .pipe(gulpif(declarationMap, sourcemaps.write(sourceMapPath, sourceMapOptions))); return merge2([js, dts]) .pipe(gulp.dest(destPath)); }); - projectGraphConfig.compileTaskCreated = true; } return projectGraph; } @@ -631,9 +644,10 @@ function ensureCompileTask(projectGraph, options) { * @param {ProjectGraph} projectGraph * @param {ResolvedTypeScript} typescript * @param {ResolvedPathOptions} paths + * @param {boolean} watch */ -function makeProjectReferenceCompileTasks(projectGraph, typescript, paths) { - return projectGraph.references.map(({target}) => compileTaskName(ensureCompileTask(target, { paths, typescript }), typescript)); +function makeProjectReferenceCompileTasks(projectGraph, typescript, paths, watch) { + return projectGraph.references.map(({target}) => compileTaskName(ensureCompileTask(target, { paths, typescript, watch }), typescript)); } /** @@ -715,7 +729,7 @@ function ensureWatcher(projectGraph, options, tasks, callback) { } /** - * @param {ProjectGraphConfiguration} config + * @param {ProjectGraphConfiguration} config * @param {import("orchestrator").Task} task */ function possiblyTriggerRecompilation(config, task) { @@ -732,7 +746,7 @@ function possiblyTriggerRecompilation(config, task) { /** * @param {import("orchestrator").Task} task - * @param {ProjectGraphConfiguration} config + * @param {ProjectGraphConfiguration} config */ function triggerRecompilation(task, config) { compilationGulp._resetTask(task);