Skip to content

Commit f2a55cf

Browse files
committed
Merge pull request #475 from TypeStrong/external-transpiler
Optional transpilation with Babel
2 parents 8fbf86a + a9b6b2b commit f2a55cf

File tree

7 files changed

+213
-11
lines changed

7 files changed

+213
-11
lines changed

dist/main/lang/modules/building.js

+84-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
var mkdirp = require('mkdirp');
22
var path = require('path');
33
var fs = require('fs');
4+
var fsUtil_1 = require("../../utils/fsUtil");
5+
var babel;
46
exports.Not_In_Context = "/* NotInContext */";
57
function diagnosticToTSError(diagnostic) {
68
var filePath = diagnostic.file.fileName;
@@ -29,10 +31,20 @@ function emitFile(proj, filePath) {
2931
var startPosition = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
3032
errors.push(diagnosticToTSError(diagnostic));
3133
});
32-
output.outputFiles.forEach(function (o) {
33-
mkdirp.sync(path.dirname(o.name));
34-
fs.writeFileSync(o.name, o.text, "utf8");
35-
});
34+
{
35+
var sourceMapContents = {};
36+
output.outputFiles.forEach(function (o) {
37+
mkdirp.sync(path.dirname(o.name));
38+
var additionalEmits = runExternalTranspiler(o, proj, sourceMapContents);
39+
if (!sourceMapContents[o.name]) {
40+
fs.writeFileSync(o.name, o.text, "utf8");
41+
}
42+
additionalEmits.forEach(function (a) {
43+
mkdirp.sync(path.dirname(a.name));
44+
fs.writeFileSync(a.name, a.text, "utf8");
45+
});
46+
});
47+
}
3648
var outputFiles = output.outputFiles.map(function (o) { return o.name; });
3749
if (path.extname(filePath) == '.d.ts') {
3850
outputFiles.push(filePath);
@@ -61,3 +73,71 @@ function getRawOutput(proj, filePath) {
6173
return output;
6274
}
6375
exports.getRawOutput = getRawOutput;
76+
function runExternalTranspiler(outputFile, project, sourceMapContents) {
77+
if (!isJSFile(outputFile.name) && !isJSSourceMapFile(outputFile.name)) {
78+
return [];
79+
}
80+
var settings = project.projectFile.project;
81+
var externalTranspiler = settings.externalTranspiler;
82+
if (!externalTranspiler) {
83+
return [];
84+
}
85+
if (isJSSourceMapFile(outputFile.name)) {
86+
var sourceMapPayload = JSON.parse(outputFile.text);
87+
var jsFileName = fsUtil_1.consistentPath(path.resolve(path.dirname(outputFile.name), sourceMapPayload.file));
88+
sourceMapContents[outputFile.name] = { jsFileName: jsFileName, sourceMapPayload: sourceMapPayload };
89+
return [];
90+
}
91+
if (externalTranspiler.toLocaleLowerCase() === "babel") {
92+
babel = require("babel");
93+
var babelOptions = {};
94+
var sourceMapFileName = getJSMapNameForJSFile(outputFile.name);
95+
if (sourceMapContents[sourceMapFileName]) {
96+
babelOptions.inputSourceMap = sourceMapContents[sourceMapFileName].sourceMapPayload;
97+
}
98+
if (settings.compilerOptions.sourceMap) {
99+
babelOptions.sourceMaps = true;
100+
}
101+
if (settings.compilerOptions.inlineSourceMap) {
102+
babelOptions.sourceMaps = "inline";
103+
}
104+
if (!settings.compilerOptions.removeComments) {
105+
babelOptions.comments = true;
106+
}
107+
var babelResult = babel.transform(outputFile.text, babelOptions);
108+
outputFile.text = babelResult.code;
109+
if (babelResult.map && settings.compilerOptions.sourceMap) {
110+
var additionalEmit = {
111+
name: sourceMapFileName,
112+
text: JSON.stringify(babelResult.map),
113+
writeByteOrderMark: settings.compilerOptions.emitBOM
114+
};
115+
if (additionalEmit.name === "") {
116+
console.warn("The TypeScript language service did not yet provide a .js.map name for file " + outputFile.name);
117+
return [];
118+
}
119+
return [additionalEmit];
120+
}
121+
return [];
122+
}
123+
function getJSMapNameForJSFile(jsFileName) {
124+
for (var jsMapName in sourceMapContents) {
125+
if (sourceMapContents.hasOwnProperty(jsMapName)) {
126+
if (sourceMapContents[jsMapName].jsFileName === jsFileName) {
127+
return jsMapName;
128+
}
129+
}
130+
}
131+
return "";
132+
}
133+
}
134+
function isJSFile(fileName) {
135+
return (path.extname(fileName).toLocaleLowerCase() === ".js");
136+
}
137+
function isJSSourceMapFile(fileName) {
138+
var lastExt = path.extname(fileName);
139+
if (lastExt === ".map") {
140+
return isJSFile(fileName.substr(0, fileName.length - 4));
141+
}
142+
return false;
143+
}

dist/main/tsconfig/tsconfig.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ function getProjectSync(pathOrSrcFile) {
240240
formatCodeOptions: formatting.makeFormatCodeOptions(projectSpec.formatCodeOptions),
241241
compileOnSave: projectSpec.compileOnSave == undefined ? true : projectSpec.compileOnSave,
242242
package: pkg,
243-
typings: []
243+
typings: [],
244+
externalTranspiler: projectSpec.externalTranspiler == undefined ? undefined : projectSpec.externalTranspiler
244245
};
245246
var validationResult = validator.validate(projectSpec.compilerOptions);
246247
if (validationResult.errorMessage) {

docs/faq.md

+16
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@ If it conforms the latest TypeScript services API then yes! Just set the path to
1414

1515
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.
1616

17+
## Can I use an alternate transpiler?
18+
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)).
19+
20+
To enable using Babel as the transpiler, make these changes to your `tsconfig.json` file:
21+
22+
**1:** Add this key in the root:
23+
24+
```js
25+
{
26+
"externalTranspiler": "babel"
27+
}
28+
```
29+
**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.
30+
31+
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.
32+
1733
## I prefer single (or double) quotes
1834
You can set that in the package settings https://atom.io/docs/latest/using-atom-atom-packages#package-settings
1935

