The "enableIvy" compiler option is the initial implementation of the Render3 (or Ivy) code generation. This commit enables generation generating "Hello, World" (example in the test) but not much else. It is currenly only useful for internal Ivy testing as Ivy is in development. PR Close #21427
212 lines
8.0 KiB
TypeScript
212 lines
8.0 KiB
TypeScript
/**
|
|
* @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
|
|
*/
|
|
|
|
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileMetadataResolver, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
|
import {ViewEncapsulation} from '@angular/core';
|
|
import * as ts from 'typescript';
|
|
|
|
import {ConstantPool} from '../../src/constant_pool';
|
|
import * as o from '../../src/output/output_ast';
|
|
import {compileComponent} from '../../src/render3/r3_view_compiler';
|
|
import {OutputContext} from '../../src/util';
|
|
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, settings, setup, toMockFileArray} from '../aot/test_util';
|
|
|
|
describe('r3_view_compiler', () => {
|
|
const angularFiles = setup({
|
|
compileAngular: true,
|
|
compileAnimations: false,
|
|
compileCommon: true,
|
|
});
|
|
|
|
describe('hello world', () => {
|
|
it('should be able to generate the hello world component', () => {
|
|
const files: MockDirectory = {
|
|
app: {
|
|
'hello.ts': `
|
|
import {Component, NgModule} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'hello-world',
|
|
template: 'Hello, world!'
|
|
})
|
|
export class HelloWorldComponent {
|
|
|
|
}
|
|
|
|
@NgModule({
|
|
declarations: [HelloWorldComponent]
|
|
})
|
|
export class HelloWorldModule {}
|
|
`
|
|
}
|
|
};
|
|
compile(files, angularFiles);
|
|
});
|
|
});
|
|
|
|
it('should be able to generate the example', () => {
|
|
const files: MockDirectory = {
|
|
app: {
|
|
'example.ts': `
|
|
import {Component, OnInit, OnDestroy, ElementRef, Input, NgModule} from '@angular/core';
|
|
import {CommonModule} from '@angular/common';
|
|
|
|
@Component({
|
|
selector: 'my-app',
|
|
template: '<todo [data]="list"></todo>'
|
|
})
|
|
export class MyApp implements OnInit {
|
|
|
|
list: any[] = [];
|
|
|
|
constructor(public elementRef: ElementRef) {}
|
|
|
|
ngOnInit(): void {
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
selector: 'todo',
|
|
template: '<ul class="list" [title]="myTitle"><li *ngFor="let item of data">{{data}}</li></ul>'
|
|
})
|
|
export class TodoComponent implements OnInit, OnDestroy {
|
|
|
|
@Input()
|
|
data: any[] = [];
|
|
|
|
myTitle: string;
|
|
|
|
constructor(public elementRef: ElementRef) {}
|
|
|
|
ngOnInit(): void {}
|
|
|
|
ngOnDestroy(): void {}
|
|
}
|
|
|
|
@NgModule({
|
|
declarations: [TodoComponent, MyApp],
|
|
imports: [CommonModule]
|
|
})
|
|
export class TodoModule{}
|
|
`
|
|
}
|
|
};
|
|
const result = compile(files, angularFiles);
|
|
expect(result.source).toContain('@angular/core');
|
|
});
|
|
});
|
|
|
|
function compile(
|
|
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
|
errorCollector: (error: any, fileName?: string) => void = error => { throw error; }) {
|
|
const testFiles = toMockFileArray(data);
|
|
const scripts = testFiles.map(entry => entry.fileName);
|
|
const angularFilesArray = toMockFileArray(angularFiles);
|
|
const files = arrayToMockDir([...testFiles, ...angularFilesArray]);
|
|
const mockCompilerHost = new MockCompilerHost(scripts, files);
|
|
const compilerHost = new MockAotCompilerHost(mockCompilerHost);
|
|
|
|
const program = ts.createProgram(scripts, {...settings}, mockCompilerHost);
|
|
|
|
// TODO(chuckj): Replace with a variant of createAotCompiler() when the r3_view_compiler is
|
|
// integrated
|
|
const translations = options.translations || '';
|
|
|
|
const urlResolver = createAotUrlResolver(compilerHost);
|
|
const symbolCache = new StaticSymbolCache();
|
|
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
|
|
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
|
|
const staticReflector =
|
|
new StaticReflector(summaryResolver, symbolResolver, [], [], errorCollector);
|
|
const htmlParser = new I18NHtmlParser(
|
|
new HtmlParser(), translations, options.i18nFormat, options.missingTranslation, console);
|
|
const config = new CompilerConfig({
|
|
defaultEncapsulation: ViewEncapsulation.Emulated,
|
|
useJit: false,
|
|
enableLegacyTemplate: options.enableLegacyTemplate === true,
|
|
missingTranslation: options.missingTranslation,
|
|
preserveWhitespaces: options.preserveWhitespaces,
|
|
strictInjectionParameters: options.strictInjectionParameters,
|
|
});
|
|
const normalizer = new DirectiveNormalizer(
|
|
{get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config);
|
|
const expressionParser = new Parser(new Lexer());
|
|
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
|
const templateParser = new TemplateParser(
|
|
config, staticReflector, expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
|
const resolver = new CompileMetadataResolver(
|
|
config, htmlParser, new NgModuleResolver(staticReflector),
|
|
new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver,
|
|
elementSchemaRegistry, normalizer, console, symbolCache, staticReflector, errorCollector);
|
|
|
|
|
|
|
|
// Create the TypeScript program
|
|
const sourceFiles = program.getSourceFiles().map(sf => sf.fileName);
|
|
|
|
// Analyze the modules
|
|
// TODO(chuckj): Eventually this should not be necessary as the ts.SourceFile should be sufficient
|
|
// to generate a template definition.
|
|
const analyzedModules = analyzeNgModules(sourceFiles, compilerHost, symbolResolver, resolver);
|
|
|
|
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
|
|
|
const fakeOuputContext: OutputContext = {
|
|
genFilePath: 'fakeFactory.ts',
|
|
statements: [],
|
|
importExpr(symbol: StaticSymbol, typeParams: o.Type[]) {
|
|
if (!(symbol instanceof StaticSymbol)) {
|
|
if (!symbol) {
|
|
throw new Error('Invalid: undefined passed to as a symbol');
|
|
}
|
|
throw new Error(`Invalid: ${(symbol as any).constructor.name} is not a symbol`);
|
|
}
|
|
return (symbol.members || [])
|
|
.reduce(
|
|
(expr, member) => expr.prop(member),
|
|
<o.Expression>o.importExpr(new o.ExternalReference(symbol.filePath, symbol.name)));
|
|
},
|
|
constantPool: new ConstantPool()
|
|
};
|
|
|
|
// Load All directives
|
|
for (const directive of directives) {
|
|
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
|
|
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
|
|
}
|
|
|
|
// Compile the directives.
|
|
for (const directive of directives) {
|
|
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
|
|
if (resolver.isDirective(directive)) {
|
|
const metadata = resolver.getDirectiveMetadata(directive);
|
|
if (metadata.isComponent) {
|
|
const fakeUrl = 'ng://fake-template-url.html';
|
|
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
|
|
|
|
const directives = module.transitiveModule.directives.map(
|
|
dir => resolver.getDirectiveSummary(dir.reference));
|
|
const pipes =
|
|
module.transitiveModule.pipes.map(pipe => resolver.getPipeSummary(pipe.reference));
|
|
const parsedTemplate = templateParser.parse(
|
|
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
|
|
|
compileComponent(fakeOuputContext, metadata, parsedTemplate.template, staticReflector);
|
|
}
|
|
}
|
|
}
|
|
|
|
fakeOuputContext.statements.unshift(...fakeOuputContext.constantPool.statements);
|
|
|
|
const emitter = new TypeScriptEmitter();
|
|
|
|
const result = emitter.emitStatementsAndContext(
|
|
fakeOuputContext.genFilePath, fakeOuputContext.statements, '', false);
|
|
|
|
return {source: result.sourceText, outputContext: fakeOuputContext};
|
|
} |