Skip to content

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

Open
Aejkatappaja wants to merge 8 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 8 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: 5ec219e

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

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.

1 participant