feat(ivy): first steps towards JIT compilation (#23833)

This commit adds a mechanism by which the @angular/core annotations
for @Component, @Injectable, and @NgModule become decorators which,
when executed at runtime, trigger just-in-time compilation of their
associated types. The activation of these decorators is configured
by the ivy_switch mechanism, ensuring that the Ivy JIT engine does
not get included in Angular bundles unless specifically requested.

PR Close #23833
This commit is contained in:
Alex Rickabaugh
2018-05-09 08:35:25 -07:00
committed by Matias Niemelä
parent 1b6b936ef4
commit 919f42fea1
37 changed files with 1248 additions and 156 deletions

View File

@ -16,6 +16,7 @@ ng_module(
module_name = "@angular/core",
deps = [
"//packages:types",
"//packages/compiler",
"@rxjs",
],
)

View File

@ -10,8 +10,9 @@ const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/compiler': 'ng.compiler',
'rxjs': 'rxjs',
'rxjs/operators': 'rxjs.operators'
'rxjs/operators': 'rxjs.operators',
};
module.exports = {

View File

@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ApplicationInitStatus} from './application_init';
import {APP_INITIALIZER, ApplicationInitStatus} from './application_init';
import {ApplicationRef} from './application_ref';
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
import {forwardRef} from './di/forward_ref';
import {Inject, Optional, SkipSelf} from './di/metadata';
import {LOCALE_ID} from './i18n/tokens';
import {Compiler} from './linker/compiler';
@ -46,7 +47,7 @@ export function _localeFactory(locale?: string): string {
useFactory: _localeFactory,
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
},
]
],
})
export class ApplicationModule {
// Inject ApplicationRef to make it eager...

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {R3_COMPILE_INJECTABLE} from '../ivy_switch';
import {ReflectionCapabilities} from '../reflection/reflection_capabilities';
import {Type} from '../type';
import {makeDecorator, makeParamDecorator} from '../util/decorators';
@ -67,10 +68,7 @@ export interface InjectableDecorator {
*
* @experimental
*/
export interface Injectable {
providedIn?: Type<any>|'root'|null;
factory: () => any;
}
export interface Injectable { providedIn?: Type<any>|'root'|null; }
const EMPTY_ARRAY: any[] = [];
@ -110,6 +108,20 @@ export function convertInjectableProviderToFactory(
}
}
/**
* Supports @Injectable() in JIT mode for Render2.
*/
function preR3InjectableCompile(
injectableType: InjectableType<any>,
options: {providedIn?: Type<any>| 'root' | null} & InjectableProvider): void {
if (options && options.providedIn !== undefined && injectableType.ngInjectableDef === undefined) {
injectableType.ngInjectableDef = defineInjectable({
providedIn: options.providedIn,
factory: convertInjectableProviderToFactory(injectableType, options),
});
}
}
/**
* Injectable decorator and metadata.
*
@ -118,16 +130,8 @@ export function convertInjectableProviderToFactory(
*/
export const Injectable: InjectableDecorator = makeDecorator(
'Injectable', undefined, undefined, undefined,
(injectableType: InjectableType<any>,
options: {providedIn?: Type<any>| 'root' | null} & InjectableProvider) => {
if (options && options.providedIn !== undefined &&
injectableType.ngInjectableDef === undefined) {
injectableType.ngInjectableDef = defineInjectable({
providedIn: options.providedIn,
factory: convertInjectableProviderToFactory(injectableType, options)
});
}
});
(type: Type<any>, meta: Injectable) =>
(R3_COMPILE_INJECTABLE || preR3InjectableCompile)(type, meta));
/**
* Type representing injectable service.

View File

@ -32,3 +32,5 @@
* symbol in `./ivy_switch_false` and `./ivy_switch_false` depending on the compilation mode.
*/
export * from './ivy_switch_false';
// TODO(alxhub): debug why metadata doesn't properly propagate through this file.

View File

@ -6,4 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
export const ivyEnabled = false;
export const ivyEnabled = false;
export const R3_COMPILE_COMPONENT: ((type: any, meta: any) => void)|null = null;
export const R3_COMPILE_INJECTABLE: ((type: any, meta: any) => void)|null = null;
export const R3_COMPILE_NGMODULE: ((type: any, meta: any) => void)|null = null;

