Skip to content

feat(extension): add browser extension package with Chrome MV3 and Firefox MV2#97

Open
Aejkatappaja wants to merge 11 commits intoQwikDev:mainfrom
Aejkatappaja:feat/browser-extension
Open

feat(extension): add browser extension package with Chrome MV3 and Firefox MV2#97
Aejkatappaja wants to merge 11 commits intoQwikDev:mainfrom
Aejkatappaja:feat/browser-extension

Conversation

@Aejkatappaja
Copy link
Copy Markdown

@Aejkatappaja Aejkatappaja commented Apr 10, 2026

Summary

Browser extension (Chrome/Firefox) that brings Qwik DevTools to the browser's DevTools panel, following the same architecture as Vue DevTools: extension for runtime features, Vite overlay for server features.

The extension shares the UI components with the existing in-app overlay and works both with and without the qwikDevtools() Vite plugin.

Quick Demo

CleanShot.2026-04-10.at.3.28.18.mp4

What's implemented

Component Tree (real-time)

  • Full VNode tree traversal using @qwik.dev/core/internal APIs
  • Real-time updates via MutationObserver + postMessage pipeline (no polling)
  • ID-based fingerprint to skip redundant re-renders
  • Stable node IDs based on q:id instead of sequential counters
  • SPA navigation detection with tree refresh on PAGE_CHANGED
  • Reuses the overlay's Tree, RenderTreeTabs, HookFiltersCard components

State inspection

  • Deep serialization of signals, stores, computed values
  • Signal editing (click a value, type new one, enter to commit)
  • Hook filters (useSignal, useStore, useComputed, etc.)
  • Component detail fetched by QRL chunk path (same matching as overlay)

Element picker

  • Main-world inspect-hook.js intercepts clicks before qwikloader
  • Resolves picked element to component via data-qwik-inspector + VNode map
  • Expands tree path, scrolls to node, flash highlights

Hover highlight

  • Hovering a component in the tree highlights it on the page
  • Uses hook.highlightNode(nodeId) which walks VNode to first DOM element
  • data-node-id attributes on tree rows for reliable targeting

Live render events

  • CSR render events streamed from perfRuntime via postMessage
  • Content script forwards RENDER_EVENT to panel
  • Performance tab seeds from existing __QWIK_PERF__ snapshot (SSR + CSR)
  • New renders append in real-time with component name, duration, phase badge

Performance & Preloads

  • Reads __QWIK_PERF__ and __QWIK_PRELOADS__ via inspectedWindow.eval()
  • Same UI as overlay (component cards, hook details, preload status)

Overview

  • Component count from VNode tree (fallback when plugin absent)
  • Package detection from q:container HTML attributes (q:version)
  • Router detection from component tree
  • "Vite plugin detected" banner when overlay is also active
  • Pages/assets cards hidden in extension mode (Vite-only data)

Standalone mode (without Vite plugin)

  • Content script injects devtools-hook.js (plain script, no imports)
  • Panel installs VNode bridge via inspectedWindow.eval() with dynamic import()
  • Resolves Qwik core URL from performance.getEntriesByType('resource') to reuse cached module (no duplicate Qwik instance)
  • Works on any Qwik dev site without qwikDevtools() in vite.config

Vite-only tabs

  • Routes, Assets, Packages, Inspect, Build Analysis grayed out
  • Clear placeholder message directing to the in-app overlay

Shared components

  • IconButton - toolbar button with icon slot and active state
  • InfoBanner - contextual info message with icon
  • IconTarget, IconExpandShrink, IconMonitor, IconInfoCircle added to Icons

Tests

  • 39 new tests: hookTreeHelpers (31) + isExtensionMessage type guard (8)
  • All 81 project tests passing

Architecture

Page (main world)                    Extension
--------------------                 --------------------
qwikloader                          content.ts
__QWIK_DEVTOOLS_HOOK__                detects Qwik
  hookRuntime (plugin)                injects devtools-hook.js
  OR devtools-hook.js (ext)           injects inspect-hook.js
                                      forwards postMessages
