Skip to content

Commit 4c28bc7

Browse files
Merge pull request #417 from preactjs/root-order
2 parents 01fcafc + b639829 commit 4c28bc7

File tree

18 files changed

+410
-39
lines changed

18 files changed

+410
-39
lines changed

src/adapter/10/options.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
getStatefulHooks,
88
getStatefulHookValue,
99
getComponent,
10+
isRoot,
11+
getActualChildren,
1012
} from "./bindings";
1113
import {
1214
addDebugValue,
@@ -42,6 +44,7 @@ function trackPrevState(Ctor: ComponentConstructor) {
4244
export function setupOptionsV10(
4345
options: Options,
4446
renderer: Renderer,
47+
roots: Map<VNode, Node>,
4548
config: RendererConfig,
4649
) {
4750
// Track component state. Only supported in Preact > 10.4.0
@@ -58,6 +61,7 @@ export function setupOptionsV10(
5861
// Store (possible) previous hooks so that we don't overwrite them
5962
const prevVNodeHook = options.vnode;
6063
const prevCommitRoot = o._commit || o.__c;
64+
const prevRoot = o._root || o.__;
6165
const prevBeforeUnmount = options.unmount;
6266
const prevBeforeDiff = o._diff || o.__b;
6367
const prevRender = o._render || o.__r;
@@ -164,18 +168,44 @@ export function setupOptionsV10(
164168
if (prevAfterDiff) prevAfterDiff(vnode);
165169
};
166170

171+
const userRootToContainer = new Map<VNode, Node>();
167172
o._commit = o.__c = (vnode: VNode | null, queue: any[]) => {
168173
if (prevCommitRoot) prevCommitRoot(vnode, queue);
169174

170175
// These cases are already handled by `unmount`
171176
if (vnode == null) return;
177+
if (isRoot(vnode, config)) {
178+
const children = getActualChildren(vnode);
179+
if (children.length > 0) {
180+
const dom = userRootToContainer.get(children[0] as any);
181+
if (dom) {
182+
roots.set(vnode, dom);
183+
}
184+
}
185+
}
172186

173187
const tmpTimings = timings;
174188
ownerStack = [];
175189
timings = createVNodeTimings();
176190
renderer.onCommit(vnode, owners, tmpTimings, null);
177191
};
178192

193+
o._root = o.__ = (vnode: VNode, parent: Node | null) => {
194+
if (parent === null) {
195+
userRootToContainer.delete(vnode);
196+
} else {
197+
// Some islands based frameworks use a virtual container node
198+
// instead of an actual DOM node.
199+
const treeParent =
200+
"Node" in globalThis && parent instanceof Node
201+
? parent
202+
: (parent as any).parentNode;
203+
userRootToContainer.set(vnode, treeParent);
204+
}
205+
206+
if (prevRoot) prevRoot(vnode, parent);
207+
};
208+
179209
options.unmount = vnode => {
180210
if (prevBeforeUnmount) prevBeforeUnmount(vnode);
181211
if (vnode.type !== null) {

src/adapter/11/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ function trackPrevState(Ctor: any) {
9090
export function setupOptionsV11(
9191
options: OptionsV11,
9292
renderer: Renderer,
93+
roots: Map<any, Node>,
9394
config: RendererConfig,
9495
profiler: ProfilerState,
9596
) {

src/adapter/adapter/adapter.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { PortPageHook } from "./port";
99
import { PropData } from "../../view/components/sidebar/inspect/parseProps";
1010
import { PROFILE_RELOAD, STATS_RELOAD } from "../../constants";
1111
import { ProfilerState } from "./profiler";
12+
import { RootData, sortRoots } from "../shared/utils";
1213

1314
export type Path = Array<string | number>;
1415

@@ -44,7 +45,7 @@ export function createAdapter(
4445
profiler: ProfilerState,
4546
renderers: Map<number, Renderer>,
4647
) {
47-
const { listen, send } = port;
48+
const { listen, send, listenToPage } = port;
4849

4950
const forAll = (fn: (renderer: Renderer) => void) => {
5051
for (const r of renderers.values()) {
@@ -131,6 +132,17 @@ export function createAdapter(
131132
inspect(id);
132133
};
133134

135+
listenToPage("root-order-page", () => {
136+
let roots: RootData[] = [];
137+
renderers.forEach(r => {
138+
const m = r.getRootMappings();
139+
roots = roots.concat(m);
140+
});
141+
142+
const sorted = sortRoots(document.body, roots);
143+
send("root-order", sorted);
144+
});
145+
134146
listen("update-prop", data => update({ ...data, type: "props" }));
135147
listen("update-state", data => update({ ...data, type: "state" }));
136148
listen("update-context", data => update({ ...data, type: "context" }));

src/adapter/adapter/port.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ export function listenToDevtools<
1818
});
1919
}
2020

21+
export function listenToPageHook<
22+
K extends keyof DevtoolEvents,
23+
T extends DevtoolEvents[K]
24+
>(ctx: Window, type: K, callback: (message: T) => void) {
25+
ctx.addEventListener("message", e => {
26+
if (e.source === window && e.data.source === PageHookName) {
27+
const data = e.data;
28+
if (data.type === type) callback(data.data);
29+
}
30+
});
31+
}
32+
2133
export function sendToDevtools<K extends keyof DevtoolEvents>(
2234
ctx: Window,
2335
type: K,
@@ -46,11 +58,16 @@ export interface PortPageHook {
4658
type: K,
4759
callback: (data: T) => void,
4860
) => void;
61+
listenToPage: <K extends keyof DevtoolEvents, T extends DevtoolEvents[K]>(
62+
type: K,
63+
callback: (data: T) => void,
64+
) => void;
4965
}
5066

5167
export function createPortForHook(ctx: Window): PortPageHook {
5268
return {
5369
send: (type, message) => sendToDevtools(ctx, type, message),
5470
listen: (type, callback) => listenToDevtools(ctx, type, callback),
71+
listenToPage: (type, callback) => listenToPageHook(ctx, type, callback),
5572
};
5673
}

src/adapter/hook.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ export interface DevtoolEvents {
5858
refresh: null;
5959
disconnect: null;
6060
suspend: { id: ID; active: boolean };
61+
"root-order-page": null;
62+
"root-order": number[];
63+
operation: number[];
64+
operation_v2: number[];
6165
}
6266
export type EmitFn = <K extends keyof DevtoolEvents>(
6367
name: K,
@@ -206,6 +210,8 @@ export function createHook(port: PortPageHook): DevtoolsHook {
206210
// multiple connected renderers
207211
const namespace = Math.floor(Math.random() * 2 ** 32);
208212

213+
const roots = new Map<any, Node>();
214+
209215
// currently we only support preact >= 10, later we can add another branch for major === 8
210216
if (preactVersionMatch.major == 10) {
211217
const supports = {
@@ -230,8 +236,9 @@ export function createHook(port: PortPageHook): DevtoolsHook {
230236
filters,
231237
idMapper,
232238
bindingsV10,
239+
roots,
233240
);
234-
setupOptionsV10(options, renderer, config as any);
241+
setupOptionsV10(options, renderer, roots, config as any);
235242
return attachRenderer(renderer, supports);
236243
} else if (preactVersionMatch.major === 11) {
237244
const idMapper = createIdMappingState(
@@ -248,8 +255,9 @@ export function createHook(port: PortPageHook): DevtoolsHook {
248255
filters,
249256
idMapper,
250257
bindingsV11,
258+
roots,
251259
);
252-
setupOptionsV11(options as any, renderer, config, profiler);
260+
setupOptionsV11(options as any, renderer, roots, config, profiler);
253261
return attachRenderer(renderer, {
254262
hooks: true,
255263
renderReasons: true,

src/adapter/protocol/events.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { recordProfilerCommit } from "../../view/components/profiler/data/commit
55
import { ops2Tree } from "./operations";
66
import { applyOperationsV1 } from "./legacy/operationsV1";
77
import { OperationInfo, Stats, stats2ops } from "../shared/stats";
8+
import { DevtoolEvents } from "../hook";
89

910
export enum MsgTypes {
1011
ADD_ROOT = 1,
@@ -192,7 +193,7 @@ export function applyOperationsV2(store: Store, data: number[]) {
192193
}
193194
}
194195

195-
export function applyEvent(store: Store, type: string, data: any) {
196+
export function applyEvent(store: Store, type: keyof DevtoolEvents, data: any) {
196197
switch (type) {
197198
case "attach":
198199
if (!store.profiler.isSupported.value) {
@@ -238,5 +239,19 @@ export function applyEvent(store: Store, type: string, data: any) {
238239
case "stop-picker":
239240
store.isPicking.value = false;
240241
break;
242+
case "root-order": {
243+
const oldRoots = store.roots.value;
244+
const newOrder = new Set(data);
245+
246+
for (let i = 0; i < oldRoots.length; i++) {
247+
const id = oldRoots[i];
248+
if (!newOrder.has(id)) {
249+
data.push(id);
250+
}
251+
}
252+
253+
store.roots.value = data;
254+
break;
255+
}
241256
}
242257
}

src/adapter/renderer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { InspectData, UpdateType } from "./adapter/adapter";
44
import { SharedVNode } from "./shared/bindings";
55
import { VNodeTimings } from "./shared/timings";
66
import { RenderReasonData } from "./shared/renderReasons";
7+
import { RootData } from "./shared/utils";
78

89
export type ObjPath = Array<string | number>;
910

@@ -31,6 +32,9 @@ export interface Renderer<T extends SharedVNode = SharedVNode> {
3132

3233
// Component actions
3334
suspend?(id: ID, active: boolean): void; // V4
35+
36+
// Get a list of root node mappings
37+
getRootMappings(): RootData[];
3438
}
3539

3640
export function getRendererByVNodeId(

0 commit comments

Comments
 (0)