diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81f7b76 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +tests/.cache diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c89f15 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +## meteor-typescript + +TypeScript wrapped for Meteor. diff --git a/cache.js b/cache.js index dfaebe6..f6b01d1 100644 --- a/cache.js +++ b/cache.js @@ -1,12 +1,12 @@ 'use strict'; -const path = require("path"); -const fs = require("fs"); -const assert = require("assert"); -const LRU = require("lru-cache"); -const utils = require("./utils"); -const pkgVersion = require("./package.json").version; -const random = require("random-js")(); +var path = require("path"); +var fs = require("fs"); +var assert = require("assert"); +var LRU = require("lru-cache"); +var utils = require("./utils"); +var pkgVersion = require("./package.json").version; +var random = require("random-js")(); function ensureCacheDir(cacheDir) { cacheDir = path.resolve( @@ -29,113 +29,111 @@ function ensureCacheDir(cacheDir) { return cacheDir; } -class Cache { +function Cache(compileFn, cacheDir) { + assert.ok(this instanceof Cache); + assert.strictEqual(typeof compileFn, "function"); - constructor(compileFn, cacheDir) { - assert.strictEqual(typeof compileFn, "function"); + this.compileFn = compileFn; + this.cacheDir = ensureCacheDir(cacheDir); - this.compileFn = compileFn; - this.cacheDir = ensureCacheDir(cacheDir); + var maxSize = process.env.TYPESCRIPT_CACHE_SIZE; + this._cache = new LRU({ + max: maxSize || 1024 * 10 * 10 + }); +}; - const maxSize = process.env.TYPESCRIPT_CACHE_SIZE; - this._cache = new LRU({ - max: maxSize || 1024 * 10 * 10 - }); - } +exports.Cache = Cache; - get(source, options) { - let cacheKey = utils.deepHash(pkgVersion, source, options); - let compileResult = this._cache.get(cacheKey); +var Cp = Cache.prototype; - if (! compileResult) { - compileResult = this._readCache(cacheKey); - } +Cp.get = function(source, options) { + var cacheKey = utils.deepHash(pkgVersion, source, options); + var compileResult = this._cache.get(cacheKey); - if (! compileResult) { - compileResult = this.compileFn(source, options); - this._cache.set(cacheKey, compileResult); - this._writeCacheAsync(cacheKey, compileResult); - } - - return compileResult; + if (! compileResult) { + compileResult = this._readCache(cacheKey); } - _cacheFilename(cacheKey) { - // We want cacheKeys to be hex so that they work on any FS - // and never end in .cache. - if (!/^[a-f0-9]+$/.test(cacheKey)) { - throw Error('bad cacheKey: ' + cacheKey); - } - - return path.join(this.cacheDir, cacheKey + '.cache'); + if (! compileResult) { + compileResult = this.compileFn(source, options); + this._cache.set(cacheKey, compileResult); + this._writeCacheAsync(cacheKey, compileResult); } - _readFileOrNull(filename) { - try { - return fs.readFileSync(filename, 'utf8'); - } catch (e) { - if (e && e.code === 'ENOENT') - return null; - throw e; - } - } + return compileResult; +} - _parseJSONOrNull(json) { - try { - return JSON.parse(json); - } catch (e) { - if (e instanceof SyntaxError) - return null; - throw e; - } +Cp._cacheFilename = function(cacheKey) { + // We want cacheKeys to be hex so that they work on any FS + // and never end in .cache. + if (!/^[a-f0-9]+$/.test(cacheKey)) { + throw Error('bad cacheKey: ' + cacheKey); } - // Returns null if the file does not exist or can't be parsed; otherwise - // returns the parsed compileResult in the file. - _readAndParseCompileResultOrNull(filename) { - var content = this._readFileOrNull(filename); - return this._parseJSONOrNull(content); - } + return path.join(this.cacheDir, cacheKey + '.cache'); +} - _readCache(cacheKey) { - if (! this.cacheDir) { +Cp._readFileOrNull = function(filename) { + try { + return fs.readFileSync(filename, 'utf8'); + } catch (e) { + if (e && e.code === 'ENOENT') return null; - } + throw e; + } +} - var cacheFilename = this._cacheFilename(cacheKey); - var compileResult = this._readAndParseCompileResultOrNull(cacheFilename); - if (! compileResult) { +Cp._parseJSONOrNull = function(json) { + try { + return JSON.parse(json); + } catch (e) { + if (e instanceof SyntaxError) return null; - } - this._cache.set(cacheKey, compileResult); - - return compileResult; + throw e; } +} - // We want to write the file atomically. - // But we also don't want to block processing on the file write. - _writeFileAsync(filename, contents) { - var tempFilename = filename + '.tmp.' + random.uuid4(); - fs.writeFile(tempFilename, contents, (err) => { - // ignore errors, it's just a cache - if (err) { - return; - } - fs.rename(tempFilename, filename, (err) => { - // ignore this error too. - }); - }); - } +// Returns null if the file does not exist or can't be parsed; otherwise +// returns the parsed compileResult in the file. +Cp._readAndParseCompileResultOrNull = function(filename) { + var content = this._readFileOrNull(filename); + return this._parseJSONOrNull(content); +} - _writeCacheAsync(cacheKey, compileResult) { - if (! this.cacheDir) return; +Cp._readCache = function(cacheKey) { + if (! this.cacheDir) { + return null; + } - var cacheFilename = this._cacheFilename(cacheKey); - var cacheContents = JSON.stringify(compileResult); - this._writeFileAsync(cacheFilename, cacheContents); + var cacheFilename = this._cacheFilename(cacheKey); + var compileResult = this._readAndParseCompileResultOrNull(cacheFilename); + if (! compileResult) { + return null; } + this._cache.set(cacheKey, compileResult); + return compileResult; } -exports.Cache = Cache; +// We want to write the file atomically. +// But we also don't want to block processing on the file write. +Cp._writeFileAsync = function(filename, contents) { + var tempFilename = filename + '.tmp.' + random.uuid4(); + fs.writeFile(tempFilename, contents, function(err) { + // ignore errors, it's just a cache + if (err) { + return; + } + fs.rename(tempFilename, filename, function(err) { + // ignore this error too. + }); + }); +} +Cp._writeCacheAsync = function(cacheKey, compileResult) { + if (! this.cacheDir) return; + + var cacheFilename = this._cacheFilename(cacheKey); + var cacheContents = JSON.stringify(compileResult); + this._writeFileAsync(cacheFilename, cacheContents); +} diff --git a/index.js b/index.js index 0c28c9a..e1d07a2 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,12 @@ 'use strict'; -const getDefaultOptions = require("./options").getDefaultOptions; -const tsCompile = require("./typescript").compile; -const Cache = require("./cache").Cache; +var getDefaultCompilerOptions = require("./options").getDefaultCompilerOptions; +var convertCompilerOptionsOrThrow = require("./options").convertCompilerOptionsOrThrow; +var tsCompile = require("./typescript").compile; +var Cache = require("./cache").Cache; +var _ = require("underscore"); -function setCacheDir(cacheDir) { +exports.setCacheDir = function setCacheDir(cacheDir) { if (compileCache && compileCache.cacheDir === cacheDir) { return; } @@ -12,13 +14,12 @@ function setCacheDir(cacheDir) { compileCache = new Cache(function(source, options) { return tsCompile(source, options); }, cacheDir); -} - -exports.setCacheDir = setCacheDir; +}; -let compileCache; +var compileCache; exports.compile = function compile(source, options) { - options = options || {compilerOptions: getDefaultOptions()}; + options = options ? convertOptionsOrThrow(options) : + {compilerOptions: getDefaultCompilerOptions()}; if (! options.useCache) { return tsCompile(source, options); @@ -30,3 +31,21 @@ exports.compile = function compile(source, options) { return compileCache.get(source, options); }; + +function convertOptionsOrThrow(options) { + if (! options.compilerOptions) return null; + + var compilerOptions = convertCompilerOptionsOrThrow(options.compilerOptions); + var result = _.clone(options); + result.compilerOptions = compilerOptions; + + return result; +} + +exports.convertOptionsOrThrow = convertOptionsOrThrow; + +exports.getDefaultOptions = function getDefaultOptions() { + return { + compilerOptions: getDefaultCompilerOptions() + } +} diff --git a/options.js b/options.js index ed608d9..5085b42 100644 --- a/options.js +++ b/options.js @@ -1,10 +1,10 @@ 'use strict'; -const ts = require("typescript"); -const _ = require("underscore"); +var ts = require("typescript"); +var _ = require("underscore"); function getCompilerOptions(customOptions) { - let compilerOptions = ts.getDefaultCompilerOptions(); + var compilerOptions = ts.getDefaultCompilerOptions(); _.extend(compilerOptions, customOptions); @@ -46,7 +46,7 @@ function getCompilerOptions(customOptions) { exports.getCompilerOptions = getCompilerOptions; // Default compiler options. -function getDefaultOptions() { +function getDefaultCompilerOptions() { return { module : ts.ModuleKind.None, target: ts.ScriptTarget.ES5, @@ -61,4 +61,54 @@ function getDefaultOptions() { } } -exports.getDefaultOptions = getDefaultOptions; +exports.getDefaultCompilerOptions = getDefaultCompilerOptions; + +var customOptions = ['useCache']; +function isCustomOption(option) { + return customOptions.indexOf(option) !== -1; +} + +function validateCustomOptions(options) { + if ('useCache' in options) { + if (typeof options['useCache'] !== 'boolean') { + throw new Error('useCache should be boolean'); + } + } +} + +// Validate compiler options and convert them from +// user-friendly format to enum values used by TypeScript: +// 'system' string converted to ts.ModuleKind.System value. +function convertCompilerOptionsOrThrow(options) { + if (! options) return null; + + var compilerOptions = _.clone(options); + var customOptions = {}; + if (compilerOptions) { + for (var option in compilerOptions) { + if (isCustomOption(option)) { + customOptions[option] = compilerOptions[option]; + delete compilerOptions[option]; + } + } + } + + var testOptions = {}; + testOptions.compilerOptions = compilerOptions; + testOptions.files = []; + var result = ts.parseJsonConfigFileContent(testOptions); + + if (result.errors && result.errors.length) { + throw new Error(result.errors[0].messageText); + } + + validateCustomOptions(customOptions); + + // Add converted compiler options plus custom options back. + compilerOptions = _.extend( + result.options, customOptions); + + return compilerOptions; +} + +exports.convertCompilerOptionsOrThrow = convertCompilerOptionsOrThrow; diff --git a/package.json b/package.json index 7f8b9a2..70b6755 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "meteor-typescript", "author": "@barbatus", - "version": "0.1.0", + "version": "0.5.2", "license": "MIT", "description": "TypeScript wrapper package for use with Meteor", "keywords": [ diff --git a/tests/tests.js b/tests/tests.js index 558d72b..6e33aa0 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -5,3 +5,8 @@ var assert = require("assert"); var result = meteorTS.compile("export const foo = 600;"); assert.equal(result.code.indexOf("exports.foo = 600;"), 0); +var converted = meteorTS.convertOptionsOrThrow({ + compilerOptions: { + module: 'system' + } +}); diff --git a/typescript.js b/typescript.js index 139ea26..63ef276 100644 --- a/typescript.js +++ b/typescript.js @@ -1,25 +1,25 @@ 'use strict'; -const assert = require("assert"); -const ts = require("typescript"); -const getCompilerOptions = require("./options").getCompilerOptions; -const _ = require("underscore"); -const deepHash = require("./utils").deepHash; +var assert = require("assert"); +var ts = require("typescript"); +var getCompilerOptions = require("./options").getCompilerOptions; +var _ = require("underscore"); +var deepHash = require("./utils").deepHash; exports.compile = function compile(fileContent, options) { - let compilerOptions = getCompilerOptions( + var compilerOptions = getCompilerOptions( options.compilerOptions); - const filePath = options.filePath || deepHash(fileContent) + ".ts"; - let sourceFile = ts.createSourceFile(filePath, + var filePath = options.filePath || deepHash(fileContent) + ".ts"; + var sourceFile = ts.createSourceFile(filePath, fileContent, compilerOptions.target); if (options.moduleName) { sourceFile.moduleName = options.moduleName; } - let defaultHost = ts.createCompilerHost(compilerOptions); + var defaultHost = ts.createCompilerHost(compilerOptions); - let customHost = { + var customHost = { getSourceFile: function(fileName, target) { // Skip reading the file content again, we have it already. if (fileName === ts.normalizeSlashes(filePath)) { @@ -29,32 +29,33 @@ exports.compile = function compile(fileContent, options) { } }; - let compilerHost = _.extend({}, defaultHost, customHost); - let fileNames = [filePath]; + var compilerHost = _.extend({}, defaultHost, customHost); + var fileNames = [filePath]; if (options.typings) { fileNames.concat(options.typings); } - let program = ts.createProgram(fileNames, compilerOptions, compilerHost); - - let code, sourceMap; - const processResult = (fileName, outputCode, writeByteOrderMark) => { - if (normalizePath(fileName) !== - normalizePath(filePath)) return; - - if (ts.fileExtensionIs(fileName, '.map')) { - var sourceMapPath = options.moduleName ? - options.moduleName : filePath; - sourceMap = prepareSourceMap(outputCode, - fileContent, sourceMapPath); - } else { - code = outputCode; + var program = ts.createProgram(fileNames, compilerOptions, compilerHost); + + var code, sourceMap; + var processResult = + function(fileName, outputCode, writeByteOrderMark) { + if (normalizePath(fileName) !== + normalizePath(filePath)) return; + + if (ts.fileExtensionIs(fileName, '.map')) { + var sourceMapPath = options.moduleName ? + options.moduleName : filePath; + sourceMap = prepareSourceMap(outputCode, + fileContent, sourceMapPath); + } else { + code = outputCode; + } } - } program.emit(sourceFile, processResult); return { - code, - sourceMap, + code: code, + sourceMap: sourceMap, referencedPaths: getReferencedPaths(sourceFile), diagnostics: readDiagnostics(program, filePath) }; @@ -63,7 +64,7 @@ exports.compile = function compile(fileContent, options) { // 1) Normalizes slashes in the file path // 2) Removes file extension function normalizePath(filePath) { - let resultName = filePath; + var resultName = filePath; if (ts.fileExtensionIs(resultName, '.map')) { resultName = resultName.replace('.map', ''); } @@ -72,19 +73,19 @@ function normalizePath(filePath) { } function prepareSourceMap(sourceMapContent, fileContent, sourceMapPath) { - let sourceMapJson = JSON.parse(sourceMapContent); + var sourceMapJson = JSON.parse(sourceMapContent); sourceMapJson.sourcesContent = [fileContent]; sourceMapJson.sources = [sourceMapPath]; return sourceMapJson; } function getReferencedPaths(sourceFile) { - let referencedPaths = []; + var referencedPaths = []; // Get resolved modules. if (sourceFile.resolvedModules) { - for (let moduleName in sourceFile.resolvedModules) { - let module = sourceFile.resolvedModules[moduleName]; + for (var moduleName in sourceFile.resolvedModules) { + var module = sourceFile.resolvedModules[moduleName]; if (module && module.resolvedFileName) { referencedPaths.push(module.resolvedFileName); } @@ -102,32 +103,34 @@ function getReferencedPaths(sourceFile) { } function readDiagnostics(program, filePath) { - let sourceFile; + var sourceFile; if (filePath) { sourceFile = program.getSourceFile(filePath); } - let syntactic = flattenDiagnostics( + var syntactic = flattenDiagnostics( program.getSyntacticDiagnostics(sourceFile)); - let semantic = flattenDiagnostics( + var semantic = flattenDiagnostics( program.getSemanticDiagnostics(sourceFile)); return { - syntactic, - semantic + syntacticErrors: syntactic, + semanticErrors: semantic }; } function flattenDiagnostics(tsDiagnostics) { - let diagnostics = []; + var diagnostics = []; - tsDiagnostics.forEach(function(diagnostic) { - if (!diagnostic.file) return; + var dLen = tsDiagnostics.length; + for (var i = 0; i < dLen; i++) { + var diagnostic = tsDiagnostics[i]; + if (!diagnostic.file) continue; - let pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); - let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); - let line = pos.line + 1; - let column = pos.character + 1; + var pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); + var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); + var line = pos.line + 1; + var column = pos.character + 1; diagnostics.push({ fileName: diagnostic.file.fileName, @@ -135,7 +138,7 @@ function flattenDiagnostics(tsDiagnostics) { line: line, column: column }); - }); + } return diagnostics; }