Skip to content

Commit af8bb0f

Browse files
Christopher BondChristopher Bond
authored andcommitted
Refactoring of CLI application
1 parent b9d0332 commit af8bb0f

29 files changed

+264
-168
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"reflect-metadata": "^0.1.9",
5353
"rimraf": "^2.5.4",
5454
"rxjs": "^5.1.0",
55+
"scoped-logger": "0.0.15",
5556
"source-map-support": "^0.4.11",
5657
"typescript": "^2.2.0"
5758
},

source/application/compiler/compiler-vm.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ export class CompilerVmHost extends DelegatingHost {
5252
const traversal = pathFromString(targetPath);
5353

5454
const files = new Set([
55-
...Array.from(traversal.files()).map(f => f.path()),
55+
...Array.from(traversal.files()).map(f => f.toString()),
5656
...Array.from(this.vm.filenames(targetPath))
5757
]);
5858

5959
const directories = new Set<string>([
60-
...Array.from(traversal.directories()).map(d => d.string()),
60+
...Array.from(traversal.directories()).map(d => d.toString()),
6161
...Array.from(this.vm.directories(targetPath))
6262
]);
6363

source/application/compiler/compiler.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@ export class Compiler {
2121

2222
constructor(private project: Project) {
2323
this.options = loadProjectOptions(project);
24-
25-
if (project.rootModule == null ||
26-
!project.rootModule.source ||
27-
!project.rootModule.symbol) {
28-
throw new CompilerException('Compilation requires a source and symbol');
29-
}
3024
}
3125

3226
async compile(): Promise<NgModuleFactory<any>> {
@@ -68,7 +62,7 @@ export class Compiler {
6862
assertDiagnostics(emitResult.diagnostics);
6963
}
7064

71-
return emit.rootModule(compilerVmHost, this.options.angular.basePath, vmoptions.options);
65+
return emit.rootModule(program, compilerVmHost, vmoptions.options);
7266
}
7367

7468
private createProgram(files: Array<string>, compilerHost: CompilerHost, previousProgram?: Program): Program {

source/application/compiler/emit.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {
2+
CompilerHost,
23
CompilerOptions,
4+
Program,
35
SourceFile,
46
resolveModuleName
57
} from 'typescript';
@@ -10,10 +12,10 @@ import {
1012
resolve
1113
} from 'path';
1214

13-
import {CompilerVmHost} from './compiler-vm';
1415
import {CompilerException} from '../../exception';
15-
import {Project} from '../../application';
16+
import {ApplicationModuleDescriptor, Project} from '../../application';
1617
import {Publisher} from '../../publisher';
18+
import {StaticAnalyzer} from '../static';
1719

1820
export type CompiledSource = {filename: string, source: string};
1921

@@ -47,20 +49,38 @@ export class CompilationEmit {
4749
}
4850
}
4951

50-
rootModule(compilerHost: CompilerVmHost, basePath: string, options: CompilerOptions): [string, string] {
51-
const containingFile = join(basePath, 'index.ts');
52+
applicationModule(program: Program): ApplicationModuleDescriptor {
53+
const parser = new StaticAnalyzer(program.getSourceFiles());
5254

53-
const rootModule = sourceToNgFactory(this.project.rootModule.source);
55+
return parser.getBootstrapModule();
56+
}
57+
58+
rootModule(program: Program, compilerHost: CompilerHost, options: CompilerOptions): [string, string] {
59+
const containingFile = join(this.project.basePath, 'index.ts');
60+
61+
const applicationModule =
62+
this.project.applicationModule
63+
? this.project.applicationModule
64+
: this.applicationModule(program);
65+
66+
if (applicationModule == null ||
67+
applicationModule.source == null ||
68+
applicationModule.symbol == null) {
69+
throw new CompilerException(`Cannot find application root @NgModule, please provide in Project structure`);
70+
}
71+
72+
const rootModule = sourceToNgFactory(applicationModule.source);
5473

5574
const resolved = resolveModuleName(rootModule, containingFile, options, compilerHost);
5675

57-
if (resolved == null || resolved.resolvedModule == null) {
76+
if (resolved == null ||
77+
resolved.resolvedModule == null) {
5878
throw new CompilerException(`Cannot resolve root module: ${rootModule}`);
5979
}
6080

6181
const moduleFile = this.getModuleFromSourceFile(resolved.resolvedModule.resolvedFileName);
6282

63-
const symbol = symbolToNgFactory(this.project.rootModule.symbol);
83+
const symbol = symbolToNgFactory(applicationModule.symbol);
6484

6585
return [moduleFile, symbol];
6686
}
@@ -94,7 +114,7 @@ const executable = (filename: string) => /\.js$/.test(filename);
94114

95115
const sourceToNgFactory = (source: string): string => {
96116
if (/\.ngfactory(\.(ts|js))?$/.test(source) === false) {
97-
return `${source}.ngfactory`;
117+
return `${source.replace(/\.(js|ts)$/, String())}.ngfactory`;
98118
}
99119
return source;
100120
};

source/application/compiler/options.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,24 @@ export const loadProjectOptions = (project: Project): CompileOptions => {
2626
ngOptions.basePath = project.basePath;
2727
ngOptions.generateCodeForLibraries = true;
2828

29+
// Test files cannot be included but ng new projects do not exclude them from the
30+
// build in tsconfig.json files, so we must manually snip them from the root file
31+
// list. This is really super nasty and it would be great if it weren't necessary.
32+
const rootSources = parsed.fileNames.filter(file => testHeuristic(file) === false);
33+
2934
return {
3035
angular: ngOptions,
31-
rootSources: parsed.fileNames,
36+
rootSources,
3237
ts: adjustOptions(parsed.options),
3338
};
3439
};
3540

3641
export const adjustOptions = (baseOptions?: CompilerOptions): CompilerOptions => {
3742
return Object.assign({}, baseOptions, {
3843
declaration: false,
39-
sourceMap: false,
40-
inlineSourceMap: false,
4144
module: ModuleKind.CommonJS,
4245
moduleResolution: ModuleResolutionKind.NodeJs,
4346
});
4447
};
48+
49+
const testHeuristic = (filename: string) => /(e2e|\.?(spec|tests?)\.)/.test(filename);

source/application/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './builder';
22
export * from './compiler';
3+
export * from './static';
34
export * from './fork';
45
export * from './project';
56
export * from './render';

source/application/project.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
export interface ApplicationModuleDescriptor {
2+
source: string;
3+
symbol: string;
4+
}
5+
16
export interface Project {
2-
// project root path
37
basePath: string;
48

59
// path to the tsconfig.json file for this project
610
tsconfig: string;
711

8-
// path to the application root NgModule file, and the name of that module
9-
rootModule: {
10-
source: string;
11-
symbol: string;
12-
}
12+
// path to the application root NgModule file, and the name of that module.
13+
// we will automatically determine this using static analysis if it is not
14+
// provided, but this will add a bit more time to the compilation process
15+
applicationModule?: ApplicationModuleDescriptor;
1316
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
CallExpression,
3+
PropertyAccessExpression,
4+
SourceFile,
5+
SyntaxKind,
6+
} from 'typescript';
7+
8+
import {findExpression} from './expression';
9+
10+
import {ApplicationModuleDescriptor} from '../project';
11+
12+
export class StaticAnalyzer {
13+
constructor(private sources: Array<SourceFile>) {}
14+
15+
getBootstrapModule(): ApplicationModuleDescriptor {
16+
for (const file of this.sources) {
17+
const expression = findExpression<CallExpression>(file, SyntaxKind.CallExpression, isBootstrapCall);
18+
if (expression) {
19+
return {source: file.fileName, symbol: expression.arguments[0].getText()}
20+
}
21+
}
22+
return null;
23+
}
24+
}
25+
26+
const isBootstrapCall = (expr: CallExpression): boolean => {
27+
if (expr.expression.kind !== SyntaxKind.PropertyAccessExpression) {
28+
return false;
29+
}
30+
31+
const accessor = <PropertyAccessExpression> expr.expression;
32+
if (accessor.name.kind !== SyntaxKind.Identifier ||
33+
accessor.expression == null ||
34+
accessor.expression.kind !== SyntaxKind.CallExpression) {
35+
return false;
36+
}
37+
38+
switch (accessor.name.text) {
39+
case 'bootstrapModule':
40+
case 'bootstrapModuleFactory':
41+
return true;
42+
default:
43+
return false;
44+
}
45+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {Node, SyntaxKind} from 'typescript';
2+
3+
export const findExpression = <N extends Node>(root: Node, kind: SyntaxKind, predicate: (node: N) => boolean): N => {
4+
const match = (n: Node) => n.kind === kind && predicate(n as N);
5+
6+
if (match(root)) {
7+
return root as N;
8+
}
9+
10+
for (const child of root.getChildren()) {
11+
if (match(child)) {
12+
return child as N;
13+
}
14+
15+
const result = findExpression(child, kind, predicate);
16+
if (result) {
17+
return result;
18+
}
19+
}
20+
21+
return null;
22+
};

source/application/static/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './analyzer';
2+
export * from './expression';

0 commit comments

Comments
 (0)