lib/main/atom/atomUtils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function getEditorPositionForBufferPosition(editor: AtomCore.IEditor, buf
1919
return buffer.characterIndexForPosition(bufferPos);
2020
}
2121

22-
export function isAllowedExtension(ext) {
22+
export function isAllowedExtension(ext: string) {
2323
return (ext == '.ts' || ext == '.tst' || ext == '.tsx');
2424
}
2525

lib/main/lang/modules/building.ts

+105-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {pathIsRelative, makeRelativePath} from "../../tsconfig/tsconfig";
66
import {consistentPath} from "../../utils/fsUtil";
77
import {createMap} from "../utils";
88

9+
let babel: any;
910
export const Not_In_Context = "/* NotInContext */";
1011

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

43-
output.outputFiles.forEach(o => {
44-
mkdirp.sync(path.dirname(o.name));
45-
fs.writeFileSync(o.name, o.text, "utf8");
46-
});
44+
{
45+
let sourceMapContents: {[index:string]: any} = {};
46+
output.outputFiles.forEach(o => {
47+
mkdirp.sync(path.dirname(o.name));
48+
let additionalEmits = runExternalTranspiler(o, proj, sourceMapContents);
49+
50+
if (!sourceMapContents[o.name]) {
51+
// .js.map files will be written as an "additional emit" later.
52+
fs.writeFileSync(o.name, o.text, "utf8");
53+
}
54+
55+
additionalEmits.forEach(a => {
56+
mkdirp.sync(path.dirname(a.name));
57+
fs.writeFileSync(a.name, a.text, "utf8");
58+
})
59+
});
60+
}
4761

4862
var outputFiles = output.outputFiles.map((o) => o.name);
4963
if (path.extname(filePath) == '.d.ts') {
@@ -71,3 +85,90 @@ export function getRawOutput(proj: project.Project, filePath: string): ts.EmitOu
7185
}
7286
return output;
7387
}
88+
89+
function runExternalTranspiler(outputFile: ts.OutputFile, project: project.Project, sourceMapContents: {[index:string]: any}) : ts.OutputFile[] {
90+
if (!isJSFile(outputFile.name) && !isJSSourceMapFile(outputFile.name)) {
91+
return [];
92+
}
93+
94+
let settings = project.projectFile.project;
95+
let externalTranspiler = settings.externalTranspiler;
96+
if (!externalTranspiler) {
97+
return [];
98+
}
99+
100+
if (isJSSourceMapFile(outputFile.name)) {
101+
let sourceMapPayload = JSON.parse(outputFile.text);
102+
let jsFileName = consistentPath(path.resolve(path.dirname(outputFile.name), sourceMapPayload.file));
103+
sourceMapContents[outputFile.name] = {jsFileName: jsFileName, sourceMapPayload};
104+
return [];
105+
}
106+
107+
if (externalTranspiler.toLocaleLowerCase() === "babel") {
108+
babel = require("babel");
109+
110+
let babelOptions : any = {};
111+
112+
let sourceMapFileName = getJSMapNameForJSFile(outputFile.name);
113+
114+
if (sourceMapContents[sourceMapFileName]) {
115+
babelOptions.inputSourceMap = sourceMapContents[sourceMapFileName].sourceMapPayload;
116+
}
117+
if (settings.compilerOptions.sourceMap) {
118+
babelOptions.sourceMaps = true;
119+
}
120+
if (settings.compilerOptions.inlineSourceMap) {
121+
babelOptions.sourceMaps = "inline";
122+
}
123+
if (!settings.compilerOptions.removeComments) {
124+
babelOptions.comments = true;
125+
}
126+
127+
let babelResult = babel.transform(outputFile.text, babelOptions);
128+
outputFile.text = babelResult.code;
129+
130+
if (babelResult.map && settings.compilerOptions.sourceMap) {
131+
let additionalEmit : ts.OutputFile = {
132+
name: sourceMapFileName,
133+
text : JSON.stringify(babelResult.map),
134+
writeByteOrderMark: settings.compilerOptions.emitBOM
135+
};
136+
137+
if (additionalEmit.name === "") {
138+
// can't emit a blank file name - this should only be reached if the TypeScript
139+
// language service returns the .js file before the .js.map file.
140+
console.warn(`The TypeScript language service did not yet provide a .js.map name for file ${outputFile.name}`);
141+
return [];
142+
}
143+
144+
return [additionalEmit];
145+
}
146+
147+
return [];
148+
}
149+
150+
function getJSMapNameForJSFile(jsFileName: string) {
151+
for (let jsMapName in sourceMapContents) {
152+
if (sourceMapContents.hasOwnProperty(jsMapName)) {
153+
if (sourceMapContents[jsMapName].jsFileName === jsFileName) {
154+
return jsMapName;
155+
}
156+
}
157+
}
158+
return "";
159+
}
160+
}
161+
162+
163+
164+
function isJSFile(fileName: string) {
165+
return (path.extname(fileName).toLocaleLowerCase() === ".js");
166+
}
167+
168+
function isJSSourceMapFile(fileName: string) {
169+
let lastExt = path.extname(fileName);
170+
if (lastExt === ".map") {
171+
return isJSFile(fileName.substr(0,fileName.length - 4));
172+
}
173+
return false;
174+
}

