mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(devtools): allow DevTools to fail gracefully for unsupported versions of Angular. (#55233)
Angular DevTools depends on many modern Angular features in order to function. As a result, at present the last officially supported version is v12. Angular DevTools may function for some Angular 9, 10 and 11 applications, but they are not officially supported. This commit fixes an issue where DevTools would not inject a backend script into an Angular application if it detected it was below version 12. This backend script is important because it's used to inform the DevTools panel that the inspected application is in fact Angular, but that it is not on a supported version. Angular 9, 10 and 11 applications that successfully have Angular DevTools initialize will now have a red highlight and tooltip on their version number, informing the user that they are using Angular DevTools on a version of Angular that is no longer supported. Angular DevTools for applications that are below version 9 will continue to display the "Angular Devtools supports Angular versions 12 and above" message. PR Close #55233
This commit is contained in:
committed by
Alex Rickabaugh
parent
832fafc6b5
commit
efe78d5565
@@ -30,12 +30,27 @@
|
||||
{{ tab }}
|
||||
</a>
|
||||
}
|
||||
@if (angularVersion) {
|
||||
@if (angularVersion()) {
|
||||
<section id="app-angular-version">
|
||||
Angular version:
|
||||
<span id="version-number">
|
||||
{{ angularVersion }}
|
||||
</span>
|
||||
|
||||
@if (majorAngularVersion() > 12 || majorAngularVersion() == 0) {
|
||||
<span id="version-number">
|
||||
{{ angularVersion() }}
|
||||
</span>
|
||||
} @else {
|
||||
<span
|
||||
id="version-number"
|
||||
matTooltip="
|
||||
Angular Devtools supports Angular versions 12 and above. Some DevTools features may be available in
|
||||
older versions of Angular, but it is not officially supported.
|
||||
"
|
||||
class="unsupported-version"
|
||||
>
|
||||
{{ angularVersion() }} (unsupported)
|
||||
</span>
|
||||
}
|
||||
|
||||
@if (latestSHA) {
|
||||
| DevTools SHA: {{ latestSHA }}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@ ng-injector-tree.hidden {
|
||||
-webkit-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
|
||||
&.unsupported-version {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
@@ -89,6 +93,10 @@ mat-icon {
|
||||
:host-context(.dark-theme) {
|
||||
#version-number {
|
||||
color: #5caace;
|
||||
|
||||
&.unsupported-version {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
.inspector-active {
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
computed,
|
||||
EventEmitter,
|
||||
inject,
|
||||
input,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
@@ -59,7 +61,6 @@ type Tabs = 'Components' | 'Profiler' | 'Router Tree' | 'Injector Tree';
|
||||
providers: [TabUpdate],
|
||||
})
|
||||
export class DevToolsTabsComponent implements OnInit, AfterViewInit {
|
||||
@Input() angularVersion: string | undefined = undefined;
|
||||
@Input() isHydrationEnabled = false;
|
||||
|
||||
@Output() frameSelected = new EventEmitter<Frame>();
|
||||
@@ -80,6 +81,15 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit {
|
||||
|
||||
TOP_LEVEL_FRAME_ID = TOP_LEVEL_FRAME_ID;
|
||||
|
||||
angularVersion = input<string | undefined>(undefined);
|
||||
majorAngularVersion = computed(() => {
|
||||
const version = this.angularVersion();
|
||||
if (!version) {
|
||||
return -1;
|
||||
}
|
||||
return parseInt(version.toString().split('.')[0], 10);
|
||||
});
|
||||
|
||||
constructor(
|
||||
public tabUpdate: TabUpdate,
|
||||
public themeService: ThemeService,
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
@switch (angularStatus) {
|
||||
@case (AngularStatus.EXISTS) {
|
||||
@if (angularIsInDevMode) {
|
||||
@if (supportedVersion) {
|
||||
@if (supportedVersion()) {
|
||||
<div class="devtools-wrapper noselect" [@enterAnimation]>
|
||||
<ng-devtools-tabs (frameSelected)="inspectFrame($event)" [isHydrationEnabled]="hydration" [angularVersion]="angularVersion"></ng-devtools-tabs>
|
||||
<ng-devtools-tabs (frameSelected)="inspectFrame($event)" [isHydrationEnabled]="hydration" [angularVersion]="angularVersion()"></ng-devtools-tabs>
|
||||
</div>
|
||||
} @else {
|
||||
<p class="text-message">
|
||||
|
||||
@@ -9,7 +9,15 @@
|
||||
import {animate, style, transition, trigger} from '@angular/animations';
|
||||
import {Platform} from '@angular/cdk/platform';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {Component, inject, OnDestroy, OnInit} from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
computed,
|
||||
inject,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
signal,
|
||||
WritableSignal,
|
||||
} from '@angular/core';
|
||||
import {Events, MessageBus} from 'protocol';
|
||||
import {interval} from 'rxjs';
|
||||
|
||||
@@ -40,6 +48,8 @@ enum AngularStatus {
|
||||
EXISTS,
|
||||
}
|
||||
|
||||
const LAST_SUPPORTED_VERSION = 9;
|
||||
|
||||
@Component({
|
||||
selector: 'ng-devtools',
|
||||
templateUrl: './devtools.component.html',
|
||||
@@ -56,10 +66,22 @@ enum AngularStatus {
|
||||
export class DevToolsComponent implements OnInit, OnDestroy {
|
||||
AngularStatus = AngularStatus;
|
||||
angularStatus: AngularStatus = AngularStatus.UNKNOWN;
|
||||
angularVersion: string | boolean | undefined = undefined;
|
||||
angularVersion: WritableSignal<string | undefined> = signal(undefined);
|
||||
angularIsInDevMode = true;
|
||||
hydration: boolean = false;
|
||||
ivy!: boolean;
|
||||
ivy: WritableSignal<boolean | undefined> = signal(undefined);
|
||||
|
||||
supportedVersion = computed(() => {
|
||||
const version = this.angularVersion();
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
const majorVersion = parseInt(version.toString().split('.')[0], 10);
|
||||
|
||||
// Check that major version is either greater or equal to the last supported version
|
||||
// or that the major version is 0 for the (0.0.0-PLACEHOLDER) dev build case.
|
||||
return (majorVersion >= LAST_SUPPORTED_VERSION || majorVersion === 0) && this.ivy();
|
||||
});
|
||||
|
||||
private readonly _firefoxStyleName = 'firefox_styles.css';
|
||||
private readonly _chromeStyleName = 'chrome_styles.css';
|
||||
@@ -85,9 +107,9 @@ export class DevToolsComponent implements OnInit, OnDestroy {
|
||||
|
||||
this._messageBus.once('ngAvailability', ({version, devMode, ivy, hydration}) => {
|
||||
this.angularStatus = version ? AngularStatus.EXISTS : AngularStatus.DOES_NOT_EXIST;
|
||||
this.angularVersion = version;
|
||||
this.angularVersion.set(version);
|
||||
this.angularIsInDevMode = devMode;
|
||||
this.ivy = ivy;
|
||||
this.ivy.set(ivy);
|
||||
this._interval$.unsubscribe();
|
||||
this.hydration = hydration;
|
||||
});
|
||||
@@ -98,17 +120,6 @@ export class DevToolsComponent implements OnInit, OnDestroy {
|
||||
this._loadStyle(browserStyleName);
|
||||
}
|
||||
|
||||
get majorAngularVersion(): number {
|
||||
if (!this.angularVersion) {
|
||||
return -1;
|
||||
}
|
||||
return parseInt(this.angularVersion.toString().split('.')[0], 10);
|
||||
}
|
||||
|
||||
get supportedVersion(): boolean {
|
||||
return (this.majorAngularVersion >= 9 || this.majorAngularVersion === 0) && this.ivy;
|
||||
}
|
||||
|
||||
/** Add a style file in header based on fileName */
|
||||
private _loadStyle(styleName: string) {
|
||||
const head = this._document.getElementsByTagName('head')[0];
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
import {Component, signal} from '@angular/core';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {FrameManager} from './frame_manager';
|
||||
import {DevToolsComponent} from './devtools.component';
|
||||
@@ -44,8 +44,8 @@ describe('DevtoolsComponent', () => {
|
||||
it('should render ng devtools tabs when Angular Status is EXISTS and is in dev mode and is supported version', () => {
|
||||
component.angularStatus = component.AngularStatus.EXISTS;
|
||||
component.angularIsInDevMode = true;
|
||||
component.angularVersion = '0.0.0';
|
||||
component.ivy = true;
|
||||
component.angularVersion = signal('0.0.0');
|
||||
component.ivy = signal(true);
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('ng-devtools-tabs')).toBeTruthy();
|
||||
});
|
||||
@@ -62,7 +62,7 @@ describe('DevtoolsComponent', () => {
|
||||
it('should render version support message when Angular Status is EXISTS and angular version is not supported', () => {
|
||||
component.angularStatus = component.AngularStatus.EXISTS;
|
||||
component.angularIsInDevMode = true;
|
||||
component.angularVersion = '1.0.0';
|
||||
component.angularVersion = signal('1.0.0');
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('.devtools').textContent).toContain(
|
||||
'Angular Devtools only supports Angular versions 12 and above',
|
||||
|
||||
@@ -238,7 +238,7 @@ export interface Events {
|
||||
shutdown: () => void;
|
||||
queryNgAvailability: () => void;
|
||||
ngAvailability: (config: {
|
||||
version: string | undefined | boolean;
|
||||
version: string | undefined;
|
||||
devMode: boolean;
|
||||
ivy: boolean;
|
||||
hydration: boolean;
|
||||
|
||||
@@ -46,14 +46,6 @@ detectAngularMessageBus.on('detectAngular', (detectionResult) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (detectionResult.isDebugMode !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (detectionResult.isSupportedAngularVersion !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Defensive check against non html page. Realistically this should never happen.
|
||||
if (document.contentType !== 'text/html') {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user