From fbb7aa517177cf8c68efcf78c903be385ae0cb58 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Thu, 15 Aug 2019 14:14:25 +0100 Subject: [PATCH] fix(core): initialize global ngDevMode without toplevel side effects Fix #31595 --- integration/_payload-limits.json | 5 ++- packages/core/src/render3/definition.ts | 6 ++- packages/core/src/render3/empty.ts | 4 +- .../src/render3/instructions/lview_debug.ts | 36 ++++++++++++----- .../core/src/render3/instructions/shared.ts | 4 +- packages/core/src/util/empty.ts | 4 +- packages/core/src/util/ng_dev_mode.ts | 39 ++++++++++++++++--- 7 files changed, 75 insertions(+), 23 deletions(-) diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index 3a2aac7f16991c..e1098a02f2638d 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -30,8 +30,9 @@ "master": { "uncompressed": { "bundle": "TODO(i): temporarily increase the payload size limit from 105779 - this is due to a closure issue related to ESM reexports that still needs to be investigated", - "bundle": 179825 + "bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.", + "bundle": 181675 } } } -} \ No newline at end of file +} diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 1c0e75365383c0..c9745425db8719 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import '../util/ng_dev_mode'; import {ChangeDetectionStrategy} from '../change_detection/constants'; import {NG_INJECTABLE_DEF, ɵɵdefineInjectable} from '../di/interface/defs'; import {Mutable, Type} from '../interface/type'; @@ -14,6 +13,7 @@ import {NgModuleDef} from '../metadata/ng_module'; import {SchemaMetadata} from '../metadata/schema'; import {ViewEncapsulation} from '../metadata/view'; import {noSideEffects} from '../util/closure'; +import {initNgDevMode} from '../util/ng_dev_mode'; import {stringify} from '../util/stringify'; import {EMPTY_ARRAY, EMPTY_OBJ} from './empty'; import {NG_BASE_DEF, NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_LOCALE_ID_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields'; @@ -243,6 +243,10 @@ export function ɵɵdefineComponent(componentDefinition: { */ schemas?: SchemaMetadata[] | null; }): never { + // Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent. + // See the `initNgDevMode` docstring for more information. + typeof ngDevMode === 'undefined' && initNgDevMode(); + const type = componentDefinition.type; const typePrototype = type.prototype; const declaredInputs: {[key: string]: string} = {} as any; diff --git a/packages/core/src/render3/empty.ts b/packages/core/src/render3/empty.ts index d0494193fd7c8b..c5c76f20bca66c 100644 --- a/packages/core/src/render3/empty.ts +++ b/packages/core/src/render3/empty.ts @@ -5,7 +5,7 @@ * 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 '../util/ng_dev_mode'; +import {initNgDevMode} from '../util/ng_dev_mode'; /** * This file contains reuseable "empty" symbols that can be used as default return values @@ -18,7 +18,7 @@ export const EMPTY_OBJ: {} = {}; export const EMPTY_ARRAY: any[] = []; // freezing the values prevents any code from accidentally inserting new values in -if (typeof ngDevMode !== 'undefined' && ngDevMode) { +if ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) { // These property accesses can be ignored because ngDevMode will be set to false // when optimizing code and the whole if statement will be dropped. // tslint:disable-next-line:no-toplevel-property-access diff --git a/packages/core/src/render3/instructions/lview_debug.ts b/packages/core/src/render3/instructions/lview_debug.ts index b554974306e162..7641d953eaa1c9 100644 --- a/packages/core/src/render3/instructions/lview_debug.ts +++ b/packages/core/src/render3/instructions/lview_debug.ts @@ -10,6 +10,7 @@ import {AttributeMarker, ComponentTemplate} from '..'; import {SchemaMetadata} from '../../core'; import {assertDefined} from '../../util/assert'; import {createNamedArrayType} from '../../util/named_array_type'; +import {initNgDevMode} from '../../util/ng_dev_mode'; import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container'; import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition'; import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n'; @@ -55,7 +56,8 @@ import {getTNode, unwrapRNode} from '../util/view_utils'; */ -export const LViewArray = ngDevMode && createNamedArrayType('LView'); +export const LViewArray = ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) && + createNamedArrayType('LView'); let LVIEW_EMPTY: unknown[]; // can't initialize here or it will not be tree shaken, because `LView` // constructor could have side-effects. /** @@ -163,7 +165,8 @@ export const TNodeConstructor = class TNode implements ITNode { } }; -const TViewData = ngDevMode && createNamedArrayType('TViewData'); +const TViewData = ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) && + createNamedArrayType('TViewData'); let TVIEWDATA_EMPTY: unknown[]; // can't initialize here or it will not be tree shaken, because `LView` // constructor could have side-effects. @@ -177,14 +180,27 @@ export function cloneToTViewData(list: any[]): TData { return TVIEWDATA_EMPTY.concat(list) as any; } -export const LViewBlueprint = ngDevMode && createNamedArrayType('LViewBlueprint'); -export const MatchesArray = ngDevMode && createNamedArrayType('MatchesArray'); -export const TViewComponents = ngDevMode && createNamedArrayType('TViewComponents'); -export const TNodeLocalNames = ngDevMode && createNamedArrayType('TNodeLocalNames'); -export const TNodeInitialInputs = ngDevMode && createNamedArrayType('TNodeInitialInputs'); -export const TNodeInitialData = ngDevMode && createNamedArrayType('TNodeInitialData'); -export const LCleanup = ngDevMode && createNamedArrayType('LCleanup'); -export const TCleanup = ngDevMode && createNamedArrayType('TCleanup'); +export const LViewBlueprint = + ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) && + createNamedArrayType('LViewBlueprint'); +export const MatchesArray = ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) && + createNamedArrayType('MatchesArray'); +export const TViewComponents = + ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) && + createNamedArrayType('TViewComponents'); +export const TNodeLocalNames = + ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) && + createNamedArrayType('TNodeLocalNames'); +export const TNodeInitialInputs = + ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) && + createNamedArrayType('TNodeInitialInputs'); +export const TNodeInitialData = + ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) && + createNamedArrayType('TNodeInitialData'); +export const LCleanup = ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) && + createNamedArrayType('LCleanup'); +export const TCleanup = ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) && + createNamedArrayType('TCleanup'); diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index c532d8a1287f10..d3b22ba2ca6e6d 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -12,6 +12,7 @@ import {validateAgainstEventAttributes, validateAgainstEventProperties} from '.. import {Sanitizer} from '../../sanitization/security'; import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertNotEqual, assertNotSame} from '../../util/assert'; import {createNamedArrayType} from '../../util/named_array_type'; +import {initNgDevMode} from '../../util/ng_dev_mode'; import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect'; import {assertFirstTemplatePass, assertLView} from '../assert'; import {attachPatchData, getComponentViewByInstance} from '../context_discovery'; @@ -1442,7 +1443,8 @@ function generateInitialInputs( ////////////////////////// // Not sure why I need to do `any` here but TS complains later. -const LContainerArray: any = ngDevMode && createNamedArrayType('LContainer'); +const LContainerArray: any = ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) && + createNamedArrayType('LContainer'); /** * Creates a LContainer, either from a container instruction, or for a ViewContainerRef. diff --git a/packages/core/src/util/empty.ts b/packages/core/src/util/empty.ts index d1d8ce9a1bebd0..f586dc392482c6 100644 --- a/packages/core/src/util/empty.ts +++ b/packages/core/src/util/empty.ts @@ -5,7 +5,7 @@ * 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 './ng_dev_mode'; +import {initNgDevMode} from './ng_dev_mode'; /** * This file contains reuseable "empty" symbols that can be used as default return values @@ -18,7 +18,7 @@ export const EMPTY_OBJ: {} = {}; export const EMPTY_ARRAY: any[] = []; // freezing the values prevents any code from accidentally inserting new values in -if (typeof ngDevMode !== 'undefined' && ngDevMode) { +if ((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode) { // These property accesses can be ignored because ngDevMode will be set to false // when optimizing code and the whole if statement will be dropped. // tslint:disable-next-line:no-toplevel-property-access diff --git a/packages/core/src/util/ng_dev_mode.ts b/packages/core/src/util/ng_dev_mode.ts index 505987072b97fb..7d83bfdb5b6bcb 100644 --- a/packages/core/src/util/ng_dev_mode.ts +++ b/packages/core/src/util/ng_dev_mode.ts @@ -9,7 +9,21 @@ import {global} from './global'; declare global { - const ngDevMode: null|NgDevModePerfCounters; + /** + * Values of ngDevMode + * Depending on the current state of the application, ngDevMode may have one of several values. + * + * For convenience, the “truthy” value which enables dev mode is also an object which contains + * Angular’s performance counters. This is not necessary, but cuts down on boilerplate for the + * perf counters. + * + * ngDevMode may also be set to false. This can happen in one of a few ways: + * - The user explicitly sets `window.ngDevMode = false` somewhere in their app. + * - The user calls `enableProdMode()`. + * - The URL contains a `ngDevMode=false` text. + * Finally, ngDevMode may not have been defined at all. + */ + const ngDevMode: undefined|false|NgDevModePerfCounters; interface NgDevModePerfCounters { namedConstructors: boolean; firstTemplatePass: number; @@ -98,15 +112,30 @@ export function ngDevModeResetPerfCounters(): NgDevModePerfCounters { } /** - * This checks to see if the `ngDevMode` has been set. If yes, + * This function checks to see if the `ngDevMode` has been set. If yes, * then we honor it, otherwise we default to dev mode with additional checks. * * The idea is that unless we are doing production build where we explicitly * set `ngDevMode == false` we should be helping the developer by providing * as much early warning and errors as possible. * - * NOTE: changes to the `ngDevMode` name must be synced with `compiler-cli/src/tooling.ts`. + * `ɵɵdefineComponent` is guaranteed to have been called before any component template functions + * (and thus Ivy instructions), so a single initialization there is sufficient to ensure ngDevMode + * is defined for the entire instruction set. + * + * When using checking `ngDevMode` on toplevel, always init it before referencing it + * (e.g. `((typeof ngDevMode === 'undefined' && initNgDevMode()) || ngDevMode)`), otherwise you can + * get a `ReferenceError` like in https://github.com/angular/angular/issues/31595. + * + * Details on possible values for `ngDevMode` can be found on its docstring. + * + * NOTE: + * - changes to the `ngDevMode` name must be synced with `compiler-cli/src/tooling.ts`. */ -if (typeof ngDevMode === 'undefined' || ngDevMode) { - ngDevModeResetPerfCounters(); +export function initNgDevMode(): boolean { + if (typeof ngDevMode === 'undefined' || ngDevMode) { + ngDevModeResetPerfCounters(); + return !!ngDevMode; + } + return false; }