diff --git a/modules/@angular/compiler-cli/index.ts b/modules/@angular/compiler-cli/index.ts index 3418eed737..edcf54702e 100644 --- a/modules/@angular/compiler-cli/index.ts +++ b/modules/@angular/compiler-cli/index.ts @@ -11,3 +11,7 @@ export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeComp export {Extractor} from './src/extractor'; export * from '@angular/tsc-wrapped'; export {VERSION} from './src/version'; + + +// 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' diff --git a/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.css b/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.css new file mode 100644 index 0000000000..5cde7b9223 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.css @@ -0,0 +1,3 @@ +:host { + background-color: blue; +} diff --git a/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.html b/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.html new file mode 100644 index 0000000000..5a532db930 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.html @@ -0,0 +1,5 @@ +
diff --git a/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.ts b/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.ts new file mode 100644 index 0000000000..505501c47a --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.ts @@ -0,0 +1,10 @@ +import {Component, ViewEncapsulation} from '@angular/core'; + + +@Component({ + selector: 'app-root', + templateUrl: 'app.component.html', + styleUrls: ['app.component.css'], + encapsulation: ViewEncapsulation.None +}) +export class AppComponent { } diff --git a/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.module.ts b/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.module.ts new file mode 100644 index 0000000000..b4f15399ea --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.module.ts @@ -0,0 +1,28 @@ +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; +import { AppComponent } from './app.component'; + +@Component({ + selector: 'home-view', + template: 'home!' +}) +export class HomeView {} + + +@NgModule({ + declarations: [ + AppComponent, + HomeView + ], + imports: [ + BrowserModule, + RouterModule.forRoot([ + {path: 'lazy', loadChildren: './lazy.module#LazyModule'}, + {path: 'feature2', loadChildren: 'feature2/feature2.module#Feature2Module'}, + {path: '', component: HomeView} + ]) + ], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature/feature.module.ts b/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature/feature.module.ts new file mode 100644 index 0000000000..f464ca028b --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature/feature.module.ts @@ -0,0 +1,20 @@ +import {NgModule, Component} from '@angular/core'; +import {RouterModule} from '@angular/router'; + +@Component({ + selector: 'feature-component', + template: 'foo.html' +}) +export class FeatureComponent {} + +@NgModule({ + declarations: [ + FeatureComponent + ], + imports: [ + RouterModule.forChild([ + { path: '', component: FeatureComponent} + ]) + ] +}) +export class FeatureModule {} diff --git a/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature/lazy-feature.module.ts b/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature/lazy-feature.module.ts new file mode 100644 index 0000000000..40a80ed122 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature/lazy-feature.module.ts @@ -0,0 +1,20 @@ +import {NgModule, Component} from '@angular/core'; +import {RouterModule} from '@angular/router'; + +@Component({ + selector: 'lazy-feature-comp', + template: 'lazy feature!' +}) +export class LazyFeatureComponent {} + +@NgModule({ + imports: [ + RouterModule.forChild([ + {path: '', component: LazyFeatureComponent, pathMatch: 'full'}, + {path: 'feature', loadChildren: './feature.module#FeatureModule'} + ]) + ], + declarations: [LazyFeatureComponent] +}) +export class LazyFeatureModule { +} diff --git a/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature2/default.module.ts b/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature2/default.module.ts new file mode 100644 index 0000000000..cb60e490c5 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature2/default.module.ts @@ -0,0 +1,20 @@ +import {NgModule, Component} from '@angular/core'; +import {RouterModule} from '@angular/router'; + +@Component({ + selector: 'feature-component', + template: 'foo.html' +}) +export class FeatureComponent {} + +@NgModule({ + declarations: [ + FeatureComponent + ], + imports: [ + RouterModule.forChild([ + { path: '', component: FeatureComponent }, + ]) + ] +}) +export default class DefaultModule {} diff --git a/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature2/feature2.module.ts b/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature2/feature2.module.ts new file mode 100644 index 0000000000..f22d5ee857 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/ngtools_src/feature2/feature2.module.ts @@ -0,0 +1,22 @@ +import {NgModule, Component} from '@angular/core'; +import {RouterModule} from '@angular/router'; + +@Component({ + selector: 'feature-component', + template: 'foo.html' +}) +export class FeatureComponent {} + +@NgModule({ + declarations: [ + FeatureComponent + ], + imports: [ + RouterModule.forChild([ + { path: '', component: FeatureComponent }, + { path: 'd', loadChildren: './default.module' } + { path: 'e', loadChildren: 'feature/feature.module#FeatureModule' } + ]) + ] +}) +export class Feature2Module {} diff --git a/modules/@angular/compiler-cli/integrationtest/ngtools_src/lazy.module.ts b/modules/@angular/compiler-cli/integrationtest/ngtools_src/lazy.module.ts new file mode 100644 index 0000000000..803b4ad549 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/ngtools_src/lazy.module.ts @@ -0,0 +1,23 @@ +import {NgModule, Component} from '@angular/core'; +import {RouterModule} from '@angular/router'; + +@Component({ + selector: 'lazy-comp', + template: 'lazy!' +}) +export class LazyComponent {} + +@NgModule({ + imports: [ + RouterModule.forChild([ + {path: '', component: LazyComponent, pathMatch: 'full'}, + {path: 'feature', loadChildren: './feature/feature.module#FeatureModule'}, + {path: 'lazy-feature', loadChildren: './feature/lazy-feature.module#LazyFeatureModule'} + ]) + ], + declarations: [LazyComponent] +}) +export class LazyModule { +} + +export class SecondModule {} diff --git a/modules/@angular/compiler-cli/integrationtest/ngtools_src/tsconfig-build.json b/modules/@angular/compiler-cli/integrationtest/ngtools_src/tsconfig-build.json new file mode 100644 index 0000000000..f5a1f98c21 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/ngtools_src/tsconfig-build.json @@ -0,0 +1,19 @@ +{ + "angularCompilerOptions": { + // For TypeScript 1.8, we have to lay out generated files + // in the same source directory with your code. + "genDir": ".", + "debug": true + }, + + "compilerOptions": { + "target": "es5", + "experimentalDecorators": true, + "noImplicitAny": true, + "moduleResolution": "node", + "rootDir": "", + "declaration": true, + "lib": ["es6", "dom"], + "baseUrl": "." + } +} \ No newline at end of file diff --git a/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts b/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts new file mode 100644 index 0000000000..2dc94c166b --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts @@ -0,0 +1,162 @@ +#!/usr/bin/env node +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +/* tslint:disable:no-console */ + +// Must be imported first, because angular2 decorators throws on load. +import 'reflect-metadata'; + +import * as path from 'path'; +import * as ts from 'typescript'; +import * as assert from 'assert'; +import {tsc} from '@angular/tsc-wrapped/src/tsc'; +import { + AngularCompilerOptions, + CodeGenerator, + CompilerHostContext, + NodeCompilerHostContext, + __NGTOOLS_PRIVATE_API_2 +} from '@angular/compiler-cli'; + +const glob = require('glob'); + + +/** + * Main method. + * Standalone program that executes codegen using the ngtools API and tests that files were + * properly read and wrote. + */ +function main() { + console.log(`testing ngtools API...`); + + Promise.resolve() + .then(() => codeGenTest()) + .then(() => lazyRoutesTest()) + .then(() => { + console.log('All done!'); + process.exit(0); + }) + .catch((err) => { + console.error(err.stack); + console.error('Test failed'); + process.exit(1); + }); +} + + +function codeGenTest() { + const basePath = path.join(__dirname, '../ngtools_src'); + const project = path.join(basePath, 'tsconfig-build.json'); + const readResources: string[] = []; + const wroteFiles: string[] = []; + + const config = tsc.readConfiguration(project, basePath); + const hostContext = new NodeCompilerHostContext(); + const delegateHost = ts.createCompilerHost(config.parsed.options, true); + const host: ts.CompilerHost = Object.assign({}, delegateHost, { + writeFile: (fileName: string, ...rest: any[]) => { + wroteFiles.push(fileName); + return delegateHost.writeFile.call(delegateHost, fileName, ...rest); + } + }); + const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host); + + config.ngOptions.basePath = basePath; + + console.log(`>>> running codegen for ${project}`); + return __NGTOOLS_PRIVATE_API_2.codeGen({ + basePath, + compilerOptions: config.parsed.options, + program, + host, + + angularCompilerOptions: config.ngOptions, + + // i18n options. + i18nFormat: null, + i18nFile: null, + locale: null, + + readResource: (fileName: string) => { + readResources.push(fileName); + return hostContext.readResource(fileName); + } + }) + .then(() => { + console.log(`>>> codegen done, asserting read and wrote files`); + + // Assert for each file that it has been read and each `ts` has a written file associated. + const allFiles = glob.sync(path.join(basePath, '**/*'), { nodir: true }); + + allFiles.forEach((fileName: string) => { + // Skip tsconfig. + if (fileName.match(/tsconfig-build.json$/)) { + return; + } + + // Assert that file was read. + if (fileName.match(/\.module\.ts$/)) { + const factory = fileName.replace(/\.module\.ts$/, '.module.ngfactory.ts'); + assert(wroteFiles.indexOf(factory) != -1, `Expected file "${factory}" to be written.`); + } else if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) { + assert(readResources.indexOf(fileName) != -1, `Expected resource "${fileName}" to be read.`); + } + }); + + console.log(`done, no errors.`); + }) + .catch((e: any) => { + console.error(e.stack); + console.error('Compilation failed'); + throw e; + }); +} + + +function lazyRoutesTest() { + const basePath = path.join(__dirname, '../ngtools_src'); + const project = path.join(basePath, 'tsconfig-build.json'); + + const config = tsc.readConfiguration(project, basePath); + const host = ts.createCompilerHost(config.parsed.options, true); + const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host); + + config.ngOptions.basePath = basePath; + + const lazyRoutes = __NGTOOLS_PRIVATE_API_2.listLazyRoutes({ + program, + host, + angularCompilerOptions: config.ngOptions, + entryModule: 'app.module#AppModule' + }); + + const expectations: {[route: string]: string} = { + './lazy.module#LazyModule': 'lazy.module.ts', + './feature/feature.module#FeatureModule': 'feature/feature.module.ts', + './feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts', + 'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts', + './default.module': 'feature2/default.module.ts', + 'feature/feature.module#FeatureModule': 'feature/feature.module.ts' + }; + + Object.keys(lazyRoutes).forEach((route: string) => { + assert(route in expectations, `Found a route that was not expected: "${route}".`); + assert(lazyRoutes[route] == path.join(basePath, expectations[route]), + `Route "${route}" does not point to the expected absolute path ` + + `"${path.join(basePath, expectations[route])}". It points to "${lazyRoutes[route]}"`); + }); + + // Verify that all expectations were met. + assert.deepEqual(Object.keys(lazyRoutes), Object.keys(expectations), + `Expected routes listed to be: \n` + + ` ${JSON.stringify(Object.keys(expectations))}\n` + + `Actual:\n` + + ` ${JSON.stringify(Object.keys(lazyRoutes))}\n`); +} + +main(); diff --git a/modules/@angular/compiler-cli/integrationtest/tsconfig-build.json b/modules/@angular/compiler-cli/integrationtest/tsconfig-build.json index 260f3090b3..c1125b2044 100644 --- a/modules/@angular/compiler-cli/integrationtest/tsconfig-build.json +++ b/modules/@angular/compiler-cli/integrationtest/tsconfig-build.json @@ -21,6 +21,7 @@ "src/module", "src/bootstrap", "test/all_spec", + "test/test_ngtools_api", "test/test_summaries", "benchmarks/src/tree/ng2/index_aot.ts", "benchmarks/src/tree/ng2_switch/index_aot.ts", diff --git a/modules/@angular/compiler-cli/src/ngtools_api.ts b/modules/@angular/compiler-cli/src/ngtools_api.ts new file mode 100644 index 0000000000..4e7730c9b7 --- /dev/null +++ b/modules/@angular/compiler-cli/src/ngtools_api.ts @@ -0,0 +1,124 @@ +/** + * This is a private API for the ngtools toolkit. + * + * This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by + * something else. + */ + +import * as ts from 'typescript'; +import {StaticReflector, AotCompilerHost} from '@angular/compiler'; +import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped'; + +import {CodeGenerator} from './codegen'; +import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host'; +import {listLazyRoutesOfModule} from './ngtools_impl'; +import {PathMappedCompilerHost} from './path_mapped_compiler_host'; + + +export interface NgTools_InternalApi_NG2_CodeGen_Options { + basePath: string; + compilerOptions: ts.CompilerOptions; + program: ts.Program; + host: ts.CompilerHost; + + angularCompilerOptions: AngularCompilerOptions; + + // i18n options. + i18nFormat: string; + i18nFile: string; + locale: string; + + readResource: (fileName: string) => Promise