Skip to content

Commit ade318a

Browse files
authored
feat: add preload/build analysis tooling and richer devtools instrumentation (#93)
1 parent 1ceb277 commit ade318a

79 files changed

Lines changed: 5113 additions & 1240 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
File renamed without changes.
File renamed without changes.

.changeset/huge-clubs-rest.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@qwik.dev/devtools': patch
3+
---
4+
5+
feat: add preload/build analysis tooling and richer devtools instrumentation
6+
7+
- Added new `Preloads` and `Build Analysis` panels, plus an improved `Inspect` view that resolves correctly from the app base URL on deep routes.
8+
- Added runtime instrumentation for SSR/CSR performance and preload tracking, including SSR preload snapshots, QRL-to-resource correlation, and richer diagnostics surfaced in DevTools.
9+
- Expanded the plugin and RPC layer to generate and serve build-analysis reports, expose the new preload/performance data to the UI, and add server-side guards around build-analysis execution.

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,7 @@ testem.log
6565
Thumbs.db
6666
.vite-inspect
6767
.pnpm-store/*
68-
related-qwik/*
69-
.cursor/skills/*
68+
qwik/*
69+
.cursor/skills/*
70+
related-folder/**
71+
**/.qwik-devtools/

packages/devtools/package.json

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,24 @@
33
"version": "0.2.8",
44
"license": "MIT",
55
"main": "./dist/plugin/index.mjs",
6+
"types": "./dist/plugin/index.d.mts",
67
"description": "Qwik devtools package",
78
"qwik": "./dist/ui/index.qwik.mjs",
89
"exports": {
910
".": {
10-
"import": "./dist/plugin/index.js",
11-
"types": "./dist/plugin/index.d.ts"
11+
"import": "./dist/plugin/index.mjs",
12+
"types": "./dist/plugin/index.d.mts"
1213
},
1314
"./ui": {
1415
"import": "./dist/ui/index.qwik.mjs",
1516
"types": "./dist/ui/lib-types/ui/src/index.d.ts",
16-
"style": "./dist/ui/style.css"
17+
"style": "./dist/ui/styles.css"
18+
},
19+
"./ui/styles.css": {
20+
"import": "./dist/ui/styles.css"
1721
},
1822
"./style": {
19-
"import": "./dist/ui/theme.css"
23+
"import": "./dist/ui/styles.css"
2024
}
2125
},
2226
"files": [
@@ -35,6 +39,7 @@
3539
"birpc": "^4.0.0",
3640
"dree": "^5.1.5",
3741
"oxc-parser": "^0.120.0",
42+
"rollup-plugin-visualizer": "^6.0.3",
3843
"superjson": "^2.2.6",
3944
"vite-hot-client": "^2.1.0",
4045
"vite-plugin-inspect": "^11.3.3"
@@ -64,4 +69,4 @@
6469
"url": "https://github.com/QwikDev/devtools/issues"
6570
},
6671
"homepage": "https://github.com/QwikDev/devtools#readme"
67-
}
72+
}

packages/kit/src/client.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
1-
import SuperJSON from 'superjson';
21
import { ClientFunctions, ServerFunctions } from './types';
3-
import { createBirpc } from 'birpc';
42
import { DEVTOOLS_VITE_MESSAGING_EVENT } from './constants';
53
import { getViteClientContext, setViteClientRpc } from './context';
4+
import { createSerializedRpc } from './rpc-core';
65

