mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
fix(devtools): support for @defer-only blocks; defer declared blocks (#66546)
Add support for `@defer`-only blocks (previously, they weren't rendered in the component tree at all); Fix declared blocks section in the details PR Close #66546
This commit is contained in:
parent
92d2498910
commit
2d4262cfbd
7 changed files with 69 additions and 46 deletions
|
|
@ -11,7 +11,7 @@ import {
|
|||
ɵDeferBlockData as DeferBlockData,
|
||||
ɵHydratedNode as HydrationNode,
|
||||
} from '@angular/core';
|
||||
import {CurrentDeferBlock, HydrationStatus} from '../../../../protocol';
|
||||
import {RenderedDeferBlock, HydrationStatus} from '../../../../protocol';
|
||||
|
||||
import {ComponentTreeNode} from '../interfaces';
|
||||
import {ngDebugClient} from '../ng-debug-api/ng-debug-api';
|
||||
|
|
@ -126,14 +126,19 @@ function groupDeferChildrenIfNeeded(
|
|||
getDirectiveMetadata?: FrameworkAgnosticGlobalUtils['getDirectiveMetadata'],
|
||||
) {
|
||||
const currentDeferBlock = deferBlocks.currentBlock;
|
||||
const isFirstDefferedChild = node === currentDeferBlock?.rootNodes[0];
|
||||
if (isFirstDefferedChild) {
|
||||
const isFirstDeferredChild = node === currentDeferBlock?.rootNodes[0];
|
||||
// Handles the case where the @defer is still unresolved but doesn't
|
||||
// have a placeholder, for instance, by which children we mark
|
||||
// the position of the block normally. In this case, we use the host.
|
||||
const isHostNode = node === currentDeferBlock?.hostNode;
|
||||
|
||||
if (isFirstDeferredChild || isHostNode) {
|
||||
deferBlocks.advance();
|
||||
|
||||
// When encountering the first child of a defer block
|
||||
// We create a synthetic TreeNode reprensenting the defer block
|
||||
// When encountering the first child of a defer block (or the host node),
|
||||
// we create a synthetic TreeNode representing the defer block.
|
||||
const childrenTree: ComponentTreeNode[] = [];
|
||||
currentDeferBlock.rootNodes.forEach((child) => {
|
||||
for (const child of currentDeferBlock.rootNodes) {
|
||||
extractViewTree(
|
||||
child,
|
||||
childrenTree,
|
||||
|
|
@ -143,7 +148,7 @@ function groupDeferChildrenIfNeeded(
|
|||
getDirectives,
|
||||
getDirectiveMetadata,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const deferBlockTreeNode = {
|
||||
children: childrenTree,
|
||||
|
|
@ -155,7 +160,7 @@ function groupDeferChildrenIfNeeded(
|
|||
defer: {
|
||||
id: `deferId-${rootId}-${deferBlocks.currentIndex}`,
|
||||
state: currentDeferBlock.state,
|
||||
currentBlock: currentBlock(currentDeferBlock),
|
||||
renderedBlock: getRenderedBlock(currentDeferBlock),
|
||||
triggers: groupTriggers(currentDeferBlock.triggers),
|
||||
blocks: {
|
||||
hasErrorBlock: currentDeferBlock.hasErrorBlock,
|
||||
|
|
@ -213,12 +218,16 @@ function groupTriggers(triggers: string[]) {
|
|||
return {defer, hydrate, prefetch};
|
||||
}
|
||||
|
||||
function currentBlock(deferBlock: DeferBlockData): CurrentDeferBlock | null {
|
||||
function getRenderedBlock(deferBlock: DeferBlockData): RenderedDeferBlock | null {
|
||||
if (['placeholder', 'loading', 'error'].includes(deferBlock.state)) {
|
||||
return deferBlock.state as 'placeholder' | 'loading' | 'error';
|
||||
}
|
||||
if (deferBlock.state === 'complete') {
|
||||
return 'defer';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export class RTreeStrategy {
|
||||
supports(): boolean {
|
||||
return (['getDirectiveMetadata', 'getComponent'] as const).every(
|
||||
|
|
@ -253,7 +262,7 @@ class DeferBlocksIterator {
|
|||
this.currentIndex++;
|
||||
}
|
||||
|
||||
get currentBlock() {
|
||||
get currentBlock(): DeferBlockData | undefined {
|
||||
return this.blocks[this.currentIndex];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,18 +29,22 @@
|
|||
</div>
|
||||
|
||||
@if (node().onPush) {
|
||||
<span class="on-push">OnPush</span>
|
||||
<span class="trait">OnPush</span>
|
||||
}
|
||||
|
||||
@let defer = node().defer;
|
||||
|
||||
@if (!defer && (!hydration || hydration.status !== 'dehydrated')) {
|
||||
<!-- Shown/hidden via CSS -->
|
||||
<span class="console-reference"> == $ng0 </span>
|
||||
<span class="console-reference trait"> == $ng0 </span>
|
||||
}
|
||||
|
||||
@if (defer && defer.currentBlock) {
|
||||
<span class="on-push">(@{{ defer.currentBlock }})</span>
|
||||
@if (defer) {
|
||||
@if (defer.renderedBlock && defer.renderedBlock !== 'defer') {
|
||||
<span class="trait">(@{{ defer.renderedBlock }})</span>
|
||||
} @else if (!defer.renderedBlock) {
|
||||
<span class="trait">(non-rendered)</span>
|
||||
}
|
||||
}
|
||||
|
||||
@switch (hydration?.status) {
|
||||
|
|
|
|||
|
|
@ -99,8 +99,7 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.console-reference,
|
||||
.on-push {
|
||||
.trait {
|
||||
color: var(--color-tree-node-console-ref);
|
||||
padding-left: 8px;
|
||||
font-style: italic;
|
||||
|
|
|
|||
|
|
@ -97,9 +97,9 @@ describe('TreeNodeComponent', () => {
|
|||
});
|
||||
await fixture.whenStable();
|
||||
|
||||
onPush = fixture.debugElement.query(By.css('.on-push'));
|
||||
onPush = fixture.debugElement.query(By.css('.trait'));
|
||||
|
||||
expect(onPush).toBeTruthy();
|
||||
expect(onPush.nativeElement.textContent).toEqual('OnPush');
|
||||
});
|
||||
|
||||
it('should handle selection', async () => {
|
||||
|
|
|
|||
|
|
@ -1,30 +1,36 @@
|
|||
<mat-toolbar>Current block</mat-toolbar>
|
||||
<mat-toolbar>Rendered block</mat-toolbar>
|
||||
<div class="defer-details">
|
||||
<span class="pill"> @{{ defer().currentBlock ?? 'defer' }}</span>
|
||||
@if (defer().renderedBlock) {
|
||||
<span class="pill"> @{{ defer().renderedBlock }}</span>
|
||||
} @else {
|
||||
<em>Nothing rendered yet</em>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (hasDeclaredBlocks()) {
|
||||
@let blocks = defer().blocks;
|
||||
<mat-toolbar>Declared blocks</mat-toolbar>
|
||||
<div class="defer-details">
|
||||
@if (blocks.placeholderBlock.exists) {
|
||||
<span class="pill">
|
||||
@placeholder<!--
|
||||
-->{{
|
||||
blocks.placeholderBlock.minimumTime
|
||||
? `(minimum ${blocks.placeholderBlock.minimumTime} ms)`
|
||||
: ''
|
||||
}}</span
|
||||
>
|
||||
}
|
||||
@if (blocks.loadingBlock.exists) {
|
||||
<span class="pill">@loading{{ loadingBlockInfo() }}</span>
|
||||
}
|
||||
@if (blocks.hasErrorBlock) {
|
||||
<span class="pill">@error</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@let triggers = defer().triggers;
|
||||
@let blocks = defer().blocks;
|
||||
<mat-toolbar>Declared blocks</mat-toolbar>
|
||||
<div class="defer-details">
|
||||
@if (blocks.placeholderBlock) {
|
||||
<span class="pill">
|
||||
@placeholder<!--
|
||||
-->{{
|
||||
blocks.placeholderBlock.minimumTime
|
||||
? `(minimum ${blocks.placeholderBlock.minimumTime} ms)`
|
||||
: ''
|
||||
}}</span
|
||||
>
|
||||
}
|
||||
@if (blocks.loadingBlock) {
|
||||
<span class="pill">@loading{{ loadingBlockInfo() }}</span>
|
||||
}
|
||||
@if (blocks.hasErrorBlock) {
|
||||
<span class="pill">@error</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<mat-toolbar>Triggers</mat-toolbar>
|
||||
<div class="defer-details">
|
||||
<h3>Defer triggers</h3>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export class DeferViewComponent {
|
|||
|
||||
readonly loadingBlockInfo = computed(() => {
|
||||
const loadingBlock = this.defer().blocks.loadingBlock;
|
||||
if (!loadingBlock) {
|
||||
if (!loadingBlock.exists) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -35,4 +35,9 @@ export class DeferViewComponent {
|
|||
}
|
||||
return info.length ? `(${info.join(', ')})` : null;
|
||||
});
|
||||
|
||||
readonly hasDeclaredBlocks = computed(() => {
|
||||
const blocks = this.defer().blocks;
|
||||
return blocks.hasErrorBlock || blocks.placeholderBlock.exists || blocks.loadingBlock.exists;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,12 +78,12 @@ export type HydrationStatus =
|
|||
actualNodeDetails: string | null;
|
||||
};
|
||||
|
||||
export type CurrentDeferBlock = 'placeholder' | 'loading' | 'error';
|
||||
export type RenderedDeferBlock = 'defer' | 'placeholder' | 'loading' | 'error';
|
||||
|
||||
export interface DeferInfo {
|
||||
id: string;
|
||||
state: 'placeholder' | 'loading' | 'complete' | 'error' | 'initial';
|
||||
currentBlock: CurrentDeferBlock | null;
|
||||
renderedBlock: RenderedDeferBlock | null;
|
||||
triggers: {
|
||||
defer: string[];
|
||||
hydrate: string[];
|
||||
|
|
@ -94,8 +94,8 @@ export interface DeferInfo {
|
|||
|
||||
export interface BlockDetails {
|
||||
hasErrorBlock: boolean;
|
||||
placeholderBlock: null | {minimumTime: number | null};
|
||||
loadingBlock: null | {minimumTime: number | null; afterTime: number | null};
|
||||
placeholderBlock: {exists: boolean; minimumTime: number | null};
|
||||
loadingBlock: {exists: boolean; minimumTime: number | null; afterTime: number | null};
|
||||
}
|
||||
|
||||
// TODO: refactor to remove nativeElement as it is not serializable
|
||||
|
|
|
|||
Loading…
Reference in a new issue