Skip to content

Optional transpilation with Babel #475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/cb1.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