refactor(core): expand error logging when the JIT compiler is not available (#42693)

If a decorator or partial declaration has not been AOT compiled, then
the compiler is needed at runtime to be able to JIT compile the code.
However, it may occur that the compiler is not available, if it has not
been loaded into the application. The error that was reported in this
case did not provide insight into which class requested compilation, nor
did it differentiate between decorators vs. partial declarations.

This commit expands the error logging to provide better insight into the
class that initiated JIT compilation and offers a specialized error
message for partial declarations. This should help a developer better
understand why the error occurs and what can be done to resolve it.

Closes #40609

PR Close #42693
This commit is contained in:
JoostK 2021-06-28 23:07:41 +02:00 committed by Alex Rickabaugh
parent 07d7e6034f
commit 31593db489
9 changed files with 227 additions and 51 deletions

View File

@ -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__<M>(
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"

View File

@ -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');
}
}

View File

@ -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<any>, 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<any>, 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,

View File

@ -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<any>, 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<any>, 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<any>, directive: Directive|null): vo
function getDirectiveMetadata(type: Type<any>, 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<any>, metadata);
facade.typeSourceSpan = compiler.createParseSourceSpan('Directive', name, sourceMapUrl);
if (facade.usesInheritance) {
@ -209,7 +212,8 @@ function addDirectiveFactoryDef(type: Type<any>, 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,

View File

@ -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;
},

View File

@ -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);
}

View File

@ -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<any>, 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<any>, 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;
},

View File

@ -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",
],
)

View File

@ -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;
}