Skip to content

Commit f4d94a7

Browse files
committed
.
1 parent 8d5a915 commit f4d94a7

File tree

7 files changed

+161
-240
lines changed

7 files changed

+161
-240
lines changed

package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@
4242
"homepage": "https://github.com/angular/angular-cli",
4343
"dependencies": {
4444
"@angular-cli/ast-tools": "^1.0.0",
45-
"@angular/compiler": "2.2.3",
46-
"@angular/compiler-cli": "2.2.3",
47-
"@angular/core": "2.2.3",
48-
"@angular/tsc-wrapped": "0.4.0",
45+
"@angular/compiler": "~2.3.1",
46+
"@angular/compiler-cli": "~2.3.1",
47+
"@angular/core": "~2.3.1",
48+
"@angular/tsc-wrapped": "~0.5.0",
4949
"async": "^2.1.4",
5050
"autoprefixer": "^6.5.3",
5151
"chalk": "^1.1.3",

packages/@ngtools/webpack/src/plugin.ts

+79-205
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ import * as fs from 'fs';
22
import * as path from 'path';
33
import * as ts from 'typescript';
44

5-
import {NgModule} from '@angular/core';
6-
import * as ngCompiler from '@angular/compiler-cli';
7-
import {tsc} from '@angular/tsc-wrapped/src/tsc';
5+
import {__NGTOOLS_PRIVATE_API_2} from '@angular/compiler-cli';
6+
import {AngularCompilerOptions} from '@angular/tsc-wrapped';
87

9-
import {patchReflectorHost} from './reflector_host';
108
import {WebpackResourceLoader} from './resource_loader';
119
import {createResolveDependenciesFromContextMap} from './utils';
1210
import {WebpackCompilerHost} from './compiler_host';
@@ -29,47 +27,22 @@ export interface AotPluginOptions {
2927
i18nFile?: string;
3028
i18nFormat?: string;
3129
locale?: string;
32-
}
33-
3430

35-
export interface LazyRoute {
36-
moduleRoute: ModuleRoute;
37-
absolutePath: string;
38-
absoluteGenDirPath: string;
39-
}
40-
41-
42-
export interface LazyRouteMap {
43-
[path: string]: LazyRoute;
44-
}
45-
46-
47-
export class ModuleRoute {
48-
constructor(public readonly path: string, public readonly className: string = null) {}
49-
50-
toString() {
51-
return `${this.path}#${this.className}`;
52-
}
53-
54-
static fromString(entry: string): ModuleRoute {
55-
const split = entry.split('#');
56-
return new ModuleRoute(split[0], split[1]);
57-
}
31+
// We do not have an include because tsconfig can be used for that.
32+
exclude?: string | string[];
5833
}
5934

6035

6136
export class AotPlugin implements Tapable {
62-
private _entryModule: ModuleRoute;
6337
private _compilerOptions: ts.CompilerOptions;
64-
private _angularCompilerOptions: ngCompiler.AngularCompilerOptions;
38+
private _angularCompilerOptions: AngularCompilerOptions;
6539
private _program: ts.Program;
66-
private _reflector: ngCompiler.StaticReflector;
67-
private _reflectorHost: ngCompiler.ReflectorHost;
6840
private _rootFilePath: string[];
6941
private _compilerHost: WebpackCompilerHost;
7042
private _resourceLoader: WebpackResourceLoader;
7143
private _lazyRoutes: { [route: string]: string };
7244
private _tsConfigPath: string;
45+
private _entryModule: string;
7346

7447
private _donePromise: Promise<void>;
7548
private _compiler: any = null;
@@ -93,7 +66,12 @@ export class AotPlugin implements Tapable {
9366
get compilerHost() { return this._compilerHost; }
9467
get compilerOptions() { return this._compilerOptions; }
9568
get done() { return this._donePromise; }
96-
get entryModule() { return this._entryModule; }
69+
get entryModule() {
70+
const splitted = this._entryModule.split('#');
71+
const path = splitted[0];
72+
const className = splitted[1] || 'default';
73+
return {path, className};
74+
}
9775
get genDir() { return this._genDir; }
9876
get program() { return this._program; }
9977
get skipCodeGeneration() { return this._skipCodeGeneration; }
@@ -119,19 +97,52 @@ export class AotPlugin implements Tapable {
11997
basePath = path.resolve(process.cwd(), options.basePath);
12098
}
12199

122-
const tsConfig = tsc.readConfiguration(this._tsConfigPath, basePath);
123-
this._rootFilePath = tsConfig.parsed.fileNames
124-
.filter(fileName => !/\.spec\.ts$/.test(fileName));
100+
let tsConfigJson: any = null;
101+
try {
102+
tsConfigJson = JSON.parse(fs.readFileSync(this._tsConfigPath, 'utf8'));
103+
} catch (err) {
104+
throw new Error(`An error happened while parsing ${this._tsConfigPath} JSON: ${err}.`);
105+
}
106+
const tsConfig = ts.parseJsonConfigFileContent(
107+
tsConfigJson, ts.sys, basePath, null, this._tsConfigPath);
108+
109+
let fileNames = tsConfig.fileNames;
110+
if (options.hasOwnProperty('exclude')) {
111+
let exclude: string[] = typeof options.exclude == 'string'
112+
? [options.exclude as string] : (options.exclude as string[]);
113+
114+
exclude.forEach((pattern: string) => {
115+
pattern = pattern
116+
// Replace characters that are used normally in regexes, except stars.
117+
.replace(/[\-\[\]\/{}()+?.\\^$|]/g, '\\$&')
118+
// Two stars replacement.
119+
.replace(/\*\*/g, '(?:.*)')
120+
.replace(/\*/g, '(?:[^/]*)')
121+
.replace(/^/, `(${basePath})?`);
122+
123+
const re = new RegExp('^' + pattern + '$');
124+
fileNames = fileNames.filter(x => !x.match(re));
125+
})
126+
} else {
127+
fileNames = fileNames.filter(fileName => !/\.spec\.ts$/.test(fileName));
128+
}
129+
this._rootFilePath = fileNames;
125130

126131
// Check the genDir.
127132
let genDir = basePath;
128-
if (tsConfig.ngOptions.hasOwnProperty('genDir')) {
129-
genDir = tsConfig.ngOptions.genDir;
130-
}
131133

132-
this._compilerOptions = tsConfig.parsed.options;
134+
this._compilerOptions = tsConfig.options;
135+
this._angularCompilerOptions = Object.assign(
136+
{ genDir },
137+
this._compilerOptions,
138+
tsConfig.raw['angularCompilerOptions'],
139+
{ basePath }
140+
);
141+
142+
if (this._angularCompilerOptions.hasOwnProperty('genDir')) {
143+
genDir = this._angularCompilerOptions.genDir;
144+
}
133145

134-
this._angularCompilerOptions = Object.assign({}, tsConfig.ngOptions, { basePath, genDir });
135146
this._basePath = basePath;
136147
this._genDir = genDir;
137148

@@ -147,21 +158,16 @@ export class AotPlugin implements Tapable {
147158
this._rootFilePath, this._compilerOptions, this._compilerHost);
148159

149160
if (options.entryModule) {
150-
this._entryModule = ModuleRoute.fromString(options.entryModule);
161+
this._entryModule = options.entryModule;
151162
} else {
152163
if (options.mainPath) {
153-
const entryModuleString = resolveEntryModuleFromMain(options.mainPath, this._compilerHost,
164+
this._entryModule = resolveEntryModuleFromMain(options.mainPath, this._compilerHost,
154165
this._program);
155-
this._entryModule = ModuleRoute.fromString(entryModuleString);
156166
} else {
157-
this._entryModule = ModuleRoute.fromString((tsConfig.ngOptions as any).entryModule);
167+
this._entryModule = (tsConfig.raw['angularCompilerOptions'] as any).entryModule;
158168
}
159169
}
160170

161-
this._reflectorHost = new ngCompiler.ReflectorHost(
162-
this._program, this._compilerHost, this._angularCompilerOptions);
163-
this._reflector = new ngCompiler.StaticReflector(this._reflectorHost);
164-
165171
if (options.hasOwnProperty('i18nFile')) {
166172
this._i18nFile = options.i18nFile;
167173
}
@@ -253,20 +259,18 @@ export class AotPlugin implements Tapable {
253259
}
254260

255261
// Create the Code Generator.
256-
const codeGenerator = ngCompiler.CodeGenerator.create(
257-
this._angularCompilerOptions,
258-
i18nOptions,
259-
this._program,
260-
this._compilerHost,
261-
new ngCompiler.NodeReflectorHostContext(this._compilerHost),
262-
this._resourceLoader
263-
);
264-
265-
// We need to temporarily patch the CodeGenerator until either it's patched or allows us
266-
// to pass in our own ReflectorHost.
267-
// TODO: remove this.
268-
patchReflectorHost(codeGenerator);
269-
return codeGenerator.codegen({ transitiveModules: true });
262+
return __NGTOOLS_PRIVATE_API_2.codeGen({
263+
basePath: this._basePath,
264+
compilerOptions: this._compilerOptions,
265+
program: this._program,
266+
host: this._compilerHost,
267+
angularCompilerOptions: this._angularCompilerOptions,
268+
i18nFormat: null,
269+
i18nFile: null,
270+
locale: null,
271+
272+
readResource: (path: string) => this._resourceLoader.get(path)
273+
});
270274
})
271275
.then(() => {
272276
// Create a new Program, based on the old one. This will trigger a resolution of all
@@ -298,155 +302,25 @@ export class AotPlugin implements Tapable {
298302
.then(() => {
299303
// Process the lazy routes
300304
this._lazyRoutes = {};
301-
const allLazyRoutes = this._processNgModule(this._entryModule, null);
305+
const allLazyRoutes = __NGTOOLS_PRIVATE_API_2.listLazyRoutes({
306+
program: this._program,
307+
host: this._compilerHost,
308+
angularCompilerOptions: this._angularCompilerOptions,
309+
entryModule: this._entryModule.toString()
310+
});
302311
Object.keys(allLazyRoutes)
303312
.forEach(k => {
304313
const lazyRoute = allLazyRoutes[k];
305314
if (this.skipCodeGeneration) {
306-
this._lazyRoutes[k] = lazyRoute.absolutePath + '.ts';
315+
this._lazyRoutes[k] = lazyRoute;
307316
} else {
308-
this._lazyRoutes[k + '.ngfactory'] = lazyRoute.absoluteGenDirPath + '.ngfactory.ts';
317+
const lr = path.relative(this.basePath, lazyRoute.replace(/\.ts$/, '.ngfactory.ts'));
318+
this._lazyRoutes[k + '.ngfactory'] = path.join(this.genDir, lr);
309319
}
310320
});
311321
})
312-
.then(() => cb(), (err: any) => { cb(err); });
313-
}
314-
315-
private _resolveModulePath(module: ModuleRoute, containingFile: string) {
316-
if (module.path.startsWith('.')) {
317-
return path.join(path.dirname(containingFile), module.path);
318-
}
319-
return module.path;
320-
}
321-
322-
private _processNgModule(module: ModuleRoute, containingFile: string | null): LazyRouteMap {
323-
const modulePath = containingFile ? module.path : ('./' + path.basename(module.path));
324-
if (containingFile === null) {
325-
containingFile = module.path + '.ts';
326-
}
327-
const relativeModulePath = this._resolveModulePath(module, containingFile);
328-
329-
const staticSymbol = this._reflectorHost
330-
.findDeclaration(modulePath, module.className, containingFile);
331-
const entryNgModuleMetadata = this.getNgModuleMetadata(staticSymbol);
332-
const loadChildrenRoute: LazyRoute[] = this.extractLoadChildren(entryNgModuleMetadata)
333-
.map(route => {
334-
const moduleRoute = ModuleRoute.fromString(route);
335-
const resolvedModule = ts.resolveModuleName(moduleRoute.path,
336-
relativeModulePath, this._compilerOptions, this._compilerHost);
337-
338-
if (!resolvedModule.resolvedModule) {
339-
throw new Error(`Could not resolve route "${route}" from file "${relativeModulePath}".`);
340-
}
341-
342-
const relativePath = path.relative(this.basePath,
343-
resolvedModule.resolvedModule.resolvedFileName).replace(/\.ts$/, '');
344-
345-
const absolutePath = path.join(this.basePath, relativePath);
346-
const absoluteGenDirPath = path.join(this._genDir, relativePath);
347-
348-
return {
349-
moduleRoute,
350-
absoluteGenDirPath,
351-
absolutePath
352-
};
322+
.then(() => cb(), (err: any) => {
323+
compilation.errors.push(err);
353324
});
354-
const resultMap: LazyRouteMap = loadChildrenRoute
355-
.reduce((acc: LazyRouteMap, curr: LazyRoute) => {
356-
const key = curr.moduleRoute.path;
357-
if (acc[key]) {
358-
if (acc[key].absolutePath != curr.absolutePath) {
359-
throw new Error(`Duplicated path in loadChildren detected: "${key}" is used in 2 ` +
360-
'loadChildren, but they point to different modules. Webpack cannot distinguish ' +
361-
'between the two based on context and would fail to load the proper one.');
362-
}
363-
} else {
364-
acc[key] = curr;
365-
}
366-
return acc;
367-
}, {});
368-
369-
// Also concatenate every child of child modules.
370-
for (const lazyRoute of loadChildrenRoute) {
371-
const mr = lazyRoute.moduleRoute;
372-
const children = this._processNgModule(mr, relativeModulePath);
373-
Object.keys(children).forEach(p => {
374-
const child = children[p];
375-
const key = child.moduleRoute.path;
376-
if (resultMap[key]) {
377-
if (resultMap[key].absolutePath != child.absolutePath) {
378-
throw new Error(`Duplicated path in loadChildren detected: "${key}" is used in 2 ` +
379-
'loadChildren, but they point to different modules. Webpack cannot distinguish ' +
380-
'between the two based on context and would fail to load the proper one.');
381-
}
382-
} else {
383-
resultMap[key] = child;
384-
}
385-
});
386-
}
387-
return resultMap;
388-
}
389-
390-
private getNgModuleMetadata(staticSymbol: ngCompiler.StaticSymbol) {
391-
const ngModules = this._reflector.annotations(staticSymbol).filter(s => s instanceof NgModule);
392-
if (ngModules.length === 0) {
393-
throw new Error(`${staticSymbol.name} is not an NgModule`);
394-
}
395-
return ngModules[0];
396-
}
397-
398-
private extractLoadChildren(ngModuleDecorator: any): any[] {
399-
const routes = (ngModuleDecorator.imports || []).reduce((mem: any[], m: any) => {
400-
return mem.concat(this.collectRoutes(m.providers));
401-
}, this.collectRoutes(ngModuleDecorator.providers));
402-
return this.collectLoadChildren(routes)
403-
.concat((ngModuleDecorator.imports || [])
404-
// Also recursively extractLoadChildren of modules we import.
405-
.map((staticSymbol: any) => {
406-
if (staticSymbol instanceof StaticSymbol) {
407-
const entryNgModuleMetadata = this.getNgModuleMetadata(staticSymbol);
408-
return this.extractLoadChildren(entryNgModuleMetadata);
409-
} else {
410-
return [];
411-
}
412-
})
413-
// Poor man's flat map.
414-
.reduce((acc: any[], i: any) => acc.concat(i), []))
415-
.filter(x => !!x);
416-
}
417-
418-
private collectRoutes(providers: any[]): any[] {
419-
if (!providers) {
420-
return [];
421-
}
422-
const ROUTES = this._reflectorHost.findDeclaration(
423-
'@angular/router/src/router_config_loader', 'ROUTES', undefined);
424-
425-
return providers.reduce((m, p) => {
426-
if (p.provide === ROUTES) {
427-
return m.concat(p.useValue);
428-
} else if (Array.isArray(p)) {
429-
return m.concat(this.collectRoutes(p));
430-
} else {
431-
return m;
432-
}
433-
}, []);
434-
}
435-
436-
private collectLoadChildren(routes: any[]): any[] {
437-
if (!routes) {
438-
return [];
439-
}
440-
return routes.reduce((m, r) => {
441-
if (r.loadChildren) {
442-
return m.concat(r.loadChildren);
443-
} else if (Array.isArray(r)) {
444-
return m.concat(this.collectLoadChildren(r));
445-
} else if (r.children) {
446-
return m.concat(this.collectLoadChildren(r.children));
447-
} else {
448-
return m;
449-
}
450-
}, []);
451325
}
452326
}

0 commit comments

Comments
 (0)