├── dist └── .gitkeep ├── .github ├── FUNDING.yml ├── workflows │ └── create-release.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── feature_request.yml └── PULL_REQUEST_TEMPLATE.md ├── cypress ├── .gitignore ├── fixtures │ └── example.json ├── .eslintrc.js ├── utils │ └── suite.js ├── support │ └── index.js ├── plugins │ └── index.js └── integration │ └── events-tab.js ├── .browserslistrc ├── packages ├── docs │ ├── src │ │ ├── .vitepress │ │ │ ├── .gitignore │ │ │ └── theme │ │ │ │ ├── index.js │ │ │ │ └── custom.css │ │ ├── public │ │ │ ├── logo.png │ │ │ ├── favicon.png │ │ │ ├── logo-edge.png │ │ │ ├── logo-header.svg │ │ │ ├── logo.svg │ │ │ └── favicon.svg │ │ ├── assets │ │ │ ├── dev-shell-url.png │ │ │ ├── chrome-settings1.png │ │ │ ├── chrome-settings2.png │ │ │ ├── devtools-version.png │ │ │ ├── edge-settings1.png │ │ │ ├── edge-settings2.png │ │ │ ├── open-in-editor.png │ │ │ ├── plugin-settings.png │ │ │ ├── firefox-settings1.png │ │ │ ├── firefox-settings2.png │ │ │ ├── firefox-settings3.png │ │ │ ├── plugin-timeline-group.png │ │ │ ├── plugin-timeline-layer.png │ │ │ ├── dev-shell-profile-start.png │ │ │ ├── dev-shell-profile-stop.png │ │ │ ├── plugin-components-tags.png │ │ │ ├── dev-shell-profile-export.png │ │ │ ├── plugin-timeline-example1.png │ │ │ ├── plugin-timeline-example2.png │ │ │ ├── pluygin-components-state.png │ │ │ ├── vue-devtools-architecture.png │ │ │ ├── plugin-custom-inspector-menu.png │ │ │ ├── plugin-custom-inspector-tree.png │ │ │ ├── plugin-timeline-group-state.png │ │ │ └── plugin-custom-inspector-state.png │ │ ├── index.md │ │ ├── guide │ │ │ └── custom-vue2-app-scan-selector.md │ │ ├── devtools-vue3.md │ │ └── components │ │ │ ├── InstallButton.vue │ │ │ └── InstallButtons.vue │ ├── package.json │ ├── postcss.config.js │ └── tailwind.config.cjs ├── api │ ├── src │ │ ├── api │ │ │ ├── app.ts │ │ │ ├── util.ts │ │ │ ├── context.ts │ │ │ └── index.ts │ │ ├── const.ts │ │ ├── time.ts │ │ ├── env.ts │ │ └── plugin.ts │ ├── tsconfig.json │ └── package.json ├── shell-firefox │ ├── .gitignore │ ├── copy.sh │ ├── package.json │ ├── src │ │ ├── hook.js │ │ ├── proxy.js │ │ ├── devtools-background.js │ │ └── backend.js │ ├── webpack.config.js │ └── manifest.json ├── app-frontend │ ├── src │ │ ├── features │ │ │ ├── chrome │ │ │ │ ├── index.ts │ │ │ │ └── pane-visibility.ts │ │ │ ├── header │ │ │ │ ├── header.ts │ │ │ │ ├── AppHistoryNav.vue │ │ │ │ └── tabs.ts │ │ │ ├── components │ │ │ │ └── composable │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── highlight.ts │ │ │ │ │ └── pick.ts │ │ │ ├── ui │ │ │ │ ├── components │ │ │ │ │ ├── VueLoadingIndicator.vue │ │ │ │ │ ├── VueDropdownButton.vue │ │ │ │ │ ├── VueIcon.vue │ │ │ │ │ ├── VueLoadingBar.vue │ │ │ │ │ ├── VueDisable.vue │ │ │ │ │ ├── icons.ts │ │ │ │ │ ├── VueGroupButton.vue │ │ │ │ │ └── VueSelectButton.vue │ │ │ │ └── composables │ │ │ │ │ ├── useDisableScroll.ts │ │ │ │ │ └── useDisabled.ts │ │ │ ├── connection │ │ │ │ ├── AppDisconnected.vue │ │ │ │ ├── AppConnecting.vue │ │ │ │ └── index.ts │ │ │ ├── timeline │ │ │ │ └── composable │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── time.ts │ │ │ │ │ └── markers.ts │ │ │ ├── layout │ │ │ │ ├── orientation.ts │ │ │ │ └── EmptyPane.vue │ │ │ ├── error │ │ │ │ ├── index.ts │ │ │ │ └── ErrorOverlay.vue │ │ │ ├── settings │ │ │ │ └── NewTag.vue │ │ │ ├── apps │ │ │ │ └── vue-version-check.ts │ │ │ ├── plugin │ │ │ │ ├── PluginSourceIcon.vue │ │ │ │ ├── PluginHome.vue │ │ │ │ ├── PluginPermission.vue │ │ │ │ ├── PluginListItem.vue │ │ │ │ └── PluginSourceDescription.vue │ │ │ └── bridge │ │ │ │ └── index.ts │ │ ├── shims-global.d.ts │ │ ├── util │ │ │ ├── format │ │ │ │ ├── index.ts │ │ │ │ └── time.ts │ │ │ ├── theme.ts │ │ │ ├── shared-data.ts │ │ │ ├── time.ts │ │ │ ├── defer.ts │ │ │ ├── color.ts │ │ │ ├── reactivity.ts │ │ │ ├── fonts.ts │ │ │ ├── keyboard.ts │ │ │ └── queue.ts │ │ ├── assets │ │ │ ├── style │ │ │ │ ├── imports.styl │ │ │ │ └── transitions.styl │ │ │ ├── Roboto-Mono.ttf │ │ │ ├── Roboto-Regular.woff2 │ │ │ ├── welcome │ │ │ │ ├── components.png │ │ │ │ ├── main-tabs.png │ │ │ │ ├── timeline.png │ │ │ │ ├── app-selector.png │ │ │ │ ├── components-menu.png │ │ │ │ ├── inspector-pinia.png │ │ │ │ ├── settings-pinia.png │ │ │ │ ├── timeline-perf.png │ │ │ │ ├── timeline-perf2.png │ │ │ │ ├── timeline-pinia.png │ │ │ │ ├── timeline-router.png │ │ │ │ ├── version-check.png │ │ │ │ ├── components-pinia.png │ │ │ │ └── inspector-pinia2.png │ │ │ ├── MaterialIcons-Regular.woff2 │ │ │ ├── breadcrumb-separator.svg │ │ │ └── devtools-logo.svg │ │ ├── shims-vue.d.ts │ │ ├── plugins │ │ │ ├── global-refs.ts │ │ │ ├── i18n.ts │ │ │ └── responsive.ts │ │ ├── types │ │ │ └── vue.d.ts │ │ ├── index.ts │ │ └── mixins │ │ │ └── keyboard.ts │ └── package.json ├── shell-dev-vue3 │ ├── src │ │ ├── router │ │ │ ├── Page1.vue │ │ │ └── Page2.vue │ │ ├── Hello.vue │ │ ├── IndexComponent │ │ │ └── index.vue │ │ ├── iframe-app.js │ │ ├── SetupTSScriptProps.vue │ │ ├── Ghost.vue │ │ ├── FormSection.vue │ │ ├── EventEmit.vue │ │ ├── Functional.vue │ │ ├── VModelExample.vue │ │ ├── SetupDataLike.vue │ │ ├── SetupRender.js │ │ ├── SuspenseExample.vue │ │ ├── IframeApp.vue │ │ ├── App3.vue │ │ ├── NestedMore.vue │ │ ├── devtools-plugin │ │ │ └── simple.js │ │ ├── AsyncComponent.vue │ │ ├── EventNesting.vue │ │ ├── AsyncSetup.vue │ │ ├── Form.vue │ │ ├── DomOrder.vue │ │ ├── Condition.vue │ │ ├── Heavy.vue │ │ ├── Nested.vue │ │ ├── SetupScript.vue │ │ ├── Animation.vue │ │ ├── store.js │ │ ├── Provide.vue │ │ ├── Other.vue │ │ └── main.js │ ├── public │ │ ├── target-iframe.html │ │ └── target.html │ ├── package.json │ └── webpack.config.js ├── app-backend-core │ ├── src │ │ ├── toast.ts │ │ ├── global-hook.ts │ │ ├── util │ │ │ ├── subscriptions.ts │ │ │ └── queue.ts │ │ ├── page-config.ts │ │ ├── backend.ts │ │ └── flash.ts │ ├── tsconfig.json │ └── package.json ├── shell-host │ ├── src │ │ ├── hook.js │ │ ├── devtools.js │ │ └── backend.js │ ├── package.json │ ├── webpack.config.js │ └── public │ │ ├── index.html │ │ └── favicon.svg ├── build-tools │ ├── src │ │ └── index.js │ └── package.json ├── shell-chrome │ ├── src │ │ ├── hook-exec.js │ │ ├── hook.js │ │ ├── detector.js │ │ ├── proxy.js │ │ ├── devtools-background.js │ │ └── backend.js │ ├── icons │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 48.png │ │ ├── 128-beta.png │ │ ├── 128-gray.png │ │ ├── 128.nuxt.png │ │ ├── 16-beta.png │ │ ├── 16-gray.png │ │ ├── 16.nuxt.png │ │ ├── 48-beta.png │ │ ├── 48-gray.png │ │ └── 48.nuxt.png │ ├── devtools-background.html │ ├── popups │ │ ├── devtools-screenshot.png │ │ ├── not-found.html │ │ ├── disabled.html │ │ ├── disabled.nuxt.html │ │ ├── enabled.html │ │ ├── enabled.nuxt.html │ │ └── popup.css │ ├── devtools.html │ ├── package.json │ └── webpack.config.js ├── shell-electron │ ├── types │ │ └── index.d.ts │ ├── icons │ │ └── 128.png │ ├── src │ │ ├── hook.js │ │ └── devtools.js │ ├── bin.js │ ├── webpack.config.js │ ├── webpack.node.config.js │ ├── app.js │ ├── index.js │ └── server.js ├── shell-dev-vue2 │ ├── src │ │ ├── MyClass.js │ │ ├── Functional.vue │ │ ├── router │ │ │ ├── ChildRoute.vue │ │ │ ├── NamedRoute.vue │ │ │ ├── RouteOne.vue │ │ │ ├── RouteTwo.vue │ │ │ ├── RouteWithAlias.vue │ │ │ ├── RouteWithBeforeEnter.vue │ │ │ ├── RouteWithQuery.vue │ │ │ ├── ParentRoute.vue │ │ │ ├── RouteWithParams.vue │ │ │ └── RouteWithProps.vue │ │ ├── iframe-app.js │ │ ├── Hidden.vue │ │ ├── NoProp.vue │ │ ├── Child.vue │ │ ├── EventChild1.vue │ │ ├── VuexObject.vue │ │ ├── Init.vue │ │ ├── RefTester.vue │ │ ├── EventChildCond.vue │ │ ├── Events.vue │ │ ├── dynamic-module.js │ │ ├── Other.vue │ │ ├── TransitionExample.vue │ │ └── EventChild.vue │ ├── public │ │ ├── target-electron.html │ │ ├── target-iframe.html │ │ └── target.html │ ├── package.json │ └── webpack.config.js ├── app-backend-api │ ├── src │ │ ├── plugin.ts │ │ ├── index.ts │ │ ├── global-hook.ts │ │ ├── app-record.ts │ │ └── backend.ts │ ├── package.json │ └── tsconfig.json ├── shared-utils │ ├── src │ │ ├── shell.ts │ │ ├── index.ts │ │ ├── raf.ts │ │ ├── plugin-permissions.ts │ │ ├── throttle.ts │ │ ├── plugin-settings.ts │ │ └── env.ts │ ├── package.json │ └── tsconfig.json ├── app-backend-vue1 │ ├── src │ │ └── index.ts │ ├── package.json │ └── tsconfig.json ├── app-backend-vue3 │ ├── src │ │ ├── util.ts │ │ └── components │ │ │ └── filter.ts │ ├── package.json │ └── tsconfig.json └── app-backend-vue2 │ ├── tsconfig.json │ ├── package.json │ └── src │ ├── events.ts │ └── components │ ├── util.ts │ └── update-tracking.ts ├── .vscode └── settings.json ├── media ├── screenshot.png ├── screenshot-shadow.png ├── screenshot-rounded.png └── chrome-webstore │ ├── screenshot1.png │ ├── screenshot2.png │ ├── screenshot3.png │ ├── screenshot4.png │ ├── screenshot1-size.png │ ├── screenshot2-size.png │ ├── screenshot3-size.png │ └── screenshot4-size.png ├── cypress.json ├── babel.config.js ├── lerna.json ├── .gitignore ├── postcss.config.js ├── tsconfig.json ├── .circleci └── config.yml ├── eslint.config.js ├── sign-firefox.js └── LICENSE /dist/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Akryum 2 | -------------------------------------------------------------------------------- /cypress/.gitignore: -------------------------------------------------------------------------------- 1 | /screenshots 2 | /videos 3 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | Chrome >= 52 2 | Firefox >= 48 3 | -------------------------------------------------------------------------------- /packages/docs/src/.vitepress/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /packages/api/src/api/app.ts: -------------------------------------------------------------------------------- 1 | export type App = any // @TODO 2 | -------------------------------------------------------------------------------- /packages/shell-firefox/.gitignore: -------------------------------------------------------------------------------- 1 | icons/ 2 | popups/ 3 | *.html 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/chrome/index.ts: -------------------------------------------------------------------------------- 1 | export * from './pane-visibility' 2 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/router/Page1.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/router/Page2.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /media/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/media/screenshot.png -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/Hello.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/app-backend-core/src/toast.ts: -------------------------------------------------------------------------------- 1 | export function installToast() { 2 | // @TODO 3 | } 4 | -------------------------------------------------------------------------------- /packages/app-frontend/src/shims-global.d.ts: -------------------------------------------------------------------------------- 1 | // @TODO remove 2 | declare const browser: any 3 | -------------------------------------------------------------------------------- /packages/app-frontend/src/util/format/index.ts: -------------------------------------------------------------------------------- 1 | export * from './time' 2 | export * from './value' 3 | -------------------------------------------------------------------------------- /media/screenshot-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/media/screenshot-shadow.png -------------------------------------------------------------------------------- /packages/shell-host/src/hook.js: -------------------------------------------------------------------------------- 1 | import { installHook } from '@back/hook' 2 | 3 | installHook(window) 4 | -------------------------------------------------------------------------------- /media/screenshot-rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/media/screenshot-rounded.png -------------------------------------------------------------------------------- /packages/build-tools/src/index.js: -------------------------------------------------------------------------------- 1 | Object.assign(module.exports, { 2 | ...require('./createConfig'), 3 | }) 4 | -------------------------------------------------------------------------------- /packages/shell-chrome/src/hook-exec.js: -------------------------------------------------------------------------------- 1 | import { installHook } from '@back/hook' 2 | 3 | installHook(window) 4 | -------------------------------------------------------------------------------- /packages/shell-electron/types/index.d.ts: -------------------------------------------------------------------------------- 1 | export function connect(host?: string, port?: number | string): void 2 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "viewportWidth": 1280, 3 | "viewportHeight": 800, 4 | "chromeWebSecurity": false 5 | } 6 | -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/style/imports.styl: -------------------------------------------------------------------------------- 1 | @import '~@vue/ui/src/style/imports' 2 | @import 'variables' 3 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/IndexComponent/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/api/src/api/util.ts: -------------------------------------------------------------------------------- 1 | export type ID = number | string 2 | 3 | export interface WithId { 4 | id: ID 5 | } 6 | -------------------------------------------------------------------------------- /packages/docs/src/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/public/logo.png -------------------------------------------------------------------------------- /packages/shell-chrome/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/128.png -------------------------------------------------------------------------------- /packages/shell-chrome/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/16.png -------------------------------------------------------------------------------- /packages/shell-chrome/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/48.png -------------------------------------------------------------------------------- /media/chrome-webstore/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/media/chrome-webstore/screenshot1.png -------------------------------------------------------------------------------- /media/chrome-webstore/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/media/chrome-webstore/screenshot2.png -------------------------------------------------------------------------------- /media/chrome-webstore/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/media/chrome-webstore/screenshot3.png -------------------------------------------------------------------------------- /media/chrome-webstore/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/media/chrome-webstore/screenshot4.png -------------------------------------------------------------------------------- /packages/docs/src/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/public/favicon.png -------------------------------------------------------------------------------- /packages/shell-electron/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-electron/icons/128.png -------------------------------------------------------------------------------- /packages/app-frontend/src/features/header/header.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export const showAppsSelector = ref(true) 4 | -------------------------------------------------------------------------------- /packages/docs/src/public/logo-edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/public/logo-edge.png -------------------------------------------------------------------------------- /packages/shell-chrome/devtools-background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/shell-chrome/icons/128-beta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/128-beta.png -------------------------------------------------------------------------------- /packages/shell-chrome/icons/128-gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/128-gray.png -------------------------------------------------------------------------------- /packages/shell-chrome/icons/128.nuxt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/128.nuxt.png -------------------------------------------------------------------------------- /packages/shell-chrome/icons/16-beta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/16-beta.png -------------------------------------------------------------------------------- /packages/shell-chrome/icons/16-gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/16-gray.png -------------------------------------------------------------------------------- /packages/shell-chrome/icons/16.nuxt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/16.nuxt.png -------------------------------------------------------------------------------- /packages/shell-chrome/icons/48-beta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/48-beta.png -------------------------------------------------------------------------------- /packages/shell-chrome/icons/48-gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/48-gray.png -------------------------------------------------------------------------------- /packages/shell-chrome/icons/48.nuxt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/icons/48.nuxt.png -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/MyClass.js: -------------------------------------------------------------------------------- 1 | export default class MyClass { 2 | constructor() { 3 | this.msg = 'hi' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /media/chrome-webstore/screenshot1-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/media/chrome-webstore/screenshot1-size.png -------------------------------------------------------------------------------- /media/chrome-webstore/screenshot2-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/media/chrome-webstore/screenshot2-size.png -------------------------------------------------------------------------------- /media/chrome-webstore/screenshot3-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/media/chrome-webstore/screenshot3-size.png -------------------------------------------------------------------------------- /media/chrome-webstore/screenshot4-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/media/chrome-webstore/screenshot4-size.png -------------------------------------------------------------------------------- /packages/docs/src/assets/dev-shell-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/dev-shell-url.png -------------------------------------------------------------------------------- /packages/docs/src/assets/chrome-settings1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/chrome-settings1.png -------------------------------------------------------------------------------- /packages/docs/src/assets/chrome-settings2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/chrome-settings2.png -------------------------------------------------------------------------------- /packages/docs/src/assets/devtools-version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/devtools-version.png -------------------------------------------------------------------------------- /packages/docs/src/assets/edge-settings1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/edge-settings1.png -------------------------------------------------------------------------------- /packages/docs/src/assets/edge-settings2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/edge-settings2.png -------------------------------------------------------------------------------- /packages/docs/src/assets/open-in-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/open-in-editor.png -------------------------------------------------------------------------------- /packages/docs/src/assets/plugin-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/plugin-settings.png -------------------------------------------------------------------------------- /packages/api/src/const.ts: -------------------------------------------------------------------------------- 1 | export const HOOK_SETUP = 'devtools-plugin:setup' 2 | export const HOOK_PLUGIN_SETTINGS_SET = 'plugin:settings:set' 3 | -------------------------------------------------------------------------------- /packages/docs/src/assets/firefox-settings1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/firefox-settings1.png -------------------------------------------------------------------------------- /packages/docs/src/assets/firefox-settings2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/firefox-settings2.png -------------------------------------------------------------------------------- /packages/docs/src/assets/firefox-settings3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/firefox-settings3.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/Roboto-Mono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/Roboto-Mono.ttf -------------------------------------------------------------------------------- /packages/docs/src/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme' 2 | import './custom.css' 3 | 4 | export default DefaultTheme 5 | -------------------------------------------------------------------------------- /packages/docs/src/assets/plugin-timeline-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/plugin-timeline-group.png -------------------------------------------------------------------------------- /packages/docs/src/assets/plugin-timeline-layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/plugin-timeline-layer.png -------------------------------------------------------------------------------- /packages/docs/src/assets/dev-shell-profile-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/dev-shell-profile-start.png -------------------------------------------------------------------------------- /packages/docs/src/assets/dev-shell-profile-stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/dev-shell-profile-stop.png -------------------------------------------------------------------------------- /packages/docs/src/assets/plugin-components-tags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/plugin-components-tags.png -------------------------------------------------------------------------------- /packages/shell-chrome/popups/devtools-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/shell-chrome/popups/devtools-screenshot.png -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/Functional.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/components.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/main-tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/main-tabs.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/timeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/timeline.png -------------------------------------------------------------------------------- /packages/docs/src/assets/dev-shell-profile-export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/dev-shell-profile-export.png -------------------------------------------------------------------------------- /packages/docs/src/assets/plugin-timeline-example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/plugin-timeline-example1.png -------------------------------------------------------------------------------- /packages/docs/src/assets/plugin-timeline-example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/plugin-timeline-example2.png -------------------------------------------------------------------------------- /packages/docs/src/assets/pluygin-components-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/pluygin-components-state.png -------------------------------------------------------------------------------- /packages/docs/src/assets/vue-devtools-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/vue-devtools-architecture.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/app-selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/app-selector.png -------------------------------------------------------------------------------- /packages/docs/src/assets/plugin-custom-inspector-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/plugin-custom-inspector-menu.png -------------------------------------------------------------------------------- /packages/docs/src/assets/plugin-custom-inspector-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/plugin-custom-inspector-tree.png -------------------------------------------------------------------------------- /packages/docs/src/assets/plugin-timeline-group-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/plugin-timeline-group-state.png -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/iframe-app.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import IframeApp from './IframeApp.vue' 3 | 4 | createApp(IframeApp).mount('#app') 5 | -------------------------------------------------------------------------------- /packages/shell-electron/src/hook.js: -------------------------------------------------------------------------------- 1 | import { installHook } from '@back/hook' 2 | import { target } from '@vue-devtools/shared-utils' 3 | 4 | installHook(target) 5 | -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/components-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/components-menu.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/inspector-pinia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/inspector-pinia.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/settings-pinia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/settings-pinia.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/timeline-perf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/timeline-perf.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/timeline-perf2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/timeline-perf2.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/timeline-pinia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/timeline-pinia.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/timeline-router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/timeline-router.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/version-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/version-check.png -------------------------------------------------------------------------------- /packages/docs/src/assets/plugin-custom-inspector-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/docs/src/assets/plugin-custom-inspector-state.png -------------------------------------------------------------------------------- /packages/api/src/api/context.ts: -------------------------------------------------------------------------------- 1 | import type { AppRecord } from './api.js' 2 | 3 | export interface Context { 4 | currentTab: string 5 | currentAppRecord: AppRecord 6 | } 7 | -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/components-pinia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/components-pinia.png -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/welcome/inspector-pinia2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs/devtools-v6/HEAD/packages/app-frontend/src/assets/welcome/inspector-pinia2.png -------------------------------------------------------------------------------- /packages/shell-firefox/copy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf icons 4 | 5 | cp -r ../shell-chrome/icons . 6 | cp -r ../shell-chrome/popups . 7 | cp ../shell-chrome/*.html . 8 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/router/ChildRoute.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/components/composable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components' 2 | export * from './highlight' 3 | export * from './pick' 4 | export * from './setup' 5 | -------------------------------------------------------------------------------- /packages/app-frontend/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { defineComponent } from 'vue' 3 | 4 | export default ReturnType 5 | } 6 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/iframe-app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Child from './Child.vue' 3 | 4 | new Vue({ 5 | ...Child, 6 | name: 'IframeApp', 7 | }).$mount('#app') 8 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/docs", 3 | "version": "0.0.0", 4 | "devDependencies": { 5 | "vitepress": "^0.22.3", 6 | "vue": "^3.2.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | presets: [ 4 | [ 5 | '@babel/env', 6 | { 7 | modules: false, 8 | }, 9 | ], 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/router/NamedRoute.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/breadcrumb-separator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/ui/components/VueLoadingIndicator.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/router/RouteOne.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/router/RouteTwo.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /packages/api/src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api.js' 2 | export * from './app.js' 3 | export * from './component.js' 4 | export * from './context.js' 5 | export * from './hooks.js' 6 | export * from './util.js' 7 | -------------------------------------------------------------------------------- /packages/shell-chrome/popups/not-found.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | Vue.js not detected 6 |

