Skip to content

Commit 5f095a5

Browse files
filipesilvamatsko
authored andcommitted
fix(core): initialize global ngDevMode without toplevel side effects (#32079)
Fix #31595 PR Close #32079
1 parent a85eccd commit 5f095a5

File tree

9 files changed

+92
-36
lines changed

9 files changed

+92
-36
lines changed

integration/_payload-limits.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"master": {
4040
"uncompressed": {
4141
"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",
42-
"bundle": 177137
42+
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
43+
"bundle": 177447
4344
}
4445
}
4546
}

packages/core/src/render3/definition.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import '../util/ng_dev_mode';
10-
119
import {ChangeDetectionStrategy} from '../change_detection/constants';
12-
import {NG_INJECTABLE_DEF, ɵɵdefineInjectable} from '../di/interface/defs';
1310
import {Mutable, Type} from '../interface/type';
1411
import {NgModuleDef} from '../metadata/ng_module';
1512
import {SchemaMetadata} from '../metadata/schema';
1613
import {ViewEncapsulation} from '../metadata/view';
1714
import {noSideEffects} from '../util/closure';
15+
import {initNgDevMode} from '../util/ng_dev_mode';
1816
import {stringify} from '../util/stringify';
1917

2018
import {EMPTY_ARRAY, EMPTY_OBJ} from './empty';
@@ -240,6 +238,10 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
240238
*/
241239
schemas?: SchemaMetadata[] | null;
242240
}): never {
241+
// Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent.
242+
// See the `initNgDevMode` docstring for more information.
243+
(typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
244+
243245
const type = componentDefinition.type;
244246
const typePrototype = type.prototype;
245247
const declaredInputs: {[key: string]: string} = {} as any;

packages/core/src/render3/empty.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import '../util/ng_dev_mode';
8+
import {initNgDevMode} from '../util/ng_dev_mode';
99

1010
/**
1111
* This file contains reuseable "empty" symbols that can be used as default return values
@@ -18,7 +18,7 @@ export const EMPTY_OBJ: {} = {};
1818
export const EMPTY_ARRAY: any[] = [];
1919

2020
// freezing the values prevents any code from accidentally inserting new values in
21-
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
21+
if ((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode()) {
2222
// These property accesses can be ignored because ngDevMode will be set to false
2323
// when optimizing code and the whole if statement will be dropped.
2424
// tslint:disable-next-line:no-toplevel-property-access

packages/core/src/render3/instructions/lview_debug.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {AttributeMarker, ComponentTemplate} from '..';
1010
import {SchemaMetadata} from '../../core';
1111
import {assertDefined} from '../../util/assert';
1212
import {createNamedArrayType} from '../../util/named_array_type';
13+
import {initNgDevMode} from '../../util/ng_dev_mode';
1314
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
1415
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
1516
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
@@ -24,6 +25,7 @@ import {isStylingContext} from '../styling_next/util';
2425
import {attachDebugObject} from '../util/debug_utils';
2526
import {getTNode, unwrapRNode} from '../util/view_utils';
2627

28+
const NG_DEV_MODE = ((typeof ngDevMode === 'undefined' || !!ngDevMode) && initNgDevMode());
2729

2830
/*
2931
* This file contains conditionally attached classes which provide human readable (debug) level
@@ -54,8 +56,7 @@ import {getTNode, unwrapRNode} from '../util/view_utils';
5456
* ```
5557
*/
5658

57-
58-
export const LViewArray = ngDevMode && createNamedArrayType('LView');
59+
export const LViewArray = NG_DEV_MODE && createNamedArrayType('LView') || null !as ArrayConstructor;
5960
let LVIEW_EMPTY: unknown[]; // can't initialize here or it will not be tree shaken, because `LView`
6061
// constructor could have side-effects.
6162
/**
@@ -64,7 +65,7 @@ let LVIEW_EMPTY: unknown[]; // can't initialize here or it will not be tree sha
6465
* Simple slice will keep the same type, and we need it to be LView
6566
*/
6667
export function cloneToLView(list: any[]): LView {
67-
if (LVIEW_EMPTY === undefined) LVIEW_EMPTY = new LViewArray !();
68+
if (LVIEW_EMPTY === undefined) LVIEW_EMPTY = new LViewArray();
6869
return LVIEW_EMPTY.concat(list) as any;
6970
}
7071

@@ -195,7 +196,7 @@ function processTNodeChildren(tNode: TNode | null, buf: string[]) {
195196
}
196197
}
197198

198-
const TViewData = ngDevMode && createNamedArrayType('TViewData');
199+
const TViewData = NG_DEV_MODE && createNamedArrayType('TViewData') || null !as ArrayConstructor;
199200
let TVIEWDATA_EMPTY:
200201
unknown[]; // can't initialize here or it will not be tree shaken, because `LView`
201202
// constructor could have side-effects.
@@ -205,18 +206,26 @@ let TVIEWDATA_EMPTY:
205206
* Simple slice will keep the same type, and we need it to be TData
206207
*/
207208
export function cloneToTViewData(list: any[]): TData {
208-
if (TVIEWDATA_EMPTY === undefined) TVIEWDATA_EMPTY = new TViewData !();
209+
if (TVIEWDATA_EMPTY === undefined) TVIEWDATA_EMPTY = new TViewData();
209210
return TVIEWDATA_EMPTY.concat(list) as any;
210211
}
211212

212-
export const LViewBlueprint = ngDevMode && createNamedArrayType('LViewBlueprint');
213-
export const MatchesArray = ngDevMode && createNamedArrayType('MatchesArray');
214-
export const TViewComponents = ngDevMode && createNamedArrayType('TViewComponents');
215-
export const TNodeLocalNames = ngDevMode && createNamedArrayType('TNodeLocalNames');
216-
export const TNodeInitialInputs = ngDevMode && createNamedArrayType('TNodeInitialInputs');
217-
export const TNodeInitialData = ngDevMode && createNamedArrayType('TNodeInitialData');
218-
export const LCleanup = ngDevMode && createNamedArrayType('LCleanup');
219-
export const TCleanup = ngDevMode && createNamedArrayType('TCleanup');
213+
export const LViewBlueprint =
214+
NG_DEV_MODE && createNamedArrayType('LViewBlueprint') || null !as ArrayConstructor;
215+
export const MatchesArray =
216+
NG_DEV_MODE && createNamedArrayType('MatchesArray') || null !as ArrayConstructor;
217+
export const TViewComponents =
218+
NG_DEV_MODE && createNamedArrayType('TViewComponents') || null !as ArrayConstructor;
219+
export const TNodeLocalNames =
220+
NG_DEV_MODE && createNamedArrayType('TNodeLocalNames') || null !as ArrayConstructor;
221+
export const TNodeInitialInputs =
222+
NG_DEV_MODE && createNamedArrayType('TNodeInitialInputs') || null !as ArrayConstructor;
223+
export const TNodeInitialData =
224+
NG_DEV_MODE && createNamedArrayType('TNodeInitialData') || null !as ArrayConstructor;
225+
export const LCleanup =
226+
NG_DEV_MODE && createNamedArrayType('LCleanup') || null !as ArrayConstructor;
227+
export const TCleanup =
228+
NG_DEV_MODE && createNamedArrayType('TCleanup') || null !as ArrayConstructor;
220229

221230

222231

packages/core/src/render3/instructions/shared.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {validateAgainstEventAttributes, validateAgainstEventProperties} from '..
1212
import {Sanitizer} from '../../sanitization/sanitizer';
1313
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertNotEqual, assertNotSame} from '../../util/assert';
1414
import {createNamedArrayType} from '../../util/named_array_type';
15+
import {initNgDevMode} from '../../util/ng_dev_mode';
1516
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
1617
import {assertFirstTemplatePass, assertLView} from '../assert';
1718
import {attachPatchData, getComponentViewByInstance} from '../context_discovery';
@@ -656,7 +657,7 @@ export function createTView(
656657
}
657658

