Created
August 29, 2015 14:34
-
-
Save schwarzeszeux/503e1d2396e52ad276d6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var tsc = require('./tsc'); | |
var fs = require('fs'); | |
var path = require('path'); | |
var util = require('util'); | |
var _ = require('lodash'); | |
var async = require('async'); | |
var byline = require('byline'); | |
var temp = require('temp'); | |
var rimraf = require('rimraf'); | |
var through = require('through2'); | |
var fsSrc = require('vinyl-fs').src; | |
var EventEmitter = require('events').EventEmitter; | |
module.exports = Compiler; | |
function Compiler(sourceFiles, options) { | |
EventEmitter.call(this); | |
this.sourceFiles = sourceFiles || []; | |
this.options = _.extend({ | |
tscPath: null, | |
tscSearch: null, | |
module: 'commonjs', | |
target: 'ES3', | |
out: null, | |
outDir: null, | |
mapRoot: null, | |
sourceRoot: null, | |
allowbool: false, | |
allowimportmodule: false, | |
declaration: false, | |
noImplicitAny: false, | |
noResolve: false, | |
removeComments: false, | |
sourcemap: false, | |
tmpDir: '', | |
noLib: false, | |
keepTree: true, | |
pathFilter: null, | |
safe: false, | |
enableDecorators: false, | |
emitDecoratorMetadata: false | |
}, options); | |
this.tscOptions = { | |
path: this.options.tscPath, | |
search: this.options.tscSearch | |
}; | |
this.tempDestination = null; | |
this.tscArgumentsFile = null; | |
this.treeKeeperFile = null; | |
} | |
util.inherits(Compiler, EventEmitter); | |
Compiler.prototype.buildTscArguments = function () { | |
var args = []; | |
if (this.options.module) args.push('--module', this.options.module.toLowerCase()); | |
if (this.options.target) args.push('--target', this.options.target.toUpperCase()); | |
if (this.options.mapRoot) args.push('--mapRoot', this.options.mapRoot); | |
if (this.options.sourceRoot) args.push('--sourceRoot', this.options.sourceRoot); | |
if (this.options.allowbool) args.push('--allowbool'); | |
if (this.options.allowimportmodule) args.push('--allowimportmodule'); | |
if (this.options.declaration) args.push('--declaration'); | |
if (this.options.noImplicitAny) args.push('--noImplicitAny'); | |
if (this.options.noResolve) args.push('--noResolve'); | |
if (this.options.removeComments) args.push('--removeComments'); | |
if (this.options.sourcemap) args.push('--sourcemap'); | |
if (this.options.noLib) args.push('--noLib'); | |
if (this.options.enableDecorators) args.push('--experimentalDecorators'); | |
if (this.options.emitDecoratorMetadata ) args.push('--emitDecoratorMetadata'); | |
if (this.tempDestination) { | |
args.push('--outDir', this.tempDestination); | |
if (this.options.out) { | |
args.push('--out', path.resolve(this.tempDestination, this.options.out)); | |
} | |
} else if (this.options.out) { | |
args.push('--out', this.options.out); | |
} | |
console.log("arguments:", args.join(" ")); | |
this.sourceFiles.forEach(function (f) { args.push(f.path); }); | |
if (this.treeKeeperFile) { | |
args.push(this.treeKeeperFile); | |
} | |
return args; | |
}; | |
Compiler.prototype.getVersion = function (callback) { | |
return tsc.version(this.tscOptions, callback); | |
}; | |
Compiler.prototype.compile = function (callback) { | |
var _this = this; | |
var checkAborted = this.checkAborted.bind(this); | |
this.emit('start'); | |
Compiler._start(this); | |
async.waterfall([ | |
checkAborted, | |
this.makeTempDestinationDir.bind(this), | |
checkAborted, | |
this.makeTreeKeeperFile.bind(this), | |
checkAborted, | |
this.prepareTscArgumentsFile.bind(this), | |
checkAborted, | |
this.runTsc.bind(this), | |
checkAborted | |
], function (err) { | |
if (err && _this.options.safe) { | |
finish(err); | |
} else { | |
_this.processOutputFiles(function (err2) { | |
finish(err || err2); | |
}); | |
} | |
}); | |
function finish(err) { | |
_this.cleanup(); | |
_this.emit('end'); | |
callback(err); | |
} | |
}; | |
Compiler.prototype.checkAborted = function (callback) { | |
if (Compiler.isAborted()) { | |
callback(new Error('aborted')); | |
} else { | |
callback(null); | |
} | |
}; | |
Compiler.prototype.makeTempDestinationDir = function (callback) { | |
var _this = this; | |
temp.track(); | |
temp.mkdir({ dir: path.resolve(process.cwd(), this.options.tmpDir), prefix: 'gulp-tsc-tmp-' }, function (err, dirPath) { | |
if (err) return callback(err); | |
_this.tempDestination = dirPath; | |
callback(null); | |
}); | |
}; | |
Compiler.prototype.makeTreeKeeperFile = function (callback) { | |
if (!this.options.keepTree || this.options.out || this.sourceFiles.length === 0) { | |
return callback(null); | |
} | |
var _this = this; | |
temp.open({ dir: this.sourceFiles[0].base, prefix: '.gulp-tsc-tmp-', suffix: '.ts' }, function (err, file) { | |
if (err) { | |
return callback(new Error( | |
'Failed to create a temporary file on source directory: ' + (err.message || err) + ', ' + | |
'To skip creating it specify { keepTree: false } to your gulp-tsc.' | |
)); | |
} | |
_this.treeKeeperFile = file.path; | |
try { | |
fs.writeSync(file.fd, '// This is a temporary file by gulp-tsc for keeping directory tree.\n'); | |
fs.closeSync(file.fd); | |
} catch (e) { | |
return callback(e); | |
} | |
callback(null); | |
}); | |
}; | |
Compiler.prototype.prepareTscArgumentsFile = function(callback) { | |
var tscArguments = this.buildTscArguments(); | |
var content = '"' + tscArguments.join('"\n"') + '"'; | |
this.tscArgumentsFile = path.join(this.tempDestination, 'tscArguments'); | |
fs.writeFile(this.tscArgumentsFile, content, callback); | |
}; | |
Compiler.prototype.runTsc = function (callback) { | |
var _this = this; | |
var proc = tsc.exec(['@' + this.tscArgumentsFile], this.tscOptions); | |
var stdout = byline(proc.stdout); | |
var stderr = byline(proc.stderr); | |
proc.on('exit', function (code) { | |
if (code !== 0) { | |
callback(new Error('tsc command has exited with code:' + code)); | |
} else { | |
callback(null); | |
} | |
}) | |
proc.on('error', function (err) { | |
_this.emit('error', err); | |
}); | |
stdout.on('data', function (chunk) { | |
_this.emit('stdout', chunk); | |
}); | |
stderr.on('data', function (chunk) { | |
_this.emit('stderr', chunk); | |
}); | |
return proc; | |
}; | |
Compiler.prototype.processOutputFiles = function (callback) { | |
var _this = this; | |
var options = { cwd: this.tempDestination, cwdbase: true }; | |
var patterns = ['**/*{.js,.js.map,.d.ts}']; | |
if (this.treeKeeperFile) { | |
patterns.push('!**/' + path.basename(this.treeKeeperFile, '.ts') + '.*'); | |
} | |
var stream = fsSrc(patterns, options); | |
stream = stream.pipe(this.fixOutputFilePath()); | |
if (this.options.sourcemap && !this.options.sourceRoot) { | |
stream = stream.pipe(this.fixSourcemapPath()); | |
} | |
if (this.options.declaration && this.options.outDir) { | |
stream = stream.pipe(this.fixReferencePath()); | |
} | |
stream.on('data', function (file) { | |
_this.emit('data', file); | |
}); | |
stream.on('error', function (err) { | |
callback(err); | |
}); | |
stream.on('end', function () { | |
callback(); | |
}); | |
}; | |
Compiler.prototype.fixOutputFilePath = function () { | |
var filter = this.options.pathFilter && this.filterOutput.bind(this); | |
var outDir; | |
if (this.options.outDir) { | |
outDir = path.resolve(process.cwd(), this.options.outDir); | |
} else { | |
outDir = this.tempDestination; | |
} | |
return through.obj(function (file, encoding, done) { | |
file.originalPath = file.path; | |
file.path = path.resolve(outDir, file.relative); | |
file.cwd = file.base = outDir; | |
if (filter) { | |
try { | |
file = filter(file); | |
} catch (e) { | |
return done(e); | |
} | |
} | |
if (file) this.push(file); | |
done(); | |
}); | |
}; | |
Compiler.prototype.filterOutput = function (file) { | |
if (!this.options.pathFilter) return file; | |
var filter = this.options.pathFilter; | |
if (_.isFunction(filter)) { | |
var ret = filter(file.relative, file); | |
if (ret === true || _.isUndefined(ret)) { | |
return file; | |
} else if (ret === false) { | |
return null; | |
} else if (_.isString(ret)) { | |
file.path = path.resolve(file.base, ret); | |
return file; | |
} else if (_.isObject(ret) && ret.path) { | |
return ret; | |
} else { | |
throw new Error('Unknown return value from pathFilter function'); | |
} | |
} else if (_.isPlainObject(filter)) { | |
_.forOwn(filter, function (val, key) { | |
if (_.isString(key) && _.isString(val)) { | |
var src = path.normalize(key) + path.sep; | |
if (file.relative.substr(0, src.length) === src) { | |
file.path = path.resolve(file.base, val, file.relative.substr(src.length) || '.'); | |
return false; | |
} | |
} | |
}); | |
return file; | |
} else { | |
throw new Error('Unknown type for pathFilter'); | |
} | |
}; | |
Compiler.prototype.fixSourcemapPath = function () { | |
return through.obj(function (file, encoding, done) { | |
if (!file.isBuffer() || !/\.js\.map/.test(file.path)) { | |
this.push(file); | |
return done(); | |
} | |
var map = JSON.parse(file.contents); | |
if (map['sources'] && map['sources'].length > 0) { | |
map['sources'] = map['sources'].map(function (sourcePath) { | |
sourcePath = path.resolve(path.dirname(file.originalPath), sourcePath); | |
sourcePath = path.relative(path.dirname(file.path), sourcePath); | |
if (path.sep == '\\') sourcePath = sourcePath.replace(/\\/g, '/'); | |
return sourcePath; | |
}); | |
file.contents = new Buffer(JSON.stringify(map)); | |
} | |
this.push(file); | |
done(); | |
}); | |
}; | |
Compiler.prototype.fixReferencePath = function () { | |
return through.obj(function (file, encoding, done) { | |
if (!file.isBuffer() || !/\.d\.ts/.test(file.path)) { | |
this.push(file); | |
return done(); | |
} | |
var newContent = file.contents.toString().replace( | |
/(\/\/\/\s*<reference\s*path\s*=\s*)(["'])(.+?)\2/g, | |
function (entire, prefix, quote, refPath) { | |
refPath = path.resolve(path.dirname(file.originalPath), refPath); | |
refPath = path.relative(path.dirname(file.path), refPath); | |
if (path.sep == '\\') refPath = refPath.replace(/\\/g, '/'); | |
return prefix + quote + refPath + quote; | |
} | |
); | |
file.contents = new Buffer(newContent); | |
this.push(file); | |
done(); | |
}); | |
}; | |
Compiler.prototype.cleanup = function () { | |
try { rimraf.sync(this.tempDestination); } catch(e) {} | |
try { fs.unlinkSync(this.treeKeeperFile); } catch(e) {} | |
}; | |
Compiler.running = 0; | |
Compiler.aborted = false; | |
Compiler.abortCallbacks = []; | |
Compiler.abortAll = function (callback) { | |
Compiler.aborted = true; | |
callback && Compiler.abortCallbacks.push(callback); | |
if (Compiler.running == 0) { | |
Compiler._allAborted(); | |
} | |
}; | |
Compiler.isAborted = function () { | |
return Compiler.aborted; | |
}; | |
Compiler._start = function (compiler) { | |
Compiler.running++; | |
compiler.once('end', function () { | |
Compiler.running--; | |
if (Compiler.running == 0 && Compiler.aborted) { | |
Compiler._allAborted(); | |
} | |
}); | |
}; | |
Compiler._allAborted = function () { | |
var callbacks = Compiler.abortCallbacks; | |
Compiler.aborted = false; | |
Compiler.abortCallbacks = []; | |
callbacks.forEach(function (fn) { | |
fn.call(null); | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment