From 2cea69133a0762cddb70d437a423d2a61f770df2 Mon Sep 17 00:00:00 2001 From: Stanislav Panferov Date: Sun, 9 Aug 2015 17:35:29 +0300 Subject: [PATCH 1/3] Fix Babel source maps and `.babelrc` resolution --- lib/main/lang/modules/building.ts | 40 ++++++++++++++++++++++++++----- lib/main/tsconfig/tsconfig.ts | 4 ++-- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/main/lang/modules/building.ts b/lib/main/lang/modules/building.ts index 5672e8f18..07cc28b66 100644 --- a/lib/main/lang/modules/building.ts +++ b/lib/main/lang/modules/building.ts @@ -4,7 +4,7 @@ import path = require('path'); import fs = require('fs'); import {pathIsRelative, makeRelativePath} from "../../tsconfig/tsconfig"; import {consistentPath} from "../../utils/fsUtil"; -import {createMap} from "../utils"; +import {createMap, assign} from "../utils"; /** Lazy loaded babel tanspiler */ let babel: any; @@ -31,6 +31,8 @@ export function emitFile(proj: project.Project, filePath: string): EmitOutput { var emitDone = !output.emitSkipped; var errors: TSError[] = []; + let sourceFile = services.getSourceFile(filePath); + // Emit is no guarantee that there are no errors var allDiagnostics = services.getCompilerOptionsDiagnostics() .concat(services.getSyntacticDiagnostics(filePath)) @@ -48,7 +50,13 @@ export function emitFile(proj: project.Project, filePath: string): EmitOutput { let sourceMapContents: {[index:string]: any} = {}; output.outputFiles.forEach(o => { mkdirp.sync(path.dirname(o.name)); - let additionalEmits = runExternalTranspiler(o, proj, sourceMapContents); + let additionalEmits = runExternalTranspiler( + filePath, + sourceFile.text, + o, + proj, + sourceMapContents + ); if (!sourceMapContents[o.name]) { // .js.map files will be written as an "additional emit" later. @@ -89,7 +97,12 @@ export function getRawOutput(proj: project.Project, filePath: string): ts.EmitOu return output; } -function runExternalTranspiler(outputFile: ts.OutputFile, project: project.Project, sourceMapContents: {[index:string]: any}) : ts.OutputFile[] { +function runExternalTranspiler(sourceFileName: string, + sourceFileText: string, + outputFile: ts.OutputFile, + project: project.Project, + sourceMapContents: {[index:string]: any}) : ts.OutputFile[] { + if (!isJSFile(outputFile.name) && !isJSSourceMapFile(outputFile.name)) { return []; } @@ -107,15 +120,30 @@ function runExternalTranspiler(outputFile: ts.OutputFile, project: project.Proje return []; } - if (externalTranspiler.toLocaleLowerCase() === "babel") { - babel = require("babel"); + if (typeof externalTranspiler === 'string') { + externalTranspiler = { + name: externalTranspiler as any, + options: {} + } + } - let babelOptions : any = {}; + if (externalTranspiler.name.toLocaleLowerCase() === "babel") { + if (!babel) { + babel = require("babel") + } + + let babelOptions : any = assign({}, externalTranspiler.options, { + filename: outputFile.name + }); let sourceMapFileName = getJSMapNameForJSFile(outputFile.name); if (sourceMapContents[sourceMapFileName]) { babelOptions.inputSourceMap = sourceMapContents[sourceMapFileName].sourceMapPayload; + let baseName = path.basename(sourceFileName); + // NOTE: Babel generates invalid source map without consistent `sources` and `file`. + babelOptions.inputSourceMap.sources = [baseName]; + babelOptions.inputSourceMap.file = baseName; } if (settings.compilerOptions.sourceMap) { babelOptions.sourceMaps = true; diff --git a/lib/main/tsconfig/tsconfig.ts b/lib/main/tsconfig/tsconfig.ts index a22b4d43d..32bf49bd2 100644 --- a/lib/main/tsconfig/tsconfig.ts +++ b/lib/main/tsconfig/tsconfig.ts @@ -116,7 +116,7 @@ interface TypeScriptProjectRawSpecification { formatCodeOptions?: formatting.FormatCodeOptions; // optional: formatting options compileOnSave?: boolean; // optional: compile on save. Ignored to build tools. Used by IDEs buildOnSave?: boolean; - externalTranspiler?: string; + externalTranspiler?: { name: string; options?: any }; scripts?: { postbuild?: string }; } @@ -133,7 +133,7 @@ export interface TypeScriptProjectSpecification { compileOnSave: boolean; buildOnSave: boolean; package?: UsefulFromPackageJson; - externalTranspiler?: string; + externalTranspiler?: { name: string; options?: any }; scripts: { postbuild?: string }; } From 66d3800297c30b97516454f6fd53e443d20ba8f5 Mon Sep 17 00:00:00 2001 From: Stanislav Panferov Date: Sun, 9 Aug 2015 17:44:48 +0300 Subject: [PATCH 2/3] Fix indentation in `building.ts` --- lib/main/lang/modules/building.ts | 234 +++++++++++++++--------------- 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/lib/main/lang/modules/building.ts b/lib/main/lang/modules/building.ts index 07cc28b66..25098350f 100644 --- a/lib/main/lang/modules/building.ts +++ b/lib/main/lang/modules/building.ts @@ -47,27 +47,27 @@ export function emitFile(proj: project.Project, filePath: string): EmitOutput { }); { - let sourceMapContents: {[index:string]: any} = {}; - output.outputFiles.forEach(o => { - mkdirp.sync(path.dirname(o.name)); - let additionalEmits = runExternalTranspiler( - filePath, - sourceFile.text, - o, - proj, - sourceMapContents - ); - - if (!sourceMapContents[o.name]) { - // .js.map files will be written as an "additional emit" later. - fs.writeFileSync(o.name, o.text, "utf8"); - } - - additionalEmits.forEach(a => { - mkdirp.sync(path.dirname(a.name)); - fs.writeFileSync(a.name, a.text, "utf8"); + let sourceMapContents: {[index:string]: any} = {}; + output.outputFiles.forEach(o => { + mkdirp.sync(path.dirname(o.name)); + let additionalEmits = runExternalTranspiler( + filePath, + sourceFile.text, + o, + proj, + sourceMapContents + ); + + if (!sourceMapContents[o.name]) { + // .js.map files will be written as an "additional emit" later. + fs.writeFileSync(o.name, o.text, "utf8"); + } + + additionalEmits.forEach(a => { + mkdirp.sync(path.dirname(a.name)); + fs.writeFileSync(a.name, a.text, "utf8"); }) - }); + }); } var outputFiles = output.outputFiles.map((o) => o.name); @@ -87,117 +87,117 @@ export function getRawOutput(proj: project.Project, filePath: string): ts.EmitOu let services = proj.languageService; let output : ts.EmitOutput; if (proj.includesSourceFile(filePath)) { - output = services.getEmitOutput(filePath); + output = services.getEmitOutput(filePath); } else { - output = { - outputFiles: [{name: filePath, text: Not_In_Context, writeByteOrderMark: false}], - emitSkipped: true - } + output = { + outputFiles: [{name: filePath, text: Not_In_Context, writeByteOrderMark: false}], + emitSkipped: true + } } return output; } function runExternalTranspiler(sourceFileName: string, - sourceFileText: string, - outputFile: ts.OutputFile, - project: project.Project, - sourceMapContents: {[index:string]: any}) : ts.OutputFile[] { - - if (!isJSFile(outputFile.name) && !isJSSourceMapFile(outputFile.name)) { - return []; - } - - let settings = project.projectFile.project; - let externalTranspiler = settings.externalTranspiler; - if (!externalTranspiler) { - return []; - } - - if (isJSSourceMapFile(outputFile.name)) { - let sourceMapPayload = JSON.parse(outputFile.text); - let jsFileName = consistentPath(path.resolve(path.dirname(outputFile.name), sourceMapPayload.file)); - sourceMapContents[outputFile.name] = {jsFileName: jsFileName, sourceMapPayload}; - return []; - } - - if (typeof externalTranspiler === 'string') { - externalTranspiler = { - name: externalTranspiler as any, - options: {} - } - } - - if (externalTranspiler.name.toLocaleLowerCase() === "babel") { - if (!babel) { - babel = require("babel") - } + sourceFileText: string, + outputFile: ts.OutputFile, + project: project.Project, + sourceMapContents: {[index:string]: any}) : ts.OutputFile[] { - let babelOptions : any = assign({}, externalTranspiler.options, { - filename: outputFile.name - }); - - let sourceMapFileName = getJSMapNameForJSFile(outputFile.name); + if (!isJSFile(outputFile.name) && !isJSSourceMapFile(outputFile.name)) { + return []; + } - if (sourceMapContents[sourceMapFileName]) { - babelOptions.inputSourceMap = sourceMapContents[sourceMapFileName].sourceMapPayload; - let baseName = path.basename(sourceFileName); - // NOTE: Babel generates invalid source map without consistent `sources` and `file`. - babelOptions.inputSourceMap.sources = [baseName]; - babelOptions.inputSourceMap.file = baseName; - } - if (settings.compilerOptions.sourceMap) { - babelOptions.sourceMaps = true; - } - if (settings.compilerOptions.inlineSourceMap) { - babelOptions.sourceMaps = "inline"; - } - if (!settings.compilerOptions.removeComments) { - babelOptions.comments = true; - } + let settings = project.projectFile.project; + let externalTranspiler = settings.externalTranspiler; + if (!externalTranspiler) { + return []; + } - let babelResult = babel.transform(outputFile.text, babelOptions); - outputFile.text = babelResult.code; + if (isJSSourceMapFile(outputFile.name)) { + let sourceMapPayload = JSON.parse(outputFile.text); + let jsFileName = consistentPath(path.resolve(path.dirname(outputFile.name), sourceMapPayload.file)); + sourceMapContents[outputFile.name] = {jsFileName: jsFileName, sourceMapPayload}; + return []; + } - if (babelResult.map && settings.compilerOptions.sourceMap) { - let additionalEmit : ts.OutputFile = { - name: sourceMapFileName, - text : JSON.stringify(babelResult.map), - writeByteOrderMark: settings.compilerOptions.emitBOM - }; + if (typeof externalTranspiler === 'string') { + externalTranspiler = { + name: externalTranspiler as any, + options: {} + } + } - if (additionalEmit.name === "") { - // can't emit a blank file name - this should only be reached if the TypeScript - // language service returns the .js file before the .js.map file. - console.warn(`The TypeScript language service did not yet provide a .js.map name for file ${outputFile.name}`); - return []; - } + if (externalTranspiler.name.toLocaleLowerCase() === "babel") { + if (!babel) { + babel = require("babel") + } + + let babelOptions: any = assign({}, externalTranspiler.options, { + filename: outputFile.name + }); + + let sourceMapFileName = getJSMapNameForJSFile(outputFile.name); + + if (sourceMapContents[sourceMapFileName]) { + babelOptions.inputSourceMap = sourceMapContents[sourceMapFileName].sourceMapPayload; + let baseName = path.basename(sourceFileName); + // NOTE: Babel generates invalid source map without consistent `sources` and `file`. + babelOptions.inputSourceMap.sources = [baseName]; + babelOptions.inputSourceMap.file = baseName; + } + if (settings.compilerOptions.sourceMap) { + babelOptions.sourceMaps = true; + } + if (settings.compilerOptions.inlineSourceMap) { + babelOptions.sourceMaps = "inline"; + } + if (!settings.compilerOptions.removeComments) { + babelOptions.comments = true; + } + + let babelResult = babel.transform(outputFile.text, babelOptions); + outputFile.text = babelResult.code; + + if (babelResult.map && settings.compilerOptions.sourceMap) { + let additionalEmit : ts.OutputFile = { + name: sourceMapFileName, + text : JSON.stringify(babelResult.map), + writeByteOrderMark: settings.compilerOptions.emitBOM + }; + + if (additionalEmit.name === "") { + // can't emit a blank file name - this should only be reached if the TypeScript + // language service returns the .js file before the .js.map file. + console.warn(`The TypeScript language service did not yet provide a .js.map name for file ${outputFile.name}`); + return []; + } + + return [additionalEmit]; + } + + return []; + } - return [additionalEmit]; + function getJSMapNameForJSFile(jsFileName: string) { + for (let jsMapName in sourceMapContents) { + if (sourceMapContents.hasOwnProperty(jsMapName)) { + if (sourceMapContents[jsMapName].jsFileName === jsFileName) { + return jsMapName; + } + } + } + return ""; + } } - return []; - } + function isJSFile(fileName: string) { + return (path.extname(fileName).toLocaleLowerCase() === ".js"); + } - function getJSMapNameForJSFile(jsFileName: string) { - for (let jsMapName in sourceMapContents) { - if (sourceMapContents.hasOwnProperty(jsMapName)) { - if (sourceMapContents[jsMapName].jsFileName === jsFileName) { - return jsMapName; + function isJSSourceMapFile(fileName: string) { + let lastExt = path.extname(fileName); + if (lastExt === ".map") { + return isJSFile(fileName.substr(0,fileName.length - 4)); } - } + return false; } - return ""; - } -} - -function isJSFile(fileName: string) { - return (path.extname(fileName).toLocaleLowerCase() === ".js"); -} - -function isJSSourceMapFile(fileName: string) { - let lastExt = path.extname(fileName); - if (lastExt === ".map") { - return isJSFile(fileName.substr(0,fileName.length - 4)); - } - return false; -} From 87bc64f69616b2d33d06bee0a1fe2069a94d685e Mon Sep 17 00:00:00 2001 From: Stanislav Panferov Date: Mon, 10 Aug 2015 11:52:45 +0300 Subject: [PATCH 3/3] Use union type for the `externalTranspiler` option --- lib/main/lang/modules/building.ts | 89 ++++++++++++++++--------------- lib/main/tsconfig/tsconfig.ts | 4 +- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/lib/main/lang/modules/building.ts b/lib/main/lang/modules/building.ts index 25098350f..b0d9aa709 100644 --- a/lib/main/lang/modules/building.ts +++ b/lib/main/lang/modules/building.ts @@ -122,60 +122,63 @@ function runExternalTranspiler(sourceFileName: string, if (typeof externalTranspiler === 'string') { externalTranspiler = { - name: externalTranspiler as any, + name: externalTranspiler as string, options: {} } } - if (externalTranspiler.name.toLocaleLowerCase() === "babel") { - if (!babel) { - babel = require("babel") - } + // We need this type guard to narrow externalTranspiler's type + if (typeof externalTranspiler === 'object') { + if (externalTranspiler.name.toLocaleLowerCase() === "babel") { + if (!babel) { + babel = require("babel") + } - let babelOptions: any = assign({}, externalTranspiler.options, { - filename: outputFile.name - }); + let babelOptions: any = assign({}, externalTranspiler.options || {}, { + filename: outputFile.name + }); - let sourceMapFileName = getJSMapNameForJSFile(outputFile.name); + let sourceMapFileName = getJSMapNameForJSFile(outputFile.name); - if (sourceMapContents[sourceMapFileName]) { - babelOptions.inputSourceMap = sourceMapContents[sourceMapFileName].sourceMapPayload; - let baseName = path.basename(sourceFileName); - // NOTE: Babel generates invalid source map without consistent `sources` and `file`. - babelOptions.inputSourceMap.sources = [baseName]; - babelOptions.inputSourceMap.file = baseName; - } - if (settings.compilerOptions.sourceMap) { - babelOptions.sourceMaps = true; - } - if (settings.compilerOptions.inlineSourceMap) { - babelOptions.sourceMaps = "inline"; - } - if (!settings.compilerOptions.removeComments) { - babelOptions.comments = true; - } + if (sourceMapContents[sourceMapFileName]) { + babelOptions.inputSourceMap = sourceMapContents[sourceMapFileName].sourceMapPayload; + let baseName = path.basename(sourceFileName); + // NOTE: Babel generates invalid source map without consistent `sources` and `file`. + babelOptions.inputSourceMap.sources = [baseName]; + babelOptions.inputSourceMap.file = baseName; + } + if (settings.compilerOptions.sourceMap) { + babelOptions.sourceMaps = true; + } + if (settings.compilerOptions.inlineSourceMap) { + babelOptions.sourceMaps = "inline"; + } + if (!settings.compilerOptions.removeComments) { + babelOptions.comments = true; + } - let babelResult = babel.transform(outputFile.text, babelOptions); - outputFile.text = babelResult.code; - - if (babelResult.map && settings.compilerOptions.sourceMap) { - let additionalEmit : ts.OutputFile = { - name: sourceMapFileName, - text : JSON.stringify(babelResult.map), - writeByteOrderMark: settings.compilerOptions.emitBOM - }; - - if (additionalEmit.name === "") { - // can't emit a blank file name - this should only be reached if the TypeScript - // language service returns the .js file before the .js.map file. - console.warn(`The TypeScript language service did not yet provide a .js.map name for file ${outputFile.name}`); - return []; + let babelResult = babel.transform(outputFile.text, babelOptions); + outputFile.text = babelResult.code; + + if (babelResult.map && settings.compilerOptions.sourceMap) { + let additionalEmit : ts.OutputFile = { + name: sourceMapFileName, + text : JSON.stringify(babelResult.map), + writeByteOrderMark: settings.compilerOptions.emitBOM + }; + + if (additionalEmit.name === "") { + // can't emit a blank file name - this should only be reached if the TypeScript + // language service returns the .js file before the .js.map file. + console.warn(`The TypeScript language service did not yet provide a .js.map name for file ${outputFile.name}`); + return []; + } + + return [additionalEmit]; } - return [additionalEmit]; + return []; } - - return []; } function getJSMapNameForJSFile(jsFileName: string) { diff --git a/lib/main/tsconfig/tsconfig.ts b/lib/main/tsconfig/tsconfig.ts index 32bf49bd2..8ba979bb4 100644 --- a/lib/main/tsconfig/tsconfig.ts +++ b/lib/main/tsconfig/tsconfig.ts @@ -116,7 +116,7 @@ interface TypeScriptProjectRawSpecification { formatCodeOptions?: formatting.FormatCodeOptions; // optional: formatting options compileOnSave?: boolean; // optional: compile on save. Ignored to build tools. Used by IDEs buildOnSave?: boolean; - externalTranspiler?: { name: string; options?: any }; + externalTranspiler?: string | { name: string; options?: any }; scripts?: { postbuild?: string }; } @@ -133,7 +133,7 @@ export interface TypeScriptProjectSpecification { compileOnSave: boolean; buildOnSave: boolean; package?: UsefulFromPackageJson; - externalTranspiler?: { name: string; options?: any }; + externalTranspiler?: string | { name: string; options?: any }; scripts: { postbuild?: string }; }