658659
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LView {
659-
const blueprint = ngDevMode ? new LViewBlueprint !() : [];
660+
const blueprint = ngDevMode ? new LViewBlueprint() : [];
660661

661662
for (let i = 0; i < initialViewLength; i++) {
662663
blueprint.push(i < bindingStartIndex ? null : NO_CHANGE);
@@ -1187,7 +1188,7 @@ function findDirectiveMatches(
11871188
for (let i = 0; i < registry.length; i++) {
11881189
const def = registry[i] as ComponentDef<any>| DirectiveDef<any>;
11891190
if (isNodeMatchingSelectorList(tNode, def.selectors !, /* isProjectionMode */ false)) {
1190-
matches || (matches = ngDevMode ? new MatchesArray !() : []);
1191+
matches || (matches = ngDevMode ? new MatchesArray() : []);
11911192
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, viewData), tView, def.type);
11921193

11931194
if (isComponentDef(def)) {
@@ -1212,7 +1213,7 @@ function findDirectiveMatches(
12121213
export function markAsComponentHost(tView: TView, hostTNode: TNode): void {
12131214
ngDevMode && assertFirstTemplatePass(tView);
12141215
hostTNode.flags = TNodeFlags.isComponentHost;
1215-
(tView.components || (tView.components = ngDevMode ? new TViewComponents !() : [
1216+
(tView.components || (tView.components = ngDevMode ? new TViewComponents() : [
12161217
])).push(hostTNode.index);
12171218
}
12181219

@@ -1222,7 +1223,7 @@ function cacheMatchingLocalNames(
12221223
tNode: TNode, localRefs: string[] | null, exportsMap: {[key: string]: number}): void {
12231224
if (localRefs) {
12241225
const localNames: (string | number)[] = tNode.localNames =
1225-
ngDevMode ? new TNodeLocalNames !() : [];
1226+
ngDevMode ? new TNodeLocalNames() : [];
12261227

12271228
// Local names must be stored in tNode in the same order that localRefs are defined
12281229
// in the template to ensure the data is loaded in the same slots as their refs
@@ -1380,7 +1381,7 @@ function setInputsFromAttrs<T>(
13801381
function generateInitialInputs(
13811382
directiveIndex: number, inputs: {[key: string]: string}, tNode: TNode): InitialInputData {
13821383
const initialInputData: InitialInputData =
1383-
tNode.initialInputs || (tNode.initialInputs = ngDevMode ? new TNodeInitialInputs !() : []);
1384+
tNode.initialInputs || (tNode.initialInputs = ngDevMode ? new TNodeInitialInputs() : []);
13841385
// Ensure that we don't create sparse arrays
13851386
for (let i = initialInputData.length; i <= directiveIndex; i++) {
13861387
initialInputData.push(null);
@@ -1408,7 +1409,7 @@ function generateInitialInputs(
14081409

14091410
if (minifiedInputName !== undefined) {
14101411
const inputsToStore: InitialInputs = initialInputData[directiveIndex] ||
1411-
(initialInputData[directiveIndex] = ngDevMode ? new TNodeInitialData !() : []);
1412+
(initialInputData[directiveIndex] = ngDevMode ? new TNodeInitialData() : []);
14121413
inputsToStore.push(attrName as string, minifiedInputName, attrValue as string);
14131414
}
14141415

@@ -1422,7 +1423,8 @@ function generateInitialInputs(
14221423
//////////////////////////
14231424

14241425
// Not sure why I need to do `any` here but TS complains later.
1425-
const LContainerArray: any = ngDevMode && createNamedArrayType('LContainer');
1426+
const LContainerArray: any = ((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode()) &&
1427+
createNamedArrayType('LContainer');
14261428

14271429
/**
14281430
* Creates a LContainer, either from a container instruction, or for a ViewContainerRef.
@@ -1777,11 +1779,11 @@ export function initializeTNodeInputs(tView: TView, tNode: TNode): PropertyAlias
17771779

17781780
export function getCleanup(view: LView): any[] {
17791781
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
1780-
return view[CLEANUP] || (view[CLEANUP] = ngDevMode ? new LCleanup !() : []);
1782+
return view[CLEANUP] || (view[CLEANUP] = ngDevMode ? new LCleanup() : []);
17811783
}
17821784

17831785
function getTViewCleanup(view: LView): any[] {
1784-
return view[TVIEW].cleanup || (view[TVIEW].cleanup = ngDevMode ? new TCleanup !() : []);
1786+
return view[TVIEW].cleanup || (view[TVIEW].cleanup = ngDevMode ? new TCleanup() : []);
17851787
}
17861788

17871789
/**

packages/core/src/render3/jit/directive.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {Query} from '../../metadata/di';
1616
import {Component, Directive, Input} from '../../metadata/directives';
1717
import {componentNeedsResolution, maybeQueueResolutionOfComponentResources} from '../../metadata/resource_loading';
1818
import {ViewEncapsulation} from '../../metadata/view';
19+
import {initNgDevMode} from '../../util/ng_dev_mode';
1920
import {getBaseDef, getComponentDef, getDirectiveDef} from '../definition';
2021
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
2122
import {NG_BASE_DEF, NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_FACTORY_DEF} from '../fields';
@@ -37,6 +38,10 @@ import {flushModuleScopingQueueAsMuchAsPossible, patchComponentDefWithScope, tra
3738
* until the global queue has been resolved with a call to `resolveComponentResources`.
3839
*/
3940
export function compileComponent(type: Type<any>, metadata: Component): void {
41+
// Initialize ngDevMode. This must be the first statement in compileComponent.
42+
// See the `initNgDevMode` docstring for more information.
43+
(typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
44+
4045
let ngComponentDef: any = null;
4146
let ngFactoryDef: any = null;
4247

packages/core/src/util/empty.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import './ng_dev_mode';
8+
import {initNgDevMode} from './ng_dev_mode';
99

1010
/**
1111
* This file contains reuseable "empty" symbols that can be used as default return values
@@ -18,7 +18,7 @@ export const EMPTY_OBJ: {} = {};
1818
export const EMPTY_ARRAY: any[] = [];
1919

2020
// freezing the values prevents any code from accidentally inserting new values in
21-
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
21+
if ((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode()) {
2222
// These property accesses can be ignored because ngDevMode will be set to false
2323
// when optimizing code and the whole if statement will be dropped.
2424
// tslint:disable-next-line:no-toplevel-property-access

packages/core/src/util/ng_dev_mode.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@
99
import {global} from './global';
1010

1111
declare global {
12+
/**
13+
* Values of ngDevMode
14+
* Depending on the current state of the application, ngDevMode may have one of several values.
15+
*
16+
* For convenience, the “truthy” value which enables dev mode is also an object which contains
17+
* Angular’s performance counters. This is not necessary, but cuts down on boilerplate for the
18+
* perf counters.
19+
*
20+
* ngDevMode may also be set to false. This can happen in one of a few ways:
21+
* - The user explicitly sets `window.ngDevMode = false` somewhere in their app.
22+
* - The user calls `enableProdMode()`.
23+
* - The URL contains a `ngDevMode=false` text.
24+
* Finally, ngDevMode may not have been defined at all.
25+
*/
1226
const ngDevMode: null|NgDevModePerfCounters;
1327
interface NgDevModePerfCounters {
1428
namedConstructors: boolean;
@@ -98,15 +112,36 @@ export function ngDevModeResetPerfCounters(): NgDevModePerfCounters {
98112
}
99113

100114
/**
101-
* This checks to see if the `ngDevMode` has been set. If yes,
115+
* This function checks to see if the `ngDevMode` has been set. If yes,
102116
* then we honor it, otherwise we default to dev mode with additional checks.
103117
*
104118
* The idea is that unless we are doing production build where we explicitly
105119
* set `ngDevMode == false` we should be helping the developer by providing
106120
* as much early warning and errors as possible.
107121
*
108-
* NOTE: changes to the `ngDevMode` name must be synced with `compiler-cli/src/tooling.ts`.
122+
* `ɵɵdefineComponent` is guaranteed to have been called before any component template functions
123+
* (and thus Ivy instructions), so a single initialization there is sufficient to ensure ngDevMode
124+
* is defined for the entire instruction set.
125+
*
126+
* When using checking `ngDevMode` on toplevel, always init it before referencing it
127+
* (e.g. `((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode())`), otherwise you can
128+
* get a `ReferenceError` like in https://github.com/angular/angular/issues/31595.
129+
*
130+
* Details on possible values for `ngDevMode` can be found on its docstring.
131+
*
132+
* NOTE:
133+
* - changes to the `ngDevMode` name must be synced with `compiler-cli/src/tooling.ts`.
109134
*/
110-
if (typeof ngDevMode === 'undefined' || ngDevMode) {
111-
ngDevModeResetPerfCounters();
135+
export function initNgDevMode(): boolean {
136+
// The below checks are to ensure that calling `initNgDevMode` multiple times does not
137+
// reset the counters.
138+
// If the `ngDevMode` is not an object, then it means we have not created the perf counters
139+
// yet.
140+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
141+
if (typeof ngDevMode !== 'object') {
142+
ngDevModeResetPerfCounters();
143+
}
144+
return !!ngDevMode;
145+
}
146+
return false;
112147
}

packages/platform-browser/src/dom/dom_renderer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export const NAMESPACE_URIS: {[ns: string]: string} = {
2020
};
2121

2222
const COMPONENT_REGEX = /%COMP%/g;
23+
const NG_DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
24+
2325
export const COMPONENT_VARIABLE = '%COMP%';
2426
export const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
2527
export const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
@@ -221,15 +223,15 @@ class DefaultDomRenderer2 implements Renderer2 {
221223
}
222224

223225
setProperty(el: any, name: string, value: any): void {
224-
ngDevMode && checkNoSyntheticProp(name, 'property');
226+
NG_DEV_MODE && checkNoSyntheticProp(name, 'property');
225227
el[name] = value;
226228
}
227229

228230
setValue(node: any, value: string): void { node.nodeValue = value; }
229231

230232
listen(target: 'window'|'document'|'body'|any, event: string, callback: (event: any) => boolean):
231233
() => void {
232-
ngDevMode && checkNoSyntheticProp(event, 'listener');
234+
NG_DEV_MODE && checkNoSyntheticProp(event, 'listener');
233235
if (typeof target === 'string') {
234236
return <() => void>this.eventManager.addGlobalEventListener(
235237
target, event, decoratePreventDefault(callback));

0 commit comments

Comments
 (0)