Skip to content

Commit d7421f1

Browse files
authored
Merge pull request #7588 from QwikDev/v2-docs-improv
feat(docs): add vnode tree & state to html tab
2 parents a6efe7c + db271f7 commit d7421f1

File tree

14 files changed

+194
-67
lines changed

14 files changed

+194
-67
lines changed

packages/docs/src/components/code-block/code-block.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import styles from './code-block.css?inline';
1515
import { CopyCode } from '../copy-code/copy-code-block';
1616
interface CodeBlockProps {
1717
path?: string;
18-
language?: 'markup' | 'css' | 'javascript' | 'json' | 'jsx' | 'tsx';
18+
language?: 'markup' | 'css' | 'javascript' | 'json' | 'jsx' | 'tsx' | 'clike';
1919
code: string;
2020
pathInView$?: QRL<(name: string) => void>;
2121
observerRootId?: string;

packages/docs/src/repl/repl-output-panel.tsx

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,68 @@
11
import { component$, useComputed$ } from '@qwik.dev/core';
22
import { CodeBlock } from '../components/code-block/code-block';
33
import { ReplOutputModules } from './repl-output-modules';
4-
import { ReplOutputSymbols } from './repl-output-symbols';
4+
import { ReplOutputSegments } from './repl-output-segments';
55
import { ReplTabButton } from './repl-tab-button';
66
import { ReplTabButtons } from './repl-tab-buttons';
77
import type { ReplAppInput, ReplStore } from './types';
8+
import { _deserialize, _getDomContainer } from '@qwik.dev/core/internal';
9+
import { _dumpState, _preprocessState, _vnode_toString } from '@qwik.dev/core/internal';
810

911
export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProps) => {
1012
const diagnosticsLen = useComputed$(
1113
() => store.diagnostics.length + store.monacoDiagnostics.length
1214
);
1315

16+
const domContainerFromResultHtml = useComputed$(() => {
17+
try {
18+
const parser = new DOMParser();
19+
const doc = parser.parseFromString(store.htmlResult.rawHtml, 'text/html');
20+
return _getDomContainer(doc.documentElement);
21+
} catch (err) {
22+
console.error(err);
23+
return null;
24+
}
25+
});
26+
27+
const parsedState = useComputed$(() => {
28+
try {
29+
const container = domContainerFromResultHtml.value;
30+
const doc = container!.element;
31+
const qwikStates = doc.querySelectorAll('script[type="qwik/state"]');
32+
if (qwikStates.length !== 0) {
33+
const data = qwikStates[qwikStates.length - 1];
34+
const origState = JSON.parse(data?.textContent || '[]');
35+
_preprocessState(origState, container as any);
36+
return origState
37+
? _dumpState(origState, false, '', null)
38+
//remove first new line
39+
.replace(/\n/, '')
40+
: 'No state found';
41+
}
42+
return 'No state found';
43+
} catch (err) {
44+
console.error(err);
45+
return null;
46+
}
47+
});
48+
49+
const vdomTree = useComputed$(() => {
50+
try {
51+
const container = domContainerFromResultHtml.value;
52+
return _vnode_toString.call(
53+
container!.rootVNode as any,
54+
Number.MAX_SAFE_INTEGER,
55+
'',
56+
true,
57+
false,
58+
false
59+
);
60+
} catch (err) {
61+
console.error(err);
62+
return null;
63+
}
64+
});
65+
1466
return (
1567
<div class="repl-panel repl-output-panel">
1668
<ReplTabButtons>
@@ -34,10 +86,10 @@ export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProp
3486

3587
{store.enableClientOutput ? (
3688
<ReplTabButton
37-
text="Symbols"
38-
isActive={store.selectedOutputPanel === 'symbols'}
89+
text="Segments"
90+
isActive={store.selectedOutputPanel === 'segments'}
3991
onClick$={async () => {
40-
store.selectedOutputPanel = 'symbols';
92+
store.selectedOutputPanel = 'segments';
4193
}}
4294
/>
4395
) : null}
@@ -103,13 +155,31 @@ export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProp
103155
</div>
104156

105157
{store.selectedOutputPanel === 'html' ? (
106-
<div class="output-result output-html">
107-
<CodeBlock language="markup" code={store.html} />
158+
<div class="output-result output-html flex flex-col gap-2">
159+
<div>
160+
<span class="code-block-info">HTML</span>
161+
<CodeBlock
162+
language="markup"
163+
code={store.htmlResult.prettyHtml || store.htmlResult.rawHtml}
164+
/>
165+
</div>
166+
{parsedState.value ? (
167+
<div>
168+
<span class="code-block-info">Parsed State</span>
169+
<CodeBlock language="clike" code={parsedState.value} />
170+
</div>
171+
) : null}
172+
{vdomTree.value ? (
173+
<div>
174+
<span class="code-block-info">VNode Tree</span>
175+
<CodeBlock language="markup" code={vdomTree.value} />
176+
</div>
177+
) : null}
108178
</div>
109179
) : null}
110180

111-
{store.selectedOutputPanel === 'symbols' ? (
112-
<ReplOutputSymbols outputs={store.transformedModules} />
181+
{store.selectedOutputPanel === 'segments' ? (
182+
<ReplOutputSegments outputs={store.transformedModules} />
113183
) : null}
114184

115185
{store.selectedOutputPanel === 'clientBundles' ? (

packages/docs/src/repl/repl-output-symbols.tsx renamed to packages/docs/src/repl/repl-output-segments.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { TransformModule } from '@qwik.dev/core/optimizer';
33
import { CodeBlock } from '../components/code-block/code-block';
44
const FILE_MODULE_DIV_ID = 'file-modules-symbol';
55

6-
export const ReplOutputSymbols = component$(({ outputs }: ReplOutputSymbolsProps) => {
6+
export const ReplOutputSegments = component$(({ outputs }: ReplOutputSegmentsProps) => {
77
const selectedPath = useSignal(outputs.length ? outputs[0].path : '');
88
const pathInView$ = $((path: string) => {
99
selectedPath.value = path;
@@ -12,7 +12,7 @@ export const ReplOutputSymbols = component$(({ outputs }: ReplOutputSymbolsProps
1212
return (
1313
<div class="output-result output-modules">
1414
<div class="file-tree">
15-
<div class="file-tree-header">Symbols</div>
15+
<div class="file-tree-header">Segments</div>
1616
<div class="file-tree-items">
1717
{outputs.map((o, i) => (
1818
<div key={o.path}>
@@ -68,6 +68,6 @@ export const ReplOutputSymbols = component$(({ outputs }: ReplOutputSymbolsProps
6868
);
6969
});
7070

71-
interface ReplOutputSymbolsProps {
71+
interface ReplOutputSegmentsProps {
7272
outputs: TransformModule[];
7373
}

packages/docs/src/repl/repl-output-update.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@ const matchByPath = (a: any, b: any) => a.path === b.path;
3030

3131
export const updateReplOutput = async (store: ReplStore, result: ReplResult) => {
3232
deepUpdate(store.diagnostics, result.diagnostics);
33+
if (store.htmlResult.rawHtml !== result.htmlResult.rawHtml) {
34+
store.htmlResult.rawHtml = result.htmlResult.rawHtml;
35+
store.htmlResult.prettyHtml = result.htmlResult.prettyHtml;
36+
}
3337

3438
if (result.diagnostics.length === 0) {
35-
if (store.html !== result.html) {
36-
store.html = result.html;
37-
}
38-
39+
deepUpdate(store.htmlResult, result.htmlResult);
3940
deepUpdate(store.transformedModules, result.transformedModules, matchByPath);
4041
deepUpdate(store.clientBundles, result.clientBundles, matchByPath);
4142
deepUpdate(store.ssrModules, result.ssrModules, matchByPath);

packages/docs/src/repl/repl.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,15 @@
199199
overflow: auto;
200200
}
201201

202+
.output-html .code-block-info {
203+
display: block;
204+
font-weight: bold;
205+
background-color: rgb(33 104 170 / 15%);
206+
padding: 5px 10px;
207+
overflow: hidden;
208+
text-overflow: ellipsis;
209+
}
210+
202211
.output-html pre {
203212
height: 100%;
204213
}

packages/docs/src/repl/repl.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ export const Repl = component$((props: ReplProps) => {
2727
clientId: Math.round(Math.random() * Number.MAX_SAFE_INTEGER)
2828
.toString(36)
2929
.toLowerCase(),
30-
html: '',
30+
htmlResult: {
31+
rawHtml: '',
32+
prettyHtml: '',
33+
},
3134
transformedModules: [],
3235
clientBundles: [],
3336
ssrModules: [],

packages/docs/src/repl/types.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface ReplInputOptions extends Omit<QwikRollupPluginOptions, 'srcDir'
2727

2828
export interface ReplStore {
2929
clientId: string;
30-
html: string;
30+
htmlResult: ReplHTMLResult;
3131
transformedModules: TransformModule[];
3232
clientBundles: ReplModuleOutput[];
3333
ssrModules: ReplModuleOutput[];
@@ -112,7 +112,7 @@ export interface ReplEvent {
112112
export interface ReplResult extends ReplMessageBase {
113113
type: 'result';
114114
buildId: number;
115-
html: string;
115+
htmlResult: ReplHTMLResult;
116116
transformedModules: TransformModule[];
117117
clientBundles: ReplModuleOutput[];
118118
ssrModules: ReplModuleOutput[];
@@ -121,10 +121,16 @@ export interface ReplResult extends ReplMessageBase {
121121
events: ReplEvent[];
122122
}
123123

124+
export interface ReplHTMLResult {
125+
rawHtml: string;
126+
prettyHtml: string;
127+
}
128+
124129
export type OutputPanel =
125130
| 'app'
126131
| 'html'
127-
| 'symbols'
132+
| 'state'
133+
| 'segments'
128134
| 'clientBundles'
129135
| 'serverModules'
130136
| 'diagnostics';

packages/docs/src/repl/worker/app-ssr-html.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export const appSsrHtml = async (options: ReplInputOptions, cache: Cache, result
8282
console.error = error;
8383
console.debug = debug;
8484

85-
result.html = ssrResult.html;
85+
result.htmlResult.rawHtml = ssrResult.html;
8686

8787
result.events.push({
8888
kind: 'pause',
@@ -94,12 +94,12 @@ export const appSsrHtml = async (options: ReplInputOptions, cache: Cache, result
9494

9595
if (options.buildMode !== 'production') {
9696
try {
97-
const html = await self.prettier?.format(result.html, {
97+
const html = await self.prettier?.format(result.htmlResult.rawHtml, {
9898
parser: 'html',
9999
plugins: self.prettierPlugins,
100100
});
101101
if (html) {
102-
result.html = html;
102+
result.htmlResult.prettyHtml = html;
103103
}
104104
} catch (e) {
105105
console.error(e);

packages/docs/src/repl/worker/app-update.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ export const appUpdate = async (
1515
type: 'result',
1616
clientId,
1717
buildId: options.buildId,
18-
html: '',
18+
htmlResult: {
19+
rawHtml: '',
20+
prettyHtml: '',
21+
},
1922
transformedModules: [],
2023
clientBundles: [],
2124
manifest: undefined,

packages/qwik/src/core/client/vnode.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,12 +1718,14 @@ export const vnode_getNode = (vnode: VNode | null): Element | Text | null => {
17181718
return vnode[TextVNodeProps.node]!;
17191719
};
17201720

1721+
/** @internal */
17211722
export function vnode_toString(
17221723
this: VNode | null,
17231724
depth: number = 20,
17241725
offset: string = '',
17251726
materialize: boolean = false,
1726-
siblings = false
1727+
siblings = false,
1728+
colorize: boolean = true
17271729
): string {
17281730
let vnode = this;
17291731
if (depth === 0) {
@@ -1736,6 +1738,8 @@ export function vnode_toString(
17361738
return 'undefined';
17371739
}
17381740
const strings: string[] = [];
1741+
const NAME_COL_PREFIX = '\x1b[34m';
1742+
const NAME_COL_SUFFIX = '\x1b[0m';
17391743
do {
17401744
if (vnode_isTextVNode(vnode)) {
17411745
strings.push(qwikDebugToString(vnode_getText(vnode)));
@@ -1749,12 +1753,16 @@ export function vnode_toString(
17491753
}
17501754
});
17511755
const name =
1752-
VirtualTypeName[vnode_getAttr(vnode, DEBUG_TYPE) || VirtualType.Virtual] ||
1753-
VirtualTypeName[VirtualType.Virtual];
1756+
(colorize ? NAME_COL_PREFIX : '') +
1757+
(VirtualTypeName[vnode_getAttr(vnode, DEBUG_TYPE) || VirtualType.Virtual] ||
1758+
VirtualTypeName[VirtualType.Virtual]) +
1759+
(colorize ? NAME_COL_SUFFIX : '');
17541760
strings.push('<' + name + attrs.join('') + '>');
17551761
const child = vnode_getFirstChild(vnode);
17561762
child &&
1757-
strings.push(' ' + vnode_toString.call(child, depth - 1, offset + ' ', true, true));
1763+
strings.push(
1764+
' ' + vnode_toString.call(child, depth - 1, offset + ' ', true, true, colorize)
1765+
);
17581766
strings.push('</' + name + '>');
17591767
} else if (vnode_isElementVNode(vnode)) {
17601768
const tag = vnode_getElementName(vnode);
@@ -1782,7 +1790,9 @@ export function vnode_toString(
17821790
if (vnode_isMaterialized(vnode) || materialize) {
17831791
const child = vnode_getFirstChild(vnode);
17841792
child &&
1785-
strings.push(' ' + vnode_toString.call(child, depth - 1, offset + ' ', true, true));
1793+
strings.push(
1794+
' ' + vnode_toString.call(child, depth - 1, offset + ' ', true, true, colorize)
1795+
);
17861796
} else {
17871797
strings.push(' <!-- not materialized --!>');
17881798
}

0 commit comments

Comments
 (0)