diff --git a/debug.js b/debug.js new file mode 100644 index 0000000..79c331c --- /dev/null +++ b/debug.js @@ -0,0 +1,29 @@ +const util = Npm.require('util'); + +DebugLog = class DebugLog { + constructor(event) { + this.debug = process.env.TYPESCRIPT_DEBUG; + this.event = event; + this.start(); + } + + start() { + if (this.debug) { + console.log('%s started', this.event); + console.time(util.format('%s time', this.event)); + } + } + + + log(msg, ...args) { + if (this.debug) { + console.log.apply(null, [msg].concat(args)); + } + } + + end() { + if (this.debug) { + console.timeEnd(util.format('%s time', this.event)); + } + } +}; diff --git a/file-mixin.js b/file-mixin.js new file mode 100644 index 0000000..e62f3b9 --- /dev/null +++ b/file-mixin.js @@ -0,0 +1,10 @@ +FileMixin = { + warn(error) { + console.log(`${error.sourcePath} (${error.line}, ${error.column}): ${error.message}`); + }, + + isBare() { + let fileOptions = this.getFileOptions(); + return fileOptions.bare; + } +}; diff --git a/package.js b/package.js index b04da40..4edad9e 100644 --- a/package.js +++ b/package.js @@ -1,13 +1,13 @@ Package.describe({ name: 'barbatus:typescript-compiler', - version: '0.5.0-beta.5', + version: '0.5.0-beta.6', summary: 'TypeScript Compiler for Meteor', git: 'https://github.com/barbatus/ts-compilers', documentation: 'README.md' }); Npm.depends({ - 'meteor-typescript': '0.6.0-beta.2', + 'meteor-typescript': 'https://github.com/barbatus/meteor-typescript/tarball/52fc6c7f3b5b5df9483a927bc96e7a901b61c4ab', 'async': '1.4.0' }); @@ -19,6 +19,8 @@ Package.onUse(function(api) { ], 'server'); api.addFiles([ + 'debug.js', + 'file-mixin.js', 'typescript-compiler.js', 'typescript.js' ], 'server'); diff --git a/tests/server/unit/compiler-tests_spec.js b/tests/server/unit/compiler-tests_spec.js index 7ecbf00..fbf2a11 100644 --- a/tests/server/unit/compiler-tests_spec.js +++ b/tests/server/unit/compiler-tests_spec.js @@ -47,8 +47,7 @@ describe('typescript-compiler', () => { describe('testing diagnostics', () => { it('should log out diagnostics by default', () => { - let logger = jasmine.createSpy(); - let compiler = new TypeScriptCompiler(null, null, logger); + let compiler = new TypeScriptCompiler(); let configFile = new ConfigFile({ compilerOptions: { @@ -57,18 +56,17 @@ describe('typescript-compiler', () => { }); let wrongImport = 'import {api} from "lib";'; let inputFile = new InputFile(wrongImport, 'foo4.ts'); + inputFile.warn = jasmine.createSpy(); compiler.processFilesForTarget([inputFile, configFile]); - expect(logger).toHaveBeenCalled(); - expect(logger.calls.first().args[0]).toBeDefined(); + expect(inputFile.warn).toHaveBeenCalled(); + expect(inputFile.warn.calls.first().args[0]).toBeDefined(); }); }); describe('testing modules', () => { - it('should render bare source code if module is none', () => { - let compiler = new TypeScriptCompiler({ - module: 'none' - }); + it('should render bare source code if there is no ES6 exports', () => { + let compiler = new TypeScriptCompiler(); let moduleFoo = 'module foo {}'; let inputFile = new InputFile(moduleFoo, 'foo5.ts'); compiler.processFilesForTarget([inputFile]); diff --git a/tests/server/unit/input-file.js b/tests/server/unit/input-file.js index 2e76a21..3fd9470 100644 --- a/tests/server/unit/input-file.js +++ b/tests/server/unit/input-file.js @@ -44,6 +44,10 @@ InputFile = class InputFile { getArch() { return 'os'; } + + warn(error) { + this.error = error; + } } ConfigFile = class ConfigFile extends InputFile { diff --git a/typescript-compiler.js b/typescript-compiler.js index 5effc72..f4667ae 100644 --- a/typescript-compiler.js +++ b/typescript-compiler.js @@ -1,90 +1,106 @@ -'use strict'; - const async = Npm.require('async'); const Future = Npm.require('fibers/future'); +const TSBuild = Npm.require('meteor-typescript').TSBuild; TypeScriptCompiler = class TypeScriptCompiler { - constructor(extraOptions, maxParallelism, logFn) { + constructor(extraOptions, maxParallelism) { TypeScript.validateExtraOptions(extraOptions); this.extraOptions = extraOptions; this.maxParallelism = maxParallelism || 10; - this.tsconfig = null; + this.tsconfig = TypeScript.getDefaultOptions(); this.cfgHash = null; - this.logFn = logFn || console.log; } processFilesForTarget(inputFiles) { + this.extendFiles(inputFiles); + // If tsconfig.json has changed, create new one. this.processConfig(inputFiles); - let filesSourceMap = new Map(); + let archMap = {}; + let filesMap = {}; inputFiles.forEach((inputFile, index) => { if (this.isConfigFile(inputFile)) return; - filesSourceMap.set(this.getExtendedPath(inputFile), index); + let arch = inputFile.getArch(); + let archFiles = archMap[arch]; + if (! archFiles) { + archFiles = []; + archMap[arch] = archFiles; + } + archFiles.push(inputFile); + filesMap[this.getExtendedPath(inputFile)] = index; }); + let getFileContent = filePath => { - let index = filesSourceMap.get(filePath); + let index = filesMap[filePath]; return index !== undefined ? inputFiles[index].getContentsAsString() : null; }; - // Filters out typings and tsconfig. - // Other files should be compiled. - let tsFiles = inputFiles.filter(inputFile => - !(this.isConfigFile(inputFile) || this.isDeclarationFile(inputFile))); + // Assemble options. + let typings = this.tsconfig.typings; + let compilerOptions = this.tsconfig.compilerOptions; + compilerOptions = TypeScript.getCompilerOptions( + compilerOptions, this.extraOptions); + let buildOptions = { compilerOptions, typings }; + let compileDebug = new DebugLog('compilation'); const future = new Future; - async.eachLimit(tsFiles, this.maxParallelism, (inputFile, cb) => { - let compilerOptions = this.tsconfig ? - this.tsconfig.compilerOptions : null; - - compilerOptions = TypeScript.getCompilerOptions( - compilerOptions, this.extraOptions); - - let source = inputFile.getContentsAsString(); - let inputFilePath = inputFile.getPathInPackage(); - let outputFilePath = removeTsExt(inputFilePath) + '.js'; - let toBeAdded = { - sourcePath: inputFilePath, - path: outputFilePath, - data: source, - hash: inputFile.getSourceHash(), - sourceMap: null, - bare: this.isBareFile(inputFile, compilerOptions) - }; - - let filePath = this.getExtendedPath(inputFile); - let typings = this.tsconfig ? this.tsconfig.typings : []; - let moduleName = this.getFileModuleName(inputFile, compilerOptions); - let arch = inputFile.getArch(); - let tsOptions = { - compilerOptions, - moduleName, - filePath, - typings, - arch - }; - - let error = null; - try { - let result = TypeScript.compile(getFileContent, tsOptions); - this.processDiagnostics(inputFile, - result.diagnostics, compilerOptions); + async.each(_.keys(archMap), (arch, cb) => { + let archFiles = archMap[arch]; + let filePaths = archFiles.map(inputFile => this.getExtendedPath(inputFile)); + compileDebug.log('process files: %s', filePaths); + buildOptions.arch = arch; + + let buildDebug = new DebugLog('tsBuild'); + let tsBuild = new TSBuild(filePaths, getFileContent, buildOptions); + + archFiles.forEach(inputFile => { + if (this.isDeclarationFile(inputFile)) return; + + let co = compilerOptions; + let source = inputFile.getContentsAsString(); + let inputFilePath = inputFile.getPathInPackage(); + let outputFilePath = removeTsExt(inputFilePath) + '.js'; + let toBeAdded = { + sourcePath: inputFilePath, + path: outputFilePath, + data: source, + hash: inputFile.getSourceHash(), + sourceMap: null, + bare: inputFile.isBare() + }; + + let filePath = this.getExtendedPath(inputFile); + let moduleName = this.getFileModuleName(inputFile, co); + + let emitDebug = new DebugLog('tsEmit'); + let result = tsBuild.emit(filePath, moduleName); + this.processDiagnostics(inputFile, result.diagnostics, co); + emitDebug.end(); toBeAdded.data = result.code; + toBeAdded.bare = toBeAdded.bare || ! result.isExternal; toBeAdded.hash = result.hash; toBeAdded.sourceMap = result.sourceMap; inputFile.addJavaScript(toBeAdded); - } catch (e) { - error = e; - } finally { - cb(error); - } + }); + + cb(); + + buildDebug.end(); }, future.resolver()); + future.wait(); + + compileDebug.end(); + } + + extendFiles(inputFiles) { + inputFiles.forEach(inputFile => _.defaults(inputFile, FileMixin)); } processDiagnostics(inputFile, diagnostics, compilerOptions) { @@ -104,13 +120,12 @@ TypeScriptCompiler = class TypeScriptCompiler { // And log out other errors except package files. if (compilerOptions && compilerOptions.diagnostics) { diagnostics.semanticErrors.forEach(diagnostic => { - let error = { + inputFile.warn({ message: diagnostic.message, sourcePath: this.getExtendedPath(inputFile), line: diagnostic.line, column: diagnostic.column - }; - this.logFn(`${error.sourcePath} (${error.line}, ${error.column}): ${error.message}`); + }); }); } } @@ -125,13 +140,6 @@ TypeScriptCompiler = class TypeScriptCompiler { return noExt ? removeTsExt(filePath) : filePath; } - isBareFile(inputFile, compilerOptions) { - let fileOptions = inputFile.getFileOptions(); - let packageName = inputFile.getPackageName(); - return (fileOptions.bare || - (! packageName && compilerOptions.module === 'none')); - } - getFileModuleName(inputFile, options) { return options.module !== 'none' ? this.getExtendedPath(inputFile, true): null; diff --git a/typescript.js b/typescript.js index 0f5294b..a2fb677 100644 --- a/typescript.js +++ b/typescript.js @@ -1,5 +1,3 @@ -'use strict'; - const meteorTS = Npm.require('meteor-typescript'); TypeScript = {