76
export function createClientRpc(functions: ClientFunctions) {
87
const client = getViteClientContext();
98

10-
const rpc = createBirpc<ServerFunctions, ClientFunctions>(functions, {
11-
post: (data) =>
12-
client.send(DEVTOOLS_VITE_MESSAGING_EVENT, SuperJSON.stringify(data)),
13-
on: (fn) =>
9+
const rpc = createSerializedRpc<ServerFunctions, ClientFunctions>(functions, {
10+
post: (data) => client.send(DEVTOOLS_VITE_MESSAGING_EVENT, data),
11+
on: (handler) =>
1412
client.on(DEVTOOLS_VITE_MESSAGING_EVENT, (data) => {
15-
fn(SuperJSON.parse(data));
13+
handler(data);
1614
}),
17-
timeout: 120_000,
1815
});
1916

2017
setViteClientRpc(rpc);

packages/kit/src/constants.ts

Lines changed: 41 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,42 @@
11
export const DEVTOOLS_VITE_MESSAGING_EVENT = 'qwik_tools:vite_messaging_event';
2-
export const USE_HOOK_LIST = [
3-
'useAsyncComputed',
4-
'useComputed',
5-
'useConstant',
6-
'useContext',
7-
'useContextProvider',
8-
'useErrorBoundary',
9-
'useId',
10-
'useOn',
11-
'useOnDocument',
12-
'useOnWindow',
13-
'useResource',
14-
'useSerializer',
15-
'useServerData',
16-
'useSignal',
17-
'useStore',
18-
'useStyles',
19-
'useStylesScoped',
20-
'useTask',
21-
'useVisibleTask',
22-
'useLocation',
23-
'useNavigate',
24-
'usePreventNavigate',
25-
'useContent',
26-
'useDocumentHead',
27-
] as const
28-
29-
30-
export const VARIABLE_DECLARATION_LIST = [
31-
'useStore',
32-
'useSignal',
33-
'useComputed',
34-
'useAsyncComputed',
35-
'useContext',
36-
'useId',
37-
'useStyles',
38-
'useStylesScoped',
39-
'useConstant',
40-
'useErrorBoundary',
41-
'useSerializer',
42-
'useServerData',
43-
'useLocation',
44-
'useNavigate',
45-
'useContent',
46-
'useDocumentHead',
47-
] as const
482

49-
50-
export const EXPRESSION_STATEMENT_LIST = [
51-
'useVisibleTask',
52-
'useTask',
53-
'useResource',
54-
'useContextProvider',
55-
'usePreventNavigate',
56-
] as const
3+
const HOOK_GROUPS = {
4+
variableDeclaration: [
5+
'useStore',
6+
'useSignal',
7+
'useComputed',
8+
'useAsyncComputed',
9+
'useContext',
10+
'useId',
11+
'useStyles',
12+
'useStylesScoped',
13+
'useConstant',
14+
'useErrorBoundary',
15+
'useSerializer',
16+
'useServerData',
17+
'useLocation',
18+
'useNavigate',
19+
'useContent',
20+
'useDocumentHead',
21+
] as const,
22+
expressionStatement: [
23+
'useVisibleTask',
24+
'useTask',
25+
'useResource',
26+
'useContextProvider',
27+
'usePreventNavigate',
28+
] as const,
29+
listener: ['useOn', 'useOnDocument', 'useOnWindow'] as const,
30+
noReturn: ['useVisibleTask', 'useTask'] as const,
31+
} as const;
32+
33+
export const VARIABLE_DECLARATION_LIST = HOOK_GROUPS.variableDeclaration;
34+
export const EXPRESSION_STATEMENT_LIST = HOOK_GROUPS.expressionStatement;
35+
export const USE_HOOK_LIST = [
36+
...VARIABLE_DECLARATION_LIST,
37+
...HOOK_GROUPS.listener,
38+
...EXPRESSION_STATEMENT_LIST,
39+
] as const;
5740

5841
export const QSEQ = 'q:seq';
5942
export const QPROPS = 'q:props';
@@ -62,13 +45,14 @@ export const QTYPE = 'q:type';
6245

6346
export const VIRTUAL_QWIK_DEVTOOLS_KEY = 'virtual-qwik-devtools.ts';
6447

65-
export const INNER_USE_HOOK= 'useCollectHooks'
48+
export const INNER_USE_HOOK = 'useCollectHooks';
6649

67-
export const QWIK_DEVTOOLS_GLOBAL_STATE = 'QWIK_DEVTOOLS_GLOBAL_STATE'
50+
export const QWIK_DEVTOOLS_GLOBAL_STATE = 'QWIK_DEVTOOLS_GLOBAL_STATE';
51+
export const QWIK_PRELOADS_UPDATE_EVENT = 'qwik:preloads-update';
6852

6953
export const QRL_KEY = '$qrl$';
7054
export const COMPUTED_QRL_KEY = '$computeQrl$';
7155
export const CHUNK_KEY = '$chunk$';
7256
export const CAPTURE_REF_KEY = '$captureRef$';
7357

74-
export const NORETURN_HOOK = [ 'useVisibleTask', 'useTask'] as const
58+
export const NORETURN_HOOK = HOOK_GROUPS.noReturn;

packages/kit/src/context.ts

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { target } from './shared';
21
import {
32
ViteClientContext,
43
CLIENT_CTX,
@@ -8,38 +7,23 @@ import {
87
CLIENT_RPC,
98
} from './globals';
109
import { ServerRpc, ClientRpc } from './types';
10+
import { createGlobalAccessor } from './global-store';
1111

12-
type GlobalTarget = Record<string, unknown>;
13-
const t = target as unknown as GlobalTarget;
12+
const clientContextAccessor =
13+
createGlobalAccessor<ViteClientContext>(CLIENT_CTX);
14+
const serverContextAccessor =
15+
createGlobalAccessor<ViteServerContext>(SERVER_CTX);
16+
const serverRpcAccessor = createGlobalAccessor<ServerRpc>(SERVER_RPC);
17+
const clientRpcAccessor = createGlobalAccessor<ClientRpc>(CLIENT_RPC);
1418

15-
export function getViteClientContext(): ViteClientContext {
16-
return t[CLIENT_CTX] as ViteClientContext;
17-
}
19+
export const getViteClientContext = clientContextAccessor.get;
20+
export const setViteClientContext = clientContextAccessor.set;
1821

19-
export function setViteClientContext(ctx: ViteClientContext) {
20-
t[CLIENT_CTX] = ctx;
21-
}
22+
export const getViteServerContext = serverContextAccessor.get;
23+
export const setViteServerContext = serverContextAccessor.set;
2224

23-
export function getViteServerContext() {
24-
return t[SERVER_CTX] as ViteServerContext;
25-
}
25+
export const getViteServerRpc = serverRpcAccessor.get;
26+
export const setViteServerRpc = serverRpcAccessor.set;
2627

27-
export function setViteServerContext(ctx: ViteServerContext) {
28-
t[SERVER_CTX] = ctx;
29-
}
30-
31-
export function getViteServerRpc() {
32-
return t[SERVER_RPC] as ServerRpc;
33-
}
34-
35-
export function setViteServerRpc(rpc: ServerRpc) {
36-
t[SERVER_RPC] = rpc;
37-
}
38-
39-
export function getViteClientRpc() {
40-
return t[CLIENT_RPC] as ClientRpc;
41-
}
42-
43-
export function setViteClientRpc(rpc: ClientRpc) {
44-
t[CLIENT_RPC] = rpc;
45-
}
28+
export const getViteClientRpc = clientRpcAccessor.get;
29+
export const setViteClientRpc = clientRpcAccessor.set;

packages/kit/src/global-store.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { target } from './shared';
2+
3+
type GlobalStore = Record<string, unknown>;
4+
5+
const globalStore = target as unknown as GlobalStore;
6+
7+
export function createGlobalAccessor<T>(key: string) {
8+
return {
9+
get: () => globalStore[key] as T,
10+
set: (value: T) => {
11+
globalStore[key] = value;
12+
},
13+
};
14+
}

packages/kit/src/globals.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,82 @@ export interface QwikPerfStoreRemembered {
4646

4747
}
4848

49+
export type QwikPreloadStatus = 'pending' | 'loaded' | 'error' | 'unknown';
50+
export type QwikPreloadSource =
51+
| 'initial-dom'
52+
| 'mutation'
53+
| 'performance'
54+
| 'qrl-correlation';
55+
export type QwikPreloadOriginKind =
56+
| 'current-project'
57+
| 'vite-plugin-injected'
58+
| 'node_modules'
59+
| 'virtual-module'
60+
| 'generated'
61+
| 'external'
62+
| 'unknown';
63+
export type QwikPreloadPhase = 'csr' | 'ssr' | 'unknown';
64+
export type QwikPreloadMatchMode =
65+
| 'href'
66+
| 'normalized-href'
67+
| 'chunk-hash'
68+
| 'resource-name'
69+
| 'none';
70+
export type QwikPreloadLoadMatchQuality = 'best-effort' | 'none';
71+
72+
export interface QwikPreloadQrlRequestRemembered {
73+
symbol: string;
74+
href?: string;
75+
normalizedHref?: string;
76+
requestedAt: number;
77+
originKind?: QwikPreloadOriginKind;
78+
phase?: QwikPreloadPhase;
79+
matchedEntryId?: number;
80+
}
81+
82+
export interface QwikPreloadEntryRemembered {
83+
id: number;
84+
href: string;
85+
normalizedHref: string;
86+
rel: string;
87+
as: string;
88+
resourceType: string;
89+
status: QwikPreloadStatus;
90+
source: QwikPreloadSource;
91+
originKind: QwikPreloadOriginKind;
92+
phase: QwikPreloadPhase;
93+
discoveredAt: number;
94+
requestedAt?: number;
95+
completedAt?: number;
96+
importDuration?: number;
97+
loadDuration?: number;
98+
duration?: number;
99+
transferSize?: number;
100+
decodedBodySize?: number;
101+
initiatorType?: string;
102+
qrlSymbol?: string;
103+
qrlRequestedAt?: number;
104+
qrlToLoadDuration?: number;
105+
loadMatchQuality?: QwikPreloadLoadMatchQuality;
106+
matchedBy: QwikPreloadMatchMode;
107+
error?: string;
108+
}
109+
110+
export type QwikSsrPreloadSnapshotRemembered =
111+
Partial<QwikPreloadEntryRemembered> &
112+
Pick<QwikPreloadEntryRemembered, 'href'>;
113+
114+
export interface QwikPreloadStoreRemembered {
115+
entries: QwikPreloadEntryRemembered[];
116+
qrlRequests: QwikPreloadQrlRequestRemembered[];
117+
startedAt: number;
118+
clear: () => void;
119+
_id: number;
120+
_initialized: boolean;
121+
_byHref: Record<string, number>;
122+
_byId: Record<number, QwikPreloadEntryRemembered>;
123+
}
124+
49125
export interface DevtoolsRenderStats {
50126
/**
51127
* In-memory performance store written by devtools instrumentation.
@@ -67,6 +143,8 @@ declare global {
67143
* Written by `@devtools/plugin` instrumentation.
68144
*/
69145
__QWIK_PERF__?: QwikPerfStoreRemembered;
146+
__QWIK_PRELOADS__?: QwikPreloadStoreRemembered;
147+
__QWIK_SSR_PRELOADS__?: QwikSsrPreloadSnapshotRemembered[];
70148
}
71149
}
72150

@@ -76,6 +154,7 @@ declare global {
76154
namespace NodeJS {
77155
interface Process {
78156
__QWIK_SSR_PERF__?: QwikPerfEntryRemembered[];
157+
__QWIK_SSR_PRELOADS__?: QwikSsrPreloadSnapshotRemembered[];
79158
__QWIK_SSR_PERF_SET__?: Set<string>;
80159
__QWIK_SSR_PERF_ID__?: number;
81160
__QWIK_SSR_PERF_INDEX__?: Record<string, number>;

0 commit comments

Comments
 (0)