feat(typescript) initial commit of built in typescript support

This commit is contained in:
basarat 2015-03-08 14:49:22 +11:00
parent f9aedb7bde
commit 722089be45
4 changed files with 301 additions and 0 deletions

View File

@ -66,6 +66,7 @@
"temp": "0.8.1",
"text-buffer": "^4.1.5",
"theorist": "^1.0.2",
"typescript": "^1.4.1",
"underscore-plus": "^1.6.6"
},
"packageDependencies": {

152
src/typescript-transpile.js Normal file
View File

@ -0,0 +1,152 @@
// From https://github.com/teppeis/typescript-simple/blob/master/index.js
// with https://github.com/teppeis/typescript-simple/pull/7
var fs = require('fs');
var os = require('os');
var path = require('path');
var ts = require('typescript');
var FILENAME_TS = 'file.ts';
function tss(code, options) {
if (options) {
return new tss.TypeScriptSimple(options).compile(code);
}
else {
return defaultTss.compile(code);
}
}
var tss;
(function (tss) {
var TypeScriptSimple = (function () {
/**
* @param {ts.CompilerOptions=} options TypeScript compile options (some options are ignored)
*/
function TypeScriptSimple(options, doSemanticChecks) {
if (options === void 0) { options = {}; }
if (doSemanticChecks === void 0) { doSemanticChecks = true; }
this.doSemanticChecks = doSemanticChecks;
this.service = null;
this.outputs = {};
this.files = {};
if (options.target == null) {
options.target = ts.ScriptTarget.ES5;
}
if (options.module == null) {
options.module = ts.ModuleKind.None;
}
this.options = options;
}
/**
* @param {string} code TypeScript source code to compile
* @param {string} only needed if you plan to use sourceMaps. Provide the complete filePath relevant to you
* @return {string} The JavaScript with inline sourceMaps if sourceMaps were enabled
*/
TypeScriptSimple.prototype.compile = function (code, filename) {
if (filename === void 0) { filename = FILENAME_TS; }
if (!this.service) {
this.service = this.createService();
}
var file = this.files[FILENAME_TS];
file.text = code;
file.version++;
return this.toJavaScript(this.service, filename);
};
TypeScriptSimple.prototype.createService = function () {
var _this = this;
var defaultLib = this.getDefaultLibFilename(this.options);
var defaultLibPath = path.join(this.getTypeScriptBinDir(), defaultLib);
this.files[defaultLib] = { version: 0, text: fs.readFileSync(defaultLibPath).toString() };
this.files[FILENAME_TS] = { version: 0, text: '' };
var servicesHost = {
getScriptFileNames: function () { return [_this.getDefaultLibFilename(_this.options), FILENAME_TS]; },
getScriptVersion: function (filename) { return _this.files[filename] && _this.files[filename].version.toString(); },
getScriptSnapshot: function (filename) {
var file = _this.files[filename];
return {
getText: function (start, end) { return file.text.substring(start, end); },
getLength: function () { return file.text.length; },
getLineStartPositions: function () { return []; },
getChangeRange: function (oldSnapshot) { return undefined; }
};
},
getCurrentDirectory: function () { return process.cwd(); },
getScriptIsOpen: function () { return true; },
getCompilationSettings: function () { return _this.options; },
getDefaultLibFilename: function (options) {
return _this.getDefaultLibFilename(options);
},
log: function (message) { return console.log(message); }
};
return ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
};
TypeScriptSimple.prototype.getTypeScriptBinDir = function () {
return path.dirname(require.resolve('typescript'));
};
TypeScriptSimple.prototype.getDefaultLibFilename = function (options) {
if (options.target === ts.ScriptTarget.ES6) {
return 'lib.es6.d.ts';
}
else {
return 'lib.d.ts';
}
};
/**
* converts {"version":3,"file":"file.js","sourceRoot":"","sources":["file.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,MAAM,CAAC"}
* to {"version":3,"sources":["foo/test.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,MAAM,CAAC","file":"foo/test.ts","sourcesContent":["var x = 'test';"]}
* derived from : https://github.com/thlorenz/convert-source-map
*/
TypeScriptSimple.prototype.getInlineSourceMap = function (mapText, filename) {
var sourceMap = JSON.parse(mapText);
sourceMap.file = filename;
sourceMap.sources = [filename];
sourceMap.sourcesContent = [this.files[FILENAME_TS].text];
delete sourceMap.sourceRoot;
return JSON.stringify(sourceMap);
};
TypeScriptSimple.prototype.toJavaScript = function (service, filename) {
if (filename === void 0) { filename = FILENAME_TS; }
var output = service.getEmitOutput(FILENAME_TS);
// Meaning of succeeded is driven by whether we need to check for semantic errors or not
var succeeded = output.emitOutputStatus === ts.EmitReturnStatus.Succeeded;
if (!this.doSemanticChecks) {
// We have an output. It implies syntactic success
if (!succeeded)
succeeded = !!output.outputFiles.length;
}
if (succeeded) {
var outputFilename = FILENAME_TS.replace(/ts$/, 'js');
var file = output.outputFiles.filter(function (file) { return file.name === outputFilename; })[0];
// Fixed in v1.5 https://github.com/Microsoft/TypeScript/issues/1653
var text = file.text.replace(/\r\n/g, os.EOL);
// If we have sourceMaps convert them to inline sourceMaps
if (this.options.sourceMap) {
var sourceMapFilename = FILENAME_TS.replace(/ts$/, 'js.map');
var sourceMapFile = output.outputFiles.filter(function (file) { return file.name === sourceMapFilename; })[0];
// Transform sourcemap
var sourceMapText = sourceMapFile.text;
sourceMapText = this.getInlineSourceMap(sourceMapText, filename);
var base64SourceMapText = new Buffer(sourceMapText).toString('base64');
text = text.replace('//# sourceMappingURL=' + sourceMapFilename, '//# sourceMappingURL=data:application/json;base64,' + base64SourceMapText);
}
return text;
}
var allDiagnostics = service.getCompilerOptionsDiagnostics().concat(service.getSyntacticDiagnostics(FILENAME_TS));
if (this.doSemanticChecks)
allDiagnostics = allDiagnostics.concat(service.getSemanticDiagnostics(FILENAME_TS));
throw new Error(this.formatDiagnostics(allDiagnostics));
};
TypeScriptSimple.prototype.formatDiagnostics = function (diagnostics) {
return diagnostics.map(function (d) {
if (d.file) {
return 'L' + d.file.getLineAndCharacterFromPosition(d.start).line + ': ' + d.messageText;
}
else {
return d.messageText;
}
}).join(os.EOL);
};
return TypeScriptSimple;
})();
tss.TypeScriptSimple = TypeScriptSimple;
})(tss || (tss = {}));
var defaultTss = new tss.TypeScriptSimple();
module.exports = tss;

141
src/typescript.coffee Normal file
View File

@ -0,0 +1,141 @@
###
Cache for source code transpiled by TypeScript.
Inspired by https://github.com/atom/atom/blob/7a719d585db96ff7d2977db9067e1d9d4d0adf1a/src/babel.coffee
###
crypto = require 'crypto'
fs = require 'fs-plus'
path = require 'path'
tss = null # Defer until used
stats =
hits: 0
misses: 0
defaultOptions =
target: 1
module: 'commonjs'
sourceMap: true
###
shasum - Hash with an update() method.
value - Must be a value that could be returned by JSON.parse().
###
updateDigestForJsonValue = (shasum, value) ->
# Implmentation is similar to that of pretty-printing a JSON object, except:
# * Strings are not escaped.
# * No effort is made to avoid trailing commas.
# These shortcuts should not affect the correctness of this function.
type = typeof value
if type is 'string'
shasum.update('"', 'utf8')
shasum.update(value, 'utf8')
shasum.update('"', 'utf8')
else if type in ['boolean', 'number']
shasum.update(value.toString(), 'utf8')
else if value is null
shasum.update('null', 'utf8')
else if Array.isArray value
shasum.update('[', 'utf8')
for item in value
updateDigestForJsonValue(shasum, item)
shasum.update(',', 'utf8')
shasum.update(']', 'utf8')
else
# value must be an object: be sure to sort the keys.
keys = Object.keys value
keys.sort()
shasum.update('{', 'utf8')
for key in keys
updateDigestForJsonValue(shasum, key)
shasum.update(': ', 'utf8')
updateDigestForJsonValue(shasum, value[key])
shasum.update(',', 'utf8')
shasum.update('}', 'utf8')
createTypeScriptVersionAndOptionsDigest = (version, options) ->
shasum = crypto.createHash('sha1')
# Include the version of typescript in the hash.
shasum.update('typescript', 'utf8')
shasum.update('\0', 'utf8')
shasum.update(version, 'utf8')
shasum.update('\0', 'utf8')
updateDigestForJsonValue(shasum, options)
shasum.digest('hex')
cacheDir = null
jsCacheDir = null
getCachePath = (sourceCode) ->
digest = crypto.createHash('sha1').update(sourceCode, 'utf8').digest('hex')
unless jsCacheDir?
tsVersion = require('typescript/package.json').version
jsCacheDir = path.join(cacheDir, createTypeScriptVersionAndOptionsDigest(tsVersion, defaultOptions))
path.join(jsCacheDir, "#{digest}.js")
getCachedJavaScript = (cachePath) ->
if fs.isFileSync(cachePath)
try
cachedJavaScript = fs.readFileSync(cachePath, 'utf8')
stats.hits++
return cachedJavaScript
null
# Returns the TypeScript options that should be used to transpile filePath.
createOptions = (filePath) ->
options = filename: filePath
for key, value of defaultOptions
options[key] = value
options
transpile = (sourceCode, filePath, cachePath) ->
options = createOptions(filePath)
tss ?= new (require './typescript-transpile').TypeScriptSimple(options, false)
js = tss.compile(sourceCode, filePath)
stats.misses++
try
fs.writeFileSync(cachePath, js)
js
# Function that obeys the contract of an entry in the require.extensions map.
# Returns the transpiled version of the JavaScript code at filePath, which is
# either generated on the fly or pulled from cache.
loadFile = (module, filePath) ->
sourceCode = fs.readFileSync(filePath, 'utf8')
cachePath = getCachePath(sourceCode)
js = getCachedJavaScript(cachePath) ? transpile(sourceCode, filePath, cachePath)
module._compile(js, filePath)
register = ->
Object.defineProperty(require.extensions, '.ts', {
enumerable: true
writable: false
value: loadFile
})
setCacheDirectory = (newCacheDir) ->
if cacheDir isnt newCacheDir
cacheDir = newCacheDir
jsCacheDir = null
module.exports =
register: register
setCacheDirectory: setCacheDirectory
getCacheMisses: -> stats.misses
getCacheHits: -> stats.hits
# Visible for testing.
createTypeScriptVersionAndOptionsDigest: createTypeScriptVersionAndOptionsDigest
addPathToCache: (filePath) ->
return if path.extname(filePath) isnt '.ts'
sourceCode = fs.readFileSync(filePath, 'utf8')
cachePath = getCachePath(sourceCode)
transpile(sourceCode, filePath, cachePath)

View File

@ -47,6 +47,7 @@ window.onload = function() {
setupCsonCache(cacheDir);
setupSourceMapCache(cacheDir);
setupBabel(cacheDir);
setupTypeScript(cacheDir);
require(loadSettings.bootstrapScript);
require('ipc').sendChannel('window-command', 'window:loaded');
@ -95,6 +96,12 @@ var setupBabel = function(cacheDir) {
babel.register();
}
var setupTypeScript = function(cacheDir) {
var typescript = require('../src/typescript');
typescript.setCacheDirectory(path.join(cacheDir, 'typescript'));
typescript.register();
}
var setupCsonCache = function(cacheDir) {
require('season').setCacheDir(path.join(cacheDir, 'cson'));
}