diff --git a/packages/core/src/application_ref.ts b/packages/core/src/application_ref.ts index c08827237f..ea4de55bca 100644 --- a/packages/core/src/application_ref.ts +++ b/packages/core/src/application_ref.ts @@ -13,7 +13,7 @@ import {share} from 'rxjs/operators'; import {ApplicationInitStatus} from './application_init'; import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens'; -import {getCompilerFacade} from './compiler/compiler_facade'; +import {getCompilerFacade, JitCompilerUsage} from './compiler/compiler_facade'; import {Console} from './console'; import {Injectable} from './di/injectable'; import {InjectionToken} from './di/injection_token'; @@ -94,7 +94,11 @@ export function compileNgModuleFactory__POST_R3__( return Promise.resolve(moduleFactory); } - const compiler = getCompilerFacade(); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.Decorator, + kind: 'NgModule', + type: moduleType, + }); const compilerInjector = Injector.create({providers: compilerProviders}); const resourceLoader = compilerInjector.get(compiler.ResourceLoader); // The resource loader can also return a string while the "resolveComponentResources" diff --git a/packages/core/src/compiler/compiler_facade.ts b/packages/core/src/compiler/compiler_facade.ts index 08b3625e31..d81956de3e 100644 --- a/packages/core/src/compiler/compiler_facade.ts +++ b/packages/core/src/compiler/compiler_facade.ts @@ -6,17 +6,51 @@ * found in the LICENSE file at https://angular.io/license */ import {global} from '../util/global'; -import {CompilerFacade, ExportedCompilerFacade} from './compiler_facade_interface'; +import {CompilerFacade, ExportedCompilerFacade, Type} from './compiler_facade_interface'; export * from './compiler_facade_interface'; -export function getCompilerFacade(): CompilerFacade { - const globalNg: ExportedCompilerFacade = global['ng']; - if (!globalNg || !globalNg.ɵcompilerFacade) { - throw new Error( - `Angular JIT compilation failed: '@angular/compiler' not loaded!\n` + - ` - JIT compilation is discouraged for production use-cases! Consider AOT mode instead.\n` + - ` - Did you bootstrap using '@angular/platform-browser-dynamic' or '@angular/platform-server'?\n` + - ` - Alternatively provide the compiler with 'import "@angular/compiler";' before bootstrapping.`); - } - return globalNg.ɵcompilerFacade; +export const enum JitCompilerUsage { + Decorator, + PartialDeclaration, +} + +interface JitCompilerUsageRequest { + usage: JitCompilerUsage; + kind: 'directive'|'component'|'pipe'|'injectable'|'NgModule'; + type: Type; +} + +export function getCompilerFacade(request: JitCompilerUsageRequest): CompilerFacade { + const globalNg: ExportedCompilerFacade = global['ng']; + if (globalNg && globalNg.ɵcompilerFacade) { + return globalNg.ɵcompilerFacade; + } + + if (typeof ngDevMode === 'undefined' || ngDevMode) { + // Log the type as an error so that a developer can easily navigate to the type from the + // console. + console.error(`JIT compilation failed for ${request.kind}`, request.type); + + let message = `The ${request.kind} '${ + request + .type.name}' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available.\n\n`; + if (request.usage === JitCompilerUsage.PartialDeclaration) { + message += `The ${request.kind} is part of a library that has been partially compiled.\n`; + message += + `However, the Angular Linker has not processed the library such that JIT compilation is used as fallback.\n`; + message += '\n'; + message += + `Ideally, the library is processed using the Angular Linker to become fully AOT compiled.\n`; + } else { + message += + `JIT compilation is discouraged for production use-cases! Consider using AOT mode instead.\n`; + } + message += + `Alternatively, the JIT compiler should be loaded by bootstrapping using '@angular/platform-browser-dynamic' or '@angular/platform-server',\n`; + message += + `or manually provide the compiler with 'import "@angular/compiler";' before bootstrapping.`; + throw new Error(message); + } else { + throw new Error('JIT compiler unavailable'); + } } diff --git a/packages/core/src/di/jit/injectable.ts b/packages/core/src/di/jit/injectable.ts index f88f5bec1a..43cab19ab5 100644 --- a/packages/core/src/di/jit/injectable.ts +++ b/packages/core/src/di/jit/injectable.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {getCompilerFacade, R3InjectableMetadataFacade} from '../../compiler/compiler_facade'; +import {getCompilerFacade, JitCompilerUsage, R3InjectableMetadataFacade} from '../../compiler/compiler_facade'; import {Type} from '../../interface/type'; import {NG_FACTORY_DEF} from '../../render3/fields'; import {getClosureSafeProperty} from '../../util/property'; @@ -33,7 +33,9 @@ export function compileInjectable(type: Type, meta?: Injectable): void { Object.defineProperty(type, NG_PROV_DEF, { get: () => { if (ngInjectableDef === null) { - ngInjectableDef = getCompilerFacade().compileInjectable( + const compiler = + getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'injectable', type}); + ngInjectableDef = compiler.compileInjectable( angularCoreDiEnv, `ng:///${type.name}/ɵprov.js`, getInjectableMetadata(type, meta)); } return ngInjectableDef; @@ -46,7 +48,8 @@ export function compileInjectable(type: Type, meta?: Injectable): void { Object.defineProperty(type, NG_FACTORY_DEF, { get: () => { if (ngFactoryDef === null) { - const compiler = getCompilerFacade(); + const compiler = + getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'injectable', type}); ngFactoryDef = compiler.compileFactory(angularCoreDiEnv, `ng:///${type.name}/ɵfac.js`, { name: type.name, type, diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index 58ea28408e..cf241f51a1 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {getCompilerFacade, R3DirectiveMetadataFacade} from '../../compiler/compiler_facade'; +import {getCompilerFacade, JitCompilerUsage, R3DirectiveMetadataFacade} from '../../compiler/compiler_facade'; import {R3ComponentMetadataFacade, R3QueryMetadataFacade} from '../../compiler/compiler_facade_interface'; import {resolveForwardRef} from '../../di/forward_ref'; import {getReflect, reflectDependencies} from '../../di/jit/util'; @@ -68,7 +68,8 @@ export function compileComponent(type: Type, metadata: Component): void { Object.defineProperty(type, NG_COMP_DEF, { get: () => { if (ngComponentDef === null) { - const compiler = getCompilerFacade(); + const compiler = + getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'component', type: type}); if (componentNeedsResolution(metadata)) { const error = [`Component '${type.name}' is not resolved:`]; @@ -180,8 +181,10 @@ export function compileDirective(type: Type, directive: Directive|null): vo // that use `@Directive()` with no selector. In that case, pass empty object to the // `directiveMetadata` function instead of null. const meta = getDirectiveMetadata(type, directive || {}); + const compiler = + getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'directive', type}); ngDirectiveDef = - getCompilerFacade().compileDirective(angularCoreEnv, meta.sourceMapUrl, meta.metadata); + compiler.compileDirective(angularCoreEnv, meta.sourceMapUrl, meta.metadata); } return ngDirectiveDef; }, @@ -193,7 +196,7 @@ export function compileDirective(type: Type, directive: Directive|null): vo function getDirectiveMetadata(type: Type, metadata: Directive) { const name = type && type.name; const sourceMapUrl = `ng:///${name}/ɵdir.js`; - const compiler = getCompilerFacade(); + const compiler = getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'directive', type}); const facade = directiveMetadata(type as ComponentType, metadata); facade.typeSourceSpan = compiler.createParseSourceSpan('Directive', name, sourceMapUrl); if (facade.usesInheritance) { @@ -209,7 +212,8 @@ function addDirectiveFactoryDef(type: Type, metadata: Directive|Component) get: () => { if (ngFactoryDef === null) { const meta = getDirectiveMetadata(type, metadata); - const compiler = getCompilerFacade(); + const compiler = + getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'directive', type}); ngFactoryDef = compiler.compileFactory(angularCoreEnv, `ng:///${type.name}/ɵfac.js`, { name: meta.metadata.name, type: meta.metadata.type, diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index 3df72f8d8e..e636138a1f 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {getCompilerFacade, R3InjectorMetadataFacade} from '../../compiler/compiler_facade'; +import {getCompilerFacade, JitCompilerUsage, R3InjectorMetadataFacade} from '../../compiler/compiler_facade'; import {resolveForwardRef} from '../../di/forward_ref'; import {NG_INJ_DEF} from '../../di/interface/defs'; import {reflectDependencies} from '../../di/jit/util'; @@ -114,20 +114,21 @@ export function compileNgModuleDefs( // go into an infinite loop before we've reached the point where we throw all the errors. throw new Error(`'${stringifyForError(moduleType)}' module can't import itself`); } - ngModuleDef = getCompilerFacade().compileNgModule( - angularCoreEnv, `ng:///${moduleType.name}/ɵmod.js`, { - type: moduleType, - bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(resolveForwardRef), - declarations: declarations.map(resolveForwardRef), - imports: flatten(ngModule.imports || EMPTY_ARRAY) - .map(resolveForwardRef) - .map(expandModuleWithProviders), - exports: flatten(ngModule.exports || EMPTY_ARRAY) - .map(resolveForwardRef) - .map(expandModuleWithProviders), - schemas: ngModule.schemas ? flatten(ngModule.schemas) : null, - id: ngModule.id || null, - }); + const compiler = getCompilerFacade( + {usage: JitCompilerUsage.Decorator, kind: 'NgModule', type: moduleType}); + ngModuleDef = compiler.compileNgModule(angularCoreEnv, `ng:///${moduleType.name}/ɵmod.js`, { + type: moduleType, + bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(resolveForwardRef), + declarations: declarations.map(resolveForwardRef), + imports: flatten(ngModule.imports || EMPTY_ARRAY) + .map(resolveForwardRef) + .map(expandModuleWithProviders), + exports: flatten(ngModule.exports || EMPTY_ARRAY) + .map(resolveForwardRef) + .map(expandModuleWithProviders), + schemas: ngModule.schemas ? flatten(ngModule.schemas) : null, + id: ngModule.id || null, + }); // Set `schemas` on ngModuleDef to an empty array in JIT mode to indicate that runtime // should verify that there are no unknown elements in a template. In AOT mode, that check // happens at compile time and `schemas` information is not present on Component and Module @@ -144,7 +145,8 @@ export function compileNgModuleDefs( Object.defineProperty(moduleType, NG_FACTORY_DEF, { get: () => { if (ngFactoryDef === null) { - const compiler = getCompilerFacade(); + const compiler = getCompilerFacade( + {usage: JitCompilerUsage.Decorator, kind: 'NgModule', type: moduleType}); ngFactoryDef = compiler.compileFactory(angularCoreEnv, `ng:///${moduleType.name}/ɵfac.js`, { name: moduleType.name, type: moduleType, @@ -175,8 +177,10 @@ export function compileNgModuleDefs( (ngModule.exports || EMPTY_ARRAY).map(resolveForwardRef), ], }; - ngInjectorDef = getCompilerFacade().compileInjector( - angularCoreEnv, `ng:///${moduleType.name}/ɵinj.js`, meta); + const compiler = getCompilerFacade( + {usage: JitCompilerUsage.Decorator, kind: 'NgModule', type: moduleType}); + ngInjectorDef = + compiler.compileInjector(angularCoreEnv, `ng:///${moduleType.name}/ɵinj.js`, meta); } return ngInjectorDef; }, diff --git a/packages/core/src/render3/jit/partial.ts b/packages/core/src/render3/jit/partial.ts index 4aaa5088c3..99aa53ac34 100644 --- a/packages/core/src/render3/jit/partial.ts +++ b/packages/core/src/render3/jit/partial.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {getCompilerFacade, R3DeclareComponentFacade, R3DeclareDirectiveFacade, R3DeclareFactoryFacade, R3DeclareInjectableFacade, R3DeclareInjectorFacade, R3DeclareNgModuleFacade, R3DeclarePipeFacade} from '../../compiler/compiler_facade'; +import {FactoryTarget, getCompilerFacade, JitCompilerUsage, R3DeclareComponentFacade, R3DeclareDirectiveFacade, R3DeclareFactoryFacade, R3DeclareInjectableFacade, R3DeclareInjectorFacade, R3DeclareNgModuleFacade, R3DeclarePipeFacade} from '../../compiler/compiler_facade'; import {Type} from '../../interface/type'; import {setClassMetadata} from '../metadata'; import {angularCoreEnv} from './environment'; @@ -17,7 +17,8 @@ import {angularCoreEnv} from './environment'; * @codeGenApi */ export function ɵɵngDeclareDirective(decl: R3DeclareDirectiveFacade): unknown { - const compiler = getCompilerFacade(); + const compiler = getCompilerFacade( + {usage: JitCompilerUsage.PartialDeclaration, kind: 'directive', type: decl.type}); return compiler.compileDirectiveDeclaration( angularCoreEnv, `ng:///${decl.type.name}/ɵfac.js`, decl); } @@ -42,7 +43,8 @@ export function ɵɵngDeclareClassMetadata(decl: { * @codeGenApi */ export function ɵɵngDeclareComponent(decl: R3DeclareComponentFacade): unknown { - const compiler = getCompilerFacade(); + const compiler = getCompilerFacade( + {usage: JitCompilerUsage.PartialDeclaration, kind: 'component', type: decl.type}); return compiler.compileComponentDeclaration( angularCoreEnv, `ng:///${decl.type.name}/ɵcmp.js`, decl); } @@ -53,18 +55,38 @@ export function ɵɵngDeclareComponent(decl: R3DeclareComponentFacade): unknown * @codeGenApi */ export function ɵɵngDeclareFactory(decl: R3DeclareFactoryFacade): unknown { - const compiler = getCompilerFacade(); + const compiler = getCompilerFacade({ + usage: JitCompilerUsage.PartialDeclaration, + kind: getFactoryKind(decl.target), + type: decl.type + }); return compiler.compileFactoryDeclaration( angularCoreEnv, `ng:///${decl.type.name}/ɵfac.js`, decl); } +function getFactoryKind(target: FactoryTarget) { + switch (target) { + case FactoryTarget.Directive: + return 'directive'; + case FactoryTarget.Component: + return 'component'; + case FactoryTarget.Injectable: + return 'injectable'; + case FactoryTarget.Pipe: + return 'pipe'; + case FactoryTarget.NgModule: + return 'NgModule'; + } +} + /** * Compiles a partial injectable declaration object into a full injectable definition object. * * @codeGenApi */ export function ɵɵngDeclareInjectable(decl: R3DeclareInjectableFacade): unknown { - const compiler = getCompilerFacade(); + const compiler = getCompilerFacade( + {usage: JitCompilerUsage.PartialDeclaration, kind: 'injectable', type: decl.type}); return compiler.compileInjectableDeclaration( angularCoreEnv, `ng:///${decl.type.name}/ɵprov.js`, decl); } @@ -80,7 +102,8 @@ export {FactoryTarget} from '../../compiler/compiler_facade'; * @codeGenApi */ export function ɵɵngDeclareInjector(decl: R3DeclareInjectorFacade): unknown { - const compiler = getCompilerFacade(); + const compiler = getCompilerFacade( + {usage: JitCompilerUsage.PartialDeclaration, kind: 'NgModule', type: decl.type}); return compiler.compileInjectorDeclaration( angularCoreEnv, `ng:///${decl.type.name}/ɵinj.js`, decl); } @@ -91,7 +114,8 @@ export function ɵɵngDeclareInjector(decl: R3DeclareInjectorFacade): unknown { * @codeGenApi */ export function ɵɵngDeclareNgModule(decl: R3DeclareNgModuleFacade): unknown { - const compiler = getCompilerFacade(); + const compiler = getCompilerFacade( + {usage: JitCompilerUsage.PartialDeclaration, kind: 'NgModule', type: decl.type}); return compiler.compileNgModuleDeclaration( angularCoreEnv, `ng:///${decl.type.name}/ɵmod.js`, decl); } @@ -102,6 +126,7 @@ export function ɵɵngDeclareNgModule(decl: R3DeclareNgModuleFacade): unknown { * @codeGenApi */ export function ɵɵngDeclarePipe(decl: R3DeclarePipeFacade): unknown { - const compiler = getCompilerFacade(); + const compiler = getCompilerFacade( + {usage: JitCompilerUsage.PartialDeclaration, kind: 'pipe', type: decl.type}); return compiler.compilePipeDeclaration(angularCoreEnv, `ng:///${decl.type.name}/ɵpipe.js`, decl); } diff --git a/packages/core/src/render3/jit/pipe.ts b/packages/core/src/render3/jit/pipe.ts index 9377f44fdf..7bc4abe746 100644 --- a/packages/core/src/render3/jit/pipe.ts +++ b/packages/core/src/render3/jit/pipe.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {getCompilerFacade, R3PipeMetadataFacade} from '../../compiler/compiler_facade'; +import {getCompilerFacade, JitCompilerUsage, R3PipeMetadataFacade} from '../../compiler/compiler_facade'; import {reflectDependencies} from '../../di/jit/util'; import {Type} from '../../interface/type'; import {Pipe} from '../../metadata/directives'; @@ -22,7 +22,8 @@ export function compilePipe(type: Type, meta: Pipe): void { get: () => { if (ngFactoryDef === null) { const metadata = getPipeMetadata(type, meta); - const compiler = getCompilerFacade(); + const compiler = getCompilerFacade( + {usage: JitCompilerUsage.Decorator, kind: 'pipe', type: metadata.type}); ngFactoryDef = compiler.compileFactory(angularCoreEnv, `ng:///${metadata.name}/ɵfac.js`, { name: metadata.name, type: metadata.type, @@ -41,8 +42,10 @@ export function compilePipe(type: Type, meta: Pipe): void { get: () => { if (ngPipeDef === null) { const metadata = getPipeMetadata(type, meta); - ngPipeDef = getCompilerFacade().compilePipe( - angularCoreEnv, `ng:///${metadata.name}/ɵpipe.js`, metadata); + const compiler = getCompilerFacade( + {usage: JitCompilerUsage.Decorator, kind: 'pipe', type: metadata.type}); + ngPipeDef = + compiler.compilePipe(angularCoreEnv, `ng:///${metadata.name}/ɵpipe.js`, metadata); } return ngPipeDef; }, diff --git a/packages/core/test/compiler/BUILD.bazel b/packages/core/test/compiler/BUILD.bazel new file mode 100644 index 0000000000..95f8ca8b39 --- /dev/null +++ b/packages/core/test/compiler/BUILD.bazel @@ -0,0 +1,30 @@ +load("//tools:defaults.bzl", "jasmine_node_test", "karma_web_test_suite", "ts_library") + +package(default_visibility = ["//visibility:private"]) + +ts_library( + name = "compiler_lib", + testonly = True, + srcs = glob( + ["**/*.ts"], + ), + deps = [ + "//packages/core/src/compiler", + "//packages/core/src/util", + ], +) + +jasmine_node_test( + name = "compiler", + bootstrap = ["//tools/testing:node_es5"], + deps = [ + ":compiler_lib", + ], +) + +karma_web_test_suite( + name = "compiler_web", + deps = [ + ":compiler_lib", + ], +) diff --git a/packages/core/test/compiler/compiler_facade_spec.ts b/packages/core/test/compiler/compiler_facade_spec.ts new file mode 100644 index 0000000000..6878c86442 --- /dev/null +++ b/packages/core/test/compiler/compiler_facade_spec.ts @@ -0,0 +1,69 @@ +/** + * @license + * Copyright Google LLC 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 {getCompilerFacade, JitCompilerUsage} from '../../src/compiler/compiler_facade'; +import {CompilerFacade, ExportedCompilerFacade} from '../../src/compiler/compiler_facade_interface'; +import {global} from '../../src/util/global'; + +describe('getCompilerFacade', () => { + describe('errors', () => { + beforeEach(clearCompilerFacade); + afterEach(restoreCompilerFacade); + + it('reports an error when requested for a decorator', () => { + try { + getCompilerFacade({usage: JitCompilerUsage.Decorator, kind: 'directive', type: TestClass}); + fail('Error expected as compiler facade is not available'); + } catch (e) { + expect(e.message).toEqual( + `The directive 'TestClass' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available. + +JIT compilation is discouraged for production use-cases! Consider using AOT mode instead. +Alternatively, the JIT compiler should be loaded by bootstrapping using '@angular/platform-browser-dynamic' or '@angular/platform-server', +or manually provide the compiler with 'import "@angular/compiler";' before bootstrapping.`); + } + }); + + it('reports an error when requested for a partial declaration', () => { + try { + getCompilerFacade( + {usage: JitCompilerUsage.PartialDeclaration, kind: 'directive', type: TestClass}); + fail('Error expected as compiler facade is not available'); + } catch (e) { + expect(e.message).toEqual( + `The directive 'TestClass' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available. + +The directive is part of a library that has been partially compiled. +However, the Angular Linker has not processed the library such that JIT compilation is used as fallback. + +Ideally, the library is processed using the Angular Linker to become fully AOT compiled. +Alternatively, the JIT compiler should be loaded by bootstrapping using '@angular/platform-browser-dynamic' or '@angular/platform-server', +or manually provide the compiler with 'import "@angular/compiler";' before bootstrapping.`); + } + }); + }); +}); + +class TestClass {} + +let ɵcompilerFacade: CompilerFacade|null = null; + +function clearCompilerFacade() { + const ng: ExportedCompilerFacade = global.ng; + ɵcompilerFacade = ng.ɵcompilerFacade; + ng.ɵcompilerFacade = undefined!; +} + +function restoreCompilerFacade() { + if (ɵcompilerFacade === null) { + return; + } + const ng: ExportedCompilerFacade = global.ng; + ng.ɵcompilerFacade = ɵcompilerFacade; + ɵcompilerFacade = null; +}