View File

@ -6,4 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
export const ivyEnabled = true;
import {compileComponentDecorator} from './render3/jit/directive';
import {compileInjectable} from './render3/jit/injectable';
import {compileNgModule} from './render3/jit/module';
export const ivyEnabled = true;
export const R3_COMPILE_COMPONENT = compileComponentDecorator;
export const R3_COMPILE_INJECTABLE = compileInjectable;
export const R3_COMPILE_NGMODULE = compileNgModule;

View File

@ -13,7 +13,7 @@
import {Attribute, ContentChild, ContentChildren, Query, ViewChild, ViewChildren} from './metadata/di';
import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
import {ModuleWithProviders, NgModule, SchemaMetadata} from './metadata/ng_module';
import {ModuleWithProviders, NgModule, NgModuleDef, SchemaMetadata, defineNgModule} from './metadata/ng_module';
import {ViewEncapsulation} from './metadata/view';
export {ANALYZE_FOR_ENTRY_COMPONENTS, Attribute, ContentChild, ContentChildDecorator, ContentChildren, ContentChildrenDecorator, Query, ViewChild, ViewChildDecorator, ViewChildren, ViewChildrenDecorator} from './metadata/di';

View File

@ -8,9 +8,9 @@
import {ChangeDetectionStrategy} from '../change_detection/constants';
import {Provider} from '../di';
import {R3_COMPILE_COMPONENT} from '../ivy_switch';
import {Type} from '../type';
import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators';
import {ViewEncapsulation} from './view';
@ -754,7 +754,8 @@ export interface Component extends Directive {
*/
export const Component: ComponentDecorator = makeDecorator(
'Component', (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}),
Directive);
Directive, undefined,
(type: Type<any>, meta: Component) => (R3_COMPILE_COMPONENT || (() => {}))(type, meta));
/**
* Type of the Pipe decorator / constructor function.

View File

@ -9,9 +9,31 @@
import {InjectorDef, InjectorType, defineInjector} from '../di/defs';
import {convertInjectableProviderToFactory} from '../di/injectable';
import {Provider} from '../di/provider';
import {R3_COMPILE_NGMODULE} from '../ivy_switch';
import {Type} from '../type';
import {TypeDecorator, makeDecorator} from '../util/decorators';
export interface NgModuleDef<T> {
type: T;
bootstrap: Type<any>[];
declarations: Type<any>[];
imports: Type<any>[];
exports: Type<any>[];
transitiveCompileScope: {directives: any[]; pipes: any[];}|undefined;
}
export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): never {
const res: NgModuleDef<T> = {
type: def.type,
bootstrap: def.bootstrap || [],
declarations: def.declarations || [],
imports: def.imports || [],
exports: def.exports || [],
transitiveCompileScope: undefined,
};
return res as never;
}
/**
* A wrapper around a module that also includes the providers.
@ -187,6 +209,19 @@ export interface NgModule {
id?: string;
}
function preR3NgModuleCompile(moduleType: InjectorType<any>, metadata: NgModule): void {
let imports = (metadata && metadata.imports) || [];
if (metadata && metadata.exports) {
imports = [...imports, metadata.exports];
}
moduleType.ngInjectorDef = defineInjector({
factory: convertInjectableProviderToFactory(moduleType, {useClass: moduleType}),
providers: metadata && metadata.providers,
imports: imports,
});
}
/**
* NgModule decorator and metadata.
*
@ -195,15 +230,4 @@ export interface NgModule {
*/
export const NgModule: NgModuleDecorator = makeDecorator(
'NgModule', (ngModule: NgModule) => ngModule, undefined, undefined,
(moduleType: InjectorType<any>, metadata: NgModule) => {
let imports = (metadata && metadata.imports) || [];
if (metadata && metadata.exports) {
imports = [...imports, metadata.exports];
}
moduleType.ngInjectorDef = defineInjector({
factory: convertInjectableProviderToFactory(moduleType, {useClass: moduleType}),
providers: metadata && metadata.providers,
imports: imports,
});
});
(type: Type<any>, meta: NgModule) => (R3_COMPILE_NGMODULE || preR3NgModuleCompile)(type, meta));

