mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-11-09 13:15:37 +03:00
feat(typescript) initial commit of built in typescript support
This commit is contained in:
parent
f9aedb7bde
commit
722089be45
@ -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
152
src/typescript-transpile.js
Normal 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
141
src/typescript.coffee
Normal 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)
|
@ -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'));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user