Skip to content

Commit 06d01b2

Browse files
chuckjazjasonaden
authored andcommitted
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
1 parent c685cc2 commit 06d01b2

File tree

18 files changed

+539
-54
lines changed

18 files changed

+539
-54
lines changed

npm-shrinkwrap.clean.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,9 @@
275275
"@types/base64-js": {
276276
"version": "1.2.5"
277277
},
278+
"@types/chokidar": {
279+
"version": "1.7.2"
280+
},
278281
"@types/fs-extra": {
279282
"version": "0.0.22-alpha"
280283
},

npm-shrinkwrap.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"@bazel/typescript": "0.0.7",
3535
"@types/angularjs": "^1.5.13-alpha",
3636
"@types/base64-js": "^1.2.5",
37+
"@types/chokidar": "^1.1.0",
3738
"@types/fs-extra": "0.0.22-alpha",
3839
"@types/hammerjs": "^2.0.33",
3940
"@types/jasmine": "^2.2.22-alpha",

packages/compiler-cli/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Sp
2020
export * from './src/transformers/api';
2121
export * from './src/transformers/entry_points';
2222

23-
export {performCompilation, readConfiguration, formatDiagnostics, calcProjectFileAndBasePath, createNgCompilerOptions} from './src/perform-compile';
23+
export {performCompilation, readConfiguration, formatDiagnostics, calcProjectFileAndBasePath, createNgCompilerOptions} from './src/perform_compile';
2424

2525
// TODO(hansl): moving to Angular 4 need to update this API.
2626
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';

packages/compiler-cli/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"@angular/tsc-wrapped": "5.0.0-beta.5",
1313
"reflect-metadata": "^0.1.2",
1414
"minimist": "^1.2.0",
15-
"tsickle": "^0.23.4"
15+
"tsickle": "^0.23.4",
16+
"chokidar": "^1.4.2"
1617
},
1718
"peerDependencies": {
1819
"typescript": "^2.0.2",

packages/compiler-cli/src/diagnostics/check_types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, NgAnalyzedModules, ParseSourceSpan, Statement, StaticReflector, TypeScriptEmitter, createAotCompiler} from '@angular/compiler';
1010
import * as ts from 'typescript';
1111

12-
import {Diagnostic} from '../transformers/api';
12+
import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from '../transformers/api';
1313

1414
interface FactoryInfo {
1515
source: ts.SourceFile;
@@ -142,8 +142,10 @@ export class TypeChecker {
142142
const fileName = span.start.file.url;
143143
const diagnosticsList = diagnosticsFor(fileName);
144144
diagnosticsList.push({
145-
message: diagnosticMessageToString(diagnostic.messageText),
146-
category: diagnostic.category, span
145+
messageText: diagnosticMessageToString(diagnostic.messageText),
146+
category: diagnostic.category, span,
147+
source: SOURCE,
148+
code: DEFAULT_ERROR_CODE
147149
});
148150
}
149151
}

packages/compiler-cli/src/main.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,19 @@ import * as path from 'path';
1717
import * as tsickle from 'tsickle';
1818
import * as api from './transformers/api';
1919
import * as ngc from './transformers/entry_points';
20-
import {performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration} from './perform-compile';
2120

21+
import {calcProjectFileAndBasePath, exitCodeFromResult, performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration, PerformCompilationResult} from './perform_compile';
22+
import {performWatchCompilation, createPerformWatchHost} from './perform_watch';
2223
import {isSyntaxError} from '@angular/compiler';
2324
import {CodeGenerator} from './codegen';
2425

2526
export function main(
2627
args: string[], consoleError: (s: string) => void = console.error): Promise<number> {
2728
const parsedArgs = require('minimist')(args);
29+
if (parsedArgs.w || parsedArgs.watch) {
30+
const result = watchMode(parsedArgs, consoleError);
31+
return Promise.resolve(exitCodeFromResult(result.firstCompileResult));
32+
}
2833
const {rootNames, options, errors: configErrors} = readCommandLineAndConfiguration(parsedArgs);
2934
if (configErrors.length) {
3035
return Promise.resolve(reportErrorsAndExit(options, configErrors, consoleError));
@@ -83,12 +88,16 @@ function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback {
8388
});
8489
}
8590

91+
function projectOf(args: any): string {
92+
return (args && (args.p || args.project)) || '.';
93+
}
94+
8695
function readCommandLineAndConfiguration(args: any): ParsedConfiguration {
87-
const project = args.p || args.project || '.';
96+
const project = projectOf(args);
8897
const allDiagnostics: Diagnostics = [];
8998
const config = readConfiguration(project);
9099
const options = mergeCommandLineParams(args, config.options);
91-
return {rootNames: config.rootNames, options, errors: config.errors};
100+
return {project, rootNames: config.rootNames, options, errors: config.errors};
92101
}
93102