View File

@ -0,0 +1,97 @@
/**
* @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 {compileComponent as compileIvyComponent, parseTemplate, ConstantPool, makeBindingParser, WrappedNodeExpr, jitPatchDefinition,} from '@angular/compiler';
import {Component} from '../../metadata/directives';
import {ReflectionCapabilities} from '../../reflection/reflection_capabilities';
import {Type} from '../../type';
import {angularCoreEnv} from './environment';
import {reflectDependencies} from './util';
let _pendingPromises: Promise<void>[] = [];
/**
* Compile an Angular component according to its decorator metadata, and patch the resulting
* ngComponentDef onto the component type.
*
* Compilation may be asynchronous (due to the need to resolve URLs for the component template or
* other resources, for example). In the event that compilation is not immediate, `compileComponent`
* will return a `Promise` which will resolve when compilation completes and the component becomes
* usable.
*/
export function compileComponent(type: Type<any>, metadata: Component): Promise<void>|null {
// TODO(alxhub): implement ResourceLoader support for template compilation.
if (!metadata.template) {
throw new Error('templateUrl not yet supported');
}
// Parse the template and check for errors.
const template = parseTemplate(metadata.template !, `ng://${type.name}/template.html`);
if (template.errors !== undefined) {
const errors = template.errors.map(err => err.toString()).join(', ');
throw new Error(`Errors during JIT compilation of template for ${type.name}: ${errors}`);
}
// The ConstantPool is a requirement of the JIT'er.
const constantPool = new ConstantPool();
// Compile the component metadata, including template, into an expression.
// TODO(alxhub): implement inputs, outputs, queries, etc.
const res = compileIvyComponent(
{
name: type.name,
type: new WrappedNodeExpr(type),
selector: metadata.selector !, template,
deps: reflectDependencies(type),
directives: new Map(),
pipes: new Map(),
host: {
attributes: {},
listeners: {},
properties: {},
},
inputs: {},
outputs: {},
lifecycle: {
usesOnChanges: false,
},
queries: [],
typeSourceSpan: null !,
viewQueries: [],
},
constantPool, makeBindingParser());
// Patch the generated expression as ngComponentDef on the type.
jitPatchDefinition(type, 'ngComponentDef', res.expression, angularCoreEnv, constantPool);
return null;
}
/**
* A wrapper around `compileComponent` which is intended to be used for the `@Component` decorator.
*
* This wrapper keeps track of the `Promise` returned by `compileComponent` and will cause
* `awaitCurrentlyCompilingComponents` to wait on the compilation to be finished.
*/
export function compileComponentDecorator(type: Type<any>, metadata: Component): void {
const res = compileComponent(type, metadata);
if (res !== null) {
_pendingPromises.push(res);
}
}
/**
* Returns a promise which will await the compilation of any `@Component`s which have been defined
* since the last time `awaitCurrentlyCompilingComponents` was called.
*/
export function awaitCurrentlyCompilingComponents(): Promise<void> {
const res = Promise.all(_pendingPromises).then(() => undefined);
_pendingPromises = [];
return res;
}

View File

@ -0,0 +1,39 @@
/**
* @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 {defineInjectable} from '../../di/defs';
import {inject} from '../../di/injector';
import {defineNgModule} from '../../metadata/ng_module';
import * as r3 from '../index';
/**
* A mapping of the @angular/core API surface used in generated expressions to the actual symbols.
*
* This should be kept up to date with the public exports of @angular/core.
*/
export const angularCoreEnv = {
'ɵdefineComponent': r3.defineComponent,
'defineInjectable': defineInjectable,
'ɵdefineNgModule': defineNgModule,
'ɵdirectiveInject': r3.directiveInject,
'inject': inject,
'ɵC': r3.C,
'ɵE': r3.E,
'ɵe': r3.e,
'ɵi1': r3.i1,
'ɵi2': r3.i2,
'ɵi3': r3.i3,
'ɵi4': r3.i4,
'ɵi5': r3.i5,
'ɵi6': r3.i6,
'ɵi7': r3.i7,
'ɵi8': r3.i8,
'ɵT': r3.T,
'ɵt': r3.t,
};

