Skip to content

Commit

Permalink
feat(compiler-cli): add watch mode to ngc (#18818)
Browse files Browse the repository at this point in the history
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 31, 2017
1 parent c685cc2 commit 06d01b2
Show file tree
Hide file tree
Showing 18 changed files with 539 additions and 54 deletions.
3 changes: 3 additions & 0 deletions npm-shrinkwrap.clean.json
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@
"@types/base64-js": {
"version": "1.2.5"
},
"@types/chokidar": {
"version": "1.7.2"
},
"@types/fs-extra": {
"version": "0.0.22-alpha"
},
Expand Down
5 changes: 5 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@bazel/typescript": "0.0.7",
"@types/angularjs": "^1.5.13-alpha",
"@types/base64-js": "^1.2.5",
"@types/chokidar": "^1.1.0",
"@types/fs-extra": "0.0.22-alpha",
"@types/hammerjs": "^2.0.33",
"@types/jasmine": "^2.2.22-alpha",
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Sp
export * from './src/transformers/api';
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.
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';
3 changes: 2 additions & 1 deletion packages/compiler-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"@angular/tsc-wrapped": "5.0.0-beta.5",
"reflect-metadata": "^0.1.2",
"minimist": "^1.2.0",
"tsickle": "^0.23.4"
"tsickle": "^0.23.4",
"chokidar": "^1.4.2"
},
"peerDependencies": {
"typescript": "^2.0.2",
Expand Down
8 changes: 5 additions & 3 deletions packages/compiler-cli/src/diagnostics/check_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, NgAnalyzedModules, ParseSourceSpan, Statement, StaticReflector, TypeScriptEmitter, createAotCompiler} from '@angular/compiler';
import * as ts from 'typescript';

import {Diagnostic} from '../transformers/api';
import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from '../transformers/api';

interface FactoryInfo {
source: ts.SourceFile;
Expand Down Expand Up @@ -142,8 +142,10 @@ export class TypeChecker {
const fileName = span.start.file.url;
const diagnosticsList = diagnosticsFor(fileName);
diagnosticsList.push({
message: diagnosticMessageToString(diagnostic.messageText),
category: diagnostic.category, span
messageText: diagnosticMessageToString(diagnostic.messageText),
category: diagnostic.category, span,
source: SOURCE,
code: DEFAULT_ERROR_CODE
});
}
}
Expand Down
24 changes: 21 additions & 3 deletions packages/compiler-cli/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ import * as path from 'path';
import * as tsickle from 'tsickle';
import * as api from './transformers/api';
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 {performWatchCompilation, createPerformWatchHost} from './perform_watch';
import {isSyntaxError} from '@angular/compiler';
import {CodeGenerator} from './codegen';

export function main(
args: string[], consoleError: (s: string) => void = console.error): Promise<number> {
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);
if (configErrors.length) {
return Promise.resolve(reportErrorsAndExit(options, configErrors, consoleError));
Expand Down Expand Up @@ -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 {
const project = args.p || args.project || '.';
const project = projectOf(args);
const allDiagnostics: Diagnostics = [];
const config = readConfiguration(project);
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(
Expand All @@ -101,6 +110,15 @@ function reportErrorsAndExit(
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(
cliArgs: {[k: string]: string}, options: api.CompilerOptions): api.CompilerOptions {
// TODO: also merge in tsc command line parameters by calling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const TS_EXT = /\.ts$/;
export type Diagnostics = Array<ts.Diagnostic|api.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 {
Expand All @@ -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})`;
}
if (d.span && d.span.details) {
res += `: ${d.span.details}, ${d.message}\n`;
res += `: ${d.span.details}, ${d.messageText}\n`;
} else {
res += `: ${d.message}\n`;
res += `: ${d.messageText}\n`;
}
return res;
}
Expand All @@ -54,6 +54,7 @@ export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnosti
}

export interface ParsedConfiguration {
project: string;
options: api.CompilerOptions;
rootNames: string[];
errors: Diagnostics;
Expand Down Expand Up @@ -81,7 +82,7 @@ export function readConfiguration(
let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile);

if (error) {
return {errors: [error], rootNames: [], options: {}};
return {project, errors: [error], rootNames: [], options: {}};
}
const parseConfigHost = {
useCaseSensitiveFileNames: true,
Expand All @@ -94,16 +95,40 @@ export function readConfiguration(
const rootNames = parsed.fileNames.map(f => path.normalize(f));

const options = createNgCompilerOptions(basePath, config, parsed.options);
return {rootNames, options, errors: parsed.errors};
return {project: projectFile, rootNames, options, errors: parsed.errors};
} catch (e) {
const errors: Diagnostics = [{
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(
{rootNames, options, host, oldProgram, emitCallback, customTransformers}: {
rootNames: string[],
Expand All @@ -112,11 +137,7 @@ export function performCompilation(
oldProgram?: api.Program,
emitCallback?: api.TsEmitCallback,
customTransformers?: api.CustomTransformers
}): {
program?: api.Program,
emitResult?: ts.EmitResult,
diagnostics: Diagnostics,
} {
}): PerformCompilationResult {
const [major, minor] = ts.version.split('.');

if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 3)) {
Expand Down Expand Up @@ -168,19 +189,24 @@ export function performCompilation(
((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
});
allDiagnostics.push(...emitResult.diagnostics);
return {diagnostics: allDiagnostics, program, emitResult};
}
return {diagnostics: allDiagnostics, program};
} catch (e) {
let errMsg: string;
let code: number;
if (isSyntaxError(e)) {
// don't report the stack for syntax errors as they are well known errors.
errMsg = e.message;
code = api.DEFAULT_ERROR_CODE;
} else {
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({
category: ts.DiagnosticCategory.Error,
message: errMsg,
});
allDiagnostics.push(
{category: ts.DiagnosticCategory.Error, messageText: errMsg, code, source: api.SOURCE});
return {diagnostics: allDiagnostics, program};
}
return {program, emitResult, diagnostics: allDiagnostics};
}
Loading

0 comments on commit 06d01b2

Please sign in to comment.