Skip to content

Added support for running the local copy of babel for a given project #727

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 1 commit into from
Nov 19, 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
136 changes: 89 additions & 47 deletions dist/main/lang/modules/building.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ var path = require('path');
var fs = require('fs');
var fsUtil_1 = require("../../utils/fsUtil");
var utils_1 = require("../utils");
var babel;
var findup = require('findup');
var babels = {};
var babelConfigs = {};
exports.Not_In_Context = "/* NotInContext */";
function diagnosticToTSError(diagnostic) {
var filePath = diagnostic.file.fileName;
Expand Down Expand Up @@ -37,13 +39,14 @@ function emitFile(proj, filePath) {
var sourceMapContents = {};
output.outputFiles.forEach(function (o) {
mkdirp.sync(path.dirname(o.name));
var additionalEmits = runExternalTranspiler(filePath, sourceFile.text, o, proj, sourceMapContents);
if (!sourceMapContents[o.name] && !proj.projectFile.project.compilerOptions.noEmit) {
fs.writeFileSync(o.name, o.text, "utf8");
}
additionalEmits.forEach(function (a) {
mkdirp.sync(path.dirname(a.name));
fs.writeFileSync(a.name, a.text, "utf8");
runExternalTranspiler(filePath, sourceFile.text, o, proj, sourceMapContents).then(function (additionalEmits) {
if (!sourceMapContents[o.name] && !proj.projectFile.project.compilerOptions.noEmit) {
fs.writeFileSync(o.name, o.text, "utf8");
}
additionalEmits.forEach(function (a) {
mkdirp.sync(path.dirname(a.name));
fs.writeFileSync(a.name, a.text, "utf8");
});
});
});
}
Expand Down Expand Up @@ -75,20 +78,60 @@ function getRawOutput(proj, filePath) {
return output;
}
exports.getRawOutput = getRawOutput;
function getBabelInstance(projectDirectory) {
return new Promise(function (resolve) {
if (!babels[projectDirectory]) {
findup(projectDirectory, 'node_modules/babel-core', function (err, dir) {
if (err) {
findup(projectDirectory, 'node_modules/babel', function (err, dir) {
if (err) {
babels[projectDirectory] = require('babel');
}
else {
babels[projectDirectory] = require(path.join(dir, 'node_modules/babel'));
}
resolve(babels[projectDirectory]);
});
}
else {
babels[projectDirectory] = require(path.join(dir, 'node_modules/babel-core'));
resolve(babels[projectDirectory]);
}
});
}
else {
resolve(babels[projectDirectory]);
}
}).then(function (babel) {
return new Promise(function (resolve) {
findup(projectDirectory, '.babelrc', function (err, dir) {
if (err)
resolve(babel);
fs.readFile(path.join(dir, '.babelrc'), function (err, data) {
try {
babelConfigs[projectDirectory] = JSON.parse(data.toString());
}
catch (e) { }
resolve(babel);
});
});
});
});
}
function runExternalTranspiler(sourceFileName, sourceFileText, outputFile, project, sourceMapContents) {
if (!isJSFile(outputFile.name) && !isJSSourceMapFile(outputFile.name)) {
return [];
return Promise.resolve([]);
}
var settings = project.projectFile.project;
var externalTranspiler = settings.externalTranspiler;
if (!externalTranspiler) {
return [];
return Promise.resolve([]);
}
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 [];
return Promise.resolve([]);
}
if (typeof externalTranspiler === 'string') {
externalTranspiler = {
Expand All @@ -98,43 +141,42 @@ function runExternalTranspiler(sourceFileName, sourceFileText, outputFile, proje
}
if (typeof externalTranspiler === 'object') {
if (externalTranspiler.name.toLocaleLowerCase() === "babel") {
if (!babel) {
babel = require("babel");
}
var babelOptions = utils_1.assign({}, externalTranspiler.options || {}, {
filename: outputFile.name
});
var sourceMapFileName = getJSMapNameForJSFile(outputFile.name);
if (sourceMapContents[sourceMapFileName]) {
babelOptions.inputSourceMap = sourceMapContents[sourceMapFileName].sourceMapPayload;
var baseName = path.basename(sourceFileName);
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;
}
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 getBabelInstance(project.projectFile.projectFileDirectory).then(function (babel) {
var babelOptions = utils_1.assign(babelConfigs[project.projectFile.projectFileDirectory] || {}, externalTranspiler.options || {}, {
filename: outputFile.name
});
var sourceMapFileName = getJSMapNameForJSFile(outputFile.name);
if (sourceMapContents[sourceMapFileName]) {
babelOptions.inputSourceMap = sourceMapContents[sourceMapFileName].sourceMapPayload;
var baseName = path.basename(sourceFileName);
babelOptions.inputSourceMap.sources = [baseName];
babelOptions.inputSourceMap.file = baseName;
}
return [additionalEmit];
}
return [];
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) {
Expand Down
157 changes: 99 additions & 58 deletions lib/main/lang/modules/building.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import fs = require('fs');
import {pathIsRelative, makeRelativePath} from "../../tsconfig/tsconfig";
import {consistentPath} from "../../utils/fsUtil";
import {createMap, assign} from "../utils";
var findup = require('findup');

/** Lazy loaded babel tanspiler */
let babel: any;
let babels: { [key: string]: any } = {};
/** Store babel configurations from .babelrc */
let babelConfigs: { [key: string]: any } = {};

/** If we get a compile request for a ts file that is not in project. We return a js file with the following content */
export const Not_In_Context = "/* NotInContext */";
Expand Down Expand Up @@ -54,23 +57,23 @@ 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(
runExternalTranspiler(
filePath,
sourceFile.text,
o,
proj,
sourceMapContents
);

if (!sourceMapContents[o.name] && !proj.projectFile.project.compilerOptions.noEmit) {
// .js.map files will be written as an "additional emit" later.
fs.writeFileSync(o.name, o.text, "utf8");
}
).then((additionalEmits) => {
if (!sourceMapContents[o.name] && !proj.projectFile.project.compilerOptions.noEmit) {
// .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");
})
additionalEmits.forEach(a => {
mkdirp.sync(path.dirname(a.name));
fs.writeFileSync(a.name, a.text, "utf8");
});
});
});
}

Expand Down Expand Up @@ -103,27 +106,66 @@ export function getRawOutput(proj: project.Project, filePath: string): ts.EmitOu
return output;
}

function getBabelInstance(projectDirectory: string) {
return new Promise<any>(resolve => {
if (!babels[projectDirectory]) {
findup(projectDirectory, 'node_modules/babel-core', function(err: any, dir: string) {
if (err) {
findup(projectDirectory, 'node_modules/babel', function(err: any, dir: string) {
if (err) {
babels[projectDirectory] = require('babel');
} else {
babels[projectDirectory] = require(path.join(dir, 'node_modules/babel'));
}

resolve(babels[projectDirectory]);
});
} else {
babels[projectDirectory] = require(path.join(dir, 'node_modules/babel-core'));
resolve(babels[projectDirectory]);
}
});
} else {
resolve(babels[projectDirectory]);
}
}).then(babel => {
return new Promise<any>(resolve => {
findup(projectDirectory, '.babelrc', function(err: any, dir) {
if (err) resolve(babel);

fs.readFile(path.join(dir, '.babelrc'), function(err, data) {
try {
babelConfigs[projectDirectory] = JSON.parse(data.toString());
} catch (e) { }

resolve(babel);
});
});
});
});
}

function runExternalTranspiler(sourceFileName: string,
sourceFileText: string,
outputFile: ts.OutputFile,
project: project.Project,
sourceMapContents: { [index: string]: any }): ts.OutputFile[] {
sourceMapContents: { [index: string]: any }): Promise<ts.OutputFile[]> {

if (!isJSFile(outputFile.name) && !isJSSourceMapFile(outputFile.name)) {
return [];
return Promise.resolve([]);
}

let settings = project.projectFile.project;
let externalTranspiler = settings.externalTranspiler;
if (!externalTranspiler) {
return [];
return Promise.resolve([]);
}

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 [];
return Promise.resolve([]);
}

if (typeof externalTranspiler === 'string') {
Expand All @@ -136,54 +178,53 @@ function runExternalTranspiler(sourceFileName: string,
// 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
});
return getBabelInstance(project.projectFile.projectFileDirectory).then((babel) => {

let sourceMapFileName = getJSMapNameForJSFile(outputFile.name);
let babelOptions: any = assign(babelConfigs[project.projectFile.projectFileDirectory] || {}, externalTranspiler.options || {}, {
filename: 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 sourceMapFileName = getJSMapNameForJSFile(outputFile.name);

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 [];
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;
}

return [additionalEmit];
}
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 [];
return [additionalEmit];
}

return [];
});
}
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"detect-indent": "^4.0.0",
"emissary": "^1.3.3",
"escape-html": "^1.0.1",
"findup": "^0.1.5",
"fuzzaldrin": "^2.1.0",
"glob": "^5.0.15",
"htmltojsx": "0.2.4",
Expand Down