7 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/router/RouteWithAlias.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "useWorkspaces": true, 4 | "version": "6.0.0-beta.2", 5 | "packages": [ 6 | "packages/*" 7 | ], 8 | "ignoreChanges": [ 9 | "**/*.md" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /cypress/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | 'cypress', 4 | ], 5 | env: { 6 | 'mocha': true, 7 | 'cypress/globals': true, 8 | }, 9 | rules: { 10 | strict: 'off', 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /cypress/utils/suite.js: -------------------------------------------------------------------------------- 1 | export function suite(description, tests) { 2 | describe(description, () => { 3 | before(() => { 4 | cy.visit('/') 5 | cy.vueCheckInit() 6 | }) 7 | tests() 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/Hidden.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/router/RouteWithBeforeEnter.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/SetupTSScriptProps.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /packages/app-backend-api/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { PluginDescriptor, SetupFunction } from '@vue/devtools-api' 2 | 3 | export interface Plugin { 4 | descriptor: PluginDescriptor 5 | setupFn: SetupFunction 6 | error: Error 7 | } 8 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/router/RouteWithQuery.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /packages/shared-utils/src/shell.ts: -------------------------------------------------------------------------------- 1 | import type { Bridge } from './bridge' 2 | 3 | export interface Shell { 4 | connect: (cb: ((bridge: Bridge) => void | Promise)) => void 5 | onReload: (cb: (() => void | Promise)) => void 6 | } 7 | -------------------------------------------------------------------------------- /packages/app-frontend/src/util/theme.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref } from 'vue' 2 | 3 | export const darkMode = ref(false) 4 | 5 | export function useDarkMode() { 6 | return { 7 | darkMode: computed(() => darkMode.value), 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/app-backend-api/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api' 2 | export * from './app-record' 3 | export * from './backend' 4 | export * from './backend-context' 5 | export * from './global-hook' 6 | export * from './hooks' 7 | export * from './plugin' 8 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/NoProp.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/Ghost.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | build 4 | /dist/* 5 | *.zip 6 | *.xpi 7 | tests_output 8 | selenium-debug.log 9 | TODOs.md 10 | .idea 11 | .web-extension-id 12 | yarn-error.log 13 | 14 | /packages/*/lib 15 | .amo.env.json 16 | build-node 17 | -------------------------------------------------------------------------------- /packages/app-backend-vue1/src/index.ts: -------------------------------------------------------------------------------- 1 | import { defineBackend } from '@vue-devtools/app-backend-api' 2 | 3 | export const backend = defineBackend({ 4 | frameworkVersion: 1, 5 | features: [], 6 | setup(_api) { 7 | // @TODO 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/router/ParentRoute.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/FormSection.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /packages/shell-host/src/devtools.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { setupPlugins } from '@front/plugins' 3 | import DevIframe from './DevIframe.vue' 4 | 5 | const app = createApp(DevIframe) 6 | setupPlugins(app) 7 | app.mount('#iframe-target') 8 | -------------------------------------------------------------------------------- /packages/shell-chrome/src/hook.js: -------------------------------------------------------------------------------- 1 | const script = document.createElement('script') 2 | script.src = chrome.runtime.getURL('build/hook-exec.js') 3 | script.onload = () => { 4 | script.remove() 5 | } 6 | ;(document.head || document.documentElement).appendChild(script) 7 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | 3 | module.exports = { 4 | plugins: [ 5 | require('autoprefixer'), 6 | require('tailwindcss')(path.resolve(__dirname, './tailwind.config.js')), 7 | require('postcss-nested'), 8 | ], 9 | } 10 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/connection/AppDisconnected.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /packages/docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | 3 | module.exports = { 4 | plugins: [ 5 | require('autoprefixer'), 6 | require('tailwindcss')(path.resolve(__dirname, './tailwind.config.cjs')), 7 | require('postcss-nested'), 8 | ], 9 | } 10 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/timeline/composable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './events' 2 | export * from './layers' 3 | export * from './markers' 4 | export * from './reset' 5 | export * from './screenshot' 6 | export * from './setup' 7 | export * from './store' 8 | export * from './time' 9 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/router/RouteWithParams.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /packages/docs/src/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .nav-bar-title .logo { 6 | margin-top: 6px; 7 | } 8 | 9 | .home-hero { 10 | figure { 11 | .image { 12 | max-height: 100px; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/EventEmit.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /packages/app-backend-core/src/global-hook.ts: -------------------------------------------------------------------------------- 1 | import type { DevtoolsHook } from '@vue-devtools/app-backend-api' 2 | import { target } from '@vue-devtools/shared-utils' 3 | 4 | // hook should have been injected before this executes. 5 | export const hook: DevtoolsHook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__ 6 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/Child.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/Functional.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/VModelExample.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | -------------------------------------------------------------------------------- /packages/app-frontend/src/util/shared-data.ts: -------------------------------------------------------------------------------- 1 | import { onUnmounted } from 'vue' 2 | import { watchSharedData } from '@vue-devtools/shared-utils' 3 | 4 | export function onSharedDataChange(prop, handler) { 5 | const off = watchSharedData(prop, handler) 6 | 7 | onUnmounted(() => { 8 | off() 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /packages/docs/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const base = require('../../tailwind.config.js') 3 | 4 | base.purge.content.push( 5 | path.resolve(__dirname, './src/**/*.{js,jsx,ts,tsx,vue}'), 6 | ) 7 | 8 | base.corePlugins = { 9 | preflight: false, 10 | } 11 | 12 | module.exports = base 13 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/SetupDataLike.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | -------------------------------------------------------------------------------- /packages/shell-chrome/popups/disabled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | Vue.js is detected on this page.
6 | Devtools inspection is not available because it's in 7 | production mode or explicitly disabled by the author. 8 |

9 | -------------------------------------------------------------------------------- /packages/shell-chrome/popups/disabled.nuxt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | Nuxt + Vue.js is detected on this page.
6 | Devtools inspection is not available because it's in 7 | production mode or explicitly disabled by the author. 8 |

9 | -------------------------------------------------------------------------------- /packages/shell-electron/bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const electron = require('electron') 3 | const spawn = require('cross-spawn') 4 | 5 | const argv = process.argv.slice(2) 6 | 7 | const result = spawn.sync( 8 | electron, 9 | [require.resolve('./app')].concat(argv), 10 | { stdio: 'ignore' }, 11 | ) 12 | 13 | process.exit(result.status) 14 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/SetupRender.js: -------------------------------------------------------------------------------- 1 | import { defineComponent, h, reactive } from 'vue' 2 | 3 | export default defineComponent({ 4 | name: 'SetupRender', 5 | 6 | setup() { 7 | const state = reactive({ 8 | name: 'Foo bar', 9 | }) 10 | 11 | return () => { 12 | return h('h1', state.name) 13 | } 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /packages/app-frontend/src/util/format/time.ts: -------------------------------------------------------------------------------- 1 | export type TimeFormat = 'ms' | 'default' 2 | 3 | export function formatTime(timestamp: string | number | Date, format?: TimeFormat) { 4 | const date = new Date(timestamp) 5 | return `${date.toString().match(/\d\d:\d\d:\d\d/)[0]}${format === 'ms' ? `.${String(date.getMilliseconds()).padStart(3, '0')}` : ''}` 6 | } 7 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/public/target-electron.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/EventChild1.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/VuexObject.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/public/target-iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/public/target-iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/ui/components/VueDropdownButton.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/header/AppHistoryNav.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/timeline/composable/time.ts: -------------------------------------------------------------------------------- 1 | import { 2 | cursorTime, 3 | endTime, 4 | maxTime, 5 | minTime, 6 | startTime, 7 | } from './store' 8 | 9 | export function useTime() { 10 | return { 11 | startTime, 12 | endTime, 13 | minTime, 14 | maxTime, 15 | } 16 | } 17 | 18 | export function useCursor() { 19 | return { 20 | cursorTime, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/Init.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/router/RouteWithProps.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/SuspenseExample.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 22 | -------------------------------------------------------------------------------- /packages/app-backend-api/src/global-hook.ts: -------------------------------------------------------------------------------- 1 | import type { AppRecordOptions } from './app-record' 2 | 3 | export interface DevtoolsHook { 4 | emit: (event: string, ...payload: any[]) => void 5 | on: (event: string, handler: T) => void 6 | once: (event: string, handler: T) => void 7 | off: (event?: string, handler?: T) => void 8 | Vue?: any 9 | apps: AppRecordOptions[] 10 | } 11 | -------------------------------------------------------------------------------- /packages/app-backend-vue3/src/util.ts: -------------------------------------------------------------------------------- 1 | export function flatten(items) { 2 | return items.reduce((acc, item) => { 3 | if (Array.isArray(item)) { 4 | acc.push(...flatten(item)) 5 | } 6 | else if (item) { 7 | acc.push(item) 8 | } 9 | 10 | return acc 11 | }, []) 12 | } 13 | 14 | export function returnError(cb: () => any) { 15 | try { 16 | return cb() 17 | } 18 | catch (e) { 19 | return e 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/ui/components/VueIcon.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/public/target.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/shared-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './backend' 2 | export * from './bridge' 3 | export * from './consts' 4 | export * from './edit' 5 | export * from './env' 6 | export * from './plugin-permissions' 7 | export * from './plugin-settings' 8 | export * from './shared-data' 9 | export * from './shell' 10 | export * from './storage' 11 | export * from './throttle' 12 | export * from './transfer' 13 | export * from './util' 14 | export * from './raf' 15 | -------------------------------------------------------------------------------- /packages/shell-chrome/src/detector.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('message', (event) => { 2 | if (event.data.key === '_vue-devtools-send-message') { 3 | chrome.runtime.sendMessage(event.data.message) 4 | } 5 | }, false) 6 | 7 | const script = document.createElement('script') 8 | script.src = chrome.runtime.getURL('build/detector-exec.js') 9 | script.onload = () => { 10 | script.remove() 11 | } 12 | ;(document.head || document.documentElement).appendChild(script) 13 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/IframeApp.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /packages/app-frontend/src/util/time.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue' 2 | import { computed, ref } from 'vue' 3 | 4 | export const reactiveNow = ref(Date.now()) 5 | 6 | setInterval(() => { 7 | reactiveNow.value = Date.now() 8 | }, 100) 9 | 10 | export function useTimeAgo(time: Ref) { 11 | return { 12 | timeAgo: computed(() => { 13 | const diff = reactiveNow.value - time.value 14 | return `${Math.round(diff / 1000)}s ago` 15 | }), 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/header/tabs.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import { useRoute } from 'vue-router' 3 | 4 | export function useTabs() { 5 | const route = useRoute() 6 | const currentTab = computed(() => { 7 | let fromMeta = route.meta.tab 8 | if (typeof fromMeta === 'function') { 9 | fromMeta = fromMeta(route) 10 | } 11 | return (fromMeta || route.name) as string 12 | }) 13 | 14 | return { 15 | currentTab, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/App3.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 31 | -------------------------------------------------------------------------------- /packages/shell-host/src/backend.js: -------------------------------------------------------------------------------- 1 | import { initBackend } from '@back' 2 | import { Bridge } from '@vue-devtools/shared-utils' 3 | 4 | const bridge = new Bridge({ 5 | listen(fn) { 6 | window.addEventListener('message', evt => fn(evt.data)) 7 | }, 8 | send(data) { 9 | if (process.env.NODE_ENV !== 'production') { 10 | console.log('%cbackend -> devtools', 'color:#888;', data) 11 | } 12 | window.parent.postMessage(data, '*') 13 | }, 14 | }) 15 | 16 | initBackend(bridge) 17 | -------------------------------------------------------------------------------- /packages/shell-chrome/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 |
18 |
19 |
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/app-frontend/src/plugins/global-refs.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | 3 | interface Options { 4 | refs: { [key: string]: any } 5 | } 6 | 7 | export default { 8 | install(app: App, options: Options) { 9 | const { refs } = options 10 | const wrapper = {} 11 | Object.keys(refs).forEach((key) => { 12 | const get = refs[key] 13 | Object.defineProperty(wrapper, key, { 14 | get, 15 | }) 16 | }) 17 | app.config.globalProperties.$globalRefs = wrapper 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /packages/docs/src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | sidebar: false 4 | heroImage: /logo.svg 5 | 6 | actionText: Install now 7 | actionLink: /guide/installation 8 | 9 | footer: MIT Licensed | Copyright © 2014-present Evan You, Guillaume Chau 10 | --- 11 | 12 | ## Sponsors 13 | 14 | [💚️ Become a Sponsor](https://github.com/sponsors/Akryum) 15 | 16 |

17 | 18 | 19 | 20 |

21 | -------------------------------------------------------------------------------- /packages/docs/src/guide/custom-vue2-app-scan-selector.md: -------------------------------------------------------------------------------- 1 | ## Customize vue2 app scan selector 2 | > For example, if you are using micro-app as your micro-frontend framework, the devtools cannot find Vue2 apps in `` by default. 3 | 4 | You can set a custom selector used to scan for Vue2 apps in your project with the following code in your frontend app: 5 | 6 | ```js 7 | if (process.env.NODE_ENV !== 'production') { 8 | window.VUE_DEVTOOLS_CONFIG = { 9 | customVue2ScanSelector: 'micro-app' 10 | } 11 | } 12 | ``` 13 | -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/style/transitions.styl: -------------------------------------------------------------------------------- 1 | .slide-up-enter 2 | opacity 0 3 | transform translate(0, 50%) 4 | 5 | .slide-up-leave-to 6 | opacity 0 7 | transform translate(0, -50%) 8 | 9 | .slide-down-enter, .slide-down-leave-to 10 | opacity 0 11 | transform translate(0, -20px) 12 | 13 | @keyframes rotate 14 | 0% 15 | transform rotate(0deg) 16 | 100% 17 | transform rotate(360deg) 18 | 19 | @keyframes pulse 20 | 0% 21 | opacity 1 22 | 50% 23 | opacity .2 24 | 100% 25 | opacity 1 26 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/RefTester.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 30 | -------------------------------------------------------------------------------- /packages/shell-host/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/shell-host", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "cross-env TAILWIND_MODE=watch webpack serve" 7 | }, 8 | "dependencies": { 9 | "@vue-devtools/app-backend-core": "^0.0.0", 10 | "@vue-devtools/app-frontend": "^0.0.0", 11 | "@vue-devtools/shared-utils": "^0.0.0", 12 | "vue": "^3.3.4" 13 | }, 14 | "devDependencies": { 15 | "cross-env": "^5.2.0", 16 | "webpack": "^5.90.1", 17 | "webpack-cli": "^5.1.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/layout/orientation.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | const orientation = ref('landscape') 4 | 5 | export function useOrientation() { 6 | return { 7 | orientation, 8 | } 9 | } 10 | 11 | const mediaQuery = window.matchMedia('(min-width: 685px)') 12 | switchOrientation(mediaQuery) 13 | mediaQuery.addEventListener('change', switchOrientation) 14 | 15 | function switchOrientation(mediaQueryEvent: MediaQueryListEvent | MediaQueryList) { 16 | orientation.value = mediaQueryEvent.matches ? 'landscape' : 'portrait' 17 | } 18 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/NestedMore.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 33 | -------------------------------------------------------------------------------- /packages/shell-electron/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const { createConfig } = require('@vue-devtools/build-tools') 3 | 4 | const target = { 5 | chrome: 52, 6 | firefox: 48, 7 | safari: 9, 8 | ie: 11, 9 | } 10 | 11 | module.exports = createConfig({ 12 | entry: { 13 | devtools: './src/devtools.js', 14 | backend: './src/backend.js', 15 | hook: './src/hook.js', 16 | }, 17 | output: { 18 | path: path.join(__dirname, '/build'), 19 | publicPath: '/build/', 20 | filename: '[name].js', 21 | }, 22 | }, target) 23 | -------------------------------------------------------------------------------- /packages/docs/src/devtools-vue3.md: -------------------------------------------------------------------------------- 1 | # How to Setup Vue DevTools for Vue 3 2 | 3 | ## Local Development 4 | 5 | ```bash 6 | # Clone repo 7 | git clone git@github.com:vuejs/vue-devtools.git 8 | 9 | # Change into devtools directory 10 | cd vue-devtools 11 | 12 | # Checkout next branch 13 | git checkout next 14 | 15 | # Install dependencies 16 | yarn 17 | 18 | # Build TypeScript dependencies 19 | yarn build:watch 20 | 21 | # Start local environment 22 | yarn dev:shell-vue3 23 | ``` 24 | 25 | Once everything is setup, you should be able to visit `http://localhost:8090/` 26 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/devtools-plugin/simple.js: -------------------------------------------------------------------------------- 1 | import { setupDevtoolsPlugin } from '@vue/devtools-api' 2 | 3 | export default { 4 | install: (app) => { 5 | setupDevtoolsPlugin({ 6 | id: 'simple-plugin', 7 | label: 'Simple devtools plugin', 8 | app, 9 | }, (api) => { 10 | api.on.visitComponentTree((payload, ctx) => { 11 | payload.treeNode.tags.push({ 12 | label: 'simple plugin', 13 | textColor: 0xFFAAAA, 14 | backgroundColor: 0xFFEEEE, 15 | }) 16 | }) 17 | }) 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /packages/shell-chrome/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/shell-chrome", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "build": "rimraf ./build && cross-env NODE_ENV=production webpack --progress" 6 | }, 7 | "dependencies": { 8 | "@vue-devtools/app-backend-core": "^0.0.0", 9 | "@vue-devtools/app-frontend": "^0.0.0", 10 | "@vue-devtools/shared-utils": "^0.0.0" 11 | }, 12 | "devDependencies": { 13 | "@vue-devtools/build-tools": "^0.0.0", 14 | "rimraf": "^3.0.2", 15 | "webpack": "^5.90.1", 16 | "webpack-cli": "^5.1.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/docs/src/components/InstallButton.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/AsyncComponent.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /packages/shell-firefox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/shell-firefox", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "build": "rimraf ./build && ./copy.sh && cross-env NODE_ENV=production webpack --progress" 6 | }, 7 | "dependencies": { 8 | "@vue-devtools/app-backend-core": "^0.0.0", 9 | "@vue-devtools/app-frontend": "^0.0.0", 10 | "@vue-devtools/shared-utils": "^0.0.0" 11 | }, 12 | "devDependencies": { 13 | "@vue-devtools/build-tools": "^0.0.0", 14 | "rimraf": "^3.0.2", 15 | "webpack": "^5.35.1", 16 | "webpack-cli": "^4.6.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/app-frontend/src/types/vue.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | SharedData, 3 | keys, 4 | } from '@vue-devtools/shared-utils' 5 | 6 | import type { 7 | Responsive, 8 | } from '@front/plugins/responsive' 9 | 10 | declare module '@vue/runtime-core' { 11 | interface ComponentCustomProperties { 12 | $t: (string, values?: Record) => string 13 | $responsive: Responsive 14 | $shared: typeof SharedData 15 | $isChrome: boolean 16 | $isFirefox: boolean 17 | $isWindows: boolean 18 | $isMac: boolean 19 | $isLinux: boolean 20 | $keys: typeof keys 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/layout/EmptyPane.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | -------------------------------------------------------------------------------- /packages/shell-host/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const { createConfig } = require('@vue-devtools/build-tools') 3 | const openInEditor = require('launch-editor-middleware') 4 | 5 | module.exports = createConfig({ 6 | entry: { 7 | devtools: './src/devtools.js', 8 | }, 9 | output: { 10 | path: path.join(__dirname, '/build'), 11 | publicPath: '/build/', 12 | filename: '[name].js', 13 | }, 14 | devServer: { 15 | port: 8091, 16 | onBeforeSetupMiddleware({ app }) { 17 | app.use('/__open-in-editor', openInEditor()) 18 | }, 19 | }, 20 | }) 21 | -------------------------------------------------------------------------------- /packages/shell-chrome/popups/enabled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 | Screenshot 7 |
8 | 9 |
10 |

11 | Vue.js is detected on this page.
12 | Open DevTools and look for the Vue panel. 13 |

14 | 15 |

16 | Troubleshooting 17 |

18 |
19 |
20 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | with: 16 | fetch-depth: 0 # Fetch all tags 17 | 18 | - name: Create Release for Tag 19 | id: release_tag 20 | uses: Akryum/release-tag@v4.0.7 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | with: 24 | tag_name: ${{ github.ref }} 25 | -------------------------------------------------------------------------------- /packages/shared-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/shared-utils", 3 | "version": "0.0.0", 4 | "private": true, 5 | "sideEffects": false, 6 | "main": "./lib/index.js", 7 | "types": "./lib/index.d.ts", 8 | "scripts": { 9 | "build": "rimraf lib && yarn ts", 10 | "build:watch": "yarn ts -w", 11 | "ts": "tsc -p tsconfig.json -d -outDir lib" 12 | }, 13 | "dependencies": { 14 | "@vue/devtools-api": "^6.0.0-beta.11" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^20.11.16", 18 | "@types/webpack-env": "^1.15.1", 19 | "typescript": "^5.3.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/EventNesting.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 32 | -------------------------------------------------------------------------------- /packages/app-backend-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/app-backend-api", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "scripts": { 8 | "build": "rimraf lib && yarn ts", 9 | "build:watch": "yarn ts -w", 10 | "ts": "tsc -d -outDir lib" 11 | }, 12 | "dependencies": { 13 | "@vue-devtools/shared-utils": "^0.0.0", 14 | "@vue/devtools-api": "^6.0.0-beta.1" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^20.11.16", 18 | "@types/webpack-env": "^1.15.1", 19 | "typescript": "^5.3.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/shell-chrome/popups/enabled.nuxt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 | Screenshot 7 |
8 | 9 |
10 |

11 | Nuxt + Vue.js is detected on this page.
12 | Open DevTools and look for the Vue panel. 13 |

14 | 15 |

16 | Troubleshooting 17 |

18 |
19 |
20 | -------------------------------------------------------------------------------- /packages/app-backend-vue1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/app-backend-vue1", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "scripts": { 8 | "build": "rimraf lib && yarn ts", 9 | "build:watch": "yarn ts -w", 10 | "ts": "tsc -d -outDir lib" 11 | }, 12 | "dependencies": { 13 | "@vue-devtools/app-backend-api": "^0.0.0", 14 | "@vue-devtools/shared-utils": "^0.0.0" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^20.11.16", 18 | "@types/webpack-env": "^1.15.1", 19 | "typescript": "^5.3.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/app-frontend/src/util/defer.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, ref } from 'vue' 2 | 3 | export function useDefer(count = 10) { 4 | const displayPriority = ref(0) 5 | 6 | function step() { 7 | requestAnimationFrame(() => { 8 | displayPriority.value++ 9 | if (displayPriority.value < count) { 10 | step() 11 | } 12 | }) 13 | } 14 | 15 | function runDisplayPriority() { 16 | step() 17 | } 18 | 19 | function defer(priority) { 20 | return displayPriority.value >= priority 21 | } 22 | 23 | onMounted(() => { 24 | runDisplayPriority() 25 | }) 26 | 27 | return { 28 | defer, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/AsyncSetup.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/shell-dev-vue2", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "cross-env TAILWIND_MODE=watch PORT=8100 webpack serve" 7 | }, 8 | "dependencies": { 9 | "@vue-devtools/shell-host": "^0.0.0", 10 | "vue": "^2.7.10", 11 | "vue-router": "^3.6.5", 12 | "vuex": "^3.6.2" 13 | }, 14 | "devDependencies": { 15 | "@vue-devtools/build-tools": "^0.0.0", 16 | "cross-env": "^5.2.0", 17 | "launch-editor-middleware": "^2.2.1", 18 | "vue-loader": "^15.7.1", 19 | "webpack": "^5.90.1", 20 | "webpack-cli": "^5.1.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/Form.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 35 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/ui/components/VueLoadingBar.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 30 | -------------------------------------------------------------------------------- /packages/app-backend-core/src/util/subscriptions.ts: -------------------------------------------------------------------------------- 1 | const activeSubs: Map> = new Map() 2 | 3 | function getSubs(type: string) { 4 | let subs = activeSubs.get(type) 5 | if (!subs) { 6 | subs = new Map() 7 | activeSubs.set(type, subs) 8 | } 9 | return subs 10 | } 11 | 12 | export function subscribe(type: string, key: string) { 13 | getSubs(type).set(key, true) 14 | } 15 | 16 | export function unsubscribe(type: string, key: string) { 17 | const subs = getSubs(type) 18 | subs.delete(key) 19 | } 20 | 21 | export function isSubscribed( 22 | type: string, 23 | key: string, 24 | ) { 25 | return getSubs(type).has(key) 26 | } 27 | -------------------------------------------------------------------------------- /packages/app-backend-vue3/src/components/filter.ts: -------------------------------------------------------------------------------- 1 | import { classify, kebabize } from '@vue-devtools/shared-utils' 2 | import { getInstanceName } from './util' 3 | 4 | export class ComponentFilter { 5 | filter: string 6 | 7 | constructor(filter: string) { 8 | this.filter = filter || '' 9 | } 10 | 11 | /** 12 | * Check if an instance is qualified. 13 | * 14 | * @param {Vue|Vnode} instance 15 | * @return {boolean} 16 | */ 17 | isQualified(instance) { 18 | const name = getInstanceName(instance) 19 | return classify(name).toLowerCase().includes(this.filter) 20 | || kebabize(name).toLowerCase().includes(this.filter) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/app-backend-vue3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/app-backend-vue3", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "scripts": { 8 | "build": "rimraf lib && yarn ts", 9 | "build:watch": "yarn ts -w", 10 | "ts": "tsc -d -outDir lib" 11 | }, 12 | "dependencies": { 13 | "@vue-devtools/app-backend-api": "^0.0.0", 14 | "@vue-devtools/shared-utils": "^0.0.0", 15 | "@vue/devtools-api": "^6.0.0-beta.1" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20.11.16", 19 | "@types/webpack-env": "^1.15.1", 20 | "typescript": "^5.3.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/shell-firefox/src/hook.js: -------------------------------------------------------------------------------- 1 | // This script is injected into every page. 2 | import { installHook } from '@back/hook' 3 | import { isFirefox } from '@vue-devtools/shared-utils' 4 | 5 | // inject the hook 6 | if (document instanceof HTMLDocument) { 7 | const source = `;(${installHook.toString()})(window)` 8 | 9 | if (isFirefox) { 10 | // eslint-disable-next-line no-eval 11 | window.eval(source) // in Firefox, this evaluates on the content window 12 | } 13 | else { 14 | const script = document.createElement('script') 15 | script.textContent = source 16 | document.documentElement.appendChild(script) 17 | script.parentNode.removeChild(script) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/shared-utils/src/raf.ts: -------------------------------------------------------------------------------- 1 | let pendingCallbacks: Array<(time: number) => void> = [] 2 | 3 | /** 4 | * requestAnimationFrame that also works on non-browser environments like Node. 5 | */ 6 | export const raf = typeof requestAnimationFrame === 'function' 7 | ? requestAnimationFrame 8 | : (fn: (time: number) => void) => { 9 | if (!pendingCallbacks.length) { 10 | setImmediate(() => { 11 | const now = performance.now() 12 | const cbs = pendingCallbacks 13 | // in case cbs add new callbacks 14 | pendingCallbacks = [] 15 | cbs.forEach(cb => cb(now)) 16 | }) 17 | } 18 | 19 | pendingCallbacks.push(fn) 20 | } 21 | -------------------------------------------------------------------------------- /packages/api/src/time.ts: -------------------------------------------------------------------------------- 1 | let supported: boolean 2 | let perf: Performance 3 | 4 | export function isPerformanceSupported() { 5 | if (supported !== undefined) { 6 | return supported 7 | } 8 | if (typeof window !== 'undefined' && window.performance) { 9 | supported = true 10 | perf = window.performance 11 | } 12 | else if (typeof globalThis !== 'undefined' && (globalThis as any).perf_hooks?.performance) { 13 | supported = true 14 | perf = (globalThis as any).perf_hooks.performance 15 | } 16 | else { 17 | supported = false 18 | } 19 | return supported 20 | } 21 | 22 | export function now() { 23 | return isPerformanceSupported() ? perf.now() : Date.now() 24 | } 25 | -------------------------------------------------------------------------------- /packages/shell-firefox/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const { createConfig } = require('@vue-devtools/build-tools') 3 | 4 | module.exports = createConfig({ 5 | entry: { 6 | 'hook': './src/hook.js', 7 | 'devtools': './src/devtools.js', 8 | 'background': './src/background.js', 9 | 'devtools-background': './src/devtools-background.js', 10 | 'backend': './src/backend.js', 11 | 'proxy': './src/proxy.js', 12 | 'detector': './src/detector.js', 13 | }, 14 | output: { 15 | path: path.join(__dirname, 'build'), 16 | filename: '[name].js', 17 | }, 18 | devtool: process.env.NODE_ENV !== 'production' 19 | ? 'inline-source-map' 20 | : false, 21 | }) 22 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/DomOrder.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 38 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/EventChildCond.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 33 | -------------------------------------------------------------------------------- /packages/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["ESNext", "DOM"], 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "resolveJsonModule": true, 8 | "types": ["node", "webpack-env"], 9 | "strictBindCallApply": true, 10 | "strictFunctionTypes": true, 11 | "alwaysStrict": true, 12 | // Strict 13 | "noImplicitAny": false, 14 | "noImplicitThis": true, 15 | "removeComments": false, 16 | "sourceMap": false, 17 | "allowSyntheticDefaultImports": true, 18 | "esModuleInterop": true, 19 | "skipLibCheck": true, 20 | "preserveWatchOutput": true 21 | }, 22 | "include": ["src/**/*"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/error/index.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref } from 'vue' 2 | 3 | export interface ErrorMessage { 4 | message: string 5 | icon: string 6 | } 7 | 8 | const errors = ref([]) 9 | 10 | export function putError(message: string, icon: string = null) { 11 | // Dedupe 12 | if (errors.value.find(e => e.message === message)) { 13 | return 14 | } 15 | 16 | errors.value.push({ 17 | message, 18 | icon, 19 | }) 20 | } 21 | 22 | export function clearError() { 23 | errors.value.shift() 24 | } 25 | 26 | export function useError() { 27 | const error = computed(() => errors.value[0]) 28 | 29 | return { 30 | error, 31 | putError, 32 | clearError, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: I have a performance issue 4 | url: https://devtools.vuejs.org/guide/devtools-perf.html 5 | about: Follow the guide to share performance profiling data with us! 6 | - name: Questions & Discussions 7 | url: https://github.com/vuejs/devtools/discussions 8 | about: Use GitHub discussions for message-board style questions and discussions. 9 | - name: Discord Chat 10 | url: https://chat.vuejs.org 11 | about: Ask questions and discuss with other Vue users in real time. 12 | - name: GitHub Sponsor 13 | url: https://github.com/sponsors/Akryum 14 | about: Love the Vue devtools? Please consider supporting us via GitHub sponsors. 15 | -------------------------------------------------------------------------------- /packages/app-backend-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "types": [ 8 | "node", 9 | "webpack-env" 10 | ], 11 | "strictBindCallApply": true, 12 | "strictFunctionTypes": true, 13 | "alwaysStrict": true, 14 | // Strict 15 | "noImplicitAny": false, 16 | "noImplicitThis": true, 17 | "sourceMap": true, 18 | "allowSyntheticDefaultImports": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "preserveWatchOutput": true 22 | }, 23 | "include": [ 24 | "src/**/*" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/app-backend-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "types": [ 8 | "node", 9 | "webpack-env" 10 | ], 11 | "strictBindCallApply": true, 12 | "strictFunctionTypes": true, 13 | "alwaysStrict": true, 14 | // Strict 15 | "noImplicitAny": false, 16 | "noImplicitThis": true, 17 | "sourceMap": true, 18 | "allowSyntheticDefaultImports": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "preserveWatchOutput": true 22 | }, 23 | "include": [ 24 | "src/**/*" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/app-backend-vue1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "types": [ 8 | "node", 9 | "webpack-env" 10 | ], 11 | "strictBindCallApply": true, 12 | "strictFunctionTypes": true, 13 | "alwaysStrict": true, 14 | // Strict 15 | "noImplicitAny": false, 16 | "noImplicitThis": true, 17 | "sourceMap": true, 18 | "allowSyntheticDefaultImports": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "preserveWatchOutput": true 22 | }, 23 | "include": [ 24 | "src/**/*" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/app-backend-vue2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "types": [ 8 | "node", 9 | "webpack-env" 10 | ], 11 | "strictBindCallApply": true, 12 | "strictFunctionTypes": true, 13 | "alwaysStrict": true, 14 | // Strict 15 | "noImplicitAny": false, 16 | "noImplicitThis": true, 17 | "sourceMap": true, 18 | "allowSyntheticDefaultImports": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "preserveWatchOutput": true 22 | }, 23 | "include": [ 24 | "src/**/*" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/app-backend-vue3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "types": [ 8 | "node", 9 | "webpack-env" 10 | ], 11 | "strictBindCallApply": true, 12 | "strictFunctionTypes": true, 13 | "alwaysStrict": true, 14 | // Strict 15 | "noImplicitAny": false, 16 | "noImplicitThis": true, 17 | "sourceMap": true, 18 | "allowSyntheticDefaultImports": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "preserveWatchOutput": true 22 | }, 23 | "include": [ 24 | "src/**/*" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/app-frontend/src/util/color.ts: -------------------------------------------------------------------------------- 1 | import tinycolor from 'tinycolor2' 2 | 3 | export function toStrHex(color: number) { 4 | return color.toString(16).padStart(6, '0') 5 | } 6 | 7 | export function dimColor(color: number, dark: boolean, amount = 20) { 8 | let c = tinycolor(toStrHex(color)) 9 | if (dark) { 10 | c = c.darken(amount) 11 | } 12 | else { 13 | c = c.lighten(amount) 14 | } 15 | return Number.parseInt(`0x${c.toHex()}`) 16 | } 17 | 18 | export function boostColor(color: number, dark: boolean, amount = 10) { 19 | let c = tinycolor(toStrHex(color)) 20 | if (dark) { 21 | c = c.lighten(amount) 22 | } 23 | else { 24 | c = c.darken(amount) 25 | } 26 | return Number.parseInt(`0x${c.toHex()}`) 27 | } 28 | -------------------------------------------------------------------------------- /packages/shared-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "CommonJS", 5 | "moduleResolution": "Node", 6 | "resolveJsonModule": true, 7 | "types": [ 8 | "node", 9 | "webpack-env" 10 | ], 11 | "strictBindCallApply": true, 12 | "strictFunctionTypes": true, 13 | "alwaysStrict": true, 14 | // Strict 15 | "noImplicitAny": false, 16 | "noImplicitThis": true, 17 | "sourceMap": true, 18 | "allowSyntheticDefaultImports": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "preserveWatchOutput": true 22 | }, 23 | "include": [ 24 | "src/**/*" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/public/target.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/Condition.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 40 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/shell-dev-vue3", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "cross-env TAILWIND_MODE=watch webpack serve" 7 | }, 8 | "dependencies": { 9 | "@vue-devtools/shell-host": "^0.0.0", 10 | "@vue/devtools-api": "^6.0.0-beta.1", 11 | "core-js": "^3.6.4", 12 | "vue": "^3.3.4", 13 | "vue-router": "^4.2.5", 14 | "vuex": "^4.0.1" 15 | }, 16 | "devDependencies": { 17 | "@vue-devtools/build-tools": "^0.0.0", 18 | "@vue/compiler-sfc": "^3.3.4", 19 | "cross-env": "^5.2.0", 20 | "launch-editor-middleware": "^2.2.1", 21 | "mini-css-extract-plugin": "^1.5.0", 22 | "webpack": "^5.90.1", 23 | "webpack-cli": "^5.1.4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/shell-electron/webpack.node.config.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const { createConfig } = require('@vue-devtools/build-tools') 3 | 4 | const target = { 5 | chrome: 52, 6 | firefox: 48, 7 | safari: 9, 8 | ie: 11, 9 | } 10 | 11 | module.exports = createConfig({ 12 | target: 'node', 13 | externals: { 14 | // from https://socket.io/docs/v4/client-with-bundlers/ 15 | 'bufferutil': 'bufferutil', 16 | 'utf-8-validate': 'utf-8-validate', 17 | }, 18 | entry: { 19 | devtools: './src/devtools.js', 20 | backend: './src/backend.js', 21 | hook: './src/hook.js', 22 | }, 23 | output: { 24 | path: path.join(__dirname, '/build-node'), 25 | publicPath: '/build-node/', 26 | filename: '[name].js', 27 | }, 28 | }, target) 29 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/ui/composables/useDisableScroll.ts: -------------------------------------------------------------------------------- 1 | import { onBeforeUnmount, onMounted } from 'vue' 2 | 3 | let count = 0 4 | 5 | function getScrollingElements() { 6 | return document.querySelectorAll('.vue-ui-disable-scroll, body') 7 | } 8 | 9 | function updateScroll() { 10 | if (count === 0) { 11 | getScrollingElements().forEach(el => 12 | el.classList.remove('vue-ui-no-scroll'), 13 | ) 14 | } 15 | else if (count === 1) { 16 | getScrollingElements().forEach(el => el.classList.add('vue-ui-no-scroll')) 17 | } 18 | } 19 | 20 | export function useDisableScroll() { 21 | onMounted(() => { 22 | count++ 23 | updateScroll() 24 | }) 25 | 26 | onBeforeUnmount(() => { 27 | count-- 28 | updateScroll() 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /packages/shared-utils/src/plugin-permissions.ts: -------------------------------------------------------------------------------- 1 | import { SharedData } from './shared-data' 2 | 3 | export enum PluginPermission { 4 | ENABLED = 'enabled', 5 | COMPONENTS = 'components', 6 | CUSTOM_INSPECTOR = 'custom-inspector', 7 | TIMELINE = 'timeline', 8 | } 9 | 10 | export function hasPluginPermission(pluginId: string, permission: PluginPermission) { 11 | const result = SharedData.pluginPermissions[`${pluginId}:${permission}`] 12 | if (result == null) { 13 | return true 14 | } 15 | return !!result 16 | } 17 | 18 | export function setPluginPermission(pluginId: string, permission: PluginPermission, active: boolean) { 19 | SharedData.pluginPermissions = { 20 | ...SharedData.pluginPermissions, 21 | [`${pluginId}:${permission}`]: active, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/app-backend-core/src/page-config.ts: -------------------------------------------------------------------------------- 1 | import { SharedData, target } from '@vue-devtools/shared-utils' 2 | 3 | export interface PageConfig { 4 | openInEditorHost?: string 5 | defaultSelectedAppId?: string 6 | customVue2ScanSelector?: string 7 | } 8 | 9 | let config: PageConfig = {} 10 | 11 | export function getPageConfig(): PageConfig { 12 | return config 13 | } 14 | 15 | export function initOnPageConfig() { 16 | // User project devtools config 17 | if (Object.hasOwnProperty.call(target, 'VUE_DEVTOOLS_CONFIG')) { 18 | config = SharedData.pageConfig = target.VUE_DEVTOOLS_CONFIG 19 | 20 | // Open in editor 21 | if (Object.hasOwnProperty.call(config, 'openInEditorHost')) { 22 | SharedData.openInEditorHost = config.openInEditorHost 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/settings/NewTag.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 26 | 27 | 38 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/components/composable/highlight.ts: -------------------------------------------------------------------------------- 1 | import { getBridge } from '@front/features/bridge' 2 | import { BridgeEvents } from '@vue-devtools/shared-utils' 3 | import type { Ref } from 'vue' 4 | import throttle from 'lodash/throttle' 5 | 6 | const throttledSend = throttle((id?: string) => { 7 | if (id) { 8 | getBridge().send(BridgeEvents.TO_BACK_COMPONENT_MOUSE_OVER, id) 9 | } 10 | else { 11 | getBridge().send(BridgeEvents.TO_BACK_COMPONENT_MOUSE_OUT) 12 | } 13 | }, 200) 14 | 15 | export function useComponentHighlight(id: Ref) { 16 | function highlight() { 17 | throttledSend(id.value) 18 | } 19 | 20 | function unhighlight() { 21 | throttledSend(null) 22 | } 23 | 24 | return { 25 | highlight, 26 | unhighlight, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/shell-chrome/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const { createConfig } = require('@vue-devtools/build-tools') 3 | 4 | module.exports = createConfig({ 5 | entry: { 6 | 'hook': './src/hook.js', 7 | 'hook-exec': './src/hook-exec.js', 8 | 'devtools': './src/devtools.js', 9 | 'service-worker': './src/service-worker.js', 10 | 'devtools-background': './src/devtools-background.js', 11 | 'backend': './src/backend.js', 12 | 'proxy': './src/proxy.js', 13 | 'detector': './src/detector.js', 14 | 'detector-exec': './src/detector-exec.js', 15 | }, 16 | output: { 17 | path: path.join(__dirname, 'build'), 18 | filename: '[name].js', 19 | }, 20 | devtool: process.env.NODE_ENV !== 'production' 21 | ? 'inline-source-map' 22 | : false, 23 | }) 24 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/timeline/composable/markers.ts: -------------------------------------------------------------------------------- 1 | import { useCurrentApp } from '@front/features/apps' 2 | import { computed, watch } from 'vue' 3 | import { getBridge } from '@front/features/bridge' 4 | import { BridgeEvents } from '@vue-devtools/shared-utils' 5 | import { markersAllApps, markersPerApp } from './store' 6 | 7 | export function useMarkers() { 8 | const { currentAppId } = useCurrentApp() 9 | const currentAppMarkers = computed(() => markersAllApps.value.concat(markersPerApp.value[currentAppId.value] ?? [])) 10 | 11 | watch(currentAppId, () => { 12 | loadMarkers() 13 | }, { 14 | immediate: true, 15 | }) 16 | 17 | return { 18 | currentAppMarkers, 19 | } 20 | } 21 | 22 | function loadMarkers() { 23 | getBridge().send(BridgeEvents.TO_BACK_TIMELINE_LOAD_MARKERS) 24 | } 25 | -------------------------------------------------------------------------------- /packages/app-backend-vue2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/app-backend-vue2", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "scripts": { 8 | "build": "rimraf lib && yarn ts", 9 | "build:watch": "yarn ts -w", 10 | "ts": "tsc -d -outDir lib" 11 | }, 12 | "dependencies": { 13 | "@vue-devtools/app-backend-api": "^0.0.0", 14 | "@vue-devtools/shared-utils": "^0.0.0", 15 | "@vue/devtools-api": "^6.0.0-beta.7", 16 | "clone-deep": "^4.0.1", 17 | "lodash": "^4.17.21" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^20.11.16", 21 | "@types/webpack-env": "^1.15.1", 22 | "core-js": "^3.20.2", 23 | "typescript": "^5.3.3", 24 | "vue": "^2.7.10", 25 | "vue-loader": "^15.7.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/docs/src/public/logo-header.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 17 | 21 | 25 | 26 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/Heavy.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 37 | 38 | 43 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/Nested.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 47 | -------------------------------------------------------------------------------- /packages/build-tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/build-tools", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "src/index.js", 6 | "dependencies": { 7 | "@babel/core": "^7.16.0", 8 | "@babel/preset-env": "^7.16.4", 9 | "@vue/compiler-sfc": "^3.3.4", 10 | "babel-loader": "^8.2.3", 11 | "css-loader": "^5.2.4", 12 | "esbuild": "^0.11.20", 13 | "esbuild-loader": "^2.13.0", 14 | "friendly-errors-webpack-plugin": "^1.7.0", 15 | "monaco-editor-webpack-plugin": "^3.1.0", 16 | "path-browserify": "^1.0.1", 17 | "postcss-loader": "^5.2.0", 18 | "style-resources-loader": "^1.2.1", 19 | "stylus": "^0.54.5", 20 | "stylus-loader": "^5.0.0", 21 | "terser-webpack-plugin": "^5.1.1", 22 | "vue-loader": "^17.2.2", 23 | "webpack": "^5.90.1", 24 | "webpack-merge": "^5.10.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/chrome/pane-visibility.ts: -------------------------------------------------------------------------------- 1 | import { isChrome } from '@vue-devtools/shared-utils' 2 | 3 | let panelShown = !isChrome 4 | let pendingAction: (() => void | Promise) | null = null 5 | 6 | if (isChrome) { 7 | chrome.runtime.onMessage.addListener((request) => { 8 | if (request === 'vue-panel-shown') { 9 | onPanelShown() 10 | } 11 | else if (request === 'vue-panel-hidden') { 12 | onPanelHidden() 13 | } 14 | }) 15 | } 16 | 17 | export function ensurePaneShown(cb: () => void | Promise) { 18 | if (panelShown) { 19 | cb() 20 | } 21 | else { 22 | pendingAction = cb 23 | } 24 | } 25 | 26 | function onPanelShown() { 27 | panelShown = true 28 | if (pendingAction) { 29 | pendingAction() 30 | pendingAction = null 31 | } 32 | } 33 | 34 | function onPanelHidden() { 35 | panelShown = false 36 | } 37 | -------------------------------------------------------------------------------- /packages/app-backend-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/app-backend-core", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "scripts": { 8 | "build": "rimraf lib && yarn ts", 9 | "build:watch": "yarn ts -w", 10 | "ts": "tsc -d -outDir lib" 11 | }, 12 | "dependencies": { 13 | "@vue-devtools/app-backend-api": "^0.0.0", 14 | "@vue-devtools/app-backend-vue1": "^0.0.0", 15 | "@vue-devtools/app-backend-vue2": "^0.0.0", 16 | "@vue-devtools/app-backend-vue3": "^0.0.0", 17 | "@vue-devtools/shared-utils": "^0.0.0", 18 | "@vue/devtools-api": "^6.0.0-beta.1", 19 | "lodash": "^4.17.21", 20 | "speakingurl": "^14.0.1" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^20.11.16", 24 | "@types/webpack-env": "^1.15.1", 25 | "typescript": "^5.3.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/shell-chrome/popups/popup.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700,700i'); 2 | 3 | body { 4 | font-family: Roboto, Avenir, Helvetica, Arial, sans-serif; 5 | font-size: 14px; 6 | font-weight: 400; 7 | line-height: 1.4; 8 | padding: 18px 24px; 9 | color: #2c3e50; 10 | } 11 | 12 | body, 13 | p { 14 | margin: 0; 15 | } 16 | 17 | p { 18 | min-width: 200px; 19 | max-width: 300px; 20 | } 21 | 22 | .short-paragraph { 23 | min-width: initial; 24 | white-space: nowrap; 25 | } 26 | 27 | a { 28 | color: #42B983; 29 | } 30 | 31 | .flex { 32 | display: flex; 33 | align-items: center; 34 | } 35 | 36 | .screenshot { 37 | position: relative; 38 | } 39 | 40 | .screenshot > img { 41 | width: 140px; 42 | height: 140px; 43 | object-fit: cover; 44 | border-radius: 100%; 45 | margin-right: 24px; 46 | box-shadow: 0 0 15px rgb(0 0 0 / 10%); 47 | } 48 | -------------------------------------------------------------------------------- /packages/shared-utils/src/throttle.ts: -------------------------------------------------------------------------------- 1 | import throttle from 'lodash/throttle' 2 | 3 | interface ThrottleQueueItem { 4 | fn: Function 5 | key: string 6 | } 7 | 8 | export function createThrottleQueue(wait: number) { 9 | const queue: ThrottleQueueItem[] = [] 10 | const tracker: Map = new Map() 11 | 12 | function flush() { 13 | for (const item of queue) { 14 | item.fn() 15 | tracker.delete(item.key) 16 | } 17 | queue.length = 0 18 | } 19 | 20 | const throttledFlush = throttle(flush, wait) 21 | 22 | function add(key: string, fn: Function) { 23 | if (!tracker.has(key)) { 24 | const item = { key, fn } 25 | queue.push(item) 26 | tracker.set(key, item) 27 | throttledFlush() 28 | } 29 | else { 30 | const item = tracker.get(key)! 31 | item.fn = fn 32 | } 33 | } 34 | 35 | return { 36 | add, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/app-frontend/src/index.ts: -------------------------------------------------------------------------------- 1 | import './assets/style/index.styl' 2 | import './assets/style/index.postcss' 3 | 4 | import type { Shell } from '@vue-devtools/shared-utils' 5 | import { initStorage } from '@vue-devtools/shared-utils' 6 | import { connectApp, createApp } from './app' 7 | import { setAppConnected } from './features/connection' 8 | import { getBridge } from './features/bridge' 9 | 10 | export { setAppConnected } from './features/connection' 11 | 12 | /** 13 | * Create the main devtools app. Expects to be called with a shell interface 14 | * which implements a connect method. 15 | */ 16 | export async function initDevTools(shell: Shell) { 17 | await initStorage() 18 | const app = createApp() 19 | app.mount('#app') 20 | connectApp(app, shell) 21 | shell.onReload(() => { 22 | setAppConnected(false, true, true) 23 | getBridge()?.removeAllListeners() 24 | connectApp(app, shell) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /packages/app-backend-api/src/app-record.ts: -------------------------------------------------------------------------------- 1 | import type { App, ComponentInstance } from '@vue/devtools-api' 2 | import type { DevtoolsBackend } from './backend' 3 | 4 | export interface AppRecordOptions { 5 | app: App 6 | version: string 7 | types: { [key: string]: string | symbol } 8 | meta?: any 9 | } 10 | 11 | export interface AppRecord { 12 | id: string 13 | name: string 14 | options: AppRecordOptions 15 | backend: DevtoolsBackend 16 | lastInspectedComponentId: string 17 | instanceMap: Map 18 | rootInstance: ComponentInstance 19 | componentFilter?: string 20 | perfGroupIds: Map 21 | iframe: string 22 | meta: any 23 | missingInstanceQueue: Set 24 | } 25 | 26 | /** 27 | * Used in the frontend 28 | */ 29 | export interface SimpleAppRecord { 30 | id: string 31 | name: string 32 | version: string 33 | iframe: string 34 | } 35 | -------------------------------------------------------------------------------- /packages/api/src/env.ts: -------------------------------------------------------------------------------- 1 | import type { ApiProxy } from './proxy.js' 2 | import type { PluginDescriptor, SetupFunction } from './index.js' 3 | 4 | export interface PluginQueueItem { 5 | pluginDescriptor: PluginDescriptor 6 | setupFn: SetupFunction 7 | proxy?: ApiProxy 8 | } 9 | 10 | interface GlobalTarget { 11 | __VUE_DEVTOOLS_PLUGINS__?: PluginQueueItem[] 12 | __VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__?: boolean 13 | } 14 | 15 | export function getDevtoolsGlobalHook(): any { 16 | return (getTarget() as any).__VUE_DEVTOOLS_GLOBAL_HOOK__ 17 | } 18 | 19 | export function getTarget(): GlobalTarget { 20 | // @ts-expect-error navigator and windows are not available in all environments 21 | return (typeof navigator !== 'undefined' && typeof window !== 'undefined') 22 | ? window 23 | : typeof globalThis !== 'undefined' 24 | ? globalThis 25 | : {} 26 | } 27 | 28 | export const isProxyAvailable = typeof Proxy === 'function' 29 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = (_on, _config) => { 15 | // `on` is used to hook into various events Cypress emits 16 | // `config` is the resolved Cypress config 17 | 18 | // on('before:browser:launch', (browser = {}, args) => { 19 | // if (browser.name === 'chrome') { 20 | // args.push('--disable-site-isolation-trials') 21 | // return args 22 | // } 23 | // }) 24 | } 25 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/SetupScript.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 47 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/Events.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "jsx": "preserve", 5 | "baseUrl": ".", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@back/*": [ 9 | "packages/app-backend-core/src/*" 10 | ], 11 | "@front/*": [ 12 | "packages/app-frontend/src/*" 13 | ] 14 | }, 15 | "resolveJsonModule": true, 16 | "types": [ 17 | "chrome", 18 | "node", 19 | "webpack-env" 20 | ], 21 | "allowJs": true, 22 | "strictBindCallApply": true, 23 | "strictFunctionTypes": true, 24 | "alwaysStrict": true, 25 | // Strict 26 | "noImplicitAny": false, 27 | "noImplicitThis": true, 28 | "outDir": "lib", 29 | "allowSyntheticDefaultImports": true, 30 | "esModuleInterop": true, 31 | "skipLibCheck": true 32 | }, 33 | "include": [ 34 | "packages/app-frontend/src/**/*", 35 | "packages/shared-utils/src/**/*" 36 | ], 37 | "exclude": [ 38 | "node_modules" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/error/ErrorOverlay.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 41 | -------------------------------------------------------------------------------- /packages/app-frontend/src/util/reactivity.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue' 2 | import { watch } from 'vue' 3 | import { getStorage, setStorage } from '@vue-devtools/shared-utils' 4 | 5 | export function nonReactive(ref: Ref) { 6 | const holder = { 7 | value: ref.value, 8 | } 9 | 10 | watch(ref, (value) => { 11 | holder.value = value 12 | }, { 13 | flush: 'sync', 14 | }) 15 | 16 | return holder 17 | } 18 | 19 | export function addNonReactiveProperties(target: T, props: Partial) { 20 | for (const key in props) { 21 | Object.defineProperty(target, key, { 22 | value: props[key], 23 | writable: true, 24 | enumerable: true, 25 | configurable: false, 26 | }) 27 | } 28 | } 29 | 30 | export function useSavedRef(ref: Ref, storageKey: string) { 31 | const savedValue = getStorage(storageKey) 32 | if (savedValue != null) { 33 | ref.value = savedValue 34 | } 35 | 36 | watch(ref, (value) => { 37 | setStorage(storageKey, value) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | # specify the version you desire here 6 | - image: node:current 7 | - image: vuejs/ci 8 | resource_class: medium+ 9 | 10 | working_directory: ~/repo 11 | 12 | steps: 13 | - checkout 14 | 15 | # Download and cache dependencies 16 | - restore_cache: 17 | keys: 18 | - v3-dependencies-{{ checksum "yarn.lock" }} 19 | # fallback to using the latest cache if no exact match is found 20 | - v3-dependencies- 21 | 22 | - run: yarn install --pure-lockfile 23 | 24 | - save_cache: 25 | paths: 26 | - node_modules 27 | - ~/.cache/yarn 28 | - ~/.cache/Cypress 29 | key: v3-dependencies-{{ checksum "yarn.lock" }} 30 | 31 | # run tests! 32 | - run: yarn build && yarn test 33 | 34 | - store_artifacts: 35 | path: cypress/videos 36 | - store_artifacts: 37 | path: cypress/screenshots 38 | -------------------------------------------------------------------------------- /packages/shared-utils/src/plugin-settings.ts: -------------------------------------------------------------------------------- 1 | import type { PluginSettingsItem } from '@vue/devtools-api' 2 | import { SharedData } from './shared-data' 3 | 4 | export function getPluginSettings = any>(pluginId: string, defaultSettings?: TSettings): TSettings { 5 | return { 6 | ...defaultSettings ?? {}, 7 | ...SharedData.pluginSettings[pluginId] ?? {}, 8 | } 9 | } 10 | 11 | export function setPluginSettings = any>(pluginId: string, settings: TSettings) { 12 | SharedData.pluginSettings = { 13 | ...SharedData.pluginSettings, 14 | [pluginId]: settings, 15 | } 16 | } 17 | 18 | export function getPluginDefaultSettings = any>(schema: Record): TSettings { 19 | const result: Record = {} 20 | if (schema) { 21 | for (const id in schema) { 22 | const item = schema[id] 23 | result[id] = item.defaultValue 24 | } 25 | } 26 | return result as TSettings 27 | } 28 | -------------------------------------------------------------------------------- /packages/app-backend-core/src/util/queue.ts: -------------------------------------------------------------------------------- 1 | export interface Job { 2 | id: string 3 | fn: () => Promise 4 | } 5 | 6 | export class JobQueue { 7 | jobs: Job[] = [] 8 | currentJob: Job 9 | 10 | queue(id: string, fn: Job['fn']) { 11 | const job: Job = { 12 | id, 13 | fn, 14 | } 15 | 16 | return new Promise((resolve) => { 17 | const onDone = () => { 18 | this.currentJob = null 19 | const nextJob = this.jobs.shift() 20 | if (nextJob) { 21 | nextJob.fn() 22 | } 23 | resolve() 24 | } 25 | 26 | const run = () => { 27 | this.currentJob = job 28 | return job.fn().then(onDone).catch((e) => { 29 | console.error(`Job ${job.id} failed:`) 30 | console.error(e) 31 | }) 32 | } 33 | 34 | if (this.currentJob) { 35 | this.jobs.push({ 36 | id: job.id, 37 | fn: () => run(), 38 | }) 39 | } 40 | else { 41 | run() 42 | } 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/shell-electron/app.js: -------------------------------------------------------------------------------- 1 | // Start middleware server 2 | require('./server') 3 | 4 | const path = require('node:path') 5 | const url = require('node:url') 6 | const { app, BrowserWindow } = require('electron') 7 | 8 | let mainWindow = null 9 | 10 | function createWindow() { 11 | mainWindow = new BrowserWindow({ 12 | width: 800, 13 | height: 600, 14 | icon: path.join(__dirname, 'icons/128.png'), 15 | webPreferences: { 16 | nodeIntegration: true, 17 | contextIsolation: false, 18 | }, 19 | }) 20 | 21 | mainWindow.loadURL(url.format({ 22 | pathname: path.join(__dirname, 'app.html'), 23 | protocol: 'file:', 24 | slashes: true, 25 | })) 26 | 27 | mainWindow.on('closed', () => { 28 | mainWindow = null 29 | }) 30 | } 31 | 32 | app.on('ready', createWindow) 33 | 34 | app.on('window-all-closed', () => { 35 | if (process.platform !== 'darwin') { 36 | app.quit() 37 | } 38 | }) 39 | 40 | app.on('activate', () => { 41 | if (mainWindow === null) { 42 | createWindow() 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /packages/app-backend-api/src/backend.ts: -------------------------------------------------------------------------------- 1 | import type { AppRecord } from './app-record' 2 | import { DevtoolsApi } from './api' 3 | import type { BackendContext } from './backend-context' 4 | 5 | export enum BuiltinBackendFeature { 6 | /** 7 | * @deprecated 8 | */ 9 | FLUSH = 'flush', 10 | } 11 | 12 | export interface DevtoolsBackendOptions { 13 | frameworkVersion: 1 | 2 | 3 14 | features: (BuiltinBackendFeature | string)[] 15 | setup: (api: DevtoolsApi) => void 16 | setupApp?: (api: DevtoolsApi, app: AppRecord) => void 17 | } 18 | 19 | export function defineBackend(options: DevtoolsBackendOptions) { 20 | return options 21 | } 22 | 23 | export interface DevtoolsBackend { 24 | options: DevtoolsBackendOptions 25 | api: DevtoolsApi 26 | } 27 | 28 | export function createBackend(options: DevtoolsBackendOptions, ctx: BackendContext): DevtoolsBackend { 29 | const backend: DevtoolsBackend = { 30 | options, 31 | api: null, 32 | } 33 | backend.api = new DevtoolsApi(backend, ctx) 34 | options.setup(backend.api) 35 | return backend 36 | } 37 | -------------------------------------------------------------------------------- /packages/app-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-devtools/app-frontend", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@pixi/events": "^6.2.0", 7 | "@pixi/unsafe-eval": "^6.2.0", 8 | "@vue-devtools/shared-utils": "^0.0.0", 9 | "@vue/devtools-api": "^6.0.0-beta.9", 10 | "@vue/ui": "^0.12.5", 11 | "@vueuse/core": "^10.7.2", 12 | "circular-json-es6": "^2.0.2", 13 | "d3": "^5.16.0", 14 | "floating-vue": "^5.2.2", 15 | "lodash": "^4.17.15", 16 | "lru-cache": "^5.1.1", 17 | "monaco-editor": "^0.24.0", 18 | "pixi.js-legacy": "^6.2.0", 19 | "scroll-into-view-if-needed": "^2.2.28", 20 | "semver": "^7.3.5", 21 | "stylus": "^0.54.7", 22 | "stylus-loader": "^3.0.2", 23 | "tinycolor2": "^1.4.2", 24 | "vue": "^3.3.4", 25 | "vue-resize": "^2.0.0-alpha.1", 26 | "vue-router": "^4.2.5", 27 | "vue-safe-teleport": "^0.1.2", 28 | "vue-virtual-scroller": "^2.0.0-alpha.1" 29 | }, 30 | "devDependencies": { 31 | "@akryum/md-icons-svg": "^1.0.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/Animation.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 43 | 44 | 50 | -------------------------------------------------------------------------------- /packages/shell-electron/index.js: -------------------------------------------------------------------------------- 1 | const isBrowser = typeof window !== 'undefined' 2 | 3 | if (isBrowser) { 4 | require('./build/hook.js') 5 | } 6 | else { 7 | require('./build-node/hook.js') 8 | } 9 | 10 | const target = isBrowser 11 | ? window 12 | : typeof globalThis !== 'undefined' 13 | ? globalThis 14 | : {} 15 | 16 | module.exports = { 17 | connect(host, port, { io, showToast, app } = {}) { 18 | target.__VUE_DEVTOOLS_HOST__ = host 19 | target.__VUE_DEVTOOLS_PORT__ = port 20 | if (io) { 21 | target.__VUE_DEVTOOLS_SOCKET__ = io 22 | } 23 | if (showToast) { 24 | target.__VUE_DEVTOOLS_TOAST__ = showToast 25 | } 26 | if (app) { 27 | target.__VUE_ROOT_INSTANCES__ = Array.isArray(app) ? app : [app] 28 | } 29 | 30 | if (isBrowser) { 31 | require('./build/backend.js') 32 | } 33 | else { 34 | require('./build-node/backend.js') 35 | } 36 | }, 37 | init: (Vue) => { 38 | const tools = target.__VUE_DEVTOOLS_GLOBAL_HOOK__ 39 | 40 | tools.emit('init', Vue) 41 | }, 42 | } 43 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/apps/vue-version-check.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, ref } from 'vue' 2 | import semver from 'semver' 3 | 4 | const packageData = ref(null) 5 | 6 | export function useVueVersionCheck() { 7 | onMounted(async () => { 8 | if (!packageData.value) { 9 | try { 10 | const response = await fetch('https://registry.npmjs.org/vue', { 11 | headers: { 12 | mode: 'no-cors', 13 | }, 14 | }) 15 | const data = await response.json() 16 | packageData.value = data 17 | } 18 | catch (e) { 19 | if (process.env.NODE_ENV !== 'development') { 20 | console.error(e) 21 | } 22 | } 23 | } 24 | }) 25 | 26 | function getLatestVersion(currentVersion: string): string { 27 | if (packageData.value && packageData.value.versions) { 28 | return semver.maxSatisfying(Object.keys(packageData.value.versions), `^${currentVersion}`) 29 | } 30 | return currentVersion 31 | } 32 | 33 | return { 34 | getLatestVersion, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/app-frontend/src/util/fonts.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import * as PIXI from 'pixi.js-legacy' 3 | 4 | let installedFonts = false 5 | 6 | export async function installFonts() { 7 | if (installedFonts) { 8 | return 9 | } 10 | 11 | try { 12 | await document.fonts.load('10px "Roboto Mono"') 13 | } 14 | catch (e) { 15 | console.error(e) 16 | } 17 | 18 | PIXI.BitmapFont.from('roboto-black', { 19 | fontFamily: 'Roboto Mono', 20 | fontSize: 9, 21 | fill: '#000000', 22 | }, { 23 | resolution: window.devicePixelRatio, 24 | }) 25 | 26 | PIXI.BitmapFont.from('roboto-white', { 27 | fontFamily: 'Roboto Mono', 28 | fontSize: 9, 29 | fill: '#ffffff', 30 | }, { 31 | resolution: window.devicePixelRatio, 32 | }) 33 | 34 | installedFonts = true 35 | } 36 | 37 | export function useFonts() { 38 | const loaded = ref(installedFonts) 39 | 40 | async function _load() { 41 | await installFonts() 42 | loaded.value = true 43 | } 44 | _load() 45 | 46 | return { 47 | loaded, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const antfu = require('@antfu/eslint-config').default 2 | 3 | module.exports = antfu({ 4 | ignores: [ 5 | '**/dist', 6 | ], 7 | }, { 8 | rules: { 9 | 'curly': ['error', 'all'], 10 | 'node/prefer-global/process': 'off', 11 | }, 12 | }, { 13 | files: [ 14 | 'packages/shell-dev*/**', 15 | ], 16 | rules: { 17 | 'no-console': 'off', 18 | 'unused-imports/no-unused-vars': 'off', 19 | 'vue/require-explicit-emits': 'off', 20 | 'vue/custom-event-name-casing': 'off', 21 | 'vue/no-deprecated-functional-template': 'off', 22 | 'vue/no-deprecated-filter': 'off', 23 | 'vue/no-unused-refs': 'off', 24 | 'vue/require-component-is': 'off', 25 | 'vue/return-in-computed-property': 'off', 26 | }, 27 | }, { 28 | files: [ 29 | 'packages/shell-host/**', 30 | ], 31 | rules: { 32 | 'no-console': 'off', 33 | }, 34 | }, { 35 | files: [ 36 | 'package.json', 37 | 'packages/*/package.json', 38 | 'packages/*/manifest.json', 39 | ], 40 | rules: { 41 | 'style/eol-last': 'off', 42 | }, 43 | }) 44 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/ui/composables/useDisabled.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * (Use with the DisabledChild mixin) 3 | * Allow disabling an entire tree of components implementing the DisabledChild mixin. 4 | */ 5 | 6 | import { computed, inject, provide, reactive, watch } from 'vue' 7 | 8 | export function useDisabledParent(props: { disabled?: boolean }) { 9 | const injectedDisableData = reactive({ 10 | value: props.disabled || false, 11 | }) 12 | 13 | provide('VueDisableMixin', { 14 | data: injectedDisableData, 15 | }) 16 | 17 | watch( 18 | () => props.disabled, 19 | (value, oldValue) => { 20 | if (value !== oldValue) { 21 | injectedDisableData.value = value 22 | } 23 | }, 24 | ) 25 | } 26 | 27 | export function useDisabledChild(props: { disabled?: boolean }) { 28 | const injectDisable = inject<{ data: { value: boolean } } | undefined>( 29 | 'VueDisableMixin', 30 | null, 31 | ) 32 | 33 | return { 34 | finalDisabled: computed(() => props.disabled || (injectDisable && injectDisable.data.value)), 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/connection/AppConnecting.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 44 | -------------------------------------------------------------------------------- /packages/shell-host/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plain Shell 6 | 7 | 8 | 30 | 31 | 32 |
33 |
Not Vue
34 |
35 |
36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /sign-firefox.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const path = require('node:path') 4 | const fs = require('node:fs') 5 | const execa = require('execa') 6 | 7 | const credFile = path.resolve(__dirname, '.amo.env.json') 8 | 9 | if (!fs.existsSync(credFile)) { 10 | fs.writeFileSync(credFile, JSON.stringify({ 11 | apiKey: '', 12 | apiSecret: '', 13 | }, null, 2), { encoding: 'utf8' }) 14 | console.log('Please provide Mozilla API credentials in .amo.env.json\nhttps://addons.mozilla.org/developers/addon/api/key/') 15 | process.exit(1) 16 | } 17 | 18 | const creds = JSON.parse(fs.readFileSync(credFile, { 19 | encoding: 'utf8', 20 | })) 21 | 22 | const child = execa('web-ext', [ 23 | 'sign', 24 | '--api-key', 25 | creds.apiKey, 26 | '--api-secret', 27 | creds.apiSecret, 28 | '-s', 29 | 'packages/shell-chrome', 30 | '-a', 31 | 'dist', 32 | '-i', 33 | 'src', 34 | '--id', 35 | '{c087fa6e-b59f-475d-b08d-f03fef34fa7f}', 36 | ], { 37 | shell: true, 38 | stdio: ['inherit', 'inherit', 'inherit'], 39 | }) 40 | 41 | child.on('exit', (code) => { 42 | process.exit(code) 43 | }) 44 | -------------------------------------------------------------------------------- /packages/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue/devtools-api", 3 | "version": "6.6.4", 4 | "description": "Interact with the Vue devtools from the page", 5 | "author": { 6 | "name": "Guillaume Chau" 7 | }, 8 | "license": "MIT", 9 | "repository": { 10 | "url": "https://github.com/vuejs/vue-devtools.git", 11 | "type": "git", 12 | "directory": "packages/api" 13 | }, 14 | "sideEffects": false, 15 | "main": "lib/cjs/index.js", 16 | "browser": "lib/esm/index.js", 17 | "module": "lib/esm/index.js", 18 | "types": "lib/esm/index.d.ts", 19 | "files": [ 20 | "lib/cjs", 21 | "lib/esm" 22 | ], 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "scripts": { 27 | "build": "rimraf lib && yarn build:esm && yarn build:cjs", 28 | "build:esm": "tsc --module es2015 --outDir lib/esm -d", 29 | "build:cjs": "tsc --module commonjs --outDir lib/cjs", 30 | "build:watch": "yarn tsc --module es2015 --outDir lib/esm -d -w --sourceMap" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^20.11.16", 34 | "@types/webpack-env": "^1.15.1", 35 | "typescript": "^5.3.3" 36 | } 37 | } -------------------------------------------------------------------------------- /packages/app-frontend/src/util/keyboard.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted } from 'vue' 2 | 3 | type KeyboardHandler = (event: KeyboardEvent) => boolean | void | Promise 4 | 5 | function handleKeyboard(type: 'keyup' | 'keydown', cb: KeyboardHandler, force: boolean) { 6 | function handler(event: KeyboardEvent) { 7 | if (!force && ( 8 | typeof HTMLElement !== 'undefined' && event.target instanceof HTMLElement && ( 9 | event.target.tagName === 'INPUT' 10 | || event.target.tagName === 'TEXTAREA' 11 | ) 12 | )) { 13 | return 14 | } 15 | 16 | const result = cb(event) 17 | if (result === false) { 18 | event.preventDefault() 19 | } 20 | } 21 | 22 | onMounted(() => { 23 | document.addEventListener(type, handler) 24 | }) 25 | 26 | onUnmounted(() => { 27 | document.removeEventListener(type, handler) 28 | }) 29 | } 30 | 31 | export function onKeyUp(cb: KeyboardHandler, force = false) { 32 | handleKeyboard('keyup', cb, force) 33 | } 34 | 35 | export function onKeyDown(cb: KeyboardHandler, force = false) { 36 | handleKeyboard('keydown', cb, force) 37 | } 38 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const openInEditor = require('launch-editor-middleware') 3 | const { createConfig } = require('@vue-devtools/build-tools') 4 | 5 | module.exports = createConfig({ 6 | context: __dirname, 7 | entry: { 8 | 'backend': require.resolve('@vue-devtools/shell-host/src/backend.js'), 9 | 'hook': require.resolve('@vue-devtools/shell-host/src/hook.js'), 10 | 'target': './src/main.js', 11 | 'iframe-app': './src/iframe-app.js', 12 | }, 13 | output: { 14 | path: path.join(__dirname, '/build'), 15 | publicPath: '/target/', 16 | filename: '[name].js', 17 | }, 18 | resolve: { 19 | symlinks: false, 20 | }, 21 | devServer: { 22 | port: 8090, 23 | onBeforeSetupMiddleware({ app }) { 24 | app.use('/__open-in-editor', openInEditor()) 25 | }, 26 | proxy: { 27 | '/': { 28 | target: 'http://localhost:8091', 29 | bypass: (req, res, proxyOptions) => { 30 | if (req.url.startsWith('/target')) { 31 | return req.url 32 | } 33 | }, 34 | }, 35 | }, 36 | }, 37 | }) 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-present Evan You 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/plugin/PluginSourceIcon.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 53 | -------------------------------------------------------------------------------- /packages/app-backend-core/src/backend.ts: -------------------------------------------------------------------------------- 1 | import type { BackendContext, DevtoolsBackend, DevtoolsBackendOptions } from '@vue-devtools/app-backend-api' 2 | import { createBackend } from '@vue-devtools/app-backend-api' 3 | 4 | import { backend as backendVue1 } from '@vue-devtools/app-backend-vue1' 5 | import { backend as backendVue2 } from '@vue-devtools/app-backend-vue2' 6 | import { backend as backendVue3 } from '@vue-devtools/app-backend-vue3' 7 | 8 | import { handleAddPerformanceTag } from './perf' 9 | 10 | export const availableBackends = [ 11 | backendVue1, 12 | backendVue2, 13 | backendVue3, 14 | ] 15 | 16 | const enabledBackends: Map = new Map() 17 | 18 | export function getBackend(backendOptions: DevtoolsBackendOptions, ctx: BackendContext) { 19 | let backend: DevtoolsBackend 20 | if (!enabledBackends.has(backendOptions)) { 21 | // Create backend 22 | backend = createBackend(backendOptions, ctx) 23 | handleAddPerformanceTag(backend, ctx) 24 | enabledBackends.set(backendOptions, backend) 25 | ctx.backends.push(backend) 26 | } 27 | else { 28 | backend = enabledBackends.get(backendOptions) 29 | } 30 | return backend 31 | } 32 | -------------------------------------------------------------------------------- /packages/app-frontend/src/util/queue.ts: -------------------------------------------------------------------------------- 1 | export class Queue { 2 | private existsMap: Map = new Map() 3 | private firstItem: QueueItem | null = null 4 | private lastItem: QueueItem | null = null 5 | 6 | add(value: T) { 7 | if (!this.existsMap.has(value)) { 8 | this.existsMap.set(value, true) 9 | const item = { 10 | current: value, 11 | next: null, 12 | } 13 | if (!this.firstItem) { 14 | this.firstItem = item 15 | } 16 | if (this.lastItem) { 17 | this.lastItem.next = item 18 | } 19 | this.lastItem = item 20 | } 21 | } 22 | 23 | shift(): T | null { 24 | if (this.firstItem) { 25 | const item = this.firstItem 26 | this.firstItem = item.next 27 | if (!this.firstItem) { 28 | this.lastItem = null 29 | } 30 | this.existsMap.delete(item.current) 31 | return item.current 32 | } 33 | return null 34 | } 35 | 36 | isEmpty() { 37 | return !this.firstItem 38 | } 39 | 40 | has(value: T) { 41 | return this.existsMap.has(value) 42 | } 43 | } 44 | 45 | interface QueueItem { 46 | current: T 47 | next: QueueItem | null 48 | } 49 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/dynamic-module.js: -------------------------------------------------------------------------------- 1 | export const dynamic = { 2 | namespaced: true, 3 | state() { 4 | return { 5 | dynamic: true, 6 | } 7 | }, 8 | getters: { 9 | notDynamic: (state) => { 10 | if (state) { 11 | return !state.dynamic 12 | } 13 | }, 14 | }, 15 | mutations: { 16 | TOGGLE: (state) => { 17 | state.dynamic = !state.dynamic 18 | }, 19 | }, 20 | } 21 | 22 | export const nested = { 23 | namespaced: true, 24 | state() { 25 | return { 26 | nested: true, 27 | } 28 | }, 29 | getters: { 30 | notNested: (state) => { 31 | if (state) { 32 | return !state.nested 33 | } 34 | }, 35 | }, 36 | mutations: { 37 | TOGGLE_NESTED: (state) => { 38 | state.nested = !state.nested 39 | }, 40 | }, 41 | } 42 | 43 | export const deeplyNested = { 44 | namespaced: true, 45 | modules: { 46 | child: { 47 | namespaced: true, 48 | state() { 49 | return { 50 | childMessage: 'hello from child', 51 | } 52 | }, 53 | getters: { 54 | upercaseChildMessage: state => state.childMessage.toUpperCase(), 55 | }, 56 | }, 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /packages/app-frontend/src/plugins/i18n.ts: -------------------------------------------------------------------------------- 1 | import { simpleGet } from '@vue-devtools/shared-utils' 2 | import type { Plugin } from 'vue' 3 | 4 | const reg = /\{\{\s*([\w.-]+)\s*\}\}/g 5 | 6 | interface StringMap { [key: string]: string | StringMap } 7 | interface ValuesMap { [key: string]: any } 8 | type Replacer = (text: string) => string 9 | 10 | let strings: StringMap 11 | let defaultValues: ValuesMap 12 | let replacer: Replacer 13 | 14 | export function translate(path: string | string[], values: ValuesMap = {}) { 15 | values = Object.assign({}, defaultValues, values) 16 | let text = simpleGet(strings, path) 17 | text = text.replace(reg, (substring, matched) => { 18 | const value = simpleGet(values, matched) 19 | return typeof value !== 'undefined' ? value : substring 20 | }) 21 | replacer && (text = replacer(text)) 22 | return text 23 | } 24 | 25 | interface Options { 26 | strings: StringMap 27 | defaultValues: ValuesMap 28 | replacer: Replacer 29 | } 30 | 31 | export default { 32 | install(app, options: Options) { 33 | strings = options.strings || {} 34 | defaultValues = options.defaultValues || {} 35 | replacer = options.replacer 36 | app.config.globalProperties.$t = translate 37 | }, 38 | } as Plugin 39 | -------------------------------------------------------------------------------- /packages/shell-chrome/src/proxy.js: -------------------------------------------------------------------------------- 1 | // This is a content-script that is injected only when the devtools are 2 | // activated. Because it is not injected using eval, it has full privilege 3 | // to the chrome runtime API. It serves as a proxy between the injected 4 | // backend and the Vue devtools panel. 5 | 6 | const port = chrome.runtime.connect({ 7 | name: 'content-script', 8 | }) 9 | 10 | port.onMessage.addListener(sendMessageToBackend) 11 | window.addEventListener('message', sendMessageToDevtools) 12 | port.onDisconnect.addListener(handleDisconnect) 13 | 14 | sendMessageToBackend('init') 15 | 16 | function sendMessageToBackend(payload) { 17 | window.postMessage({ 18 | source: 'vue-devtools-proxy', 19 | payload, 20 | }, '*') 21 | } 22 | 23 | function sendMessageToDevtools(e) { 24 | if (e.data && e.data.source === 'vue-devtools-backend') { 25 | port.postMessage(e.data.payload) 26 | } 27 | else if (e.data && e.data.source === 'vue-devtools-backend-injection') { 28 | if (e.data.payload === 'listening') { 29 | sendMessageToBackend('init') 30 | } 31 | } 32 | } 33 | 34 | function handleDisconnect() { 35 | window.removeEventListener('message', sendMessageToDevtools) 36 | sendMessageToBackend('shutdown') 37 | } 38 | -------------------------------------------------------------------------------- /packages/shell-firefox/src/proxy.js: -------------------------------------------------------------------------------- 1 | // This is a content-script that is injected only when the devtools are 2 | // activated. Because it is not injected using eval, it has full privilege 3 | // to the chrome runtime API. It serves as a proxy between the injected 4 | // backend and the Vue devtools panel. 5 | 6 | const port = chrome.runtime.connect({ 7 | name: 'content-script', 8 | }) 9 | 10 | port.onMessage.addListener(sendMessageToBackend) 11 | window.addEventListener('message', sendMessageToDevtools) 12 | port.onDisconnect.addListener(handleDisconnect) 13 | 14 | sendMessageToBackend('init') 15 | 16 | function sendMessageToBackend(payload) { 17 | window.postMessage({ 18 | source: 'vue-devtools-proxy', 19 | payload, 20 | }, '*') 21 | } 22 | 23 | function sendMessageToDevtools(e) { 24 | if (e.data && e.data.source === 'vue-devtools-backend') { 25 | port.postMessage(e.data.payload) 26 | } 27 | else if (e.data && e.data.source === 'vue-devtools-backend-injection') { 28 | if (e.data.payload === 'listening') { 29 | sendMessageToBackend('init') 30 | } 31 | } 32 | } 33 | 34 | function handleDisconnect() { 35 | window.removeEventListener('message', sendMessageToDevtools) 36 | sendMessageToBackend('shutdown') 37 | } 38 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/plugin/PluginHome.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 43 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/plugin/PluginPermission.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 50 | -------------------------------------------------------------------------------- /packages/app-frontend/src/plugins/responsive.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, Plugin, Ref } from 'vue' 2 | import { computed, inject, reactive, toRefs } from 'vue' 3 | 4 | export interface Responsive { 5 | wide: ComputedRef 6 | tall: ComputedRef 7 | width: Ref 8 | height: Ref 9 | } 10 | 11 | const responsiveKey = Symbol('responsive') 12 | 13 | export default { 14 | install(app) { 15 | function buildResponsive() { 16 | const data = reactive({ 17 | width: window.innerWidth, 18 | height: window.innerHeight, 19 | }) 20 | 21 | const wide = computed(() => data.width >= 1050) 22 | const tall = computed(() => data.height >= 350) 23 | 24 | return { 25 | ...toRefs(data), 26 | wide, 27 | tall, 28 | } 29 | } 30 | 31 | const responsive = buildResponsive() 32 | 33 | app.config.globalProperties.$responsive = responsive 34 | 35 | app.provide(responsiveKey, responsive) 36 | 37 | window.addEventListener('resize', () => { 38 | responsive.width.value = window.innerWidth 39 | responsive.height.value = window.innerHeight 40 | }) 41 | }, 42 | } as Plugin 43 | 44 | export const useResponsive = () => inject(responsiveKey) 45 | -------------------------------------------------------------------------------- /packages/docs/src/components/InstallButtons.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 47 | -------------------------------------------------------------------------------- /packages/app-backend-vue2/src/events.ts: -------------------------------------------------------------------------------- 1 | import type { BackendContext } from '@vue-devtools/app-backend-api' 2 | import { HookEvents } from '@vue-devtools/shared-utils' 3 | 4 | const internalRE = /^(?:pre-)?hook:/ 5 | 6 | function wrap(app, Vue, method, ctx: BackendContext) { 7 | const original = Vue.prototype[method] 8 | if (original) { 9 | Vue.prototype[method] = function (...args) { 10 | const res = original.apply(this, args) 11 | logEvent(this, method, args[0], args.slice(1)) 12 | return res 13 | } 14 | } 15 | 16 | function logEvent(vm, type, eventName, payload) { 17 | // The string check is important for compat with 1.x where the first 18 | // argument may be an object instead of a string. 19 | // this also ensures the event is only logged for direct $emit (source) 20 | // instead of by $dispatch/$broadcast 21 | if (typeof eventName === 'string' && !internalRE.test(eventName)) { 22 | const instance = vm._self || vm 23 | ctx.hook.emit(HookEvents.COMPONENT_EMIT, app, instance, eventName, payload) 24 | } 25 | } 26 | } 27 | 28 | export function wrapVueForEvents(app, Vue, ctx: BackendContext) { 29 | ['$emit', '$broadcast', '$dispatch'].forEach((method) => { 30 | wrap(app, Vue, method, ctx) 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 New feature proposal 2 | description: Suggest an idea for this project 3 | labels: [':sparkles: feature request'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | **Before You Start...** 9 | 10 | This form is only for submitting feature requests. If you have a usage question 11 | or are unsure if this is really a bug, make sure to: 12 | 13 | - Read the [docs](https://devtools.vuejs.org/) 14 | - Read the [FAQ](https://devtools.vuejs.org/guide/faq.html) 15 | - Ask on [GitHub Discussions](https://github.com/vuejs/devtools/discussions) 16 | - Ask on [Discord Chat](https://chat.vuejs.org/) 17 | 18 | Also try to search for your issue - another user may have already requested something similar! 19 | 20 | - type: textarea 21 | id: problem-description 22 | attributes: 23 | label: What problem does this feature solve? 24 | description: | 25 | Explain your use case, context, and rationale behind this feature request. More importantly, what is the **end user experience** you are trying to build that led to the need for this feature? 26 | placeholder: Problem description 27 | validations: 28 | required: true 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Description 4 | 5 | 6 | 7 | ### Additional context 8 | 9 | 10 | 11 | --- 12 | 13 | ### What is the purpose of this pull request? 14 | 15 | - [ ] Bug fix 16 | - [ ] New Feature 17 | - [ ] Documentation update 18 | - [ ] Other 19 | 20 | ### Before submitting the PR, please make sure you do the following 21 | 22 | - [ ] Read the [Contributing Guidelines](https://devtools.vuejs.org/guide/contributing.html). 23 | - [ ] Read the [Pull Request Guidelines](https://devtools.vuejs.org/guide/contributing.html#pull-request-guidelines) and follow the [Commit Convention](https://github.com/vuejs/devtools/blob/main/.github/commit-convention.md). 24 | - [ ] Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate. 25 | - [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`). 26 | 27 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/ui/components/VueDisable.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 49 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/ui/components/icons.ts: -------------------------------------------------------------------------------- 1 | const icons = require.context( 2 | '@akryum/md-icons-svg/svg/', 3 | true, 4 | /materialicons\/24px\.svg$/, 5 | ) 6 | 7 | export default { 8 | install() { 9 | const sprites = [''] 10 | let spriteIndex = 0 11 | // Load all the SVG symbols 12 | icons.keys().forEach((key, index) => { 13 | let result = icons(key) 14 | const [, iconName] = /(\w+)\/materialicons/.exec(key) 15 | // eslint-disable-next-line regexp/no-super-linear-backtracking 16 | const [, content] = /(.*)<\/svg>/.exec(result) 17 | result = `${content}` 18 | sprites[spriteIndex] += result 19 | if ((index + 1) % 40 === 0) { 20 | sprites.push('') 21 | spriteIndex++ 22 | } 23 | }) 24 | for (const html of sprites) { 25 | const iconsWrapper = document.createElement('div') 26 | iconsWrapper.style.display = 'none' 27 | iconsWrapper.innerHTML = html 28 | document.body.insertBefore(iconsWrapper, document.body.firstChild) 29 | } 30 | }, 31 | } 32 | 33 | export function generateHtmlIcon(icon: string) { 34 | return `
` 35 | } 36 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | 3 | const store = createStore({ 4 | state() { 5 | return { 6 | rootState: 'root', 7 | answer: 42, 8 | } 9 | }, 10 | getters: { 11 | answer: state => state.answer, 12 | throws: () => { 13 | throw new Error('getter error') 14 | }, 15 | }, 16 | mutations: { 17 | increment(state) { 18 | state.answer++ 19 | }, 20 | }, 21 | modules: { 22 | nested: { 23 | state() { 24 | return { 25 | foo: 'bar', 26 | } 27 | }, 28 | getters: { 29 | twoFoo: state => state.foo.repeat(2), 30 | }, 31 | }, 32 | namespacedModule: { 33 | namespaced: true, 34 | state() { 35 | return { 36 | count: 0, 37 | } 38 | }, 39 | getters: { 40 | doubleCount: state => state.count * 2, 41 | tripleCount: state => state.count * 3, 42 | }, 43 | modules: { 44 | animals: { 45 | namespaced: true, 46 | state() { 47 | return { 48 | cat: 'Meow', 49 | } 50 | }, 51 | getters: { 52 | dog: () => 'Waf', 53 | }, 54 | }, 55 | }, 56 | }, 57 | }, 58 | }) 59 | 60 | export default store 61 | -------------------------------------------------------------------------------- /packages/docs/src/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 17 | 21 | 25 | 29 | 33 | 37 | 38 | -------------------------------------------------------------------------------- /packages/docs/src/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 22 | 25 | 30 | 35 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /packages/shell-host/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 22 | 25 | 30 | 35 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/Provide.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 52 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/components/composable/pick.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import { BridgeEvents } from '@vue-devtools/shared-utils' 3 | import { useBridge } from '@front/features/bridge' 4 | import { setComponentOpen, useComponentRequests } from '.' 5 | 6 | export function useComponentPick() { 7 | const { bridge, onBridge } = useBridge() 8 | const { selectComponent, requestComponentTree } = useComponentRequests() 9 | 10 | const pickingComponent = ref(false) 11 | 12 | function startPickingComponent() { 13 | pickingComponent.value = true 14 | bridge.send(BridgeEvents.TO_BACK_COMPONENT_PICK) 15 | } 16 | 17 | function stopPickingComponent() { 18 | pickingComponent.value = false 19 | bridge.send(BridgeEvents.TO_BACK_COMPONENT_PICK_CANCELED) 20 | } 21 | 22 | onBridge(BridgeEvents.TO_FRONT_COMPONENT_PICK, ({ id, parentIds }) => { 23 | pickingComponent.value = false 24 | selectComponent(id) 25 | parentIds.reverse().forEach((id) => { 26 | // Ignore root 27 | if (id.endsWith('root')) { 28 | return 29 | } 30 | setComponentOpen(id, true) 31 | requestComponentTree(id) 32 | }) 33 | }) 34 | 35 | onBridge(BridgeEvents.TO_FRONT_COMPONENT_PICK_CANCELED, () => { 36 | pickingComponent.value = false 37 | }) 38 | 39 | return { 40 | pickingComponent, 41 | startPickingComponent, 42 | stopPickingComponent, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/plugin/PluginListItem.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 50 | 51 | 56 | -------------------------------------------------------------------------------- /packages/shell-electron/server.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const fs = require('node:fs') 3 | const { createServer } = require('node:http') 4 | const app = require('express')() 5 | const { Server } = require('socket.io') 6 | 7 | const port = process.env.PORT || 8098 8 | 9 | const httpServer = createServer(app) 10 | const io = new Server(httpServer, { 11 | cors: { 12 | origin: true, 13 | }, 14 | }) 15 | 16 | app.get('/', (req, res) => { 17 | const hookContent = fs.readFileSync(path.join(__dirname, '/build/hook.js'), 'utf8') 18 | const backendContent = fs.readFileSync(path.join(__dirname, '/build/backend.js'), 'utf8') 19 | res.send([hookContent, backendContent].join('\n')) 20 | }) 21 | 22 | // Middleman 23 | io.on('connection', (socket) => { 24 | // Disconnect any previously connected apps 25 | socket.broadcast.emit('vue-devtools-disconnect-backend') 26 | 27 | socket.on('vue-devtools-init', () => { 28 | socket.broadcast.emit('vue-devtools-init') 29 | }) 30 | 31 | socket.on('disconnect', (reason) => { 32 | if (reason.indexOf('client')) { 33 | socket.broadcast.emit('vue-devtools-disconnect-devtools') 34 | } 35 | }) 36 | 37 | socket.on('vue-message', (data) => { 38 | socket.broadcast.emit('vue-message', data) 39 | }) 40 | }) 41 | 42 | httpServer.listen(port, '0.0.0.0', () => { 43 | // eslint-disable-next-line no-console 44 | console.log(`listening on 0.0.0.0:${port}`) 45 | }) 46 | -------------------------------------------------------------------------------- /packages/api/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { App } from './api/index.js' 2 | 3 | export interface PluginDescriptor { 4 | id: string 5 | label: string 6 | app: App 7 | packageName?: string 8 | homepage?: string 9 | componentStateTypes?: string[] 10 | logo?: string 11 | disableAppScope?: boolean 12 | disablePluginScope?: boolean 13 | /** 14 | * Run the plugin setup and expose the api even if the devtools is not opened yet. 15 | * Useful to record timeline events early. 16 | */ 17 | enableEarlyProxy?: boolean 18 | settings?: Record 19 | } 20 | 21 | export type PluginSettingsItem = { 22 | label: string 23 | description?: string 24 | } & ({ 25 | type: 'boolean' 26 | defaultValue: boolean 27 | } | { 28 | type: 'choice' 29 | defaultValue: string | number 30 | options: { value: string | number, label: string }[] 31 | component?: 'select' | 'button-group' 32 | } | { 33 | type: 'text' 34 | defaultValue: string 35 | }) 36 | 37 | type InferSettingsType< 38 | T extends PluginSettingsItem, 39 | > = [T] extends [{ type: 'boolean' }] 40 | ? boolean 41 | : [T] extends [{ type: 'choice' }] 42 | ? T['options'][number]['value'] 43 | : [T] extends [{ type: 'text' }] 44 | ? string 45 | : unknown 46 | 47 | export type ExtractSettingsTypes< 48 | O extends Record, 49 | > = { 50 | [K in keyof O]: InferSettingsType 51 | } 52 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/ui/components/VueGroupButton.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 57 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/bridge/index.ts: -------------------------------------------------------------------------------- 1 | import { onUnmounted } from 'vue' 2 | import type { Bridge } from '@vue-devtools/shared-utils' 3 | import { BridgeEvents } from '@vue-devtools/shared-utils' 4 | 5 | let bridge: Bridge 6 | 7 | interface Sub { 8 | type: string 9 | key: string 10 | } 11 | 12 | export function useBridge() { 13 | const cbs = [] 14 | 15 | function onBridge(event: BridgeEvents, cb: (payload: any) => void | Promise) { 16 | cbs.push({ event, cb }) 17 | bridge.on(event, cb) 18 | } 19 | 20 | const subs: Sub[] = [] 21 | 22 | function subscribe(type: string, key: string) { 23 | const sub = { type, key } 24 | subs.push(sub) 25 | bridge.send(BridgeEvents.TO_BACK_SUBSCRIBE, key) 26 | return () => { 27 | const index = subs.indexOf(sub) 28 | if (index !== -1) { 29 | subs.splice(index, 1) 30 | } 31 | bridge.send(BridgeEvents.TO_BACK_UNSUBSCRIBE, key) 32 | } 33 | } 34 | 35 | onUnmounted(() => { 36 | for (const { event, cb } of cbs) { 37 | bridge.off(event, cb) 38 | } 39 | 40 | for (const sub of subs) { 41 | bridge.send(BridgeEvents.TO_BACK_UNSUBSCRIBE, sub.key) 42 | } 43 | }) 44 | 45 | return { 46 | bridge, 47 | onBridge, 48 | subscribe, 49 | } 50 | } 51 | 52 | export function setBridge(b: Bridge) { 53 | bridge = b 54 | } 55 | 56 | export function getBridge(): Bridge | null { 57 | return bridge 58 | } 59 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/Other.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 60 | 61 | 65 | -------------------------------------------------------------------------------- /packages/app-frontend/src/assets/devtools-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 17 | 21 | 25 | 29 | 33 | 37 | 38 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/TransitionExample.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 55 | 56 | 64 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/ui/components/VueSelectButton.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 56 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/Other.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 62 | 63 | 67 | -------------------------------------------------------------------------------- /packages/shell-firefox/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vue.js devtools", 3 | "version": "6.6.4", 4 | "version_name": "6.6.4", 5 | "description": "Browser DevTools extension for debugging Vue.js applications.", 6 | "manifest_version": 2, 7 | "icons": { 8 | "16": "icons/16.png", 9 | "48": "icons/48.png", 10 | "128": "icons/128.png" 11 | }, 12 | "browser_action": { 13 | "default_icon": { 14 | "16": "icons/16-gray.png", 15 | "48": "icons/48-gray.png", 16 | "128": "icons/128-gray.png" 17 | }, 18 | "default_title": "Vue Devtools", 19 | "default_popup": "popups/not-found.html" 20 | }, 21 | "web_accessible_resources": [ 22 | "devtools.html", 23 | "devtools-background.html", 24 | "build/backend.js" 25 | ], 26 | "devtools_page": "devtools-background.html", 27 | "background": { 28 | "scripts": [ 29 | "build/background.js" 30 | ], 31 | "persistent": true 32 | }, 33 | "permissions": [ 34 | "", 35 | "storage" 36 | ], 37 | "content_scripts": [ 38 | { 39 | "matches": [ 40 | "" 41 | ], 42 | "js": [ 43 | "build/hook.js" 44 | ], 45 | "run_at": "document_start" 46 | }, 47 | { 48 | "matches": [ 49 | "" 50 | ], 51 | "js": [ 52 | "build/detector.js" 53 | ], 54 | "run_at": "document_idle" 55 | } 56 | ], 57 | "content_security_policy": "script-src 'self'; object-src 'self'" 58 | } -------------------------------------------------------------------------------- /packages/shell-electron/src/devtools.js: -------------------------------------------------------------------------------- 1 | import io from 'socket.io-client' 2 | import { initDevTools } from '@front' 3 | import { Bridge } from '@vue-devtools/shared-utils' 4 | 5 | const port = window.process.env.PORT || 8098 6 | const socket = io(`http://localhost:${port}`) 7 | const $ = document.querySelector.bind(document) 8 | const $intro = $('#intro') 9 | 10 | let reload = null 11 | let introTimer 12 | 13 | socket.on('vue-devtools-disconnect-devtools', () => { 14 | introTimer = setTimeout(() => { 15 | $intro.classList.remove('hidden') 16 | }, 2000) 17 | }) 18 | 19 | socket.on('vue-devtools-init', () => { 20 | clearTimeout(introTimer) 21 | $intro.classList.add('hidden') 22 | 23 | // Reset attached listeners 24 | socket.off('vue-message') 25 | 26 | // If new page is opened reload devtools 27 | if (reload) { 28 | return reload() 29 | } 30 | 31 | initDevTools({ 32 | connect(callback) { 33 | const wall = { 34 | listen(fn) { 35 | socket.on('vue-message', data => fn(data)) 36 | }, 37 | send(data) { 38 | if (process.env.NODE_ENV !== 'production') { 39 | // eslint-disable-next-line no-console 40 | console.log('%cdevtools -> backend', 'color:#888;', data) 41 | } 42 | socket.emit('vue-message', data) 43 | }, 44 | } 45 | const bridge = new Bridge(wall) 46 | 47 | callback(bridge) 48 | }, 49 | onReload(fn) { 50 | reload = fn 51 | }, 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /cypress/integration/events-tab.js: -------------------------------------------------------------------------------- 1 | import { suite } from '../utils/suite' 2 | 3 | suite('events tab', () => { 4 | it('should display new events counter', () => { 5 | cy.get('#target').iframe().then(({ get }) => { 6 | get('.btn-emit-event').click({ force: true }) 7 | get('.btn-emit-event1').click({ force: true }) 8 | get('.btn-emit-event2').click({ force: true }) 9 | }) 10 | cy.get('.events-tab .tag').contains(3) 11 | cy.get('.events-tab').click() 12 | cy.get('.events-tab .tag').should('not.be.visible') 13 | }) 14 | 15 | it('should display events', () => { 16 | cy.get('.history .entry').should('have.length', 3) 17 | }) 18 | 19 | it('should add event', () => { 20 | cy.get('#target').iframe().then(({ get }) => { 21 | get('.btn-emit-log-event').click({ force: true }) 22 | }) 23 | cy.get('.history .entry').should('have.length', 4) 24 | }) 25 | 26 | it('should search events', () => { 27 | cy.get('.left .search input').clear().type('event') 28 | cy.get('.history .entry[data-active="true"]').should('have.length', 3) 29 | cy.get('.left .search input').clear().type('') 30 | cy.get('.history .entry[data-active="true"]').should('have.length', 1) 31 | cy.get('.left .search input').clear().type('/^event$/') 32 | cy.get('.history .entry[data-active="true"]').should('have.length', 1) 33 | cy.get('.left .search input').clear() 34 | cy.get('.button.reset').click() 35 | cy.get('.history .entry[data-active="true"]').should('have.length', 0) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /packages/app-backend-vue2/src/components/util.ts: -------------------------------------------------------------------------------- 1 | import { getComponentName } from '@vue-devtools/shared-utils' 2 | import type { AppRecord } from '@vue-devtools/app-backend-api' 3 | 4 | export function isBeingDestroyed(instance) { 5 | return instance._isBeingDestroyed 6 | } 7 | 8 | /** 9 | * Get the appropriate display name for an instance. 10 | */ 11 | export function getInstanceName(instance) { 12 | const name = getComponentName(instance.$options || instance.fnOptions || {}) 13 | if (name) { 14 | return name 15 | } 16 | return instance.$root === instance 17 | ? 'Root' 18 | : 'Anonymous Component' 19 | } 20 | 21 | export function getRenderKey(value): string { 22 | if (value == null) { 23 | return 24 | } 25 | const type = typeof value 26 | if (type === 'number') { 27 | return value.toString() 28 | } 29 | else if (type === 'string') { 30 | return `'${value}'` 31 | } 32 | else if (Array.isArray(value)) { 33 | return 'Array' 34 | } 35 | else { 36 | return 'Object' 37 | } 38 | } 39 | 40 | /** 41 | * Returns a devtools unique id for instance. 42 | */ 43 | export function getUniqueId(instance, appRecord?: AppRecord): string { 44 | if (instance.__VUE_DEVTOOLS_UID__ != null) { 45 | return instance.__VUE_DEVTOOLS_UID__ 46 | } 47 | let rootVueId = instance.$root.__VUE_DEVTOOLS_APP_RECORD_ID__ 48 | if (!rootVueId && appRecord) { 49 | rootVueId = appRecord.id 50 | } 51 | if (!rootVueId) { 52 | rootVueId = '_unmounted' 53 | } 54 | return `${rootVueId}:${instance._uid}` 55 | } 56 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/connection/index.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref } from 'vue' 2 | import { useNow } from '@vueuse/core' 3 | 4 | const isConnected = ref(false) 5 | const isInitializing = ref(true) 6 | const lastDisconnect = ref(0) 7 | const reloadTimes = ref(0) 8 | let reloadRegistered = false 9 | 10 | export function useAppConnection() { 11 | const now = useNow({ 12 | interval: 1000, 13 | }) 14 | const showDisplayDisconnected = computed(() => { 15 | if (isInitializing.value) { 16 | return false 17 | } 18 | if (lastDisconnect.value === 0) { 19 | return false 20 | } 21 | // Wait for 5 seconds before showing the disconnected message 22 | return !isConnected && now.value.getTime() - lastDisconnect.value > 5000 23 | }) 24 | 25 | return { 26 | isConnected, 27 | isInitializing, 28 | lastDisconnect, 29 | showDisplayDisconnected, 30 | reloadTimes, 31 | } 32 | } 33 | 34 | export function setAppConnected(value: boolean, force = false, fromReload = false) { 35 | // We got disconnected from a page reload 36 | if (!value) { 37 | reloadRegistered = fromReload 38 | } 39 | 40 | if (force) { 41 | lastDisconnect.value = 0 42 | } 43 | else if (!value && isConnected.value) { 44 | lastDisconnect.value = Date.now() 45 | } 46 | isConnected.value = value 47 | 48 | // We are reconnected after a page reload 49 | if (value && reloadRegistered) { 50 | reloadTimes.value++ 51 | } 52 | } 53 | 54 | export function setAppInitializing(value: boolean) { 55 | isInitializing.value = value 56 | } 57 | -------------------------------------------------------------------------------- /packages/app-backend-core/src/flash.ts: -------------------------------------------------------------------------------- 1 | import type { DevtoolsBackend } from '@vue-devtools/app-backend-api' 2 | import type { ComponentInstance } from '@vue/devtools-api' 3 | 4 | export async function flashComponent(instance: ComponentInstance, backend: DevtoolsBackend) { 5 | const bounds = await backend.api.getComponentBounds(instance) 6 | if (bounds) { 7 | let overlay: HTMLDivElement = instance.__VUE_DEVTOOLS_FLASH 8 | if (!overlay) { 9 | overlay = document.createElement('div') 10 | instance.__VUE_DEVTOOLS_FLASH = overlay 11 | overlay.style.border = '2px rgba(65, 184, 131, 0.7) solid' 12 | overlay.style.position = 'fixed' 13 | overlay.style.zIndex = '99999999999998' 14 | overlay.style.pointerEvents = 'none' 15 | overlay.style.borderRadius = '3px' 16 | overlay.style.boxSizing = 'border-box' 17 | document.body.appendChild(overlay) 18 | } 19 | overlay.style.opacity = '1' 20 | overlay.style.transition = null 21 | overlay.style.width = `${Math.round(bounds.width)}px` 22 | overlay.style.height = `${Math.round(bounds.height)}px` 23 | overlay.style.left = `${Math.round(bounds.left)}px` 24 | overlay.style.top = `${Math.round(bounds.top)}px` 25 | requestAnimationFrame(() => { 26 | overlay.style.transition = 'opacity 1s' 27 | overlay.style.opacity = '0' 28 | }) 29 | clearTimeout((overlay as any)._timer) 30 | ;(overlay as any)._timer = setTimeout(() => { 31 | document.body.removeChild(overlay) 32 | instance.__VUE_DEVTOOLS_FLASH = null 33 | }, 1000) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/shell-chrome/src/devtools-background.js: -------------------------------------------------------------------------------- 1 | // This is the devtools script, which is called when the user opens the 2 | // Chrome devtool on a page. We check to see if we global hook has detected 3 | // Vue presence on the page. If yes, create the Vue panel; otherwise poll 4 | // for 10 seconds. 5 | 6 | let created = false 7 | let checkCount = 0 8 | 9 | chrome.devtools.network.onNavigated.addListener(createPanelIfHasVue) 10 | const checkVueInterval = setInterval(createPanelIfHasVue, 1000) 11 | createPanelIfHasVue() 12 | 13 | function createPanelIfHasVue() { 14 | if (created || checkCount++ > 10) { 15 | clearInterval(checkVueInterval) 16 | return 17 | } 18 | chrome.devtools.inspectedWindow.eval( 19 | '!!(window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && (window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue || window.__VUE_DEVTOOLS_GLOBAL_HOOK__.apps.length))', 20 | (hasVue) => { 21 | if (!hasVue || created) { 22 | return 23 | } 24 | clearInterval(checkVueInterval) 25 | created = true 26 | chrome.devtools.panels.create( 27 | 'Vue (Legacy)', 28 | 'icons/128.png', 29 | 'devtools.html', 30 | (panel) => { 31 | // panel loaded 32 | panel.onShown.addListener(onPanelShown) 33 | panel.onHidden.addListener(onPanelHidden) 34 | }, 35 | ) 36 | }, 37 | ) 38 | } 39 | 40 | // Manage panel visibility 41 | 42 | function onPanelShown() { 43 | chrome.runtime.sendMessage('vue-panel-shown') 44 | } 45 | 46 | function onPanelHidden() { 47 | chrome.runtime.sendMessage('vue-panel-hidden') 48 | } 49 | -------------------------------------------------------------------------------- /packages/shell-firefox/src/devtools-background.js: -------------------------------------------------------------------------------- 1 | // This is the devtools script, which is called when the user opens the 2 | // Chrome devtool on a page. We check to see if we global hook has detected 3 | // Vue presence on the page. If yes, create the Vue panel; otherwise poll 4 | // for 10 seconds. 5 | 6 | let created = false 7 | let checkCount = 0 8 | 9 | chrome.devtools.network.onNavigated.addListener(createPanelIfHasVue) 10 | const checkVueInterval = setInterval(createPanelIfHasVue, 1000) 11 | createPanelIfHasVue() 12 | 13 | function createPanelIfHasVue() { 14 | if (created || checkCount++ > 10) { 15 | clearInterval(checkVueInterval) 16 | return 17 | } 18 | chrome.devtools.inspectedWindow.eval( 19 | '!!(window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && (window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue || window.__VUE_DEVTOOLS_GLOBAL_HOOK__.apps.length))', 20 | (hasVue) => { 21 | if (!hasVue || created) { 22 | return 23 | } 24 | clearInterval(checkVueInterval) 25 | created = true 26 | chrome.devtools.panels.create( 27 | 'Vue (Legacy)', 28 | 'icons/128.png', 29 | 'devtools.html', 30 | (panel) => { 31 | // panel loaded 32 | panel.onShown.addListener(onPanelShown) 33 | panel.onHidden.addListener(onPanelHidden) 34 | }, 35 | ) 36 | }, 37 | ) 38 | } 39 | 40 | // Manage panel visibility 41 | 42 | function onPanelShown() { 43 | chrome.runtime.sendMessage('vue-panel-shown') 44 | } 45 | 46 | function onPanelHidden() { 47 | chrome.runtime.sendMessage('vue-panel-hidden') 48 | } 49 | -------------------------------------------------------------------------------- /packages/shared-utils/src/env.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | 3 | export const isBrowser = typeof navigator !== 'undefined' 4 | export const target: any = isBrowser 5 | ? window 6 | : typeof globalThis !== 'undefined' 7 | ? globalThis 8 | : {} 9 | export const isChrome = typeof target.chrome !== 'undefined' && !!target.chrome.devtools 10 | export const isFirefox = isBrowser && navigator.userAgent.includes('Firefox') 11 | export const isWindows = isBrowser && navigator.platform.indexOf('Win') === 0 12 | export const isMac = isBrowser && navigator.platform === 'MacIntel' 13 | export const isLinux = isBrowser && navigator.platform.indexOf('Linux') === 0 14 | export const keys = { 15 | ctrl: isMac ? '⌘' : 'Ctrl', 16 | shift: 'Shift', 17 | alt: isMac ? '⌥' : 'Alt', 18 | del: 'Del', 19 | enter: 'Enter', 20 | esc: 'Esc', 21 | } 22 | 23 | export function initEnv(app: App) { 24 | if (Object.prototype.hasOwnProperty.call(app.config.globalProperties, '$isChrome')) { 25 | return 26 | } 27 | 28 | Object.defineProperties(app.config.globalProperties, { 29 | $isChrome: { get: () => isChrome }, 30 | $isFirefox: { get: () => isFirefox }, 31 | $isWindows: { get: () => isWindows }, 32 | $isMac: { get: () => isMac }, 33 | $isLinux: { get: () => isLinux }, 34 | $keys: { get: () => keys }, 35 | }) 36 | 37 | if (isWindows) { 38 | document.body.classList.add('platform-windows') 39 | } 40 | if (isMac) { 41 | document.body.classList.add('platform-mac') 42 | } 43 | if (isLinux) { 44 | document.body.classList.add('platform-linux') 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path') 2 | const { createConfig } = require('@vue-devtools/build-tools') 3 | const { VueLoaderPlugin } = require('vue-loader') 4 | 5 | const vueLoaderPath = require.resolve('vue-loader') 6 | const openInEditor = require('launch-editor-middleware') 7 | 8 | const config = createConfig({ 9 | entry: { 10 | // devtools: require.resolve('@vue-devtools/shell-host/src/devtools.js'), 11 | 'backend': require.resolve('@vue-devtools/shell-host/src/backend.js'), 12 | 'hook': require.resolve('@vue-devtools/shell-host/src/hook.js'), 13 | 'target': './src/index.js', 14 | 'iframe-app': './src/iframe-app.js', 15 | }, 16 | resolve: { 17 | alias: { 18 | vue: require.resolve('vue/dist/vue.esm.js'), 19 | }, 20 | symlinks: false, 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.vue$/, 26 | loader: vueLoaderPath, 27 | options: {}, 28 | }, 29 | ], 30 | }, 31 | output: { 32 | path: path.join(__dirname, '/build'), 33 | publicPath: '/target/', 34 | filename: '[name].js', 35 | }, 36 | devServer: { 37 | onBeforeSetupMiddleware({ app }) { 38 | app.use('/__open-in-editor', openInEditor()) 39 | }, 40 | proxy: { 41 | '/': { 42 | target: 'http://localhost:8091', 43 | bypass: (req, res, proxyOptions) => { 44 | if (req.url.startsWith('/target')) { 45 | return req.url 46 | } 47 | }, 48 | }, 49 | }, 50 | }, 51 | }) 52 | 53 | config.plugins[0] = new VueLoaderPlugin() 54 | module.exports = config 55 | -------------------------------------------------------------------------------- /packages/app-frontend/src/features/plugin/PluginSourceDescription.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 67 | -------------------------------------------------------------------------------- /packages/shell-chrome/src/backend.js: -------------------------------------------------------------------------------- 1 | // this is injected to the app page when the panel is activated. 2 | 3 | import { initBackend } from '@back' 4 | import { Bridge } from '@vue-devtools/shared-utils' 5 | 6 | window.addEventListener('message', handshake) 7 | 8 | function sendListening() { 9 | window.postMessage({ 10 | source: 'vue-devtools-backend-injection', 11 | payload: 'listening', 12 | }, '*') 13 | } 14 | sendListening() 15 | 16 | function handshake(e) { 17 | if (e.data.source === 'vue-devtools-proxy' && e.data.payload === 'init') { 18 | window.removeEventListener('message', handshake) 19 | 20 | let listeners = [] 21 | const bridge = new Bridge({ 22 | listen(fn) { 23 | const listener = (evt) => { 24 | if (evt.data.source === 'vue-devtools-proxy' && evt.data.payload) { 25 | fn(evt.data.payload) 26 | } 27 | } 28 | window.addEventListener('message', listener) 29 | listeners.push(listener) 30 | }, 31 | send(data) { 32 | // if (process.env.NODE_ENV !== 'production') { 33 | // console.log('[chrome] backend -> devtools', data) 34 | // } 35 | window.postMessage({ 36 | source: 'vue-devtools-backend', 37 | payload: data, 38 | }, '*') 39 | }, 40 | }) 41 | 42 | bridge.on('shutdown', () => { 43 | listeners.forEach((l) => { 44 | window.removeEventListener('message', l) 45 | }) 46 | listeners = [] 47 | window.addEventListener('message', handshake) 48 | }) 49 | 50 | initBackend(bridge) 51 | } 52 | else { 53 | sendListening() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/shell-firefox/src/backend.js: -------------------------------------------------------------------------------- 1 | // this is injected to the app page when the panel is activated. 2 | 3 | import { initBackend } from '@back' 4 | import { Bridge } from '@vue-devtools/shared-utils' 5 | 6 | window.addEventListener('message', handshake) 7 | 8 | function sendListening() { 9 | window.postMessage({ 10 | source: 'vue-devtools-backend-injection', 11 | payload: 'listening', 12 | }, '*') 13 | } 14 | sendListening() 15 | 16 | function handshake(e) { 17 | if (e.data.source === 'vue-devtools-proxy' && e.data.payload === 'init') { 18 | window.removeEventListener('message', handshake) 19 | 20 | let listeners = [] 21 | const bridge = new Bridge({ 22 | listen(fn) { 23 | const listener = (evt) => { 24 | if (evt.data.source === 'vue-devtools-proxy' && evt.data.payload) { 25 | fn(evt.data.payload) 26 | } 27 | } 28 | window.addEventListener('message', listener) 29 | listeners.push(listener) 30 | }, 31 | send(data) { 32 | // if (process.env.NODE_ENV !== 'production') { 33 | // console.log('[chrome] backend -> devtools', data) 34 | // } 35 | window.postMessage({ 36 | source: 'vue-devtools-backend', 37 | payload: data, 38 | }, '*') 39 | }, 40 | }) 41 | 42 | bridge.on('shutdown', () => { 43 | listeners.forEach((l) => { 44 | window.removeEventListener('message', l) 45 | }) 46 | listeners = [] 47 | window.addEventListener('message', handshake) 48 | }) 49 | 50 | initBackend(bridge) 51 | } 52 | else { 53 | sendListening() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/app-backend-vue2/src/components/update-tracking.ts: -------------------------------------------------------------------------------- 1 | import type { DevtoolsApi } from '@vue-devtools/app-backend-api' 2 | import { HookEvents, SharedData } from '@vue-devtools/shared-utils' 3 | import throttle from 'lodash/throttle' 4 | import { getUniqueId } from './util.js' 5 | 6 | export function initUpdateTracking(api: DevtoolsApi, Vue) { 7 | // Global mixin 8 | Vue.mixin({ 9 | beforeCreate() { 10 | applyTrackingUpdateHook(api, this) 11 | }, 12 | }) 13 | } 14 | 15 | const COMPONENT_HOOKS = [ 16 | 'created', 17 | 'updated', 18 | ] 19 | 20 | export function applyTrackingUpdateHook(api: DevtoolsApi, vm) { 21 | if (vm.$options.$_devtoolsUpdateTrackingHooks) { 22 | return 23 | } 24 | vm.$options.$_devtoolsUpdateTrackingHooks = true 25 | 26 | const handler = throttle(async function (this: any) { 27 | if (SharedData.trackUpdates) { 28 | api.ctx.hook.emit(HookEvents.TRACK_UPDATE, getUniqueId(this), api.ctx) 29 | 30 | const parents = await api.walkComponentParents(this) 31 | for (const parent of parents) { 32 | api.ctx.hook.emit(HookEvents.TRACK_UPDATE, getUniqueId(parent), api.ctx) 33 | } 34 | } 35 | 36 | if (SharedData.flashUpdates) { 37 | api.ctx.hook.emit(HookEvents.FLASH_UPDATE, this, api.backend) 38 | } 39 | }, 100) 40 | for (const hook of COMPONENT_HOOKS) { 41 | const currentValue = vm.$options[hook] 42 | if (Array.isArray(currentValue)) { 43 | vm.$options[hook] = [handler, ...currentValue] 44 | } 45 | else if (typeof currentValue === 'function') { 46 | vm.$options[hook] = [handler, currentValue] 47 | } 48 | else { 49 | vm.$options[hook] = [handler] 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/shell-dev-vue3/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp, h } from 'vue' 2 | import { createRouter, createWebHashHistory } from 'vue-router' 3 | import App from './App.vue' 4 | import App3 from './App3.vue' 5 | import TestPlugin from './devtools-plugin' 6 | import SimplePlugin from './devtools-plugin/simple' 7 | import store from './store' 8 | 9 | // eslint-disable-next-line no-extend-native 10 | Array.prototype.foo = 'bar' 11 | 12 | const router = createRouter({ 13 | history: createWebHashHistory(), 14 | 15 | routes: [ 16 | { 17 | path: '/p1', 18 | component: import('./router/Page1.vue'), 19 | }, 20 | { 21 | path: '/p2', 22 | component: import('./router/Page2.vue'), 23 | }, 24 | ], 25 | }) 26 | 27 | const app = createApp(App) 28 | app.component('global', { 29 | render: () => 'I\'m a global component', 30 | }) 31 | app.use(router) 32 | app.use(store) 33 | app.use(TestPlugin) 34 | app.mount('#app') 35 | 36 | const app2 = createApp({ 37 | name: 'App2', 38 | render: () => h('h1', 'App 2'), 39 | }) 40 | app2.mount('#app2') 41 | 42 | const app2bis = createApp({ 43 | name: 'App2', 44 | render: () => h('h1', 'App 2 Bis'), 45 | }) 46 | app2bis.mount('#app2bis') 47 | 48 | createApp(App3).mount('#app3') 49 | 50 | createApp({ 51 | render: () => h('button', { 52 | onClick: () => app2.unmount(), 53 | }, 'Remove app 2'), 54 | devtools: { 55 | hide: true, 56 | }, 57 | }).mount('#ghost-app') 58 | 59 | setTimeout(() => { 60 | const app = createApp({ 61 | name: 'DelayedApp', 62 | render: () => h('h1', 'Delayed app'), 63 | }) 64 | app.use(SimplePlugin) 65 | app.mount('#delay-app') 66 | }, 1000) 67 | 68 | window.top.document.title = 'Vue 3 Dev Shell' 69 | -------------------------------------------------------------------------------- /packages/app-frontend/src/mixins/keyboard.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | 3 | export const LEFT = 'ArrowLeft' 4 | export const UP = 'ArrowUp' 5 | export const RIGHT = 'ArrowRight' 6 | export const DOWN = 'ArrowDown' 7 | export const ENTER = 'Enter' 8 | export const DEL = 'Delete' 9 | export const BACKSPACE = 'Backspace' 10 | 11 | const activeInstances = [] 12 | 13 | function processEvent(event, type) { 14 | if ( 15 | event.target.tagName === 'INPUT' 16 | || event.target.tagName === 'TEXTAREA' 17 | ) { 18 | return 19 | } 20 | const modifiers: string[] = [] 21 | if (event.ctrlKey || event.metaKey) { 22 | modifiers.push('ctrl') 23 | } 24 | if (event.shiftKey) { 25 | modifiers.push('shift') 26 | } 27 | if (event.altKey) { 28 | modifiers.push('alt') 29 | } 30 | const info = { 31 | key: event.key, 32 | code: event.code, 33 | modifiers: modifiers.join('+'), 34 | } 35 | let result = true 36 | activeInstances.forEach((opt) => { 37 | if (opt[type]) { 38 | const r = opt[type].call(opt.vm, info) 39 | if (r === false) { 40 | result = false 41 | } 42 | } 43 | }) 44 | if (!result) { 45 | event.preventDefault() 46 | } 47 | } 48 | 49 | document.addEventListener('keydown', (event) => { 50 | processEvent(event, 'onKeyDown') 51 | }) 52 | 53 | export default function (options) { 54 | return defineComponent({ 55 | mounted() { 56 | activeInstances.push({ 57 | vm: this, 58 | ...options, 59 | }) 60 | }, 61 | unmounted() { 62 | const i = activeInstances.findIndex( 63 | o => o.vm === this, 64 | ) 65 | if (i >= 0) { 66 | activeInstances.splice(i, 1) 67 | } 68 | }, 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /packages/shell-dev-vue2/src/EventChild.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 77 | --------------------------------------------------------------------------------