View File

@ -0,0 +1,114 @@
/**
* @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 {Expression, LiteralExpr, R3DependencyMetadata, WrappedNodeExpr, compileInjectable as compileIvyInjectable, jitPatchDefinition} from '@angular/compiler';
import {Injectable} from '../../di/injectable';
import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from '../../di/provider';
import {Type} from '../../type';
import {getClosureSafeProperty} from '../../util/property';
import {angularCoreEnv} from './environment';
import {convertDependencies, reflectDependencies} from './util';
/**
* Compile an Angular injectable according to its `Injectable` metadata, and patch the resulting
* `ngInjectableDef` onto the injectable type.
*/
export function compileInjectable(type: Type<any>, meta?: Injectable): void {
// TODO(alxhub): handle JIT of bare @Injectable().
if (!meta) {
return;
}
// Check whether the injectable metadata includes a provider specification.
const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) ||
isUseValueProvider(meta) || isUseExistingProvider(meta);
let deps: R3DependencyMetadata[]|undefined = undefined;
if (!hasAProvider || (isUseClassProvider(meta) && type === meta.useClass)) {
deps = reflectDependencies(type);
} else if (isUseClassProvider(meta)) {
deps = meta.deps && convertDependencies(meta.deps);
} else if (isUseFactoryProvider(meta)) {
deps = meta.deps && convertDependencies(meta.deps) || [];
}
// Decide which flavor of factory to generate, based on the provider specified.
// Only one of the use* fields should be set.
let useClass: Expression|undefined = undefined;
let useFactory: Expression|undefined = undefined;
let useValue: Expression|undefined = undefined;
let useExisting: Expression|undefined = undefined;
if (!hasAProvider) {
// In the case the user specifies a type provider, treat it as {provide: X, useClass: X}.
// The deps will have been reflected above, causing the factory to create the class by calling
// its constructor with injected deps.
useClass = new WrappedNodeExpr(type);
} else if (isUseClassProvider(meta)) {
// The user explicitly specified useClass, and may or may not have provided deps.
useClass = new WrappedNodeExpr(meta.useClass);
} else if (isUseValueProvider(meta)) {
// The user explicitly specified useValue.
useValue = new WrappedNodeExpr(meta.useValue);
} else if (isUseFactoryProvider(meta)) {
// The user explicitly specified useFactory.
useFactory = new WrappedNodeExpr(meta.useFactory);
} else if (isUseExistingProvider(meta)) {
// The user explicitly specified useExisting.
useExisting = new WrappedNodeExpr(meta.useExisting);
} else {
// Can't happen - either hasAProvider will be false, or one of the providers will be set.
throw new Error(`Unreachable state.`);
}
const {expression} = compileIvyInjectable({
name: type.name,
type: new WrappedNodeExpr(type),
providedIn: computeProvidedIn(meta.providedIn),
useClass,
useFactory,
useValue,
useExisting,
deps,
});
jitPatchDefinition(type, 'ngInjectableDef', expression, angularCoreEnv);
}
function computeProvidedIn(providedIn: Type<any>| string | null | undefined): Expression {
if (providedIn == null || typeof providedIn === 'string') {
return new LiteralExpr(providedIn);
} else {
return new WrappedNodeExpr(providedIn);
}
}
type UseClassProvider = Injectable & ClassSansProvider & {deps?: any[]};
function isUseClassProvider(meta: Injectable): meta is UseClassProvider {
return (meta as UseClassProvider).useClass !== undefined;
}
const GET_PROPERTY_NAME = {} as any;
const USE_VALUE = getClosureSafeProperty<ValueProvider>(
{provide: String, useValue: GET_PROPERTY_NAME}, GET_PROPERTY_NAME);
function isUseValueProvider(meta: Injectable): meta is Injectable&ValueSansProvider {
return USE_VALUE in meta;
}
function isUseFactoryProvider(meta: Injectable): meta is Injectable&FactorySansProvider {
return (meta as FactorySansProvider).useFactory !== undefined;
}
function isUseExistingProvider(meta: Injectable): meta is Injectable&ExistingSansProvider {
return (meta as ExistingSansProvider).useExisting !== undefined;
}

