UNPKG

grunt-ts

Version:

Compile and manage your TypeScript project

357 lines (302 loc) 13.1 kB
/// <reference path="../../defs/tsd.d.ts"/> /// <reference path="./interfaces.d.ts"/> 'use strict'; import path = require('path'); import fs = require('fs'); import _ = require('lodash'); import utils = require('./utils'); import cache = require('./cacheUtils'); import {Promise} from 'es6-promise'; export var grunt: IGrunt = require('grunt'); /////////////////////////// // Helper /////////////////////////// var executeNode: ICompilePromise; var executeNodeDefault : ICompilePromise = function(args, optionalInfo) { return new Promise((resolve, reject) => { grunt.util.spawn({ cmd: process.execPath, args: args }, (error, result, code) => { var ret: ICompileResult = { code: code, // New TypeScript compiler uses stdout for user code errors. Old one used stderr. output: result.stdout || result.stderr }; resolve(ret); }); }); }; ///////////////////////////////////////////////////////////////// // Fast Compilation ///////////////////////////////////////////////////////////////// // Map to store if the cache was cleared after the gruntfile was parsed var cacheClearedOnce: { [targetName: string]: boolean } = {}; function getChangedFiles(files, targetName: string) { files = cache.getNewFilesForTarget(files, targetName); _.forEach(files, (file) => { grunt.log.writeln(('### Fast Compile >>' + file).cyan); }); return files; } function resetChangedFiles(files, targetName: string) { cache.compileSuccessfull(files, targetName); } function clearCache(targetName: string) { cache.clearCache(targetName); cacheClearedOnce[targetName] = true; } ///////////////////////////////////////////////////////////////////// // tsc handling //////////////////////////////////////////////////////////////////// function resolveTypeScriptBinPath(): string { var ownRoot = path.resolve(path.dirname((module).filename), '../..'); var userRoot = path.resolve(ownRoot, '..', '..'); var binSub = path.join('node_modules', 'typescript', 'bin'); if (fs.existsSync(path.join(userRoot, binSub))) { // Using project override return path.join(userRoot, binSub); } return path.join(ownRoot, binSub); } function getTsc(binPath: string): string { var pkg = JSON.parse(fs.readFileSync(path.resolve(binPath, '..', 'package.json')).toString()); grunt.log.writeln('Using tsc v' + pkg.version); return path.join(binPath, 'tsc'); } export function compileAllFiles(options: IGruntTSOptions, compilationInfo: IGruntTSCompilationInfo): Promise<ICompileResult> { let targetFiles: string[] = compilationInfo.src; // Make a local copy so we can modify files without having external side effects let files = _.map(targetFiles, (file) => file); var newFiles: string[] = files; if (options.fast === 'watch') { // if we only do fast compile if target is watched // if this is the first time its running after this file was loaded if (cacheClearedOnce[grunt.task.current.target] === undefined) { // Then clear the cache for this target clearCache(options.targetName); } } if (options.fast !== 'never') { if (compilationInfo.out) { grunt.log.writeln('Fast compile will not work when --out is specified. Ignoring fast compilation'.cyan); } else { newFiles = getChangedFiles(files, options.targetName); if (newFiles.length !== 0 || options.testExecute || utils.shouldPassThrough(options)) { files = newFiles; // If outDir is specified but no baseDir is specified we need to determine one if (compilationInfo.outDir && !options.baseDir) { options.baseDir = utils.findCommonPath(files, '/'); } } else { grunt.log.writeln('No file changes were detected. Skipping Compile'.green); return new Promise((resolve) => { var ret: ICompileResult = { code: 0, fileCount: 0, output: 'No files compiled as no change detected' }; resolve(ret); }); } } } // Transform files as needed. Currently all of this logic in is one module // transformers.transformFiles(newFiles, targetFiles, target, task); // If baseDir is specified create a temp tsc file to make sure that `--outDir` works fine // see https://github.com/grunt-ts/grunt-ts/issues/77 var baseDirFile: string = '.baseDir.ts'; var baseDirFilePath: string; if (compilationInfo.outDir && options.baseDir && files.length > 0) { baseDirFilePath = path.join(options.baseDir, baseDirFile); if (!fs.existsSync(baseDirFilePath)) { grunt.file.write(baseDirFilePath, '// Ignore this file. See https://github.com/grunt-ts/grunt-ts/issues/77'); } files.push(baseDirFilePath); } // If reference and out are both specified. // Then only compile the updated reference file as that contains the correct order if (options.reference && compilationInfo.out) { var referenceFile = path.resolve(options.reference); files = [referenceFile]; } // Quote the files to compile. Needed for command line parsing by tsc files = _.map(files, (item) => `"${path.resolve(item)}"`); // if (outFile) { // outFile = `"${path.resolve(outFile)}"`; // } var args: string[] = files.slice(0); const tsconfig: ITSConfigSupport = options.tsconfig; if (tsconfig && tsconfig.passThrough) { args.push('--project', tsconfig.tsconfig); } else { if (options.sourceMap) { args.push('--sourcemap'); } if (options.emitDecoratorMetadata) { args.push('--emitDecoratorMetadata'); } if (options.declaration) { args.push('--declaration'); } if (options.removeComments) { args.push('--removeComments'); } if (options.noImplicitAny) { args.push('--noImplicitAny'); } if (options.noResolve) { args.push('--noResolve'); } if (options.noEmitOnError) { args.push('--noEmitOnError'); } if (options.preserveConstEnums) { args.push('--preserveConstEnums'); } if (options.suppressImplicitAnyIndexErrors) { args.push('--suppressImplicitAnyIndexErrors'); } if (options.noEmit) { args.push('--noEmit'); } if (options.inlineSources) { args.push('--inlineSources'); } if (options.inlineSourceMap) { args.push('--inlineSourceMap'); } if (options.newLine && !utils.newLineIsRedundant(options.newLine)) { args.push('--newLine', options.newLine); } if (options.isolatedModules) { args.push('--isolatedModules'); } if (options.noEmitHelpers) { args.push('--noEmitHelpers'); } if (options.experimentalDecorators) { args.push('--experimentalDecorators'); } if (options.experimentalAsyncFunctions) { args.push('--experimentalAsyncFunctions'); } if (options.jsx) { args.push('--jsx', options.jsx.toLocaleLowerCase()); } if (options.moduleResolution) { args.push('--moduleResolution', options.moduleResolution.toLocaleLowerCase()); } if (options.rootDir) { args.push('--rootDir', options.rootDir); } args.push('--target', options.target.toUpperCase()); if (options.module) { let moduleOptionString: string = ('' + options.module).toLowerCase(); if ('amd|commonjs|system|umd'.indexOf(moduleOptionString) > -1) { args.push('--module', moduleOptionString); } else { console.warn('WARNING: Option "module" only supports "amd" | "commonjs" | "system" | "umd" '.magenta); } } if (compilationInfo.outDir) { if (compilationInfo.out) { console.warn('WARNING: Option "out" and "outDir" should not be used together'.magenta); } args.push('--outDir', compilationInfo.outDir); } if (compilationInfo.out) { args.push('--out', compilationInfo.out); } if (compilationInfo.dest && (!compilationInfo.out) && (!compilationInfo.outDir)) { if (utils.isJavaScriptFile(compilationInfo.dest)) { args.push('--out', compilationInfo.dest); } else { if (compilationInfo.dest === 'src') { console.warn(('WARNING: Destination for target "' + options.targetName + '" is "src", which is the default. If you have' + ' forgotten to specify a "dest" parameter, please add it. If this is correct, you may wish' + ' to change the "dest" parameter to "src/" or just ignore this warning.').magenta); } if (Array.isArray(compilationInfo.dest)) { if ((<string[]><any>compilationInfo.dest).length === 0) { // ignore it and do nothing. } else if ((<string[]><any>compilationInfo.dest).length > 0) { console.warn((('WARNING: "dest" for target "' + options.targetName + '" is an array. This is not supported by the' + ' TypeScript compiler or grunt-ts.' + (((<string[]><any>compilationInfo.dest).length > 1) ? ' Only the first "dest" will be used. The' + ' remaining items will be truncated.' : ''))).magenta); args.push('--outDir', (<string[]><any>compilationInfo.dest)[0]); } } else { args.push('--outDir', compilationInfo.dest); } } } if (args.indexOf('--out') > -1 && args.indexOf('--module') > -1) { console.warn(('WARNING: TypeScript does not allow external modules to be concatenated with' + ' --out. Any exported code may be truncated. See TypeScript issue #1544 for' + ' more details.').magenta); } if (options.sourceRoot) { args.push('--sourceRoot', options.sourceRoot); } if (options.mapRoot) { args.push('--mapRoot', options.mapRoot); } } if (options.additionalFlags) { args.push(options.additionalFlags); } // Locate a compiler let tsc: string; if (options.compiler) { // Custom compiler (task.compiler) grunt.log.writeln('Using the custom compiler : ' + options.compiler); tsc = options.compiler; } else { // the bundled OR npm module based compiler tsc = getTsc(resolveTypeScriptBinPath()); } // To debug the tsc command if (options.verbose) { console.log(args.join(' ').yellow); } else { grunt.log.verbose.writeln(args.join(' ').yellow); } // Create a temp last command file and use that to guide tsc. // Reason: passing all the files on the command line causes TSC to go in an infinite loop. let tempfilename = utils.getTempFile('tscommand'); if (!tempfilename) { throw (new Error('cannot create temp file')); } fs.writeFileSync(tempfilename, args.join(' ')); let command: string[]; // Switch implementation if a test version of executeNode exists. if ('testExecute' in options) { if (_.isFunction(options.testExecute)) { command = [tsc, args.join(' ')]; executeNode = options.testExecute; } else { const invalidTestExecuteError = 'Invalid testExecute node present on target "' + options.targetName + '". Value of testExecute must be a function.'; throw (new Error(invalidTestExecuteError)); } } else { // this is the normal path. command = [tsc, '@' + tempfilename]; executeNode = executeNodeDefault; } // Execute command return executeNode(command, options).then((result: ICompileResult) => { if (options.fast !== 'never' && result.code === 0) { resetChangedFiles(newFiles, options.targetName); } result.fileCount = files.length; fs.unlinkSync(tempfilename); grunt.log.writeln(result.output); return (<any>Promise).cast(result); }, (err) => { fs.unlinkSync(tempfilename); throw err; }); }