lib/main/tsconfig/tsconfig.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ interface TypeScriptProjectRawSpecification {
102102
filesGlob?: string[]; // optional: An array of 'glob / minimatch / RegExp' patterns to specify source files
103103
formatCodeOptions?: formatting.FormatCodeOptions; // optional: formatting options
104104
compileOnSave?: boolean; // optional: compile on save. Ignored to build tools. Used by IDEs
105+
externalTranspiler?: string;
105106
}
106107

107108
interface UsefulFromPackageJson {
@@ -123,6 +124,7 @@ export interface TypeScriptProjectSpecification {
123124
formatCodeOptions: ts.FormatCodeOptions;
124125
compileOnSave: boolean;
125126
package?: UsefulFromPackageJson;
127+
externalTranspiler?: string;
126128
}
127129

128130
///////// FOR USE WITH THE API /////////////
@@ -404,7 +406,8 @@ export function getProjectSync(pathOrSrcFile: string): TypeScriptProjectFileDeta
404406
formatCodeOptions: formatting.makeFormatCodeOptions(projectSpec.formatCodeOptions),
405407
compileOnSave: projectSpec.compileOnSave == undefined ? true : projectSpec.compileOnSave,
406408
package: pkg,
407-
typings: []
409+
typings: [],
410+
externalTranspiler: projectSpec.externalTranspiler == undefined ? undefined : projectSpec.externalTranspiler
408411
};
409412

410413
// Validate the raw compiler options before converting them to TS compiler options

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"dependencies": {
4343
"atom-package-dependencies": "https://github.com/basarat/atom-package-dependencies/archive/cb2.tar.gz",
4444
"atom-space-pen-views": "^2.0.4",
45+
"babel": "^5.6.23",
4546
"basarat-text-buffer": "6.0.0",
4647
"d3": "^3.5.5",
4748
"emissary": "^1.3.3",

0 commit comments

Comments
 (0)