VNode bridge
  vnodeBridge (plugin)               background.ts
  OR eval'd bridge (ext)               relays content <-> panel
  MutationObserver
    -> postMessage                   panel.html
       COMPONENT_TREE_UPDATE           mounts QwikDevtoolsExtension
       RENDER_EVENT                    uses RemotePageDataSource
                                       evalInPage for data reads

What works per mode

Feature Extension only Extension + Plugin
Component Tree yes yes
Hover highlight yes yes
Element picker yes yes
SPA navigation yes yes
Packages yes (from HTML) yes
Signals/State no yes
Live renders no yes
Perf snapshot no yes
Preloads no yes
Routes/Assets no no (Vite overlay only)

What's NOT in this PR (follow-up work)

Production support

The VNode bridge uses import('@qwik.dev/core/internal') which only resolves in dev mode (Vite serves bare imports). In production, the Qwik core is bundled and minified, internal APIs aren't accessible. This requires either:

  • Qwik core exposing a public devtools API (like React/Vue hooks)
  • A symbol map embedded in production builds
  • Parsing <script type="qwik/vnode"> directly

Signals without Vite plugin

QWIK_DEVTOOLS_GLOBAL_STATE is populated by the plugin's component instrumentation. Without it, signals live in closures inside components and can't be accessed externally. Requires Qwik core to register signals on the hook when useSignal() is called (same as Vue's approach).

Both items above are best addressed after the devtools repo moves into the Qwik monorepo, where internal APIs can be tightly coupled (as discussed in the RFC thread).

Testing

# Build everything
bun run build
pnpm --filter @devtools/browser-extension build
pnpm --filter @devtools/browser-extension build:firefox

# Run tests
pnpm --filter @devtools/ui exec vitest run
pnpm --filter @devtools/plugin exec vitest --run
pnpm --filter @devtools/browser-extension exec vitest run

# Test with plugin
pnpm --filter playground dev

# Test without plugin (comment out qwikDevtools() in playgrounds/vite.config.mts)
pnpm --filter playground dev

Load the Chrome extension from packages/browser-extension/.output/chrome-mv3/ via chrome://extensions (developer mode).
Load the Firefox extension from packages/browser-extension/.output/firefox-mv2/ via about:debugging.

…refox MV2

New packages/browser-extension package using WXT framework.
Includes content script with Qwik detection, SPA navigation tracking,
element picker with main-world click interception, background relay,
and DevTools panel entry points. Supports Chrome MV3 and Firefox MV2.
Add hookRuntime.ts that installs window.__QWIK_DEVTOOLS_HOOK__ with
signal inspection, component snapshots, state editing, and render
event tracking. Add vnodeBridge.ts that traverses the VNode tree using
Qwik internal APIs and pushes real-time updates via MutationObserver.
Bridge render events from perfRuntime via postMessage for extension
consumption.
Add PageDataSource interface abstracting page data access for both
overlay (direct window) and extension (inspectedWindow.eval) modes.
Add RemotePageDataSource with VNode bridge injection via dynamic
import, package detection from container attributes, Vite plugin
detection, and render event subscription. Add QwikDevtoolsExtension
component and CSR entry point for the extension panel.
Full component tree panel reusing Tree, RenderTreeTabs, and
HookFiltersCard from the overlay. Features: real-time updates via
subscribeTreeUpdates, SPA navigation handling with treeVersion key,
name-based fingerprint to skip redundant re-renders, element picker
integration, hover highlight via hook.highlightNode, expand/collapse
toggle, inline signal editing, and data-node-id attributes on tree
rows for reliable hover targeting.
…ents

Adapt Overview for extension mode: hide pages/assets cards, show
packages from container attributes, Vite plugin detection banner.
Add live render events to Performance tab with SSR+CSR seed data.
Gray out Vite-only tabs with placeholder messages. Add shared
IconButton, InfoBanner components and IconTarget, IconExpandShrink,
IconMonitor, IconInfoCircle icons. Fix HookFiltersCard grid overflow.
Add devtools-hook.js injected by content script for hook installation
without the Vite plugin. Add vnode-bridge.js as ES module fallback.
VNode bridge is also installed via inspectedWindow.eval with dynamic
import that reuses the page's cached Qwik core module URL from
performance entries. Extension now works on any Qwik dev site.
31 tests for hookTreeHelpers (toTreeNodes, treeIdFingerprint,
findNodeById, findNodeByDomAttr, getElementType, valueToTree,
buildDetailTree) and 8 tests for isExtensionMessage type guard.
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 10, 2026

🦋 Changeset detected

Latest commit: 3ce0da9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@qwik.dev/devtools Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new browser extension package (Chrome MV3 / Firefox MV2) that embeds the Qwik DevTools UI inside the browser’s DevTools panel, while extending the existing plugin + UI to support extension/remote data access and real-time component/render instrumentation.

Changes:

  • Introduces @devtools/browser-extension (WXT-based) with DevTools panel, background/content scripts, and page eval/data plumbing.
  • Refactors UI data loading to use injectable DataProvider + PageDataSource, and adds an extension-only UI entry/layout.
  • Adds a runtime devtools hook + VNode bridge (virtual module + SSR injection) and streams live render events via postMessage.

Reviewed changes

Copilot reviewed 60 out of 70 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/ui/vite.config.mts Builds an additional extension entry bundle.
packages/ui/src/features/RenderTree/components/HookFiltersCard.tsx Layout tweaks for hook filter grid/labels.
packages/ui/src/features/Preloads/Preloads.tsx Reads/clears/subscribes via PageDataSource abstraction.
packages/ui/src/features/Performance/Performance.tsx Adds live render events + remote perf reading support.
packages/ui/src/features/Overview/Overview.tsx Adds extension-mode UI adjustments + plugin-detected banner.
packages/ui/src/features/HookTree/hookTreeHelpers.ts Shared helpers for VNode/state tree manipulation.
packages/ui/src/features/HookTree/hookTreeHelpers.test.ts Tests for HookTree helper utilities.
packages/ui/src/features/HookTree/HookTree.tsx New extension-mode component tree + state inspector UI.
packages/ui/src/entry.extension.tsx New CSR-only extension entry point mounting QwikDevtoolsExtension.
packages/ui/src/devtools/tabs.tsx Marks tabs as viteOnly for extension disabling/placeholder.
packages/ui/src/devtools/state.ts Adds vitePluginDetected flag to state.
packages/ui/src/devtools/rpc.ts Introduces DataProvider indirection (Vite RPC vs extension).
packages/ui/src/devtools/QwikDevtoolsExtension.tsx Extension-only devtools layout (sidebar + content, no overlay chrome).
packages/ui/src/devtools/QwikDevtools.tsx Skips preload runtime setup when running under extension provider.
packages/ui/src/devtools/page-data-source.ts Adds PageDataSource abstraction + default in-page implementation.
packages/ui/src/devtools/DevtoolsSidebar.tsx Disables viteOnly tabs in extension mode.
packages/ui/src/devtools/DevtoolsContent.tsx Adds extension-mode placeholders + HookTree swap-in.
packages/ui/src/devtools/data-provider.ts Defines DataProvider interface + window injection point.
packages/ui/src/components/Tree/Tree.tsx Adds data-node-* attributes for hover/picker targeting.
packages/ui/src/components/Tree/filterVnode.ts Lazily caches container lookup for VNode traversal.
packages/ui/src/components/Tab/Tab.tsx Adds disabled state + title handling for overlay-only tabs.
packages/ui/src/components/InfoBanner/InfoBanner.tsx New reusable informational banner component.
packages/ui/src/components/Icons/Icons.tsx Adds icons used by extension UI/placeholder/toolbars.
packages/ui/src/components/IconButton/IconButton.tsx New small toolbar button component.
packages/ui/package.json Exports ./entry.extension + bumps Qwik peer/dev deps.
packages/plugin/src/virtualmodules/vnodeBridge.ts New VNode bridge virtual module for serializable tree/DOM helpers.
packages/plugin/src/virtualmodules/virtualModules.ts Registers VNode bridge virtual module.
packages/plugin/src/virtualmodules/useCollectHooks.ts Formatting/termination fixes.
packages/plugin/src/virtualmodules/perfRuntime.ts Posts RENDER_EVENT messages + notifies devtools hook.
packages/plugin/src/virtualmodules/hookRuntime.ts New runtime hook string installed via SSR injection / perf runtime.
packages/plugin/src/plugin/statistics/ssrPerfMiddleware.ts Injects hook runtime + bridge script tags into HTML.
packages/plugin/package.json Bumps Qwik dev dependency.
packages/playgrounds/package.json Bumps Qwik core/router + eslint-plugin-qwik versions.
packages/kit/src/index.ts Re-exports new hook types module.
packages/kit/src/hook-types.ts Adds typed definition of __QWIK_DEVTOOLS_HOOK__ API.
packages/kit/src/globals.ts Adds __QWIK_DEVTOOLS_HOOK__ global typing.
packages/kit/package.json Bumps eslint-plugin-qwik version.
packages/devtools/package.json Bumps peer ranges for Qwik core/router.
packages/browser-extension/wxt.config.ts New WXT config + manifest for DevTools extension.
packages/browser-extension/tsconfig.json New TS config for extension package.
packages/browser-extension/src/styles/panel.css Imports UI styles + adapts “glass” styling for panel context.
packages/browser-extension/src/lib/types.ts Defines extension messaging types + type guard.
packages/browser-extension/src/lib/types.test.ts Tests for isExtensionMessage type guard.
packages/browser-extension/src/lib/extension-data-provider.ts Implements extension DataProvider + remote PageDataSource via eval.
packages/browser-extension/src/lib/constants.ts Shared constants + utilities for extension DOM parsing/detection.
packages/browser-extension/src/entrypoints/panel.html DevTools panel bootstrap: connects port + mounts UI extension entry.
packages/browser-extension/src/entrypoints/devtools.html Creates the “Qwik” DevTools panel when Qwik is detected.
packages/browser-extension/src/entrypoints/content.ts Content script: detection, SPA nav, picker overlay, message forwarding.
packages/browser-extension/src/entrypoints/background.ts Background relay between DevTools panel and content scripts.
packages/browser-extension/public/vnode-bridge.js Public ES module VNode bridge for injection scenarios.
packages/browser-extension/public/theme-init.js Initializes theme attribute from stored preference.
packages/browser-extension/public/qwikloader.js Bundled Qwik loader script for panel runtime.
packages/browser-extension/public/nav-hook.js Main-world SPA navigation hook (pushState/replaceState).
packages/browser-extension/public/inspect-hook.js Main-world element picker (pre-qwikloader click interception).
packages/browser-extension/public/icon-48.png Extension icon asset.
packages/browser-extension/public/icon-32.png Extension icon asset.
packages/browser-extension/public/icon-16.png Extension icon asset.
packages/browser-extension/public/icon-128.png Extension icon asset.
packages/browser-extension/package.json New extension package definition + scripts/deps.
packages/browser-extension/.wxt/wxt.d.ts WXT generated types.
packages/browser-extension/.wxt/types/paths.d.ts WXT generated public-path typings.
packages/browser-extension/.wxt/types/imports-module.d.ts WXT generated #imports typings.
packages/browser-extension/.wxt/types/i18n.d.ts WXT generated i18n typings.
packages/browser-extension/.wxt/types/globals.d.ts WXT generated env typings.
packages/browser-extension/.wxt/tsconfig.json WXT generated TS config.
packages/browser-extension/.gitignore Ignores extension build output and node_modules.
package.json Adds root scripts for extension dev/build workflows.
.changeset/browser-extension.md Announces new browser extension feature release.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants