Permalink
Browse files

feat(compiler-cli): add watch mode to `ngc` (#18818)

With this change ngc now accepts a `-w` or a `--watch`
command-line option that will automatically perform a
recompile whenever any source files change on disk.

PR Close #18818
  • Loading branch information...
chuckjaz authored and jasonaden committed Aug 18, 2017
1 parent c685cc2 commit 06d01b22878c489025931c863c1f8129adb0195c
@@ -275,6 +275,9 @@
"@types/base64-js": { "@types/base64-js": {
"version": "1.2.5" "version": "1.2.5"
}, },
"@types/chokidar": {
"version": "1.7.2"
},
"@types/fs-extra": { "@types/fs-extra": {
"version": "0.0.22-alpha" "version": "0.0.22-alpha"
}, },
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View
@@ -34,6 +34,7 @@
"@bazel/typescript": "0.0.7", "@bazel/typescript": "0.0.7",
"@types/angularjs": "^1.5.13-alpha", "@types/angularjs": "^1.5.13-alpha",
"@types/base64-js": "^1.2.5", "@types/base64-js": "^1.2.5",
"@types/chokidar": "^1.1.0",
"@types/fs-extra": "0.0.22-alpha", "@types/fs-extra": "0.0.22-alpha",
"@types/hammerjs": "^2.0.33", "@types/hammerjs": "^2.0.33",
"@types/jasmine": "^2.2.22-alpha", "@types/jasmine": "^2.2.22-alpha",
@@ -20,7 +20,7 @@ export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Sp
export * from './src/transformers/api'; export * from './src/transformers/api';
export * from './src/transformers/entry_points'; export * from './src/transformers/entry_points';
export {performCompilation, readConfiguration, formatDiagnostics, calcProjectFileAndBasePath, createNgCompilerOptions} from './src/perform-compile'; export {performCompilation, readConfiguration, formatDiagnostics, calcProjectFileAndBasePath, createNgCompilerOptions} from './src/perform_compile';
// TODO(hansl): moving to Angular 4 need to update this API. // TODO(hansl): moving to Angular 4 need to update this API.
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api'; export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';
@@ -12,7 +12,8 @@
"@angular/tsc-wrapped": "5.0.0-beta.5", "@angular/tsc-wrapped": "5.0.0-beta.5",
"reflect-metadata": "^0.1.2", "reflect-metadata": "^0.1.2",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"tsickle": "^0.23.4" "tsickle": "^0.23.4",
"chokidar": "^1.4.2"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^2.0.2", "typescript": "^2.0.2",
@@ -9,7 +9,7 @@
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, NgAnalyzedModules, ParseSourceSpan, Statement, StaticReflector, TypeScriptEmitter, createAotCompiler} from '@angular/compiler'; import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, NgAnalyzedModules, ParseSourceSpan, Statement, StaticReflector, TypeScriptEmitter, createAotCompiler} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {Diagnostic} from '../transformers/api'; import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from '../transformers/api';
interface FactoryInfo { interface FactoryInfo {
source: ts.SourceFile; source: ts.SourceFile;
@@ -142,8 +142,10 @@ export class TypeChecker {
const fileName = span.start.file.url; const fileName = span.start.file.url;
const diagnosticsList = diagnosticsFor(fileName); const diagnosticsList = diagnosticsFor(fileName);
diagnosticsList.push({ diagnosticsList.push({
message: diagnosticMessageToString(diagnostic.messageText), messageText: diagnosticMessageToString(diagnostic.messageText),
category: diagnostic.category, span category: diagnostic.category, span,
source: SOURCE,
code: DEFAULT_ERROR_CODE
}); });
} }
} }
@@ -17,14 +17,19 @@ import * as path from 'path';
import * as tsickle from 'tsickle'; import * as tsickle from 'tsickle';
import * as api from './transformers/api'; import * as api from './transformers/api';
import * as ngc from './transformers/entry_points'; import * as ngc from './transformers/entry_points';
import {performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration} from './perform-compile';
import {calcProjectFileAndBasePath, exitCodeFromResult, performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration, PerformCompilationResult} from './perform_compile';
import {performWatchCompilationcreatePerformWatchHost} from './perform_watch';
import {isSyntaxError} from '@angular/compiler'; import {isSyntaxError} from '@angular/compiler';
import {CodeGenerator} from './codegen'; import {CodeGenerator} from './codegen';
export function main( export function main(
args: string[], consoleError: (s: string) => void = console.error): Promise<number> { args: string[], consoleError: (s: string) => void = console.error): Promise<number> {
const parsedArgs = require('minimist')(args); const parsedArgs = require('minimist')(args);
if (parsedArgs.w || parsedArgs.watch) {
const result = watchMode(parsedArgs, consoleError);
return Promise.resolve(exitCodeFromResult(result.firstCompileResult));
}
const {rootNames, options, errors: configErrors} = readCommandLineAndConfiguration(parsedArgs); const {rootNames, options, errors: configErrors} = readCommandLineAndConfiguration(parsedArgs);
if (configErrors.length) { if (configErrors.length) {
return Promise.resolve(reportErrorsAndExit(options, configErrors, consoleError)); return Promise.resolve(reportErrorsAndExit(options, configErrors, consoleError));
@@ -83,12 +88,16 @@ function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback {
}); });
} }
function projectOf(args: any): string {
return (args && (args.p || args.project)) || '.';
}
function readCommandLineAndConfiguration(args: any): ParsedConfiguration { function readCommandLineAndConfiguration(args: any): ParsedConfiguration {
const project = args.p || args.project || '.'; const project = projectOf(args);
const allDiagnostics: Diagnostics = []; const allDiagnostics: Diagnostics = [];
const config = readConfiguration(project); const config = readConfiguration(project);
const options = mergeCommandLineParams(args, config.options); const options = mergeCommandLineParams(args, config.options);
return {rootNames: config.rootNames, options, errors: config.errors}; return {project, rootNames: config.rootNames, options, errors: config.errors};
} }
function reportErrorsAndExit( function reportErrorsAndExit(
@@ -101,6 +110,15 @@ function reportErrorsAndExit(
return exitCode; return exitCode;
} }
export function watchMode(args: any, consoleError: (s: string) => void) {
const project = projectOf(args);
const {projectFile, basePath} = calcProjectFileAndBasePath(project);
const config = readConfiguration(project);
return performWatchCompilation(createPerformWatchHost(projectFile, diagnostics => {
consoleError(formatDiagnostics(config.options, diagnostics));
}, options => createEmitCallback(options)));
}
function mergeCommandLineParams( function mergeCommandLineParams(
cliArgs: {[k: string]: string}, options: api.CompilerOptions): api.CompilerOptions { cliArgs: {[k: string]: string}, options: api.CompilerOptions): api.CompilerOptions {
// TODO: also merge in tsc command line parameters by calling // TODO: also merge in tsc command line parameters by calling
@@ -20,7 +20,7 @@ const TS_EXT = /\.ts$/;
export type Diagnostics = Array<ts.Diagnostic|api.Diagnostic>; export type Diagnostics = Array<ts.Diagnostic|api.Diagnostic>;
function isTsDiagnostic(diagnostic: any): diagnostic is ts.Diagnostic { function isTsDiagnostic(diagnostic: any): diagnostic is ts.Diagnostic {
return diagnostic && (diagnostic.file || diagnostic.messageText); return diagnostic && diagnostic.source != 'angular';
} }
export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string { export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string {
@@ -41,9 +41,9 @@ export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnosti
` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`; ` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`;
} }
if (d.span && d.span.details) { if (d.span && d.span.details) {
res += `: ${d.span.details}, ${d.message}\n`; res += `: ${d.span.details}, ${d.messageText}\n`;
} else { } else {
res += `: ${d.message}\n`; res += `: ${d.messageText}\n`;
} }
return res; return res;
} }
@@ -54,6 +54,7 @@ export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnosti
} }
export interface ParsedConfiguration { export interface ParsedConfiguration {
project: string;
options: api.CompilerOptions; options: api.CompilerOptions;
rootNames: string[]; rootNames: string[];
errors: Diagnostics; errors: Diagnostics;
@@ -81,7 +82,7 @@ export function readConfiguration(
let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile); let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile);
if (error) { if (error) {
return {errors: [error], rootNames: [], options: {}}; return {project, errors: [error], rootNames: [], options: {}};
} }
const parseConfigHost = { const parseConfigHost = {
useCaseSensitiveFileNames: true, useCaseSensitiveFileNames: true,
@@ -94,16 +95,40 @@ export function readConfiguration(
const rootNames = parsed.fileNames.map(f => path.normalize(f)); const rootNames = parsed.fileNames.map(f => path.normalize(f));
const options = createNgCompilerOptions(basePath, config, parsed.options); const options = createNgCompilerOptions(basePath, config, parsed.options);
return {rootNames, options, errors: parsed.errors}; return {project: projectFile, rootNames, options, errors: parsed.errors};
} catch (e) { } catch (e) {
const errors: Diagnostics = [{ const errors: Diagnostics = [{
category: ts.DiagnosticCategory.Error, category: ts.DiagnosticCategory.Error,
message: e.stack, messageText: e.stack,
source: api.SOURCE,
code: api.UNKNOWN_ERROR_CODE
}]; }];
return {errors, rootNames: [], options: {}}; return {project: '', errors, rootNames: [], options: {}};
} }
} }
export interface PerformCompilationResult {
diagnostics: Diagnostics;
program?: api.Program;
emitResult?: ts.EmitResult;
}
export function exitCodeFromResult(result: PerformCompilationResult | undefined): number {
if (!result) {
// If we didn't get a result we should return failure.
return 1;
}
if (!result.diagnostics || result.diagnostics.length === 0) {
// If we have a result and didn't get any errors, we succeeded.
return 0;
}
// Return 2 if any of the errors were unknown.
return result.diagnostics.some(d => d.source === 'angular' && d.code === api.UNKNOWN_ERROR_CODE) ?
2 :
1;
}
export function performCompilation( export function performCompilation(
{rootNames, options, host, oldProgram, emitCallback, customTransformers}: { {rootNames, options, host, oldProgram, emitCallback, customTransformers}: {
rootNames: string[], rootNames: string[],
@@ -112,11 +137,7 @@ export function performCompilation(
oldProgram?: api.Program, oldProgram?: api.Program,
emitCallback?: api.TsEmitCallback, emitCallback?: api.TsEmitCallback,
customTransformers?: api.CustomTransformers customTransformers?: api.CustomTransformers
}): { }): PerformCompilationResult {
program?: api.Program,
emitResult?: ts.EmitResult,
diagnostics: Diagnostics,
} {
const [major, minor] = ts.version.split('.'); const [major, minor] = ts.version.split('.');
if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 3)) { if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 3)) {
@@ -168,19 +189,24 @@ export function performCompilation(
((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata) ((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
}); });
allDiagnostics.push(...emitResult.diagnostics); allDiagnostics.push(...emitResult.diagnostics);
return {diagnostics: allDiagnostics, program, emitResult};
} }
return {diagnostics: allDiagnostics, program};
} catch (e) { } catch (e) {
let errMsg: string; let errMsg: string;
let code: number;
if (isSyntaxError(e)) { if (isSyntaxError(e)) {
// don't report the stack for syntax errors as they are well known errors. // don't report the stack for syntax errors as they are well known errors.
errMsg = e.message; errMsg = e.message;
code = api.DEFAULT_ERROR_CODE;
} else { } else {
errMsg = e.stack; errMsg = e.stack;
// It is not a syntax error we might have a program with unknown state, discard it.
program = undefined;
code = api.UNKNOWN_ERROR_CODE;
} }
allDiagnostics.push({ allDiagnostics.push(
category: ts.DiagnosticCategory.Error, {category: ts.DiagnosticCategory.Error, messageText: errMsg, code, source: api.SOURCE});
message: errMsg, return {diagnostics: allDiagnostics, program};
});
} }
return {program, emitResult, diagnostics: allDiagnostics};
} }
Oops, something went wrong.

0 comments on commit 06d01b2

Please sign in to comment.