Skip to content

Commit

Permalink
Merge pull request #475 from TypeStrong/external-transpiler
Browse files Browse the repository at this point in the history
Optional transpilation with Babel
  • Loading branch information
basarat committed Jul 24, 2015
2 parents 8fbf86a + a9b6b2b commit f2a55cf
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 11 deletions.
88 changes: 84 additions & 4 deletions dist/main/lang/modules/building.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
var mkdirp = require('mkdirp');
var path = require('path');
var fs = require('fs');
var fsUtil_1 = require("../../utils/fsUtil");
var babel;
exports.Not_In_Context = "/* NotInContext */";
function diagnosticToTSError(diagnostic) {
var filePath = diagnostic.file.fileName;
Expand Down Expand Up @@ -29,10 +31,20 @@ function emitFile(proj, filePath) {
var startPosition = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
errors.push(diagnosticToTSError(diagnostic));
});
output.outputFiles.forEach(function (o) {
mkdirp.sync(path.dirname(o.name));
fs.writeFileSync(o.name, o.text, "utf8");
});
{
var sourceMapContents = {};
output.outputFiles.forEach(function (o) {
mkdirp.sync(path.dirname(o.name));
var additionalEmits = runExternalTranspiler(o, proj, sourceMapContents);
if (!sourceMapContents[o.name]) {
fs.writeFileSync(o.name, o.text, "utf8");
}
additionalEmits.forEach(function (a) {
mkdirp.sync(path.dirname(a.name));
fs.writeFileSync(a.name, a.text, "utf8");
});
});
}
var outputFiles = output.outputFiles.map(function (o) { return o.name; });
if (path.extname(filePath) == '.d.ts') {
outputFiles.push(filePath);
Expand Down Expand Up @@ -61,3 +73,71 @@ function getRawOutput(proj, filePath) {
return output;
}
exports.getRawOutput = getRawOutput;
function runExternalTranspiler(outputFile, project, sourceMapContents) {
if (!isJSFile(outputFile.name) && !isJSSourceMapFile(outputFile.name)) {
return [];
}
var settings = project.projectFile.project;
var externalTranspiler = settings.externalTranspiler;
if (!externalTranspiler) {
return [];
}
if (isJSSourceMapFile(outputFile.name)) {
var sourceMapPayload = JSON.parse(outputFile.text);
var jsFileName = fsUtil_1.consistentPath(path.resolve(path.dirname(outputFile.name), sourceMapPayload.file));
sourceMapContents[outputFile.name] = { jsFileName: jsFileName, sourceMapPayload: sourceMapPayload };
return [];
}
if (externalTranspiler.toLocaleLowerCase() === "babel") {
babel = require("babel");
var babelOptions = {};
var sourceMapFileName = getJSMapNameForJSFile(outputFile.name);
if (sourceMapContents[sourceMapFileName]) {
babelOptions.inputSourceMap = sourceMapContents[sourceMapFileName].sourceMapPayload;
}
if (settings.compilerOptions.sourceMap) {
babelOptions.sourceMaps = true;
}
if (settings.compilerOptions.inlineSourceMap) {
babelOptions.sourceMaps = "inline";
}
if (!settings.compilerOptions.removeComments) {
babelOptions.comments = true;
}
var babelResult = babel.transform(outputFile.text, babelOptions);
outputFile.text = babelResult.code;
if (babelResult.map && settings.compilerOptions.sourceMap) {
var additionalEmit = {
name: sourceMapFileName,
text: JSON.stringify(babelResult.map),
writeByteOrderMark: settings.compilerOptions.emitBOM
};
if (additionalEmit.name === "") {
console.warn("The TypeScript language service did not yet provide a .js.map name for file " + outputFile.name);
return [];
}
return [additionalEmit];
}
return [];
}
function getJSMapNameForJSFile(jsFileName) {
for (var jsMapName in sourceMapContents) {
if (sourceMapContents.hasOwnProperty(jsMapName)) {
if (sourceMapContents[jsMapName].jsFileName === jsFileName) {
return jsMapName;
}
}
}
return "";
}
}
function isJSFile(fileName) {
return (path.extname(fileName).toLocaleLowerCase() === ".js");
}
function isJSSourceMapFile(fileName) {
var lastExt = path.extname(fileName);
if (lastExt === ".map") {
return isJSFile(fileName.substr(0, fileName.length - 4));
}
return false;
}
3 changes: 2 additions & 1 deletion dist/main/tsconfig/tsconfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ function getProjectSync(pathOrSrcFile) {
formatCodeOptions: formatting.makeFormatCodeOptions(projectSpec.formatCodeOptions),
compileOnSave: projectSpec.compileOnSave == undefined ? true : projectSpec.compileOnSave,
package: pkg,
typings: []
typings: [],
externalTranspiler: projectSpec.externalTranspiler == undefined ? undefined : projectSpec.externalTranspiler
};
var validationResult = validator.validate(projectSpec.compilerOptions);
if (validationResult.errorMessage) {
Expand Down
16 changes: 16 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ If it conforms the latest TypeScript services API then yes! Just set the path to

However, please note that the [version](https://github.com/TypeStrong/atom-typescript/blob/master/docs/tsconfig.md#version) in `tsconfig.json` does not indicate the compiler atom is using. That's strictly an aide-mémoire - it's to remind you which version of the TypeScript this project is intended to work with.

## Can I use an alternate transpiler?
Atom-typescript supports using Babel as an alternate ES5 transpiler in coordination with the TypeScript language service. This may be useful if TypeScript does not yet support transpiling a certain feature correctly (for example [scope per for loop iteration with let](https://github.com/Microsoft/TypeScript/issues/3915)).

To enable using Babel as the transpiler, make these changes to your `tsconfig.json` file:

**1:** Add this key in the root:

```js
{
"externalTranspiler": "babel"
}
```
**2:** Set the `target` compiler option to `"es6"`. This is not *technically* required, but if you don't do this, you'll just be transpiling an already-transpiled file.

Note that atom-typescript's Babel integraion works with in concert with the `removeComments`, `sourceMap`, and `inlineSourceMap` compiler options settings in `tsconfig.json`, so those items should just work as expected. Any source maps should be doubly-mapped back to the original TypeScript.

## I prefer single (or double) quotes
You can set that in the package settings https://atom.io/docs/latest/using-atom-atom-packages#package-settings

Expand Down
2 changes: 1 addition & 1 deletion lib/main/atom/atomUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function getEditorPositionForBufferPosition(editor: AtomCore.IEditor, buf
return buffer.characterIndexForPosition(bufferPos);
}

export function isAllowedExtension(ext) {
export function isAllowedExtension(ext: string) {
return (ext == '.ts' || ext == '.tst' || ext == '.tsx');
}

Expand Down
109 changes: 105 additions & 4 deletions lib/main/lang/modules/building.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {pathIsRelative, makeRelativePath} from "../../tsconfig/tsconfig";
import {consistentPath} from "../../utils/fsUtil";
import {createMap} from "../utils";

let babel: any;
export const Not_In_Context = "/* NotInContext */";

export function diagnosticToTSError(diagnostic: ts.Diagnostic): TSError {
Expand Down Expand Up @@ -40,10 +41,23 @@ export function emitFile(proj: project.Project, filePath: string): EmitOutput {
errors.push(diagnosticToTSError(diagnostic));
});

output.outputFiles.forEach(o => {
mkdirp.sync(path.dirname(o.name));
fs.writeFileSync(o.name, o.text, "utf8");
});
{
let sourceMapContents: {[index:string]: any} = {};
output.outputFiles.forEach(o => {
mkdirp.sync(path.dirname(o.name));
let additionalEmits = runExternalTranspiler(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);
if (path.extname(filePath) == '.d.ts') {
Expand Down Expand Up @@ -71,3 +85,90 @@ 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[] {
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 (externalTranspiler.toLocaleLowerCase() === "babel") {
babel = require("babel");

let babelOptions : any = {};

let sourceMapFileName = getJSMapNameForJSFile(outputFile.name);

if (sourceMapContents[sourceMapFileName]) {
babelOptions.inputSourceMap = sourceMapContents[sourceMapFileName].sourceMapPayload;
}
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 [];
}

function getJSMapNameForJSFile(jsFileName: string) {
for (let jsMapName in sourceMapContents) {
if (sourceMapContents.hasOwnProperty(jsMapName)) {
if (sourceMapContents[jsMapName].jsFileName === jsFileName) {
return jsMapName;
}
}
}
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;
}
5 changes: 4 additions & 1 deletion lib/main/tsconfig/tsconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ interface TypeScriptProjectRawSpecification {
filesGlob?: string[]; // optional: An array of 'glob / minimatch / RegExp' patterns to specify source files
formatCodeOptions?: formatting.FormatCodeOptions; // optional: formatting options
compileOnSave?: boolean; // optional: compile on save. Ignored to build tools. Used by IDEs
externalTranspiler?: string;
}

interface UsefulFromPackageJson {
Expand All @@ -123,6 +124,7 @@ export interface TypeScriptProjectSpecification {
formatCodeOptions: ts.FormatCodeOptions;
compileOnSave: boolean;
package?: UsefulFromPackageJson;
externalTranspiler?: string;
}

///////// FOR USE WITH THE API /////////////
Expand Down Expand Up @@ -404,7 +406,8 @@ export function getProjectSync(pathOrSrcFile: string): TypeScriptProjectFileDeta
formatCodeOptions: formatting.makeFormatCodeOptions(projectSpec.formatCodeOptions),
compileOnSave: projectSpec.compileOnSave == undefined ? true : projectSpec.compileOnSave,
package: pkg,
typings: []
typings: [],
externalTranspiler: projectSpec.externalTranspiler == undefined ? undefined : projectSpec.externalTranspiler
};

// Validate the raw compiler options before converting them to TS compiler options
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"dependencies": {
"atom-package-dependencies": "https://github.com/basarat/atom-package-dependencies/archive/cb2.tar.gz",
"atom-space-pen-views": "^2.0.4",
"babel": "^5.6.23",
"basarat-text-buffer": "6.0.0",
"d3": "^3.5.5",
"emissary": "^1.3.3",
Expand Down

0 comments on commit f2a55cf

Please sign in to comment.