diff --git a/.changeset/proud-ears-wonder.md b/.changeset/proud-ears-wonder.md new file mode 100644 index 0000000..32cbba6 --- /dev/null +++ b/.changeset/proud-ears-wonder.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/devtools': patch +--- + +chore: update package versions and add CodeBreack feature diff --git a/packages/devtools/package.json b/packages/devtools/package.json index 82be54c..c39f4cb 100644 --- a/packages/devtools/package.json +++ b/packages/devtools/package.json @@ -21,8 +21,8 @@ "README.md" ], "peerDependencies": { - "@qwik.dev/core": "2.0.0-beta.9", - "@qwik.dev/router": "2.0.0-beta.9", + "@qwik.dev/core": "2.0.0-beta.11", + "@qwik.dev/router": "2.0.0-beta.11", "vite": "7.1.3" }, "dependencies": { diff --git a/packages/kit/package.json b/packages/kit/package.json index ac5b5fd..4c9c6b7 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -22,7 +22,7 @@ "superjson": "^2.2.2" }, "peerDependencies": { - "vite": "^6.2.6" + "vite": "^7.1.3" }, "devDependencies": { "@types/eslint": "8.56.10", @@ -31,7 +31,7 @@ "@typescript-eslint/parser": "7.16.1", "cpy-cli": "^5.0.0", "eslint": "8.57.0", - "eslint-plugin-qwik": "2.0.0-beta.9", + "eslint-plugin-qwik": "2.0.0-beta.11", "np": "^8.0.4", "prettier": "3.3.3", "typescript": "5.4.5", diff --git a/packages/playgrounds/package.json b/packages/playgrounds/package.json index 2f0c894..fa4d337 100644 --- a/packages/playgrounds/package.json +++ b/packages/playgrounds/package.json @@ -29,14 +29,14 @@ "devDependencies": { "@devtools/plugin": "workspace:*", "@devtools/ui": "workspace:*", - "@qwik.dev/core": "2.0.0-beta.9", - "@qwik.dev/router": "2.0.0-beta.9", + "@qwik.dev/core": "2.0.0-beta.11", + "@qwik.dev/router": "2.0.0-beta.11", "@types/eslint": "8.56.10", "@types/node": "20.14.11", "@typescript-eslint/eslint-plugin": "7.16.1", "@typescript-eslint/parser": "7.16.1", "eslint": "8.57.0", - "eslint-plugin-qwik": "2.0.0-beta.9", + "eslint-plugin-qwik": "2.0.0-beta.11", "prettier": "3.3.3", "typescript": "5.4.5", "vite": "7.1.3", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index c9295ea..3de2118 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -24,7 +24,7 @@ "devDependencies": { "@babel/types": "^7.26.7", "@devtools/kit": "workspace:*", - "@qwik.dev/core": "2.0.0-beta.9", + "@qwik.dev/core": "2.0.0-beta.11", "@types/eslint": "8.56.10", "@types/node": "20.14.11", "@typescript-eslint/eslint-plugin": "7.16.1", diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index 480b3c3..5986854 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -35,7 +35,7 @@ export function qwikDevtools(): Plugin[] { ) { return { code: useCollectHooksSource, - map: null, + map: { mappings: '' }, }; } }, @@ -57,8 +57,11 @@ export function qwikDevtools(): Plugin[] { // Ensure virtual import is present at the very top once when a component$ is present if (id.endsWith('.tsx') && code.includes('component$')) { if (!code.includes(VIRTUAL_QWIK_DEVTOOLS_KEY)) { + const importLine = `import { ${INNER_USE_HOOK} } from '${VIRTUAL_QWIK_DEVTOOLS_KEY}';\n` code = importLine + code + }else { + console.log('importing virtual qwik devtools', VIRTUAL_QWIK_DEVTOOLS_KEY, code); } code = parseQwikCode(code, {path: id}) } @@ -88,7 +91,7 @@ export function qwikDevtools(): Plugin[] { return { code, - map: null, + map: { mappings: '' }, }; }, }, diff --git a/packages/ui/package.json b/packages/ui/package.json index 2e60937..67005bd 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -34,7 +34,7 @@ }, "peerDependencies": { "@devtools/plugin": "workspace:*", - "@qwik.dev/core": "2.0.0-beta.9" + "@qwik.dev/core": "2.0.0-beta.11" }, "devDependencies": { "@devtools/kit": "workspace:*", diff --git a/packages/ui/src/components/ThemeToggle/ThemeToggle.tsx b/packages/ui/src/components/ThemeToggle/ThemeToggle.tsx index 98e2d14..7426032 100644 --- a/packages/ui/src/components/ThemeToggle/ThemeToggle.tsx +++ b/packages/ui/src/components/ThemeToggle/ThemeToggle.tsx @@ -37,10 +37,8 @@ export const ThemeToggle = component$(() => { currentTheme = 'light'; } else if (currentTheme === 'light') { currentTheme = 'auto'; - } else { - currentTheme = window.matchMedia('(prefers-color-scheme: dark)').matches - ? 'light' - : 'dark'; + } else if (currentTheme === 'auto') { + currentTheme = 'dark'; } setTheme(currentTheme); }); diff --git a/packages/ui/src/components/Tree/filterVnode.ts b/packages/ui/src/components/Tree/filterVnode.ts index 2932442..d81dae5 100644 --- a/packages/ui/src/components/Tree/filterVnode.ts +++ b/packages/ui/src/components/Tree/filterVnode.ts @@ -2,11 +2,8 @@ import { _ElementVNode, _VirtualVNode, _VNode, - _vnode_getAttr, _vnode_getAttrKeys, _vnode_getFirstChild, - _vnode_getNextSibling, - _vnode_getProps, _vnode_isMaterialized, _vnode_isVirtualVNode, QRL, @@ -70,10 +67,8 @@ function buildTreeRecursive( const value = container.getHostProp(currentVNode!, key) as QRL; // Update the underlying VNode props array and the new object's props. - _vnode_getProps(currentVNode!)[ - _vnode_getProps(currentVNode!).indexOf(key) + 1 - ] = value; - vnodeObject.props![key] = _vnode_getAttr(currentVNode!, key); + currentVNode?.setProp(key, value); + vnodeObject.props![key] = currentVNode?.getAttr(key); // Special handling to set the label from the render function's symbol. if (key === QRENDERFN) { @@ -106,7 +101,7 @@ function buildTreeRecursive( } // Move to the next sibling in the tree. - currentVNode = _vnode_getNextSibling(currentVNode); + currentVNode = currentVNode.nextSibling as _VNode | null; } return result; diff --git a/packages/ui/src/devtools.tsx b/packages/ui/src/devtools.tsx index d5229b8..261f024 100644 --- a/packages/ui/src/devtools.tsx +++ b/packages/ui/src/devtools.tsx @@ -12,6 +12,8 @@ import { HiPhotoOutline, HiMegaphoneMini, HiCubeOutline, + + HiCodeBracketSolid } from '@qwikest/icons/heroicons'; import { BsDiagram3 } from '@qwikest/icons/bootstrap'; import { LuFolderTree } from '@qwikest/icons/lucide'; @@ -38,6 +40,7 @@ import { Packages } from './features/Packages/Packages'; import { Inspect } from './features/inspect/Inspect'; import { ThemeToggle } from './components/ThemeToggle/ThemeToggle'; import { ThemeScript } from './components/ThemeToggle/theme-script'; +import { CodeBreack } from './features/CodeBreack/CodeBreack'; function getClientRpcFunctions() { return { healthCheck: () => true, @@ -146,7 +149,9 @@ export const QwikDevtools = component$(() => { - + + < HiCodeBracketSolid class="h-5 w-5" /> +
@@ -211,6 +216,12 @@ export const QwikDevtools = component$(() => { )} + {state.activeTab === 'codeBreack' && ( + + + + + )} )} diff --git a/packages/ui/src/features/CodeBreack/CodeBreack.tsx b/packages/ui/src/features/CodeBreack/CodeBreack.tsx new file mode 100644 index 0000000..1340d9b --- /dev/null +++ b/packages/ui/src/features/CodeBreack/CodeBreack.tsx @@ -0,0 +1,59 @@ +import { component$, useSignal, useStyles$ } from '@qwik.dev/core'; +import { StateParser } from './StateParser'; +import { HtmlParser } from './HtmlParser'; + +type ParserTab = 'state' | 'html'; + +export const CodeBreack = component$(() => { + useStyles$(` + .code-output-container { + overflow-x: auto; + overflow-y: auto; + } + .code-output-container > div { + width: fit-content; + min-width: 100%; + } + .code-output-container pre[class*='language-'] { + margin: 0; + min-width: max-content; + } + `); + + const currentTab = useSignal('state'); + + + return ( +
+ {/* Segmented Navigation */} +
+
+ + +
+
+ + {/* Content */} + {currentTab.value === 'state' && } + {currentTab.value === 'html' && } +
+ ); +}); diff --git a/packages/ui/src/features/CodeBreack/HtmlParser.tsx b/packages/ui/src/features/CodeBreack/HtmlParser.tsx new file mode 100644 index 0000000..cd16673 --- /dev/null +++ b/packages/ui/src/features/CodeBreack/HtmlParser.tsx @@ -0,0 +1,121 @@ +import { $, component$, useSignal, useVisibleTask$, useStyles$ } from '@qwik.dev/core'; +import { _getDomContainer, _vnode_toString } from '@qwik.dev/core/internal'; +import { createHighlighter } from 'shiki'; + +export const HtmlParser = component$(() => { + useStyles$(` + pre.shiki { overflow: auto; padding: 10px; height: 100%; } + `); + const inputHtml = useSignal(''); + const parsingTime = useSignal(null); + const htmlResult = useSignal(''); + const highlightedHtml = useSignal(''); + + const onParseHtml$ = $(() => { + if (!inputHtml.value.trim()) { + parsingTime.value = null; + htmlResult.value = '// Paste HTML on the left to parse'; + return; + } + + const startTime = performance.now(); + + try { + const parser = new DOMParser(); + const doc = parser.parseFromString(inputHtml.value, 'text/html'); + let output = ''; + try { + const container = _getDomContainer(doc.documentElement); + if (container) { + output += '// Qwik Container Found:\n'; + output += `- Container Type: ${container.qContainer}\n`; + output += `- Manifest Hash: ${container.qManifestHash}\n\n`; + try { + const vdomTree = _vnode_toString.call( + container!.rootVNode as any, + Number.MAX_SAFE_INTEGER, + '', + true, + false, + false + ); + output += '// VNode Tree:\n' + vdomTree + '\n\n'; + } catch (vnodeErr) { + output += '// VNode parsing error: ' + vnodeErr + '\n\n'; + } + } else { + output = '// No Qwik container found in the HTML'; + } + } catch (containerErr) { + output = '// No Qwik container found or error: ' + containerErr; + } + parsingTime.value = performance.now() - startTime; + htmlResult.value = output; + return; + } catch (error) { + parsingTime.value = performance.now() - startTime; + htmlResult.value = `// Error parsing HTML: ${error instanceof Error ? error.message : 'Invalid HTML format'}\n\n// Raw input:\n${inputHtml.value}`; + return; + } + }); + + const shikiRef = useSignal(null); + useVisibleTask$(async ({ track }) => { + track(() => htmlResult.value); + if (!htmlResult.value) { + highlightedHtml.value = ''; + return; + } + if (!shikiRef.value) { + shikiRef.value = await createHighlighter({ themes: ['nord'], langs: ['html'] }); + } + highlightedHtml.value = shikiRef.value.codeToHtml(htmlResult.value, { lang: 'html', theme: 'nord' }); + }); + + return ( +
+
+
+
Input HTML
+ {parsingTime.value !== null && ( + + {parsingTime.value}ms + + )} +
+
+