mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(devtools): use a custom logger and error
Use a custom logger that prefixes the messages with `[Angular DevTools]`;
Add ability for dev-only log messages;
Add Angular-DevTools-specific `Error`;
(cherry picked from commit 1371c946aa)
This commit is contained in:
@@ -10,5 +10,6 @@ ts_project(
|
||||
"//devtools/projects/ng-devtools-backend/src/lib:client_event_subscribers",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest/component-tree",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/router-tree",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared:interfaces",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -7,11 +7,7 @@ ts_project(
|
||||
srcs = ["index.ts"],
|
||||
deps = [
|
||||
":client_event_subscribers",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/component-inspector",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/profiling/profiler",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/ng-debug-api",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/state-serializer",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared:interfaces",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
)
|
||||
@@ -61,6 +57,7 @@ ts_project(
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/ng-debug-api",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/state-serializer",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:general",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:log",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:serialization",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:signal-graph-ref",
|
||||
"//devtools/projects/protocol",
|
||||
|
||||
@@ -43,7 +43,10 @@ describe('ClientEventSubscriber', () => {
|
||||
});
|
||||
|
||||
it('should setup inspector', () => {
|
||||
subscribeToClientEvents(messageBusMock, {profiler: MockProfiler});
|
||||
subscribeToClientEvents(messageBusMock, {
|
||||
devtoolsDevMode: true,
|
||||
depsForTestOnly: {profiler: MockProfiler},
|
||||
});
|
||||
|
||||
expect(messageBusMock.on).toHaveBeenCalledWith('inspectorStart', jasmine.any(Function));
|
||||
expect(messageBusMock.on).toHaveBeenCalledWith('inspectorEnd', jasmine.any(Function));
|
||||
|
||||
@@ -53,7 +53,7 @@ import {
|
||||
import {start as startProfiling, stop as stopProfiling} from './profiling/capture';
|
||||
import {disableTimingAPI, enableTimingAPI} from './profiling/timing-api';
|
||||
import {getProfiler, Profiler} from './profiling/profiler';
|
||||
import {ComponentTreeNode} from './shared/interfaces';
|
||||
import {ComponentTreeNode, DevtoolsBackendConfig} from './shared/interfaces';
|
||||
import {
|
||||
ngDebugClient,
|
||||
ngDebugDependencyInjectionApiIsSupported,
|
||||
@@ -75,16 +75,20 @@ import {
|
||||
removeHydrationHighlights,
|
||||
} from './hydration/hydration-highlighting';
|
||||
import {removeAllHighlights} from './shared/highlighter';
|
||||
import {debugLog, log, setupLogging} from './shared/utils/log';
|
||||
|
||||
type InspectorRef = {ref: ComponentInspector | null};
|
||||
|
||||
export const subscribeToClientEvents = (
|
||||
messageBus: MessageBus<Events>,
|
||||
depsForTestOnly?: {
|
||||
profiler?: new (...args: any[]) => Profiler;
|
||||
config?: DevtoolsBackendConfig & {
|
||||
depsForTestOnly?: {
|
||||
profiler?: new (...args: any[]) => Profiler;
|
||||
};
|
||||
},
|
||||
): void => {
|
||||
const inspector: InspectorRef = {ref: null};
|
||||
setupLogging(config?.devtoolsDevMode ?? false);
|
||||
|
||||
messageBus.on('shutdown', shutdownCallback(messageBus));
|
||||
|
||||
@@ -123,9 +127,9 @@ export const subscribeToClientEvents = (
|
||||
const SAFE_LOG_LEVELS = new Set(['log', 'info', 'warn', 'debug', 'error']);
|
||||
messageBus.on('log', ({message, level}) => {
|
||||
if (SAFE_LOG_LEVELS.has(level)) {
|
||||
console[level](`[Angular DevTools]: ${message}`);
|
||||
log[level](message);
|
||||
} else {
|
||||
console.warn(`[Angular DevTools]: Invalid log level attempted: ${level}`);
|
||||
debugLog.warn(`Invalid log level attempted: ${level}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -139,7 +143,7 @@ export const subscribeToClientEvents = (
|
||||
// update requests, instead we want to request an update at most
|
||||
// once every 250ms
|
||||
runOutsideAngular(() => {
|
||||
getProfiler(depsForTestOnly)
|
||||
getProfiler(config?.depsForTestOnly)
|
||||
.changeDetection$.pipe(debounceTime(250))
|
||||
.subscribe(() => messageBus.emit('componentTreeDirty'));
|
||||
});
|
||||
@@ -198,7 +202,7 @@ const navigateRouteCallback = (messageBus: MessageBus<Events>) => (path: string)
|
||||
if (router) {
|
||||
ngDebugClient().ɵnavigateByUrl?.(router, path);
|
||||
} else {
|
||||
console.warn('Router not found or navigateByUrl method not available');
|
||||
log.warn('Router not found or navigateByUrl method not available');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -255,7 +259,7 @@ const getNestedPropertiesCallback =
|
||||
for (const prop of propPath) {
|
||||
data = unwrapSignal(data[prop]);
|
||||
if (!data) {
|
||||
console.error('Cannot access the properties', propPath, 'of', node);
|
||||
log.error('Cannot access the properties', propPath, 'of', node);
|
||||
}
|
||||
}
|
||||
messageBus.emit('nestedProperties', [
|
||||
@@ -313,7 +317,7 @@ const getSignalNestedPropertiesCallback =
|
||||
for (const prop of propPath) {
|
||||
data = (data as Record<string, object>)[prop];
|
||||
if (!data) {
|
||||
console.error('Cannot access the properties', propPath, 'of', node);
|
||||
log.error('Cannot access the properties', propPath, 'of', node);
|
||||
}
|
||||
}
|
||||
messageBus.emit('signalNestedProperties', [
|
||||
|
||||
@@ -26,6 +26,7 @@ ts_project(
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest/identity-tracker",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/profiling/profiler",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared:interfaces",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:log",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
)
|
||||
|
||||
+2
@@ -20,7 +20,9 @@ ts_project(
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared:interfaces",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/ng-debug-api",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/state-serializer",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:error",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:general",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:log",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
)
|
||||
|
||||
+17
-13
@@ -52,6 +52,8 @@ import {AcxChangeDetectionStrategy, ChangeDetectionStrategy, Framework} from '..
|
||||
import {mutateNestedProp} from '../property-mutation/property-mutation';
|
||||
import {getLViewFromDirectiveOrElementInstance} from '../tree-strategies/ltree';
|
||||
import {getAppRoots} from './get-roots';
|
||||
import {log} from '../../shared/utils/log';
|
||||
import {AngularDevtoolsError} from '../../shared/utils/error';
|
||||
|
||||
export const injectorToId = new WeakMap<Injector | HTMLElement, string>();
|
||||
export const nodeInjectorToResolutionPath = new WeakMap<HTMLElement, SerializedInjector[]>();
|
||||
@@ -263,7 +265,7 @@ const getDirectiveMetadata = (dir: any): DirectiveMetadata => {
|
||||
};
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknown framework: "${framework}".`);
|
||||
throw new AngularDevtoolsError(`Unknown framework: "${framework}".`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,7 +275,7 @@ const getDirectiveMetadata = (dir: any): DirectiveMetadata => {
|
||||
try {
|
||||
return dir.constructor.ɵcmp ? dir.constructor.ɵcmp[key] : dir.constructor.ɵdir[key];
|
||||
} catch {
|
||||
console.warn(`Could not find metadata for key: ${key} in directive:`, dir);
|
||||
log.warn(`Could not find metadata for key: ${key} in directive:`, dir);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
@@ -311,7 +313,9 @@ export function getDirectiveCdStrategy(dir: any): ChangeDetection | undefined {
|
||||
return undefined;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown framework: "${(metadata as {framework: string}).framework}".`);
|
||||
throw new AngularDevtoolsError(
|
||||
`Unknown framework: "${(metadata as {framework: string}).framework}".`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,7 +453,7 @@ export function serializeInjector(injector: Injector): Omit<SerializedInjector,
|
||||
const metadata = getInjectorMetadata(injector);
|
||||
|
||||
if (metadata === null) {
|
||||
console.error('Angular DevTools: Could not serialize injector.', injector);
|
||||
log.error('Could not serialize injector.', injector);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -480,7 +484,7 @@ export function serializeInjector(injector: Injector): Omit<SerializedInjector,
|
||||
return {type: 'environment', name: stripUnderscore(metadata.source ?? ''), providers};
|
||||
}
|
||||
|
||||
console.error('Angular DevTools: Could not serialize injector.', injector);
|
||||
log.error('Could not serialize injector.', injector);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -545,7 +549,7 @@ function elementToDirectiveNames(element: HTMLElement): string[] {
|
||||
|
||||
export function getElementInjectorElement(elementInjector: Injector): HTMLElement {
|
||||
if (!isElementInjector(elementInjector)) {
|
||||
throw new Error('Injector is not an element injector');
|
||||
throw new AngularDevtoolsError('Injector is not an element injector');
|
||||
}
|
||||
|
||||
return getInjectorMetadata(elementInjector)!.source as HTMLElement;
|
||||
@@ -742,7 +746,7 @@ export const updateState = (updatedStateData: UpdatedStateData): void => {
|
||||
const ng = ngDebugClient();
|
||||
const node = queryDirectiveForest(updatedStateData.directiveId.element, buildDirectiveForest());
|
||||
if (!node) {
|
||||
console.warn(
|
||||
log.warn(
|
||||
'Could not update the state of component',
|
||||
updatedStateData,
|
||||
'because the component was not found',
|
||||
@@ -771,7 +775,7 @@ export function logValue(valueInfo: {
|
||||
}): void {
|
||||
const node = queryDirectiveForest(valueInfo.directiveId.element, buildDirectiveForest());
|
||||
if (!node) {
|
||||
console.warn(
|
||||
log.warn(
|
||||
'Could not log the value of component',
|
||||
valueInfo,
|
||||
'because the directive was not found',
|
||||
@@ -782,27 +786,27 @@ export function logValue(valueInfo: {
|
||||
if (node.directives && valueInfo.directiveId.directive !== undefined) {
|
||||
const directiveInstance = node.directives[valueInfo.directiveId.directive].instance;
|
||||
if (valueInfo.keyPath === null) {
|
||||
logToConsole(directiveInstance);
|
||||
logSignalToConsole(directiveInstance);
|
||||
return;
|
||||
}
|
||||
|
||||
const value = valueInfo.keyPath.reduce((obj, key) => obj && obj[key], directiveInstance);
|
||||
logToConsole(value);
|
||||
logSignalToConsole(value);
|
||||
return;
|
||||
}
|
||||
if (node.component) {
|
||||
const compInstance = node.component.instance;
|
||||
if (valueInfo.keyPath === null) {
|
||||
logToConsole(compInstance);
|
||||
logSignalToConsole(compInstance);
|
||||
return;
|
||||
}
|
||||
const value = valueInfo.keyPath.reduce((obj, key) => obj && obj[key], compInstance);
|
||||
logToConsole(value);
|
||||
logSignalToConsole(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function logToConsole(value: unknown) {
|
||||
function logSignalToConsole(value: unknown) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(unwrapSignal(value));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {ElementPosition} from '../../../../protocol';
|
||||
import {ComponentTreeNode, DirectiveInstance} from '../shared/interfaces';
|
||||
import {getProfiler} from '../profiling/profiler';
|
||||
import {IdentityTracker, IndexedNode, IndexingOutput} from './identity-tracker/identity-tracker';
|
||||
import {debugLog} from '../shared/utils/log';
|
||||
|
||||
// Global reference.
|
||||
let directiveForestManager: DirectiveForestManager;
|
||||
@@ -27,7 +28,7 @@ export class DirectiveForestManager {
|
||||
getDirectivePosition(dir: DirectiveInstance): ElementPosition | undefined {
|
||||
const result = this._tracker.getDirectivePosition(dir);
|
||||
if (result === undefined) {
|
||||
console.warn('Unable to find position of', dir);
|
||||
debugLog.warn('Unable to find position of', dir);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -35,7 +36,7 @@ export class DirectiveForestManager {
|
||||
getDirectiveId(dir: DirectiveInstance): number | undefined {
|
||||
const result = this._tracker.getDirectiveId(dir);
|
||||
if (result === undefined) {
|
||||
console.warn('Unable to find ID of', result);
|
||||
debugLog.warn('Unable to find ID of', result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+1
@@ -7,6 +7,7 @@ ts_project(
|
||||
srcs = ["property-mutation.ts"],
|
||||
deps = [
|
||||
"//:node_modules/@angular/core",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:error",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:general",
|
||||
],
|
||||
)
|
||||
|
||||
+17
-10
@@ -8,6 +8,7 @@
|
||||
|
||||
import type {Signal, WritableSignal} from '@angular/core';
|
||||
import {isSignal} from '../../shared/utils/general';
|
||||
import {AngularDevtoolsError} from '../../shared/utils/error';
|
||||
|
||||
/** Represents a property access operation. */
|
||||
interface PropertyAccess {
|
||||
@@ -76,13 +77,13 @@ interface PropertyAccess {
|
||||
* (`receiver[prop] = newValue;`).
|
||||
*/
|
||||
export function mutateNestedProp(obj: any, keyPath: string[], newValue: unknown): void {
|
||||
if (keyPath.length === 0) throw new Error('At least one key is required.');
|
||||
if (keyPath.length === 0) throw new AngularDevtoolsError('At least one key is required.');
|
||||
|
||||
const nestedProps = Array.from(getNestedProps(obj, keyPath));
|
||||
|
||||
// Check for nested signals.
|
||||
const signalCount = nestedProps.filter((prop) => isSignal(prop.value)).length;
|
||||
if (signalCount > 1) throw new Error('Cannot mutate nested signals.');
|
||||
if (signalCount > 1) throw new AngularDevtoolsError('Cannot mutate nested signals.');
|
||||
|
||||
// Check for a single signal.
|
||||
const signalIndex = nestedProps.findIndex((prop) => isSignal(prop.value));
|
||||
@@ -95,7 +96,7 @@ export function mutateNestedProp(obj: any, keyPath: string[], newValue: unknown)
|
||||
.slice(0, signalIndex + 1)
|
||||
.map((prop) => prop.key)
|
||||
.join('.');
|
||||
throw new Error(`Cannot mutate a readonly signal at \`${propPath}\`.`);
|
||||
throw new AngularDevtoolsError(`Cannot mutate a readonly signal at \`${propPath}\`.`);
|
||||
}
|
||||
|
||||
sig.value.set(immutableUpdate(props, newValue));
|
||||
@@ -107,7 +108,7 @@ export function mutateNestedProp(obj: any, keyPath: string[], newValue: unknown)
|
||||
const finalProp = nestedProps[nestedProps.length - 1];
|
||||
const descriptor = getInheritedPropertyDescriptor(finalProp.receiver, finalProp.key);
|
||||
if (descriptor && descriptor.get && !descriptor.set) {
|
||||
throw new Error(`Cannot mutate getter property: ${finalProp.key}`);
|
||||
throw new AngularDevtoolsError(`Cannot mutate getter property: ${finalProp.key}`);
|
||||
}
|
||||
|
||||
finalProp.receiver[finalProp.key] = newValue;
|
||||
@@ -124,15 +125,19 @@ function* getNestedProps(
|
||||
|
||||
// Prevent Prototype Pollution
|
||||
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
||||
throw new Error(`Access to property \`${key}\` is blocked for security reasons.`);
|
||||
throw new AngularDevtoolsError(
|
||||
`Access to property \`${key}\` is blocked for security reasons.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(receiver) && parseInt(key) >= receiver.length) {
|
||||
throw new Error(`Cannot access index ${key} for array of length ${receiver.length}.`);
|
||||
throw new AngularDevtoolsError(
|
||||
`Cannot access index ${key} for array of length ${receiver.length}.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!(key in receiver)) {
|
||||
throw new Error(`Property \`${key}\` is not defined on the object.`);
|
||||
throw new AngularDevtoolsError(`Property \`${key}\` is not defined on the object.`);
|
||||
}
|
||||
|
||||
const value = receiver[key];
|
||||
@@ -170,7 +175,9 @@ function immutableUpdate(props: PropertyAccess[], newValue: unknown): any {
|
||||
[prop.key]: immutableUpdate(remainingProps, newValue),
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Cannot immutably update type: ${prop.receiver.constructor.name}`);
|
||||
throw new AngularDevtoolsError(
|
||||
`Cannot immutably update type: ${prop.receiver.constructor.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +217,7 @@ function isWritableSignal<Value>(sig: Signal<Value>): sig is WritableSignal<Valu
|
||||
function assertSafeToImmutablyUpdate(obj: {}): void {
|
||||
// `new MyClass()` case.
|
||||
if (obj.constructor !== Object) {
|
||||
throw new Error(`Cannot immutably update type: ${obj.constructor.name}`);
|
||||
throw new AngularDevtoolsError(`Cannot immutably update type: ${obj.constructor.name}`);
|
||||
}
|
||||
|
||||
// `{ get foo() { return 'foo'; } }` case.
|
||||
@@ -220,7 +227,7 @@ function assertSafeToImmutablyUpdate(obj: {}): void {
|
||||
if (!descriptor) continue;
|
||||
|
||||
if (descriptor.get || descriptor.set) {
|
||||
throw new Error('Cannot immutably update object with getters or setters.');
|
||||
throw new AngularDevtoolsError('Cannot immutably update object with getters or setters.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -18,6 +18,7 @@ ts_project(
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest/utils:control-flow",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared:interfaces",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/ng-debug-api",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:error",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:general",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
|
||||
+2
-1
@@ -20,6 +20,7 @@ import {
|
||||
createControlFlowTreeNode,
|
||||
isControlFlowBlock,
|
||||
} from '../utils/control-flow';
|
||||
import {AngularDevtoolsError} from '../../shared/utils/error';
|
||||
|
||||
interface TreeExtractionContext {
|
||||
blocksIterator: ControlFlowBlocksIterator;
|
||||
@@ -107,7 +108,7 @@ function groupControlFlowBlocksChildren(
|
||||
) {
|
||||
const currentBlock = ctx.blocksIterator.currentBlock;
|
||||
if (!currentBlock) {
|
||||
throw new Error('There is no current block in the control flow block iterator.');
|
||||
throw new AngularDevtoolsError('There is no current block in the control flow block iterator.');
|
||||
}
|
||||
|
||||
ctx.blocksIterator.advance();
|
||||
|
||||
@@ -9,6 +9,7 @@ ts_project(
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest:manager",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared:interfaces",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/highlighter",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:error",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
HighlightType,
|
||||
} from '../shared/highlighter/highlights';
|
||||
import {ComponentTreeNode} from '../shared/interfaces';
|
||||
import {AngularDevtoolsError} from '../shared/utils/error';
|
||||
|
||||
export function highlightHydrationNodes(): void {
|
||||
const forest: ComponentTreeNode[] = getDirectiveForestManager().getDirectiveForest();
|
||||
@@ -58,7 +59,7 @@ function highlightHydrationElement(node: Element, {status}: HydrationStatus) {
|
||||
template = hydrationSkippedHighlightTemplate;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported hydration status highlighting: ${status}`);
|
||||
throw new AngularDevtoolsError(`Unsupported hydration status highlighting: ${status}`);
|
||||
}
|
||||
|
||||
highlightElement(node, template, {'icon': [status]});
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
*/
|
||||
|
||||
import {Events, MessageBus} from '../../../protocol';
|
||||
import {DevtoolsBackendConfig} from './shared/interfaces';
|
||||
|
||||
import {subscribeToClientEvents} from './client-event-subscribers';
|
||||
|
||||
export const initializeMessageBus = (messageBus: MessageBus<Events>) => {
|
||||
subscribeToClientEvents(messageBus);
|
||||
export const initializeMessageBus = (
|
||||
messageBus: MessageBus<Events>,
|
||||
config?: DevtoolsBackendConfig,
|
||||
) => {
|
||||
subscribeToClientEvents(messageBus, config);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,9 @@ ts_project(
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/profiling/profiler",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared:interfaces",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/highlighter",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:error",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:general",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:log",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -22,6 +22,8 @@ import {DirectiveForestManager, getDirectiveForestManager} from '../directive-fo
|
||||
import {IdentityTracker} from '../directive-forest/identity-tracker/identity-tracker';
|
||||
import {getProfiler, Hooks} from './profiler';
|
||||
import {getDirectiveName} from '../directive-forest/component-tree/component-tree';
|
||||
import {debugLog} from '../shared/utils/log';
|
||||
import {AngularDevtoolsError} from '../shared/utils/error';
|
||||
|
||||
let inProgress = false;
|
||||
let inChangeDetection = false;
|
||||
@@ -36,7 +38,7 @@ const DIRECTIVE_CONTROL_FLOW: {[key in ControlFlowBlockType]: ElementProfile['ty
|
||||
|
||||
export const start = (onFrame: (frame: ProfilerFrame) => void): void => {
|
||||
if (inProgress) {
|
||||
throw new Error('Recording already in progress');
|
||||
throw new AngularDevtoolsError('Recording already in progress');
|
||||
}
|
||||
eventMap = new Map<any, DirectiveProfile>();
|
||||
inProgress = true;
|
||||
@@ -131,7 +133,7 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
|
||||
profile.changeDetection = current + duration;
|
||||
frameDuration += duration;
|
||||
} else {
|
||||
console.warn('Could not find profile for', component);
|
||||
debugLog.warn('Could not find profile for', component);
|
||||
}
|
||||
},
|
||||
onDestroy(
|
||||
@@ -183,7 +185,7 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
|
||||
return;
|
||||
}
|
||||
if (!dir) {
|
||||
console.warn('Could not find directive in onLifecycleHook callback', directive, hookName);
|
||||
debugLog.warn('Could not find directive in onLifecycleHook callback', directive, hookName);
|
||||
return;
|
||||
}
|
||||
const duration = performance.now() - startTimestamp;
|
||||
@@ -216,7 +218,7 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
|
||||
return;
|
||||
}
|
||||
if (!entry) {
|
||||
console.warn(
|
||||
debugLog.warn(
|
||||
'Could not find directive or component in onOutputEnd callback',
|
||||
componentOrDirective,
|
||||
outputName,
|
||||
@@ -272,7 +274,7 @@ const insertElementProfile = (
|
||||
const pos = position[i];
|
||||
if (!frames[pos]) {
|
||||
// TODO(mgechev): consider how to ensure we don't hit this case
|
||||
console.warn('Unable to find parent node for', profile, original);
|
||||
debugLog.warn('Unable to find parent node for', profile, original);
|
||||
return;
|
||||
}
|
||||
frames = frames[pos].children;
|
||||
|
||||
@@ -12,6 +12,8 @@ ts_project(
|
||||
deps = [
|
||||
"//:node_modules/@angular/core",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest/component-tree",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:error",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:log",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
)
|
||||
@@ -25,6 +27,7 @@ ts_test_library(
|
||||
deps = [
|
||||
":highlighter",
|
||||
"//:node_modules/@angular/core",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:log",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import {EventEmitter} from '@angular/core';
|
||||
import {Highlight, HighlightLabelDefinition, HighlightTemplate, HighlightType} from './highlights';
|
||||
import {OVERLAY_CLASS} from './dom';
|
||||
import {debugLog} from '../utils/log';
|
||||
|
||||
function createTemplate(overrides?: Partial<HighlightTemplate<any>>): HighlightTemplate<any> {
|
||||
return {
|
||||
@@ -222,12 +223,12 @@ describe('Highlight', () => {
|
||||
const highlight = createHighlight(createTemplate(), {
|
||||
title: document.createElement('div'),
|
||||
});
|
||||
spyOn(console, 'warn');
|
||||
spyOn(debugLog, 'warn');
|
||||
|
||||
highlight.destroy();
|
||||
highlight.destroy();
|
||||
|
||||
expect(console.warn).toHaveBeenCalledOnceWith(
|
||||
expect(debugLog.warn).toHaveBeenCalledOnceWith(
|
||||
'The highlight has already been destroyed. Check references storing.',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import {EventEmitter} from '@angular/core';
|
||||
import {HydrationStatus} from '../../../../../protocol';
|
||||
import {positionOverlayElement, setLabelElementVisibility} from './dom';
|
||||
import {debugLog} from '../utils/log';
|
||||
import {AngularDevtoolsError} from '../utils/error';
|
||||
|
||||
//
|
||||
// Types & classes
|
||||
@@ -112,7 +114,7 @@ export class Highlight<T extends HighlightLabelDefinition = HighlightLabelDefini
|
||||
// Getting the warning, means that there might be a problem
|
||||
// with the code (i.e. there is chance for a memory leak).
|
||||
if (this.destroyed) {
|
||||
console.warn('The highlight has already been destroyed. Check references storing.');
|
||||
debugLog.warn('The highlight has already been destroyed. Check references storing.');
|
||||
return;
|
||||
}
|
||||
this.destroyEvents.emit([this]);
|
||||
@@ -152,7 +154,7 @@ function validateTemplateLabels(template: HighlightTemplate) {
|
||||
|
||||
for (const {x} of Object.values(template.labels)) {
|
||||
if (usedXPos.has(x)) {
|
||||
throw new Error(
|
||||
throw new AngularDevtoolsError(
|
||||
`The template (type: ${template.type}) has multiple labels with '${x}' X position.`,
|
||||
);
|
||||
}
|
||||
@@ -219,7 +221,7 @@ function createHydrationHighlightTemplate(
|
||||
} else if (type === 'skipped') {
|
||||
icon = HYDRATION_SKIPPED_SVG;
|
||||
} else {
|
||||
throw new Error(`No icon specified for type ${type}`);
|
||||
throw new AngularDevtoolsError(`No icon specified for type ${type}`);
|
||||
}
|
||||
|
||||
const svg = new DOMParser().parseFromString(icon, 'image/svg+xml')
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
import {DevToolsNode} from '../../../../protocol';
|
||||
|
||||
export interface DevtoolsBackendConfig {
|
||||
devtoolsDevMode?: boolean;
|
||||
}
|
||||
|
||||
export interface DebuggingAPI {
|
||||
getComponent(node: Node): ComponentInstance;
|
||||
getDirectives(node: Node): DirectiveInstance[];
|
||||
|
||||
@@ -12,6 +12,7 @@ ng_project(
|
||||
"//:node_modules/@angular/core",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest:core-enums",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest/component-tree:get-roots",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/shared/utils:error",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
import type {ɵFrameworkAgnosticGlobalUtils as GlobalUtils} from '@angular/core';
|
||||
import {getAppRoots} from '../../directive-forest/component-tree/get-roots';
|
||||
import {Framework} from '../../directive-forest/core-enums';
|
||||
import {AngularDevtoolsError} from '../utils/error';
|
||||
|
||||
/** Returns a handle to window.ng APIs (global angular debugging). */
|
||||
export const ngDebugClient = () => {
|
||||
if (typeof (window as any).ng === 'undefined') {
|
||||
throw new Error(
|
||||
'Angular DevTools: Angular debugging APIs are not available. Ensure that your Angular app is in development mode and does not invoke `enableProdMode()`.',
|
||||
throw new AngularDevtoolsError(
|
||||
'Angular debugging APIs are not available. Ensure that your Angular app is in development mode and does not invoke `enableProdMode()`.',
|
||||
);
|
||||
}
|
||||
return (window as any).ng as Partial<GlobalUtils>;
|
||||
|
||||
@@ -23,13 +23,25 @@ ts_project(
|
||||
],
|
||||
)
|
||||
|
||||
ts_project(
|
||||
name = "log",
|
||||
srcs = ["log.ts"],
|
||||
)
|
||||
|
||||
ts_project(
|
||||
name = "error",
|
||||
srcs = ["error.ts"],
|
||||
)
|
||||
|
||||
ts_test_library(
|
||||
name = "test_lib",
|
||||
srcs = [
|
||||
"log.spec.ts",
|
||||
"serialization.spec.ts",
|
||||
"signal-graph-ref.spec.ts",
|
||||
],
|
||||
deps = [
|
||||
":log",
|
||||
":serialization",
|
||||
":signal-graph-ref",
|
||||
"//:node_modules/@angular/core",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @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.dev/license
|
||||
*/
|
||||
|
||||
/** Angular DevTools error. Use instead of `Error`. */
|
||||
export class AngularDevtoolsError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'AngularDevtoolsError';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* @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.dev/license
|
||||
*/
|
||||
|
||||
// tslint:disable:no-console
|
||||
|
||||
import {DEBUG_LOG_MSG_PREFIX, LOG_MSG_PREFIX, debugLog, log, setupLogging} from './log';
|
||||
|
||||
describe('log utils', () => {
|
||||
afterEach(() => setupLogging(false));
|
||||
|
||||
describe('log', () => {
|
||||
it('should log with the user-facing prefix', () => {
|
||||
spyOn(console, 'log');
|
||||
|
||||
log('hello', 42);
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith(LOG_MSG_PREFIX, 'hello', 42);
|
||||
});
|
||||
|
||||
it('should log warnings with the user-facing prefix', () => {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
log.warn('careful');
|
||||
|
||||
expect(console.warn).toHaveBeenCalledWith(LOG_MSG_PREFIX, 'careful');
|
||||
});
|
||||
|
||||
it('should log debug messages with the user-facing prefix', () => {
|
||||
spyOn(console, 'debug');
|
||||
|
||||
log.debug('debugging');
|
||||
|
||||
expect(console.debug).toHaveBeenCalledWith(LOG_MSG_PREFIX, 'debugging');
|
||||
});
|
||||
|
||||
it('should log info messages with the user-facing prefix', () => {
|
||||
spyOn(console, 'info');
|
||||
|
||||
log.info('info');
|
||||
|
||||
expect(console.info).toHaveBeenCalledWith(LOG_MSG_PREFIX, 'info');
|
||||
});
|
||||
|
||||
it('should log errors with the user-facing prefix', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
log.error('oops');
|
||||
|
||||
expect(console.error).toHaveBeenCalledWith(LOG_MSG_PREFIX, 'oops');
|
||||
});
|
||||
|
||||
it('should support log.log for compatibility', () => {
|
||||
spyOn(console, 'log');
|
||||
|
||||
log.log('compat');
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith(LOG_MSG_PREFIX, 'compat');
|
||||
});
|
||||
|
||||
it('should log regardless of dev mode', () => {
|
||||
spyOn(console, 'log');
|
||||
setupLogging(true);
|
||||
|
||||
log('dev');
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith(LOG_MSG_PREFIX, 'dev');
|
||||
});
|
||||
});
|
||||
|
||||
describe('debugLog', () => {
|
||||
it('should not log when dev mode is disabled', () => {
|
||||
spyOn(console, 'log');
|
||||
|
||||
debugLog('hidden');
|
||||
|
||||
expect(console.log).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not log warnings when dev mode is disabled', () => {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
debugLog.warn('hidden');
|
||||
|
||||
expect(console.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not log debug messages when dev mode is disabled', () => {
|
||||
spyOn(console, 'debug');
|
||||
|
||||
debugLog.debug('hidden');
|
||||
|
||||
expect(console.debug).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not log info messages when dev mode is disabled', () => {
|
||||
spyOn(console, 'info');
|
||||
|
||||
debugLog.info('hidden');
|
||||
|
||||
expect(console.info).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not log errors when dev mode is disabled', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
debugLog.error('hidden');
|
||||
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should log with the dev-only prefix when dev mode is enabled', () => {
|
||||
spyOn(console, 'log');
|
||||
setupLogging(true);
|
||||
|
||||
debugLog('shown', {a: 1});
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith(DEBUG_LOG_MSG_PREFIX, 'shown', {a: 1});
|
||||
});
|
||||
|
||||
it('should log warnings with the dev-only prefix when dev mode is enabled', () => {
|
||||
spyOn(console, 'warn');
|
||||
setupLogging(true);
|
||||
|
||||
debugLog.warn('shown');
|
||||
|
||||
expect(console.warn).toHaveBeenCalledWith(DEBUG_LOG_MSG_PREFIX, 'shown');
|
||||
});
|
||||
|
||||
it('should log debug messages with the dev-only prefix when dev mode is enabled', () => {
|
||||
spyOn(console, 'debug');
|
||||
setupLogging(true);
|
||||
|
||||
debugLog.debug('shown');
|
||||
|
||||
expect(console.debug).toHaveBeenCalledWith(DEBUG_LOG_MSG_PREFIX, 'shown');
|
||||
});
|
||||
|
||||
it('should log info messages with the dev-only prefix when dev mode is enabled', () => {
|
||||
spyOn(console, 'info');
|
||||
setupLogging(true);
|
||||
|
||||
debugLog.info('shown');
|
||||
|
||||
expect(console.info).toHaveBeenCalledWith(DEBUG_LOG_MSG_PREFIX, 'shown');
|
||||
});
|
||||
|
||||
it('should log errors with the dev-only prefix when dev mode is enabled', () => {
|
||||
spyOn(console, 'error');
|
||||
setupLogging(true);
|
||||
|
||||
debugLog.error('shown');
|
||||
|
||||
expect(console.error).toHaveBeenCalledWith(DEBUG_LOG_MSG_PREFIX, 'shown');
|
||||
});
|
||||
|
||||
it('should support debugLog.log for compatibility', () => {
|
||||
spyOn(console, 'log');
|
||||
setupLogging(true);
|
||||
|
||||
debugLog.log('compat');
|
||||
|
||||
expect(console.log).toHaveBeenCalledWith(DEBUG_LOG_MSG_PREFIX, 'compat');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @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.dev/license
|
||||
*/
|
||||
|
||||
let isNgDevtoolsDevMode: boolean = false;
|
||||
|
||||
export const LOG_MSG_PREFIX = '[Angular DevTools]';
|
||||
export const DEBUG_LOG_MSG_PREFIX = '[Angular DevTools DEV]';
|
||||
|
||||
type LogType = 'log' | 'warn' | 'debug' | 'info' | 'error';
|
||||
|
||||
export function setupLogging(devtoolsDevMode: boolean) {
|
||||
isNgDevtoolsDevMode = devtoolsDevMode;
|
||||
}
|
||||
|
||||
function logInternal(logType: LogType, prefix: string, logMsg: boolean, ...data: any[]) {
|
||||
if (logMsg) {
|
||||
console[logType](prefix, ...data);
|
||||
}
|
||||
}
|
||||
|
||||
const debugLogInternal = (logType: LogType, ...data: any[]) =>
|
||||
logInternal(logType, DEBUG_LOG_MSG_PREFIX, isNgDevtoolsDevMode, ...data);
|
||||
|
||||
const userLogInternal = (logType: LogType, ...data: any[]) =>
|
||||
logInternal(logType, LOG_MSG_PREFIX, true, ...data);
|
||||
|
||||
/**
|
||||
* Log Angular DevTools dev-only messages.
|
||||
* Use `log` for user-facing messages.
|
||||
*/
|
||||
export function debugLog(...data: any[]) {
|
||||
debugLogInternal('log', ...data);
|
||||
}
|
||||
|
||||
debugLog.warn = (...data: any[]) => debugLogInternal('warn', ...data);
|
||||
debugLog.debug = (...data: any[]) => debugLogInternal('debug', ...data);
|
||||
debugLog.info = (...data: any[]) => debugLogInternal('info', ...data);
|
||||
debugLog.error = (...data: any[]) => debugLogInternal('error', ...data);
|
||||
|
||||
// For compatibility purposes
|
||||
debugLog.log = (...data: any[]) => debugLogInternal('log', ...data);
|
||||
|
||||
/**
|
||||
* Log Angular DevTools user-facing messages.
|
||||
* Use `debugLog` for dev-only messages.
|
||||
*/
|
||||
export function log(...data: any[]) {
|
||||
userLogInternal('log', ...data);
|
||||
}
|
||||
|
||||
log.warn = (...data: any[]) => userLogInternal('warn', ...data);
|
||||
log.debug = (...data: any[]) => userLogInternal('debug', ...data);
|
||||
log.info = (...data: any[]) => userLogInternal('info', ...data);
|
||||
log.error = (...data: any[]) => userLogInternal('error', ...data);
|
||||
|
||||
// For compatibility purposes
|
||||
log.log = (...data: any[]) => userLogInternal('log', ...data);
|
||||
@@ -14,3 +14,4 @@ export * from './lib';
|
||||
export {findNodeFromSerializedPosition} from './lib/directive-forest/component-tree/component-tree';
|
||||
export {viewSourceFromRouter} from './lib/client-event-subscribers';
|
||||
export {type RoutePropertyType} from './lib/router-tree/router-tree';
|
||||
export type {DevtoolsBackendConfig} from './lib/shared/interfaces';
|
||||
|
||||
@@ -44,4 +44,7 @@ export const DEMO_ROUTES: Routes = [
|
||||
|
||||
initializeMessageBus(
|
||||
new ZoneUnawareIFrameMessageBus(DEVTOOLS_BACKEND_URI, DEVTOOLS_FRONTEND_URI, () => window.parent),
|
||||
{
|
||||
devtoolsDevMode: true,
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user