94103
function reportErrorsAndExit(
@@ -101,6 +110,15 @@ function reportErrorsAndExit(
101110
return exitCode;
102111
}
103112

113+
export function watchMode(args: any, consoleError: (s: string) => void) {
114+
const project = projectOf(args);
115+
const {projectFile, basePath} = calcProjectFileAndBasePath(project);
116+
const config = readConfiguration(project);
117+
return performWatchCompilation(createPerformWatchHost(projectFile, diagnostics => {
118+
consoleError(formatDiagnostics(config.options, diagnostics));
119+
}, options => createEmitCallback(options)));
120+
}
121+
104122
function mergeCommandLineParams(
105123
cliArgs: {[k: string]: string}, options: api.CompilerOptions): api.CompilerOptions {
106124
// TODO: also merge in tsc command line parameters by calling

packages/compiler-cli/src/perform-compile.ts renamed to packages/compiler-cli/src/perform_compile.ts

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const TS_EXT = /\.ts$/;
2020
export type Diagnostics = Array<ts.Diagnostic|api.Diagnostic>;
2121

2222
function isTsDiagnostic(diagnostic: any): diagnostic is ts.Diagnostic {
23-
return diagnostic && (diagnostic.file || diagnostic.messageText);
23+
return diagnostic && diagnostic.source != 'angular';
2424
}
2525

2626
export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string {
@@ -41,9 +41,9 @@ export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnosti
4141
` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`;
4242
}
4343
if (d.span && d.span.details) {
44-
res += `: ${d.span.details}, ${d.message}\n`;
44+
res += `: ${d.span.details}, ${d.messageText}\n`;
4545
} else {
46-
res += `: ${d.message}\n`;
46+
res += `: ${d.messageText}\n`;
4747
}
4848
return res;
4949
}
@@ -54,6 +54,7 @@ export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnosti
5454
}
5555

5656
export interface ParsedConfiguration {
57+
project: string;
5758
options: api.CompilerOptions;
5859
rootNames: string[];
5960
errors: Diagnostics;
@@ -81,7 +82,7 @@ export function readConfiguration(
8182
let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile);
8283

8384
if (error) {
84-
return {errors: [error], rootNames: [], options: {}};
85+
return {project, errors: [error], rootNames: [], options: {}};
8586
}
8687
const parseConfigHost = {
8788
useCaseSensitiveFileNames: true,
@@ -94,16 +95,40 @@ export function readConfiguration(
9495
const rootNames = parsed.fileNames.map(f => path.normalize(f));
9596

9697
const options = createNgCompilerOptions(basePath, config, parsed.options);
97-
return {rootNames, options, errors: parsed.errors};
98+
return {project: projectFile, rootNames, options, errors: parsed.errors};
9899
} catch (e) {
99100
const errors: Diagnostics = [{
100101
category: ts.DiagnosticCategory.Error,
101-
message: e.stack,
102+
messageText: e.stack,
103+
source: api.SOURCE,
104+
code: api.UNKNOWN_ERROR_CODE
102105
}];
103-
return {errors, rootNames: [], options: {}};
106+
return {project: '', errors, rootNames: [], options: {}};
104107
}
105108
}
106109

110+
export interface PerformCompilationResult {
111+
diagnostics: Diagnostics;
112+
program?: api.Program;
113+
emitResult?: ts.EmitResult;
114+
}
115+
116+
export function exitCodeFromResult(result: PerformCompilationResult | undefined): number {
117+
if (!result) {
118+
// If we didn't get a result we should return failure.
119+
return 1;
120+
}
121+
if (!result.diagnostics || result.diagnostics.length === 0) {
122+
// If we have a result and didn't get any errors, we succeeded.
123+
return 0;
124+
}
125+
126+
// Return 2 if any of the errors were unknown.
127+
return result.diagnostics.some(d => d.source === 'angular' && d.code === api.UNKNOWN_ERROR_CODE) ?
128+
2 :
129+
1;
130+
}
131+
107132
export function performCompilation(
108133
{rootNames, options, host, oldProgram, emitCallback, customTransformers}: {
109134
rootNames: string[],
@@ -112,11 +137,7 @@ export function performCompilation(
112137
oldProgram?: api.Program,
113138
emitCallback?: api.TsEmitCallback,
114139
customTransformers?: api.CustomTransformers
115-
}): {
116-
program?: api.Program,
117-
emitResult?: ts.EmitResult,
118-
diagnostics: Diagnostics,
119-
} {
140+
}): PerformCompilationResult {
120141
const [major, minor] = ts.version.split('.');
121142

122143
if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 3)) {
@@ -168,19 +189,24 @@ export function performCompilation(
168189
((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
169190
});
170191
allDiagnostics.push(...emitResult.diagnostics);
192+
return {diagnostics: allDiagnostics, program, emitResult};
171193
}
194+
return {diagnostics: allDiagnostics, program};
172195
} catch (e) {
173196
let errMsg: string;
197+
let code: number;
174198
if (isSyntaxError(e)) {
175199
// don't report the stack for syntax errors as they are well known errors.
176200
errMsg = e.message;
201+
code = api.DEFAULT_ERROR_CODE;
177202
} else {
178203
errMsg = e.stack;
204+
// It is not a syntax error we might have a program with unknown state, discard it.
205+
program = undefined;
206+
code = api.UNKNOWN_ERROR_CODE;
179207
}
180-
allDiagnostics.push({
181-
category: ts.DiagnosticCategory.Error,
182-
message: errMsg,
183-
});
208+
allDiagnostics.push(
209+
{category: ts.DiagnosticCategory.Error, messageText: errMsg, code, source: api.SOURCE});
210+
return {diagnostics: allDiagnostics, program};
184211
}
185-
return {program, emitResult, diagnostics: allDiagnostics};
186212
}

0 commit comments

Comments
 (0)