Skip to content

Commit

Permalink
fix(core): initialize global ngDevMode without toplevel side effects
Browse files Browse the repository at this point in the history
  • Loading branch information
filipesilva authored and alan-agius4 committed Aug 23, 2019
1 parent c624b14 commit 626ff82
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 21 deletions.
3 changes: 2 additions & 1 deletion integration/_payload-limits.json
Expand Up @@ -30,7 +30,8 @@
"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
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/render3/definition.ts
Expand Up @@ -6,14 +6,14 @@
* 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';
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';
Expand Down Expand Up @@ -243,6 +243,10 @@ export function ɵɵdefineComponent<T>(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;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/render3/empty.ts
Expand Up @@ -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
Expand All @@ -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
Expand Down
36 changes: 26 additions & 10 deletions packages/core/src/render3/instructions/lview_debug.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
/**
Expand Down Expand Up @@ -195,7 +197,8 @@ function processTNodeChildren(tNode: TNode | null, buf: string[]) {
}
}

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.
Expand All @@ -209,14 +212,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');



Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/render3/instructions/shared.ts
Expand Up @@ -12,6 +12,7 @@ import {validateAgainstEventAttributes, validateAgainstEventProperties} from '..
import {Sanitizer} from '../../sanitization/sanitizer';
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';
Expand Down Expand Up @@ -1466,7 +1467,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.
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/util/empty.ts
Expand Up @@ -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
Expand All @@ -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
Expand Down
37 changes: 33 additions & 4 deletions packages/core/src/util/ng_dev_mode.ts
Expand Up @@ -9,6 +9,20 @@
import {global} from './global';

declare global {
/**
* 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: null|NgDevModePerfCounters;
interface NgDevModePerfCounters {
namedConstructors: boolean;
Expand Down Expand Up @@ -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;
}

0 comments on commit 626ff82

Please sign in to comment.