View File

@ -0,0 +1,94 @@
/**
* @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 {Expression, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule as compileIvyNgModule, jitPatchDefinition} from '@angular/compiler';
import {ModuleWithProviders, NgModule, NgModuleDef} from '../../metadata/ng_module';
import {Type} from '../../type';
import {ComponentDef} from '../interfaces/definition';
import {flatten} from '../util';
import {angularCoreEnv} from './environment';
const EMPTY_ARRAY: Type<any>[] = [];
export function compileNgModule(type: Type<any>, ngModule: NgModule): void {
const meta: R3NgModuleMetadata = {
type: wrap(type),
bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(wrap),
declarations: flatten(ngModule.declarations || EMPTY_ARRAY).map(wrap),
imports: flatten(ngModule.imports || EMPTY_ARRAY).map(expandModuleWithProviders).map(wrap),
exports: flatten(ngModule.exports || EMPTY_ARRAY).map(expandModuleWithProviders).map(wrap),
emitInline: true,
};
const res = compileIvyNgModule(meta);
// Compute transitiveCompileScope
const transitiveCompileScope = {
directives: [] as any[],
pipes: [] as any[],
};
flatten(ngModule.declarations || EMPTY_ARRAY).forEach(decl => {
if (decl.ngPipeDef) {
transitiveCompileScope.pipes.push(decl);
} else if (decl.ngComponentDef) {
transitiveCompileScope.directives.push(decl);
patchComponentWithScope(decl, type as any);
} else {
transitiveCompileScope.directives.push(decl);
decl.ngSelectorScope = type;
}
});
function addExportsFrom(module: Type<any>& {ngModuleDef: NgModuleDef<any>}): void {
module.ngModuleDef.exports.forEach((exp: any) => {
if (isNgModule(exp)) {
addExportsFrom(exp);
} else if (exp.ngPipeDef) {
transitiveCompileScope.pipes.push(exp);
} else {
transitiveCompileScope.directives.push(exp);
}
});
}
flatten([(ngModule.imports || EMPTY_ARRAY), (ngModule.exports || EMPTY_ARRAY)])
.filter(importExport => isNgModule(importExport))
.forEach(mod => addExportsFrom(mod));
jitPatchDefinition(type, 'ngModuleDef', res.expression, angularCoreEnv);
((type as any).ngModuleDef as NgModuleDef<any>).transitiveCompileScope = transitiveCompileScope;
}
export function patchComponentWithScope<C, M>(
component: Type<C>& {ngComponentDef: ComponentDef<C>},
module: Type<M>& {ngModuleDef: NgModuleDef<M>}) {
component.ngComponentDef.directiveDefs = () =>
module.ngModuleDef.transitiveCompileScope !.directives.map(
dir => dir.ngDirectiveDef || dir.ngComponentDef);
component.ngComponentDef.pipeDefs = () =>
module.ngModuleDef.transitiveCompileScope !.pipes.map(pipe => pipe.ngPipeDef);
}
function expandModuleWithProviders(value: Type<any>| ModuleWithProviders): Type<any> {
if (isModuleWithProviders(value)) {
return value.ngModule;
}
return value;
}
function wrap(value: Type<any>): Expression {
return new WrappedNodeExpr(value);
}
function isModuleWithProviders(value: any): value is ModuleWithProviders {
return value.ngModule !== undefined;
}
function isNgModule(value: any): value is Type<any>&{ngModuleDef: NgModuleDef<any>} {
return value.ngModuleDef !== undefined;
}

View File

@ -0,0 +1,86 @@
/**
* @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 {LiteralExpr, R3DependencyMetadata, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler';
import {Injector} from '../../di/injector';
import {Host, Inject, Optional, Self, SkipSelf} from '../../di/metadata';
import {ElementRef} from '../../linker/element_ref';
import {TemplateRef} from '../../linker/template_ref';
import {ViewContainerRef} from '../../linker/view_container_ref';
import {Attribute} from '../../metadata/di';
import {ReflectionCapabilities} from '../../reflection/reflection_capabilities';
import {Type} from '../../type';
let _reflect: ReflectionCapabilities|null = null;
export function reflectDependencies(type: Type<any>): R3DependencyMetadata[] {
_reflect = _reflect || new ReflectionCapabilities();
return convertDependencies(_reflect.parameters(type));
}
export function convertDependencies(deps: any[]): R3DependencyMetadata[] {
return deps.map(dep => reflectDependency(dep));
}
function reflectDependency(dep: any | any[]): R3DependencyMetadata {
const meta: R3DependencyMetadata = {
token: new LiteralExpr(null),
host: false,
optional: false,
resolved: R3ResolvedDependencyType.Token,
self: false,
skipSelf: false,
};
function setTokenAndResolvedType(token: any): void {
if (token === ElementRef) {
meta.resolved = R3ResolvedDependencyType.ElementRef;
} else if (token === Injector) {
meta.resolved = R3ResolvedDependencyType.Injector;
} else if (token === TemplateRef) {
meta.resolved = R3ResolvedDependencyType.TemplateRef;
} else if (token === ViewContainerRef) {
meta.resolved = R3ResolvedDependencyType.ViewContainerRef;
} else {
meta.resolved = R3ResolvedDependencyType.Token;
}
meta.token = new WrappedNodeExpr(token);
}
if (Array.isArray(dep)) {
if (dep.length === 0) {
throw new Error('Dependency array must have arguments.');
}
for (let j = 0; j < dep.length; j++) {
const param = dep[j];
if (param instanceof Optional || param.__proto__.ngMetadataName === 'Optional') {
meta.optional = true;
} else if (param instanceof SkipSelf || param.__proto__.ngMetadataName === 'SkipSelf') {
meta.skipSelf = true;
} else if (param instanceof Self || param.__proto__.ngMetadataName === 'Self') {
meta.self = true;
} else if (param instanceof Host || param.__proto__.ngMetadataName === 'Host') {
meta.host = true;
} else if (param instanceof Inject) {
meta.token = new WrappedNodeExpr(param.token);
} else if (param instanceof Attribute) {
if (param.attributeName === undefined) {
throw new Error(`Attribute name must be defined.`);
}
meta.token = new LiteralExpr(param.attributeName);
meta.resolved = R3ResolvedDependencyType.Attribute;
} else {
setTokenAndResolvedType(param);
}
}
} else {
setTokenAndResolvedType(dep);
}
return meta;
}

View File

@ -0,0 +1,58 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library", "ivy_ng_module")
load("//tools/symbol-extractor:index.bzl", "js_expected_symbol_test")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test", "rollup_bundle")
load("//tools/http-server:http_server.bzl", "http_server")
ts_library(
name = "hello_world_jit",
srcs = ["index.ts"],
deps = [
"//packages/core",
],
)
rollup_bundle(
name = "bundle",
# TODO(alexeagle): This is inconsistent.
# We try to teach users to always have their workspace at the start of a
# path, to disambiguate from other workspaces.
# Here, the rule implementation is looking in an execroot where the layout
# has an "external" directory for external dependencies.
# This should probably start with "angular/" and let the rule deal with it.
entry_point = "packages/core/test/bundling/hello_world_jit/index.js",
deps = [
":hello_world_jit",
"//packages/core",
],
)
ts_library(
name = "test_lib",
testonly = 1,
srcs = glob(["*_spec.ts"]),
deps = [
"//packages:types",
"//packages/core",
"//packages/core/testing",
],
)
jasmine_node_test(
name = "test",
data = [
":bundle",
":bundle.js",
],
deps = [":test_lib"],
)
http_server(
name = "devserver",
data = [
"index.html",
":bundle.min.js",
":bundle.min_debug.js",
],
)

View File

@ -0,0 +1,31 @@
<!doctype html>
<html>
<head>
<title>Angular Hello World Example</title>
</head>
<body>
<!-- The Angular application will be bootstrapped into this element. -->
<hello-world></hello-world>
<!--
Script tag which bootstraps the application. Use `?debug` in URL to select
the debug version of the script.
There are two scripts sources: `bundle.min.js` and `bundle.min_debug.js` You can
switch between which bundle the browser loads to experiment with the application.
- `bundle.min.js`: Is what the site would serve to their users. It has gone
through rollup, build-optimizer, and uglify with tree shaking.
- `bundle.min_debug.js`: Is what the developer would like to see when debugging
the application. It has also done through full pipeline of rollup, build-optimizer,
and uglify, however special flags were passed to uglify to prevent inlining and
property renaming.
-->
<script>
document.write('<script src="' +
(document.location.search.endsWith('debug') ? '/bundle.min_debug.js' : '/bundle.min.js') +
'"></' + 'script>');
</script>
</body>
</html>

View File

@ -0,0 +1,38 @@
/**
* @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 'reflect-metadata';
import {Component, NgModule, ɵrenderComponent as renderComponent} from '@angular/core';
@Component({
selector: 'greeting-cmp',
template: 'Hello World!',
})
export class Greeting {
}
@NgModule({
declarations: [Greeting],
exports: [Greeting],
})
export class GreetingModule {
}
@Component({selector: 'hello-world', template: '<greeting-cmp></greeting-cmp>'})
export class HelloWorld {
}
@NgModule({
declarations: [HelloWorld],
imports: [GreetingModule],
})
export class HelloWorldModule {
}
renderComponent(HelloWorld);

View File

@ -0,0 +1,23 @@
/**
* @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 {ɵivyEnabled as ivyEnabled} from '@angular/core';
import {withBody} from '@angular/core/testing';
import * as fs from 'fs';
import * as path from 'path';
const PACKAGE = 'angular/packages/core/test/bundling/hello_world_jit';
ivyEnabled && describe('Ivy JIT hello world', () => {
it('should render hello world', withBody('<hello-world></hello-world>', () => {
require(path.join(PACKAGE, 'bundle.js'));
expect(document.body.textContent).toEqual('Hello World!');
}));
});
xit('ensure at least one spec exists', () => {});

View File

@ -1325,6 +1325,9 @@
{
"name": "Quote"
},
{
"name": "R3_COMPILE_INJECTABLE"
},
{
"name": "REMOVE_EVENT_LISTENER"
},
@ -3620,6 +3623,9 @@
{
"name": "platformCoreDynamic"
},
{
"name": "preR3InjectableCompile"
},
{
"name": "preparseElement"
},

View File

@ -12,6 +12,7 @@ ts_library(
"**/*_perf.ts",
"domino.d.ts",
"load_domino.ts",
"jit_spec.ts",
],
),
deps = [
@ -29,19 +30,28 @@ ts_library(
)
ts_library(
name = "render3_node_lib",
name = "domino",
testonly = 1,
srcs = [
"domino.d.ts",
"load_domino.ts",
],
deps = [
":render3_lib",
"//packages/platform-browser",
"//packages/platform-server",
],
)
ts_library(
name = "render3_node_lib",
testonly = 1,
srcs = [],
deps = [
":domino",
":render3_lib",
],
)
jasmine_node_test(
name = "render3",
bootstrap = [

View File

@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library", "ts_web_test")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
ts_library(
name = "ivy_lib",
testonly = 1,
srcs = glob(["**/*.ts"]),
deps = [
"//packages:types",
"//packages/core",
],
)
ts_library(
name = "ivy_node_lib",
testonly = 1,
srcs = [],
deps = [
":ivy_lib",
"//packages/core/test/render3:domino",
],
)
jasmine_node_test(
name = "ivy",
bootstrap = [
"angular/packages/core/test/render3/load_domino",
],
deps = [
":ivy_node_lib",
],
)
ts_web_test(
name = "ivy_web",
deps = [
":ivy_lib",
],
)

View File

@ -0,0 +1,166 @@
/**
* @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 {Injectable} from '@angular/core/src/di/injectable';
import {inject, setCurrentInjector} from '@angular/core/src/di/injector';
import {ivyEnabled} from '@angular/core/src/ivy_switch';
import {Component} from '@angular/core/src/metadata/directives';
import {NgModule, NgModuleDef} from '@angular/core/src/metadata/ng_module';
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
ivyEnabled && describe('render3 jit', () => {
let injector: any;
beforeAll(() => { injector = setCurrentInjector(null); });
afterAll(() => { setCurrentInjector(injector); });
it('compiles a component', () => {
@Component({
template: 'test',
selector: 'test-cmp',
})
class SomeCmp {
}
const SomeCmpAny = SomeCmp as any;
expect(SomeCmpAny.ngComponentDef).toBeDefined();
expect(SomeCmpAny.ngComponentDef.factory() instanceof SomeCmp).toBe(true);
});
it('compiles an injectable with a type provider', () => {
@Injectable({providedIn: 'root'})
class Service {
}
const ServiceAny = Service as any;
expect(ServiceAny.ngInjectableDef).toBeDefined();
expect(ServiceAny.ngInjectableDef.providedIn).toBe('root');
expect(inject(Service) instanceof Service).toBe(true);
});
it('compiles an injectable with a useValue provider', () => {
@Injectable({providedIn: 'root', useValue: 'test'})
class Service {
}
expect(inject(Service)).toBe('test');
});
it('compiles an injectable with a useExisting provider', () => {
@Injectable({providedIn: 'root', useValue: 'test'})
class Existing {
}
@Injectable({providedIn: 'root', useExisting: Existing})
class Service {
}
expect(inject(Service)).toBe('test');
});
it('compiles an injectable with a useFactory provider, without deps', () => {
@Injectable({providedIn: 'root', useFactory: () => 'test'})
class Service {
}
expect(inject(Service)).toBe('test');
});
it('compiles an injectable with a useFactory provider, with deps', () => {
@Injectable({providedIn: 'root', useValue: 'test'})
class Existing {
}
@Injectable({providedIn: 'root', useFactory: (existing: any) => existing, deps: [Existing]})
class Service {
}
expect(inject(Service)).toBe('test');
});
it('compiles an injectable with a useClass provider, with deps', () => {
@Injectable({providedIn: 'root', useValue: 'test'})
class Existing {
}
class Other {
constructor(public value: any) {}
}
@Injectable({providedIn: 'root', useClass: Other, deps: [Existing]})
class Service {
get value(): any { return null; }
}
const ServiceAny = Service as any;
expect(inject(Service).value).toBe('test');
});
it('compiles an injectable with a useClass provider, without deps', () => {
let _value = 1;
@Injectable({providedIn: 'root'})
class Existing {
readonly value = _value++;
}
@Injectable({providedIn: 'root', useClass: Existing})
class Service {
get value(): number { return 0; }
}
expect(inject(Existing).value).toBe(1);
const injected = inject(Service);
expect(injected instanceof Existing).toBe(true);
expect(injected.value).toBe(2);
});
it('compiles a module to a definition', () => {
@Component({
template: 'foo',
selector: 'foo',
})
class Cmp {
}
@NgModule({
declarations: [Cmp],
})
class Module {
}
const moduleDef: NgModuleDef<Module> = (Module as any).ngModuleDef;
expect(moduleDef).toBeDefined();
expect(moduleDef.declarations.length).toBe(1);
expect(moduleDef.declarations[0]).toBe(Cmp);
});
it('patches a module onto the component', () => {
@Component({
template: 'foo',
selector: 'foo',
})
class Cmp {
}
const cmpDef: ComponentDef<Cmp> = (Cmp as any).ngComponentDef;
expect(cmpDef.directiveDefs).toBeNull();
@NgModule({
declarations: [Cmp],
})
class Module {
}
const moduleDef: NgModuleDef<Module> = (Module as any).ngModuleDef;
expect(cmpDef.directiveDefs instanceof Function).toBe(true);
expect((cmpDef.directiveDefs as Function)()).toEqual([cmpDef]);
});
});
it('ensure at least one spec exists', () => {});