Showing preview only (1,008K chars total). Download the full file or copy to clipboard to get everything.
Repository: vuejs/devtools-v6
Branch: main
Commit: dd2ab5d42751
Files: 404
Total size: 910.8 KB
Directory structure:
gitextract_85f6hf0f/
├── .browserslistrc
├── .circleci/
│ └── config.yml
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ └── feature_request.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── commit-convention.md
│ └── workflows/
│ └── create-release.yml
├── .gitignore
├── .vscode/
│ └── settings.json
├── LICENSE
├── README.md
├── babel.config.js
├── cypress/
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── fixtures/
│ │ └── example.json
│ ├── integration/
│ │ ├── component-data-edit.js
│ │ ├── components-tab.js
│ │ ├── events-tab.js
│ │ ├── vuex-edit.js
│ │ └── vuex-tab.js
│ ├── plugins/
│ │ └── index.js
│ ├── support/
│ │ ├── commands.js
│ │ └── index.js
│ └── utils/
│ └── suite.js
├── cypress.json
├── eslint.config.js
├── extension-zips.js
├── lerna.json
├── package.json
├── packages/
│ ├── api/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── api/
│ │ │ │ ├── api.ts
│ │ │ │ ├── app.ts
│ │ │ │ ├── component.ts
│ │ │ │ ├── context.ts
│ │ │ │ ├── hooks.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── util.ts
│ │ │ ├── const.ts
│ │ │ ├── env.ts
│ │ │ ├── index.ts
│ │ │ ├── plugin.ts
│ │ │ ├── proxy.ts
│ │ │ └── time.ts
│ │ └── tsconfig.json
│ ├── app-backend-api/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── api.ts
│ │ │ ├── app-record.ts
│ │ │ ├── backend-context.ts
│ │ │ ├── backend.ts
│ │ │ ├── global-hook.ts
│ │ │ ├── hooks.ts
│ │ │ ├── index.ts
│ │ │ └── plugin.ts
│ │ └── tsconfig.json
│ ├── app-backend-core/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── app.ts
│ │ │ ├── backend.ts
│ │ │ ├── component-pick.ts
│ │ │ ├── component.ts
│ │ │ ├── flash.ts
│ │ │ ├── global-hook.ts
│ │ │ ├── highlighter.ts
│ │ │ ├── hook.ts
│ │ │ ├── index.ts
│ │ │ ├── inspector.ts
│ │ │ ├── legacy/
│ │ │ │ └── scan.ts
│ │ │ ├── page-config.ts
│ │ │ ├── perf.ts
│ │ │ ├── plugin.ts
│ │ │ ├── timeline-builtins.ts
│ │ │ ├── timeline-marker.ts
│ │ │ ├── timeline-screenshot.ts
│ │ │ ├── timeline.ts
│ │ │ ├── toast.ts
│ │ │ └── util/
│ │ │ ├── queue.ts
│ │ │ └── subscriptions.ts
│ │ └── tsconfig.json
│ ├── app-backend-vue1/
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── app-backend-vue2/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── components/
│ │ │ │ ├── data.ts
│ │ │ │ ├── el.ts
│ │ │ │ ├── perf.ts
│ │ │ │ ├── tree.ts
│ │ │ │ ├── update-tracking.ts
│ │ │ │ └── util.ts
│ │ │ ├── events.ts
│ │ │ ├── index.ts
│ │ │ └── plugin.ts
│ │ └── tsconfig.json
│ ├── app-backend-vue3/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── components/
│ │ │ │ ├── data.ts
│ │ │ │ ├── el.ts
│ │ │ │ ├── filter.ts
│ │ │ │ ├── tree.ts
│ │ │ │ └── util.ts
│ │ │ ├── index.ts
│ │ │ └── util.ts
│ │ └── tsconfig.json
│ ├── app-frontend/
│ │ ├── package.json
│ │ └── src/
│ │ ├── app.ts
│ │ ├── assets/
│ │ │ ├── github-theme/
│ │ │ │ ├── dark.json
│ │ │ │ └── light.json
│ │ │ └── style/
│ │ │ ├── imports.styl
│ │ │ ├── index.postcss
│ │ │ ├── index.styl
│ │ │ ├── transitions.styl
│ │ │ └── variables.styl
│ │ ├── features/
│ │ │ ├── App.vue
│ │ │ ├── apps/
│ │ │ │ ├── AppSelect.vue
│ │ │ │ ├── AppSelectItem.vue
│ │ │ │ ├── AppSelectPane.vue
│ │ │ │ ├── AppSelectPaneItem.vue
│ │ │ │ ├── index.ts
│ │ │ │ └── vue-version-check.ts
│ │ │ ├── bridge/
│ │ │ │ └── index.ts
│ │ │ ├── chrome/
│ │ │ │ ├── index.ts
│ │ │ │ └── pane-visibility.ts
│ │ │ ├── code/
│ │ │ │ └── CodeEditor.vue
│ │ │ ├── components/
│ │ │ │ ├── ComponentTreeNode.vue
│ │ │ │ ├── ComponentsInspector.vue
│ │ │ │ ├── RenderCode.vue
│ │ │ │ ├── SelectedComponentPane.vue
│ │ │ │ └── composable/
│ │ │ │ ├── components.ts
│ │ │ │ ├── highlight.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── pick.ts
│ │ │ │ └── setup.ts
│ │ │ ├── connection/
│ │ │ │ ├── AppConnecting.vue
│ │ │ │ ├── AppDisconnected.vue
│ │ │ │ └── index.ts
│ │ │ ├── error/
│ │ │ │ ├── ErrorOverlay.vue
│ │ │ │ └── index.ts
│ │ │ ├── header/
│ │ │ │ ├── AppHeader.vue
│ │ │ │ ├── AppHeaderSelect.vue
│ │ │ │ ├── AppHistoryNav.vue
│ │ │ │ ├── header.ts
│ │ │ │ └── tabs.ts
│ │ │ ├── inspector/
│ │ │ │ ├── DataField.vue
│ │ │ │ ├── StateFields.vue
│ │ │ │ ├── StateInspector.vue
│ │ │ │ ├── StateType.vue
│ │ │ │ └── custom/
│ │ │ │ ├── CustomInspector.vue
│ │ │ │ ├── CustomInspectorNode.vue
│ │ │ │ ├── CustomInspectorSelectedNodePane.vue
│ │ │ │ └── composable.ts
│ │ │ ├── layout/
│ │ │ │ ├── EmptyPane.vue
│ │ │ │ ├── SplitPane.vue
│ │ │ │ └── orientation.ts
│ │ │ ├── plugin/
│ │ │ │ ├── PluginDetails.vue
│ │ │ │ ├── PluginHome.vue
│ │ │ │ ├── PluginListItem.vue
│ │ │ │ ├── PluginPermission.vue
│ │ │ │ ├── PluginSettings.vue
│ │ │ │ ├── PluginSettingsItem.vue
│ │ │ │ ├── PluginSourceDescription.vue
│ │ │ │ ├── PluginSourceIcon.vue
│ │ │ │ ├── Plugins.vue
│ │ │ │ └── index.ts
│ │ │ ├── settings/
│ │ │ │ ├── GlobalSettings.vue
│ │ │ │ └── NewTag.vue
│ │ │ ├── timeline/
│ │ │ │ ├── AskScreenshotPermission.vue
│ │ │ │ ├── LayerItem.vue
│ │ │ │ ├── Timeline.vue
│ │ │ │ ├── TimelineEventInspector.vue
│ │ │ │ ├── TimelineEventList.vue
│ │ │ │ ├── TimelineEventListItem.vue
│ │ │ │ ├── TimelineScrollbar.vue
│ │ │ │ ├── TimelineView.vue
│ │ │ │ └── composable/
│ │ │ │ ├── events.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── layers.ts
│ │ │ │ ├── markers.ts
│ │ │ │ ├── reset.ts
│ │ │ │ ├── screenshot.ts
│ │ │ │ ├── setup.ts
│ │ │ │ ├── store.ts
│ │ │ │ └── time.ts
│ │ │ ├── ui/
│ │ │ │ ├── components/
│ │ │ │ │ ├── VueButton.vue
│ │ │ │ │ ├── VueDisable.vue
│ │ │ │ │ ├── VueDropdown.vue
│ │ │ │ │ ├── VueDropdownButton.vue
│ │ │ │ │ ├── VueFormField.vue
│ │ │ │ │ ├── VueGroup.vue
│ │ │ │ │ ├── VueGroupButton.vue
│ │ │ │ │ ├── VueIcon.vue
│ │ │ │ │ ├── VueInput.vue
│ │ │ │ │ ├── VueLoadingBar.vue
│ │ │ │ │ ├── VueLoadingIndicator.vue
│ │ │ │ │ ├── VueModal.vue
│ │ │ │ │ ├── VueSelect.vue
│ │ │ │ │ ├── VueSelectButton.vue
│ │ │ │ │ ├── VueSwitch.vue
│ │ │ │ │ └── icons.ts
│ │ │ │ ├── composables/
│ │ │ │ │ ├── useDisableScroll.ts
│ │ │ │ │ └── useDisabled.ts
│ │ │ │ └── index.ts
│ │ │ └── welcome/
│ │ │ └── WelcomeSlideshow.vue
│ │ ├── index.ts
│ │ ├── locales/
│ │ │ └── en.js
│ │ ├── mixins/
│ │ │ ├── data-field-edit.js
│ │ │ ├── entry-list.ts
│ │ │ └── keyboard.ts
│ │ ├── plugins/
│ │ │ ├── global-refs.ts
│ │ │ ├── i18n.ts
│ │ │ ├── index.ts
│ │ │ └── responsive.ts
│ │ ├── router.ts
│ │ ├── shims-global.d.ts
│ │ ├── shims-vue.d.ts
│ │ ├── types/
│ │ │ └── vue.d.ts
│ │ └── util/
│ │ ├── color.ts
│ │ ├── defer.ts
│ │ ├── fonts.ts
│ │ ├── format/
│ │ │ ├── index.ts
│ │ │ ├── time.ts
│ │ │ └── value.ts
│ │ ├── keyboard.ts
│ │ ├── queue.ts
│ │ ├── reactivity.ts
│ │ ├── shared-data.ts
│ │ ├── theme.ts
│ │ └── time.ts
│ ├── build-tools/
│ │ ├── package.json
│ │ └── src/
│ │ ├── createConfig.js
│ │ └── index.js
│ ├── docs/
│ │ ├── package.json
│ │ ├── postcss.config.js
│ │ ├── src/
│ │ │ ├── .vitepress/
│ │ │ │ ├── .gitignore
│ │ │ │ ├── config.js
│ │ │ │ └── theme/
│ │ │ │ ├── custom.css
│ │ │ │ └── index.js
│ │ │ ├── assets/
│ │ │ │ └── vue-devtools-architecture.drawio
│ │ │ ├── components/
│ │ │ │ ├── InstallButton.vue
│ │ │ │ └── InstallButtons.vue
│ │ │ ├── devtools-vue3.md
│ │ │ ├── guide/
│ │ │ │ ├── contributing.md
│ │ │ │ ├── custom-vue2-app-scan-selector.md
│ │ │ │ ├── devtools-perf.md
│ │ │ │ ├── faq.md
│ │ │ │ ├── installation.md
│ │ │ │ └── open-in-editor.md
│ │ │ ├── index.md
│ │ │ ├── plugin/
│ │ │ │ ├── api-reference.md
│ │ │ │ └── plugins-guide.md
│ │ │ └── release.md
│ │ └── tailwind.config.cjs
│ ├── shared-utils/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── backend.ts
│ │ │ ├── bridge.ts
│ │ │ ├── consts.ts
│ │ │ ├── edit.ts
│ │ │ ├── env.ts
│ │ │ ├── index.ts
│ │ │ ├── plugin-permissions.ts
│ │ │ ├── plugin-settings.ts
│ │ │ ├── raf.ts
│ │ │ ├── shared-data.ts
│ │ │ ├── shell.ts
│ │ │ ├── storage.ts
│ │ │ ├── throttle.ts
│ │ │ ├── transfer.ts
│ │ │ └── util.ts
│ │ └── tsconfig.json
│ ├── shell-chrome/
│ │ ├── devtools-background.html
│ │ ├── devtools.html
│ │ ├── manifest.json
│ │ ├── package.json
│ │ ├── popups/
│ │ │ ├── disabled.html
│ │ │ ├── disabled.nuxt.html
│ │ │ ├── enabled.html
│ │ │ ├── enabled.nuxt.html
│ │ │ ├── not-found.html
│ │ │ └── popup.css
│ │ ├── src/
│ │ │ ├── backend.js
│ │ │ ├── detector-exec.js
│ │ │ ├── detector.js
│ │ │ ├── devtools-background.js
│ │ │ ├── devtools.js
│ │ │ ├── hook-exec.js
│ │ │ ├── hook.js
│ │ │ ├── proxy.js
│ │ │ └── service-worker.js
│ │ └── webpack.config.js
│ ├── shell-dev-vue2/
│ │ ├── package.json
│ │ ├── public/
│ │ │ ├── target-electron.html
│ │ │ ├── target-iframe.html
│ │ │ └── target.html
│ │ ├── src/
│ │ │ ├── Child.vue
│ │ │ ├── Counter.vue
│ │ │ ├── EventChild.vue
│ │ │ ├── EventChild1.vue
│ │ │ ├── EventChildCond.vue
│ │ │ ├── Events.vue
│ │ │ ├── Functional.vue
│ │ │ ├── Hidden.vue
│ │ │ ├── Init.vue
│ │ │ ├── MyClass.js
│ │ │ ├── NativeTypes.vue
│ │ │ ├── NoProp.vue
│ │ │ ├── Other.vue
│ │ │ ├── RefTester.vue
│ │ │ ├── Target.vue
│ │ │ ├── TransitionExample.vue
│ │ │ ├── VuexObject.vue
│ │ │ ├── dynamic-module.js
│ │ │ ├── iframe-app.js
│ │ │ ├── index.js
│ │ │ ├── router/
│ │ │ │ ├── ChildRoute.vue
│ │ │ │ ├── NamedRoute.vue
│ │ │ │ ├── ParentRoute.vue
│ │ │ │ ├── RouteOne.vue
│ │ │ │ ├── RouteTwo.vue
│ │ │ │ ├── RouteWithAlias.vue
│ │ │ │ ├── RouteWithBeforeEnter.vue
│ │ │ │ ├── RouteWithParams.vue
│ │ │ │ ├── RouteWithProps.vue
│ │ │ │ ├── RouteWithQuery.vue
│ │ │ │ └── Router.vue
│ │ │ ├── router.js
│ │ │ └── store.js
│ │ └── webpack.config.js
│ ├── shell-dev-vue3/
│ │ ├── package.json
│ │ ├── public/
│ │ │ ├── target-iframe.html
│ │ │ └── target.html
│ │ ├── src/
│ │ │ ├── Animation.vue
│ │ │ ├── App.vue
│ │ │ ├── App3.vue
│ │ │ ├── AsyncComponent.vue
│ │ │ ├── AsyncSetup.vue
│ │ │ ├── Child.vue
│ │ │ ├── Condition.vue
│ │ │ ├── DomOrder.vue
│ │ │ ├── EventEmit.vue
│ │ │ ├── EventNesting.vue
│ │ │ ├── Form.vue
│ │ │ ├── FormSection.vue
│ │ │ ├── Functional.vue
│ │ │ ├── Ghost.vue
│ │ │ ├── Heavy.vue
│ │ │ ├── Hello.vue
│ │ │ ├── IframeApp.vue
│ │ │ ├── IndexComponent/
│ │ │ │ └── index.vue
│ │ │ ├── Mixins.vue
│ │ │ ├── NativeTypes.vue
│ │ │ ├── Nested.vue
│ │ │ ├── NestedMore.vue
│ │ │ ├── Other.vue
│ │ │ ├── Provide.vue
│ │ │ ├── SetupDataLike.vue
│ │ │ ├── SetupRender.js
│ │ │ ├── SetupScript.vue
│ │ │ ├── SetupTSScriptProps.vue
│ │ │ ├── SuspenseExample.vue
│ │ │ ├── VModelExample.vue
│ │ │ ├── devtools-plugin/
│ │ │ │ ├── index.js
│ │ │ │ └── simple.js
│ │ │ ├── iframe-app.js
│ │ │ ├── main.js
│ │ │ ├── router/
│ │ │ │ ├── Page1.vue
│ │ │ │ └── Page2.vue
│ │ │ └── store.js
│ │ └── webpack.config.js
│ ├── shell-electron/
│ │ ├── README.md
│ │ ├── app.html
│ │ ├── app.js
│ │ ├── bin.js
│ │ ├── index.js
│ │ ├── package.json
│ │ ├── server.js
│ │ ├── src/
│ │ │ ├── backend.js
│ │ │ ├── devtools.js
│ │ │ └── hook.js
│ │ ├── types/
│ │ │ └── index.d.ts
│ │ ├── webpack.config.js
│ │ └── webpack.node.config.js
│ ├── shell-firefox/
│ │ ├── .gitignore
│ │ ├── copy.sh
│ │ ├── manifest.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── backend.js
│ │ │ ├── background.js
│ │ │ ├── detector.js
│ │ │ ├── devtools-background.js
│ │ │ ├── devtools.js
│ │ │ ├── hook.js
│ │ │ └── proxy.js
│ │ └── webpack.config.js
│ └── shell-host/
│ ├── package.json
│ ├── public/
│ │ └── index.html
│ ├── src/
│ │ ├── DevIframe.vue
│ │ ├── backend.js
│ │ ├── devtools.js
│ │ └── hook.js
│ └── webpack.config.js
├── postcss.config.js
├── release.js
├── sign-firefox.js
├── tailwind.config.js
├── tsconfig.json
└── vue1-test.html
================================================
FILE CONTENTS
================================================
================================================
FILE: .browserslistrc
================================================
Chrome >= 52
Firefox >= 48
================================================
FILE: .circleci/config.yml
================================================
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: node:current
- image: vuejs/ci
resource_class: medium+
working_directory: ~/repo
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v3-dependencies-{{ checksum "yarn.lock" }}
# fallback to using the latest cache if no exact match is found
- v3-dependencies-
- run: yarn install --pure-lockfile
- save_cache:
paths:
- node_modules
- ~/.cache/yarn
- ~/.cache/Cypress
key: v3-dependencies-{{ checksum "yarn.lock" }}
# run tests!
- run: yarn build && yarn test
- store_artifacts:
path: cypress/videos
- store_artifacts:
path: cypress/screenshots
================================================
FILE: .github/FUNDING.yml
================================================
github: Akryum
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: 🐞 Bug report
description: Create a report to help us improve
body:
- type: markdown
attributes:
value: |
**Before You Start...**
This form is only for submitting bug reports. If you have a usage question
or are unsure if this is really a bug, make sure to:
- Read the [docs](https://devtools.vuejs.org/)
- Read the [FAQ](https://devtools.vuejs.org/guide/faq.html)
- Ask on [GitHub Discussions](https://github.com/vuejs/devtools/discussions)
- Ask on [Discord Chat](https://chat.vuejs.org/)
Also try to search for your issue - it may have already been answered or even fixed in the development branch.
However, if you find that an old, closed issue still persists in the latest version,
you should open a new issue using the form below instead of commenting on the old issue.
- type: input
id: version
attributes:
label: Vue devtools version
description: |
Open your browser Extensions page and find the Vue devtools extension. Then write in this field the version number shown next to the Vue devtools extension name.
validations:
required: true
- type: input
id: reproduction-link
attributes:
label: Link to minimal reproduction
description: |
The easiest way to provide a reproduction is by showing the bug in [The SFC Playground](https://sfc.vuejs.org/).
If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://vite.new/vue).
If neither of these are suitable, you can always provide a GitHub repository.
The reproduction should be **minimal** - i.e. it should contain only the bare minimum amount of code needed
to show the bug. See [Bug Reproduction Guidelines](https://github.com/vuejs/core/blob/main/.github/bug-repro-guidelines.md) for more details.
Please do not just fill in a random link. The issue will be closed if no valid reproduction is provided.
placeholder: Reproduction Link
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce & screenshots
description: |
What do we need to do after opening your repro in order to make the bug happen in the devtools? Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. Note that you can upload screenshots and use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code.
placeholder: Steps to reproduce
validations:
required: true
- type: textarea
id: expected
attributes:
label: What is expected?
validations:
required: true
- type: textarea
id: actually-happening
attributes:
label: What is actually happening?
validations:
required: true
- type: textarea
id: system-info
attributes:
label: System Info
description: Output of `npx envinfo --system --npmPackages vue --binaries --browsers`
render: shell
placeholder: System, Binaries, Browsers
validations:
required: true
- type: textarea
id: additional-comments
attributes:
label: Any additional comments?
description: e.g. some background/context of how you ran into this bug.
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: I have a performance issue
url: https://devtools.vuejs.org/guide/devtools-perf.html
about: Follow the guide to share performance profiling data with us!
- name: Questions & Discussions
url: https://github.com/vuejs/devtools/discussions
about: Use GitHub discussions for message-board style questions and discussions.
- name: Discord Chat
url: https://chat.vuejs.org
about: Ask questions and discuss with other Vue users in real time.
- name: GitHub Sponsor
url: https://github.com/sponsors/Akryum
about: Love the Vue devtools? Please consider supporting us via GitHub sponsors.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: 🚀 New feature proposal
description: Suggest an idea for this project
labels: [':sparkles: feature request']
body:
- type: markdown
attributes:
value: |
**Before You Start...**
This form is only for submitting feature requests. If you have a usage question
or are unsure if this is really a bug, make sure to:
- Read the [docs](https://devtools.vuejs.org/)
- Read the [FAQ](https://devtools.vuejs.org/guide/faq.html)
- Ask on [GitHub Discussions](https://github.com/vuejs/devtools/discussions)
- Ask on [Discord Chat](https://chat.vuejs.org/)
Also try to search for your issue - another user may have already requested something similar!
- type: textarea
id: problem-description
attributes:
label: What problem does this feature solve?
description: |
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?
placeholder: Problem description
validations:
required: true
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!-- Thank you for contributing! -->
### Description
<!-- Please insert your description here and provide especially info about the "what" this PR is solving -->
### Additional context
<!-- e.g. is there anything you'd like reviewers to focus on? -->
---
### What is the purpose of this pull request? <!-- (put an "X" next to an item) -->
- [ ] Bug fix
- [ ] New Feature
- [ ] Documentation update
- [ ] Other
### Before submitting the PR, please make sure you do the following
- [ ] Read the [Contributing Guidelines](https://devtools.vuejs.org/guide/contributing.html).
- [ ] 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).
- [ ] Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
- [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`).
<!-- @TODO tests - [ ] Ideally, include relevant tests that fail without this PR but pass with it. -->
================================================
FILE: .github/commit-convention.md
================================================
## Git Commit Message Convention
> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
#### TL;DR:
Messages must be matched by the following regex:
<!-- prettier-ignore -->
```js
/^(revert: )?(feat|fix|docs|dx|refactor|perf|test|workflow|build|ci|chore|types|wip|release|deps)(\(.+\))?: .{1,50}/
```
#### Examples
Appears under "Features" header, `dev` subheader:
```
feat(dev): add 'comments' option
```
Appears under "Bug Fixes" header, `dev` subheader, with a link to issue #28:
```
fix(dev): fix dev error
close #28
```
Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation:
```
perf(build): remove 'foo' option
BREAKING CHANGE: The 'foo' option has been removed.
```
The following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the "Reverts" header.
```
revert: feat(compiler): add 'comments' option
This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
```
### Full Message Format
A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
The **header** is mandatory and the **scope** of the header is optional.
### Revert
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
### Type
If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However, if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.
Other prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.
### Scope
The scope could be anything specifying the place of the commit change. For example `dev`, `build`, `workflow`, `cli` etc...
### Subject
The subject contains a succinct description of the change:
- use the imperative, present tense: "change" not "changed" nor "changes"
- don't capitalize the first letter
- no dot (.) at the end
### Body
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.
### Footer
The footer should contain any information about **Breaking Changes** and is also the place to
reference GitHub issues that this commit **Closes**.
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
================================================
FILE: .github/workflows/create-release.yml
================================================
name: Create release
on:
push:
tags:
- 'v*'
jobs:
build:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@master
with:
fetch-depth: 0 # Fetch all tags
- name: Create Release for Tag
id: release_tag
uses: Akryum/release-tag@v4.0.7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
================================================
FILE: .gitignore
================================================
node_modules
.DS_Store
build
/dist/*
*.zip
*.xpi
tests_output
selenium-debug.log
TODOs.md
.idea
.web-extension-id
yarn-error.log
/packages/*/lib
.amo.env.json
build-node
================================================
FILE: .vscode/settings.json
================================================
{
"typescript.tsdk": "node_modules/typescript/lib"
}
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2014-present Evan You
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
# Try the next iteration of Vue Devtools!
We have a brand new version of Devtools being developed at [vuejs/devtools-next](https://github.com/vuejs/devtools-next). It is now in beta, please help us [test it out](https://devtools-next.vuejs.org/getting-started/installation)!
---
# vue-devtools

[Documentation](https://devtools.vuejs.org/) | [Install the extension](https://devtools.vuejs.org/guide/installation.html)
## Monorepo
|Package|Description|
|-------|-----------|
[api](./packages/api) | The public devtools API that can be installed in Vue plugins |
[app-backend-api](./packages/app-backend-api) | Abstract API to link the Public API, the core and Vue handlers |
[app-backend-core](./packages/app-backend-core) | The main logic injected in the page to interact with Vue apps |
[app-backend-vue1](./packages/app-backend-vue1) | Decoupled handlers to support Vue 1 (soon) |
[app-backend-vue2](./packages/app-backend-vue2) | Decoupled handlers to support Vue 2 |
[app-backend-vue3](./packages/app-backend-vue3) | Decoupled handlers to support Vue 3 |
[app-frontend](./packages/app-frontend) | Vue app displayed in the browser devtools pane |
[shell-chrome](./packages/shell-chrome) | Chrome/Firefox extension |
[shell-electron](./packages/shell-electron) | Electron standalone app |
[shell-host](./packages/shell-host) | Development environment |
[shell-dev-vue2](./packages/shell-dev-vue2) | Demo app for development (Vue 2) |
[shell-dev-vue3](./packages/shell-dev-vue3) | Demo app for development (Vue 3) |
## Contributing
See the [Contributing guide](https://devtools.vuejs.org/guide/contributing.html).
## License
[MIT](http://opensource.org/licenses/MIT)
## Sponsors
[💚️ Become a Sponsor](https://github.com/sponsors/Akryum)
<p align="center">
<a href="https://guillaume-chau.info/sponsors/" target="_blank">
<img src='https://akryum.netlify.app/sponsors.svg'/>
</a>
</p>
================================================
FILE: babel.config.js
================================================
module.exports = {
root: true,
presets: [
[
'@babel/env',
{
modules: false,
},
],
],
}
================================================
FILE: cypress/.eslintrc.js
================================================
module.exports = {
plugins: [
'cypress',
],
env: {
'mocha': true,
'cypress/globals': true,
},
rules: {
strict: 'off',
},
}
================================================
FILE: cypress/.gitignore
================================================
/screenshots
/videos
================================================
FILE: cypress/fixtures/example.json
================================================
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
================================================
FILE: cypress/integration/component-data-edit.js
================================================
import { suite } from '../utils/suite'
suite('component data edit', () => {
it('should edit data using the decrease button', () => {
// select Instance
cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()
cy.get('.component-state-inspector .data-type').should('contain', 'data')
cy.get('.data-field').eq(7).find('.actions .vue-ui-button').eq(1).click({ force: true })
cy.get('.component-state-inspector').within(() => {
cy.get('.key').contains('0').parent().get('.value').contains('0')
})
cy.get('.data-field').eq(7).find('.actions .vue-ui-button').eq(1).click({ force: true })
cy.get('.component-state-inspector').within(() => {
cy.get('.key').contains('0').parent().contains('-1')
})
// expect DOM element to be updated
cy.get('#target').iframe().then(({ get }) => {
get('#target div').eq(0).contains('-1')
})
})
it('should edit data using the increase button', () => {
cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()
cy.get('.component-state-inspector .data-type').should('contain', 'data')
cy.get('.data-field').eq(7).find('.actions .vue-ui-button').eq(2).click({ force: true })
cy.get('.component-state-inspector').within(() => {
cy.get('.key').contains('0').parent().get('.value').contains('0')
})
// expect DOM element to be updated
cy.get('#target').iframe().then(({ get }) => {
get('#target div').eq(0).contains('0')
})
})
it('should edit data using the edit input', () => {
cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()
cy.get('.data-field').eq(7).find('.actions .vue-ui-button').eq(0).click({ force: true })
cy.get('.edit-input').type('12')
cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()
cy.get('.component-state-inspector').within(() => {
cy.get('.key').contains('0').parent().get('.value').contains('12')
})
// expect DOM element to be updated
cy.get('#target').iframe().then(({ get }) => {
get('#target div').eq(0).contains('12')
})
})
it('should add elements to array', () => {
cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()
cy.get('.data-field').eq(6).find('.actions .vue-ui-button').eq(1).click({ force: true })
cy.get('.edit-input').type('55')
cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()
cy.get('.data-field').eq(6).find('.children .data-field').should('have.length', '3', { timeout: 5000 })
cy.get('.component-state-inspector').within(() => {
cy.get('.key').contains('2').parent().get('.value').contains('55')
})
// expect DOM element to be updated
cy.get('#target').iframe().then(({ get }) => {
get('#target div').eq(4).contains('55')
})
})
it('should remove elements from array', () => {
cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()
cy.get('.data-field').eq(9).find('.actions .vue-ui-button').eq(3).click({ force: true })
cy.get('.data-field').eq(6).find('.children .data-field').should('have.length', '2', { timeout: 5000 })
})
it('should parse object through edit input', () => {
cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()
cy.get('.data-field').eq(7).find('.actions .vue-ui-button').eq(0).click({ force: true })
cy.get('.edit-input').type('{{}"count":42}')
cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()
cy.get('.data-field').eq(7).should('contain', 'Object', { timeout: 5000 })
// expand object
cy.get('.data-field').eq(7).click()
cy.get('.data-field').eq(8).find('.key').should('contain', 'count', { timeout: 5000 })
cy.get('.data-field').eq(8).find('.value').should('contain', 42, { timeout: 5000 })
})
it('should rename object\'s property', () => {
cy.get('.instance:nth-child(1) .instance:nth-child(2)').eq(0).click()
cy.get('.data-field').eq(8).find('.actions .vue-ui-button').eq(0).click({ force: true })
cy.get('.edit-input.key-input').clear().type('name')
cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()
cy.get('.data-field').eq(8).find('.key').should('contain', 'name', { timeout: 5000 })
})
})
================================================
FILE: cypress/integration/components-tab.js
================================================
import { suite } from '../utils/suite'
const baseInstanceCount = 12
suite('components tab', () => {
beforeEach(() => cy.reload())
it('should detect instances inside shadow DOM', () => {
cy.get('.tree > .instance:last-child').contains('Shadow')
})
it('should select instance', () => {
cy.get('.instance .self').eq(0).click().should('have.class', 'selected')
cy.get('.tree').should('be.visible')
cy.get('.action-header .title').contains('Root')
cy.get('.data-field').contains('$route')
})
it('should expand root by default', () => {
cy.get('.instance').should('have.length', baseInstanceCount)
})
it('should detect functional components', () => {
cy.get('.tree > .instance .instance:nth-child(2)').within(() => {
cy.get('.arrow').click().then(() => {
cy.get('.instance:last-child').contains('Functional')
})
})
})
it('should display 0 key', () => {
cy.get('.tree > .instance .instance:nth-child(2)').within(() => {
cy.get('.arrow').click().then(() => {
cy.get('.instance:nth-child(3) .attr').contains('key=0')
})
})
})
it('should detect components in transition', () => {
cy.get('.tree > .instance .instance:nth-child(7)').within(() => {
cy.get('.arrow').click().then(() => {
cy.get('.instance').eq(1).within(() => {
cy.get('.arrow').click().then(() => {
cy.get('.instance').contains('TestComponent')
})
})
})
})
})
it('should select child instance', () => {
cy.get('.instance .instance:nth-child(1) .self').eq(0).click()
cy.get('.action-header .title').contains('Counter')
cy.get('.data-el.vuex-bindings .data-field').contains('count:0')
cy.get('.data-el.computed .data-field').contains('test:1')
cy.get('.data-el.firebase-bindings .data-field').contains('hello:undefined')
})
it('should display prop of different types', () => {
cy.get('.instance .instance:nth-child(2) .self').eq(0).click()
cy.get('.action-header .title').contains('Target')
cy.get('.data-el.props .data-field:nth-child(1)').contains('ins:Object')
cy.get('.data-el.props .data-field:nth-child(2)').contains('msg:"hi"')
cy.get('.data-el.props .data-field:nth-child(3)').contains('obj:undefined')
// Regexp
cy.get('.data-el.data .data-field:nth-child(8)').then((el) => {
expect(el.text()).to.include('regex:/(a\\w+b)/g')
})
// Literals
cy.get('.data-el.data .data-field:nth-child(5)').contains('NaN')
cy.get('.data-el.data .data-field:nth-child(2)').contains('Infinity')
cy.get('.data-el.data .data-field:nth-child(6)').contains('-Infinity')
})
it('should expand child instance', () => {
cy.get('.instance .instance:nth-child(2) .arrow-wrapper').click()
cy.get('.instance').should('have.length', baseInstanceCount + 10)
})
it('should add/remove component from app side', () => {
cy.get('.instance .instance:nth-child(2) .arrow-wrapper').click()
cy.get('.instance').should('have.length', baseInstanceCount + 10)
cy.get('#target').iframe().then(({ get }) => {
get('.add').click({ force: true })
})
cy.get('.instance').should('have.length', baseInstanceCount + 13)
cy.get('#target').iframe().then(({ get }) => {
get('.remove').click({ force: true })
})
cy.get('.instance').should('have.length', baseInstanceCount + 12)
})
it('should filter components', () => {
cy.get('.left .search input').clear().type('counter')
cy.get('.instance').should('have.length', 2)
cy.get('.left .search input').clear().type('target')
cy.get('.instance').should('have.length', 12)
cy.get('.left .search input').clear()
})
it('should select component', () => {
cy.get('.select-component').click()
cy.get('#target').iframe().then(({ get }) => {
get('.mine').eq(0)
.trigger('mouseover', { force: true })
.click({ force: true })
})
cy.get('.action-header .title').contains('Mine')
cy.get('.tree').then((el) => {
expect(el.text()).to.include('<Mine>')
})
})
it('should display render key', () => {
cy.get('.instance .instance:nth-child(2) .arrow-wrapper').click()
cy.get('.instance .self .attr-title').contains('key')
cy.get('.instance .self .attr-value').contains('1')
})
it('should display injected props', () => {
cy.get('.left .search input').clear().type('Mine')
cy.get('.instance').eq(1).click()
cy.get('.right .data-wrapper')
.should('contain', 'injected')
.should('contain', 'answer:42')
.should('contain', 'foo:"bar"')
.should('contain', 'noop:ƒ noop(a, b, c)')
cy.get('.left .search input').clear()
})
it('should display $refs', () => {
cy.get('.instance .item-name').contains('RefTester').click()
cy.get('.right .data-wrapper')
.should('contain', 'list:Array[4]')
.should('contain', '<li>')
.should('contain', 'tester:<p id="testing"')
})
it('should display $attrs', () => {
cy.get('.instance .instance:nth-child(2) .arrow-wrapper').click()
cy.get('.instance .instance .instance:nth-child(1) .item-name').click()
cy.get('.right .data-wrapper')
.should('contain', '$attrs')
.should('contain', 'attr:"some-attr"')
})
})
================================================
FILE: cypress/integration/events-tab.js
================================================
import { suite } from '../utils/suite'
suite('events tab', () => {
it('should display new events counter', () => {
cy.get('#target').iframe().then(({ get }) => {
get('.btn-emit-event').click({ force: true })
get('.btn-emit-event1').click({ force: true })
get('.btn-emit-event2').click({ force: true })
})
cy.get('.events-tab .tag').contains(3)
cy.get('.events-tab').click()
cy.get('.events-tab .tag').should('not.be.visible')
})
it('should display events', () => {
cy.get('.history .entry').should('have.length', 3)
})
it('should add event', () => {
cy.get('#target').iframe().then(({ get }) => {
get('.btn-emit-log-event').click({ force: true })
})
cy.get('.history .entry').should('have.length', 4)
})
it('should search events', () => {
cy.get('.left .search input').clear().type('event')
cy.get('.history .entry[data-active="true"]').should('have.length', 3)
cy.get('.left .search input').clear().type('<eventchild1>')
cy.get('.history .entry[data-active="true"]').should('have.length', 1)
cy.get('.left .search input').clear().type('/^event$/')
cy.get('.history .entry[data-active="true"]').should('have.length', 1)
cy.get('.left .search input').clear()
cy.get('.button.reset').click()
cy.get('.history .entry[data-active="true"]').should('have.length', 0)
})
})
================================================
FILE: cypress/integration/vuex-edit.js
================================================
import { suite } from '../utils/suite'
suite('vuex edit', () => {
it('should edit state using the decrease button', () => {
cy.get('.vuex-tab').click()
cy.get('[data-id="load-vuex-state"]').click()
// using the decrease button
cy.get('.state .data-field').eq(0)
.find('.actions .vue-ui-button').eq(1)
.click({ force: true })
cy.get('.vuex-state-inspector').within(() => {
cy.get('.key').contains('count').parent().contains('-1')
})
cy.get('.state .data-field').eq(0)
.find('.actions .vue-ui-button').eq(1)
.click({ force: true })
cy.get('.vuex-state-inspector').within(() => {
cy.get('.key').contains('count').parent().contains('-2')
})
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('-2')
})
})
it('should edit state using the increase button', () => {
// using the increase button
cy.get('.state .data-field').eq(0).click()
.find('.actions .vue-ui-button').eq(2)
.click({ force: true })
cy.get('.vuex-state-inspector').within(() => {
cy.get('.key').contains('count').parent().contains('-1')
})
cy.get('.state .data-field').eq(0).click()
.find('.actions .vue-ui-button').eq(2)
.click({ force: true })
cy.get('.vuex-state-inspector').within(() => {
cy.get('.key').contains('count').parent().contains('0')
})
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('0')
})
})
it('should edit state using the edit input', () => {
// using the edit input
cy.get('.state .data-field').eq(0).click()
.find('.actions .vue-ui-button').eq(0).click({ force: true })
cy.get('.edit-input').type('12')
cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()
cy.wait(200)
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('12')
})
// change count back to 1
cy.get('.state .data-field').eq(0).click()
.find('.actions .vue-ui-button').eq(0).click({ force: true })
cy.get('.edit-input').type('0')
cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()
cy.wait(200)
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('0')
})
})
it('should edit state nested field', () => {
// using the decrease button
cy.get('.data-field > .children > .data-field').eq(4)
.find('.actions .vue-ui-button').eq(1)
.click({ force: true })
.click({ force: true })
cy.wait(200)
cy.get('#target').iframe().then(({ get }) => {
get('#vuex-object pre').contains('-2')
})
// using the increase button
cy.get('.data-field > .children > .data-field').eq(4)
.find('.actions .vue-ui-button').eq(2)
.click({ force: true })
.click({ force: true })
cy.wait(200)
cy.get('#target').iframe().then(({ get }) => {
get('#vuex-object pre').contains('0')
})
// using the input
cy.get('.data-field > .children > .data-field').eq(4)
.find('.actions .vue-ui-button').eq(0).click({ force: true })
cy.get('.edit-input').eq(1).type('12')
cy.get('.edit-overlay > .actions > :nth-child(2) > .content > .vue-ui-icon').click()
cy.wait(200)
cy.get('#target').iframe().then(({ get }) => {
get('#vuex-object pre').contains('12')
})
})
})
================================================
FILE: cypress/integration/vuex-tab.js
================================================
import { suite } from '../utils/suite'
suite('vuex tab', () => {
it('should display mutations history', () => {
cy.get('#target').iframe().then(({ get }) => {
get('.increment')
.click({ force: true })
.click({ force: true })
get('.decrement').click({ force: true })
get('#counter p').contains('1')
})
cy.get('.vuex-tab').click()
cy.get('.history .entry').should('have.length', 6)
cy.get('[data-id="load-vuex-state"]').click()
cy.get('.recording-vuex-state').should('not.be.visible')
cy.get('.loading-vuex-state').should('not.be.visible')
cy.get('.vuex-state-inspector')
.should('contain', 'type:"DECREMENT"')
.should('contain', 'count:1')
cy.get('.history .entry').eq(5).should('have.class', 'inspected').should('have.class', 'active')
})
it('should filter state & getters', () => {
cy.get('.right .search input').clear().type('cou')
cy.get('.data-field').should('have.length', 2)
cy.get('.right .search input').clear().type('no value')
cy.get('.data-field').should('have.length', 0)
cy.get('.right .search input').clear()
})
it('should filter history', () => {
cy.get('.left .search input').clear().type('inc')
cy.get('.history .entry[data-active="true"]').should('have.length', 2)
cy.get('.history .entry[data-active="true"].inspected').should('have.length', 0)
cy.get('.history .entry[data-active="true"].active').should('have.length', 0)
cy.get('.left .search input').clear().type('/dec/i')
cy.get('.history .entry[data-active="true"]').should('have.length', 1)
cy.get('.history .entry[data-active="true"].inspected').should('have.length', 0)
cy.get('.history .entry[data-active="true"].active').should('have.length', 0)
cy.get('.left .search input').clear().type('/dec)/i')
cy.get('.history .entry[data-active="true"]').should('have.length', 5)
cy.get('.history .entry[data-active="true"].inspected').should('have.length', 0)
cy.get('.history .entry[data-active="true"].active').should('have.length', 1)
cy.get('.left .search input').clear()
})
it('should inspect state', () => {
cy.get('.history .entry .mutation-type').eq(2).click()
cy.get('.history .entry').eq(2)
.should('have.class', 'inspected')
.should('not.have.class', 'active')
cy.get('.recording-vuex-state').should('not.be.visible')
cy.get('.loading-vuex-state').should('not.be.visible')
cy.get('.vuex-state-inspector').then((el) => {
expect(el.text()).to.include('type:"INCREMENT"')
expect(el.text()).to.include('count:2')
expect(el.text()).to.include('Error from getter')
})
cy.get('.data-field .key').contains('lastCountPayload').click()
cy.get('.vuex-state-inspector').then((el) => {
expect(el.text()).to.include('a:1')
expect(el.text()).to.include('b:Object')
})
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('1')
})
})
it('should time-travel', () => {
cy.get('.history .entry[data-index="4"] .entry-actions .action-time-travel').click({ force: true })
cy.get('.history .entry[data-index="4"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('2')
})
cy.get('.history .entry[data-index="3"] .mutation-type').click({ force: true })
cy.get('.history .entry[data-index="3"]')
.should('have.class', 'inspected')
.should('not.have.class', 'active')
cy.get('.history .entry[data-index="4"]')
.should('not.have.class', 'inspected')
.should('have.class', 'active')
cy.get('.recording-vuex-state').should('not.be.visible')
cy.get('.loading-vuex-state').should('not.be.visible')
cy.get('.recording-vuex-state').should('not.be.visible')
cy.get('.vuex-state-inspector').then((el) => {
expect(el.text()).to.include('type:"INCREMENT"')
expect(el.text()).to.include('count:1')
})
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('2')
})
cy.get('.history .entry[data-index="3"] .entry-actions .action-time-travel').click({ force: true })
cy.get('.history .entry[data-index="3"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
cy.get('.history .entry[data-index="4"]')
.should('not.have.class', 'inspected')
.should('not.have.class', 'active')
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('1')
})
// Base state
cy.get('.history .entry[data-index="0"] .mutation-type').click({ force: true })
cy.get('.history .entry[data-index="0"]')
.should('have.class', 'inspected')
.should('not.have.class', 'active')
cy.get('.vuex-state-inspector').then((el) => {
expect(el.text()).to.include('count:0')
})
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('1')
})
cy.get('.history .entry[data-index="2"] .entry-actions .action-time-travel').click({ force: true })
cy.get('.history .entry[data-index="2"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('0')
})
})
it('should revert', () => {
cy.get('.history .entry[data-index="5"] .mutation-type').click({ force: true })
cy.get('.history .entry[data-index="5"]').find('.action-revert').click({ force: true })
cy.get('.history .entry[data-active="true"]').should('have.length', 5)
cy.get('.history .entry[data-index="4"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
cy.get('.vuex-state-inspector').then((el) => {
expect(el.text()).to.include('count:2')
})
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('2')
})
})
it('should commit', () => {
cy.get('.history .entry[data-index="4"] .action-commit').click({ force: true })
cy.get('.history .entry[data-active="true"]').should('have.length', 1)
cy.get('.history .entry[data-index="0"]')
.should('have.class', 'inspected')
.should('have.class', 'active')
cy.get('.vuex-state-inspector').then((el) => {
expect(el.text()).to.include('count:2')
})
cy.get('#target').iframe().then(({ get }) => {
get('#counter p').contains('2')
})
})
it('should display getters', () => {
cy.get('.vuex-state-inspector').within(() => {
cy.get('.key').contains('count').parent().contains('2')
cy.get('.key').contains('isPositive').parent().contains('true')
})
cy.get('#target').iframe().then(({ get }) => {
get('.decrement')
.click({ force: true })
.click({ force: true })
.click({ force: true })
get('#counter p').contains('-1')
})
cy.get('.history .entry[data-index="3"]').click({ force: true })
cy.get('.recording-vuex-state').should('not.be.visible')
cy.get('.loading-vuex-state').should('not.be.visible')
cy.get('.vuex-state-inspector').within(() => {
cy.get('.key').contains('count').parent().contains('-1')
cy.get('.key').contains('isPositive').parent().contains('false')
})
})
it('should toggle recording', () => {
cy.get('.toggle-recording')
.click()
.contains('Paused')
cy.get('.toggle-recording .svg-icon').should('not.have.class', 'enabled')
// should not record
cy.get('#target').iframe().then(({ get }) => {
get('.increment').click({ force: true })
})
cy.get('.history .entry[data-active="true"]').should('have.length', 4)
})
it('should copy vuex state', () => {
cy.get('.export').click()
cy.get('.export .message')
.contains('(Copied to clipboard!)')
.should('not.be.visible', { timeout: 5000 })
})
it('should import vuex state', () => {
cy.get('.import').click()
cy.get('.import-state').should('be.visible')
cy.get('.import-state textarea').clear().type('{{}invalid: json}')
cy.get('.message.invalid-json').should('be.visible')
cy.get('.import-state textarea').clear().type('{{}"count":42,"date":"[native Date Fri Dec 22 2017 10:12:04 GMT+0100 (CET)]","nested":{{}"foo":"meow"},"instant":{{}"hey":"hi"}}')
cy.wait(500)
cy.get('.message.invalid-json').should('not.be.visible')
cy.wait(500)
cy.get('.vuex-state-inspector').then((el) => {
expect(el.text()).to.include('count:42')
expect(el.text()).to.include(`date:${new Date('Fri Dec 22 2017 10:12:04 GMT+0100 (CET)')}`)
})
cy.get('.import').click()
cy.get('.import-state').should('not.be.visible')
})
})
================================================
FILE: cypress/plugins/index.js
================================================
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
module.exports = (_on, _config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// on('before:browser:launch', (browser = {}, args) => {
// if (browser.name === 'chrome') {
// args.push('--disable-site-isolation-trials')
// return args
// }
// })
}
================================================
FILE: cypress/support/commands.js
================================================
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
Cypress.Commands.add('vueCheckInit', () => {
cy.get('.message .text').should('be.visible', { timeout: 10000 }).then((el) => {
expect(el.text()).to.include('Ready. Detected Vue')
})
cy.get('.instance').eq(0).contains('Root')
})
// Add iframe support until becomes part of the framework
Cypress.Commands.add('iframe', { prevSubject: 'element' }, ($iframe) => {
const get = selector => cy.wait(500).wrap($iframe.contents().find(selector))
const el = $iframe[0]
const iframeDoc = el.contentDocument || el.contentWindow.document
if (iframeDoc.readyState === 'complete') {
return Cypress.Promise.resolve({ body: $iframe.contents().find('body'), get })
}
return new Cypress.Promise((resolve) => {
$iframe.on('load', () => {
resolve({ body: $iframe.contents().find('body'), get })
})
})
})
================================================
FILE: cypress/support/index.js
================================================
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
================================================
FILE: cypress/utils/suite.js
================================================
export function suite(description, tests) {
describe(description, () => {
before(() => {
cy.visit('/')
cy.vueCheckInit()
})
tests()
})
}
================================================
FILE: cypress.json
================================================
{
"viewportWidth": 1280,
"viewportHeight": 800,
"chromeWebSecurity": false
}
================================================
FILE: eslint.config.js
================================================
const antfu = require('@antfu/eslint-config').default
module.exports = antfu({
ignores: [
'**/dist',
],
}, {
rules: {
'curly': ['error', 'all'],
'node/prefer-global/process': 'off',
},
}, {
files: [
'packages/shell-dev*/**',
],
rules: {
'no-console': 'off',
'unused-imports/no-unused-vars': 'off',
'vue/require-explicit-emits': 'off',
'vue/custom-event-name-casing': 'off',
'vue/no-deprecated-functional-template': 'off',
'vue/no-deprecated-filter': 'off',
'vue/no-unused-refs': 'off',
'vue/require-component-is': 'off',
'vue/return-in-computed-property': 'off',
},
}, {
files: [
'packages/shell-host/**',
],
rules: {
'no-console': 'off',
},
}, {
files: [
'package.json',
'packages/*/package.json',
'packages/*/manifest.json',
],
rules: {
'style/eol-last': 'off',
},
})
================================================
FILE: extension-zips.js
================================================
// require modules
const fs = require('node:fs')
const path = require('node:path')
const process = require('node:process')
const archiver = require('archiver')
const IS_CI = !!(process.env.CIRCLECI || process.env.GITHUB_ACTIONS)
const ProgressBar = !IS_CI ? require('progress') : {}
const readDirGlob = !IS_CI ? require('readdir-glob') : {}
const INCLUDE_GLOBS = [
'build/**',
'icons/**',
'popups/**',
'devtools.html',
'devtools-background.html',
'manifest.json',
'package.json',
]
// SKIP_GLOBS makes glob searches more efficient
const SKIP_DIR_GLOBS = ['node_modules', 'src']
function bytesToSize(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
if (bytes === 0) {
return '0 Byte'
}
const i = Number.parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))
return `${Math.round(bytes / 1024 ** i, 2)} ${sizes[i]}`
}
(async () => {
await writeZip('devtools-chrome.zip', 'shell-chrome')
await writeZip('devtools-firefox.zip', 'shell-firefox')
async function writeZip(fileName, packageDir) {
// create a file to stream archive data to.
const output = fs.createWriteStream(path.join(__dirname, 'dist', fileName))
const archive = archiver('zip', {
zlib: { level: 9 }, // Sets the compression level.
})
if (!IS_CI) {
const status = {
total: 0,
cFile: '...',
cSize: '0 Bytes',
tBytes: 0,
tSize: '0 Bytes',
}
async function parseFileStats() {
return new Promise((resolve, reject) => {
const globber = readDirGlob(path.join('packages', packageDir), { pattern: INCLUDE_GLOBS, skip: SKIP_DIR_GLOBS, mark: true, stat: true })
globber.on('match', (match) => {
if (!match.stat.isDirectory()) {
status.total++
}
})
globber.on('error', (err) => {
reject(err)
})
globber.on('end', () => {
resolve()
})
})
}
await parseFileStats().catch((err) => {
console.error(err)
process.exit(1)
})
const bar = new ProgressBar(`${fileName} @ :tSize [:bar] :current/:total :percent +:cFile@:cSize`, {
width: 18,
incomplete: ' ',
total: status.total,
})
bar.tick(0, status)
archive.on('entry', (entry) => {
if (!entry.stats.isDirectory()) {
const n = entry.name
status.written++
status.cFile = n.length > 14
? `...${n.slice(n.length - 11)}`
: n
status.cSize = bytesToSize(entry.stats.size)
status.tBytes += entry.stats.size
status.tSize = bytesToSize(status.tBytes)
bar.tick(1, status)
}
})
}
const end = new Promise((resolve) => {
// listen for all archive data to be written
// 'close' event is fired only when a file descriptor is involved
output.on('close', () => {
if (archive.pointer() < 1000) {
console.warn(`Zip file (${fileName}) is only ${archive.pointer()} bytes`)
}
resolve()
})
})
// This event is fired when the data source is drained no matter what was the data source.
// It is not part of this library but rather from the NodeJS Stream API.
// @see: https://nodejs.org/api/stream.html#stream_event_end
output.on('end', () => {
'nothing'
})
// good practice to catch warnings (ie stat failures and other non-blocking errors)
archive.on('warning', (err) => {
if (err.code !== 'ENOENT') {
// throw error
console.error(err)
process.exit(1)
}
})
// good practice to catch this error explicitly
archive.on('error', (err) => {
console.error(err)
process.exit(1)
})
// pipe archive data to the file
archive.pipe(output)
INCLUDE_GLOBS.forEach((glob) => {
// append files from a glob pattern
archive.glob(glob, { cwd: path.join('packages', packageDir), skip: SKIP_DIR_GLOBS })
})
// finalize the archive (ie we are done appending files but streams have to finish yet)
// 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
archive.finalize()
await end
}
})()
================================================
FILE: lerna.json
================================================
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "6.0.0-beta.2",
"packages": [
"packages/*"
],
"ignoreChanges": [
"**/*.md"
]
}
================================================
FILE: package.json
================================================
{
"name": "vue-devtools",
"version": "6.6.4",
"private": true,
"description": "devtools for Vue.js!",
"workspaces": [
"packages/*"
],
"author": "Evan You",
"license": "MIT",
"homepage": "https://github.com/vuejs/vue-devtools#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue-devtools.git"
},
"bugs": {
"url": "https://github.com/vuejs/vue-devtools/issues"
},
"engines": {
"node": ">=8.10"
},
"scripts": {
"dev:vue2": "concurrently \"cd packages/shell-dev-vue2 && yarn dev\" \"cd packages/shell-host && yarn dev\"",
"dev:vue3": "concurrently \"cd packages/shell-dev-vue3 && yarn dev\" \"cd packages/shell-host && yarn dev\"",
"dev:chrome": "cd packages/shell-chrome && webpack --watch",
"dev:chrome:prod": "cd packages/shell-chrome && cross-env NODE_ENV=production webpack --watch",
"dev:firefox": "cd packages/shell-firefox && webpack --watch",
"dev:electron": "cd packages/shell-electron && npm run dev",
"build": "lerna run build",
"build:watch": "lerna run build --scope @vue-devtools/app-backend* --scope @vue-devtools/shared-* --scope @vue/devtools-api && lerna run build:watch --stream --no-sort --concurrency 99",
"lint": "eslint .",
"run:firefox": "web-ext run -s packages/shell-firefox -a dist -i src -u http://localhost:8090/target.html",
"zip": "node ./extension-zips.js",
"sign:firefox": "node ./sign-firefox.js",
"release": "npm run test && node release.js && npm run build && npm run zip && npm run pub",
"release:beta": "cross-env RELEASE_CHANNEL=beta npm run release && npm run sign:firefox",
"pub": "npm run pub:electron && npm run pub:api",
"pub:electron": "cd packages/shell-electron && npm publish",
"pub:api": "cd packages/api && npm publish",
"test": "npm run lint && npm run test:types:front",
"test:types:front": "tsc --noEmit",
"test:e2e": "cross-env PORT=4040 start-server-and-test dev:shell http://localhost:4040 test:e2e:run",
"test:e2e:run": "cypress run --config baseUrl=http://localhost:4040",
"test:open": "cypress open --config baseUrl=http://localhost:8100",
"docs:dev": "cd packages/docs && vitepress dev src",
"docs:build": "cd packages/docs && vitepress build src",
"docs:serve": "cd packages/docs && vitepress serve src"
},
"devDependencies": {
"@antfu/eslint-config": "^2.19.1",
"@tailwindcss/postcss7-compat": "^2.0.4",
"@types/chrome": "^0.0.139",
"@types/speakingurl": "^13.0.3",
"archiver": "^5.3.0",
"autoprefixer": "^9.1.5",
"concurrently": "^5.1.0",
"cross-env": "^5.2.0",
"cypress": "^3.1.0",
"eslint": "^9.3.0",
"execa": "^4.0.3",
"inquirer": "^6.2.0",
"lerna": "^4.0.0",
"postcss-nested": "^4.2.1",
"rimraf": "^3.0.2",
"semver": "^5.5.1",
"start-server-and-test": "^1.7.1",
"svg-inline-loader": "^0.8.2",
"tailwindcss": "npm:@tailwindcss/postcss7-compat",
"vue-loader": "^17.2.2",
"webpack-dev-server": "^4.15.1"
},
"resolutions": {
"cypress": "=3.4.1",
"webpack-dev-server": "^4.15.1"
}
}
================================================
FILE: packages/api/package.json
================================================
{
"name": "@vue/devtools-api",
"version": "6.6.4",
"description": "Interact with the Vue devtools from the page",
"author": {
"name": "Guillaume Chau"
},
"license": "MIT",
"repository": {
"url": "https://github.com/vuejs/vue-devtools.git",
"type": "git",
"directory": "packages/api"
},
"sideEffects": false,
"main": "lib/cjs/index.js",
"browser": "lib/esm/index.js",
"module": "lib/esm/index.js",
"types": "lib/esm/index.d.ts",
"files": [
"lib/cjs",
"lib/esm"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "rimraf lib && yarn build:esm && yarn build:cjs",
"build:esm": "tsc --module es2015 --outDir lib/esm -d",
"build:cjs": "tsc --module commonjs --outDir lib/cjs",
"build:watch": "yarn tsc --module es2015 --outDir lib/esm -d -w --sourceMap"
},
"devDependencies": {
"@types/node": "^20.11.16",
"@types/webpack-env": "^1.15.1",
"typescript": "^5.3.3"
}
}
================================================
FILE: packages/api/src/api/api.ts
================================================
import type { ComponentBounds, Hookable } from './hooks.js'
import type { Context } from './context.js'
import type { ComponentInstance, ComponentState, StateBase } from './component.js'
import type { App } from './app.js'
import type { ID } from './util.js'
export interface DevtoolsPluginApi<TSettings> {
on: Hookable<Context>
notifyComponentUpdate: (instance?: ComponentInstance) => void
addTimelineLayer: (options: TimelineLayerOptions) => void
addTimelineEvent: (options: TimelineEventOptions) => void
addInspector: (options: CustomInspectorOptions) => void
sendInspectorTree: (inspectorId: string) => void
sendInspectorState: (inspectorId: string) => void
selectInspectorNode: (inspectorId: string, nodeId: string) => void
getComponentBounds: (instance: ComponentInstance) => Promise<ComponentBounds>
getComponentName: (instance: ComponentInstance) => Promise<string>
getComponentInstances: (app: App) => Promise<ComponentInstance[]>
highlightElement: (instance: ComponentInstance) => void
unhighlightElement: () => void
getSettings: (pluginId?: string) => TSettings
now: () => number
/**
* @private
*/
setSettings: (values: TSettings) => void
}
export interface AppRecord {
id: string
name: string
instanceMap: Map<string, ComponentInstance>
rootInstance: ComponentInstance
}
export interface TimelineLayerOptions<TData = any, TMeta = any> {
id: string
label: string
color: number
skipScreenshots?: boolean
groupsOnly?: boolean
ignoreNoDurationGroups?: boolean
screenshotOverlayRender?: (event: TimelineEvent<TData, TMeta> & ScreenshotOverlayEvent, ctx: ScreenshotOverlayRenderContext) => ScreenshotOverlayRenderResult | Promise<ScreenshotOverlayRenderResult>
}
export interface ScreenshotOverlayEvent {
layerId: string
renderMeta: any
}
export interface ScreenshotOverlayRenderContext<TData = any, TMeta = any> {
screenshot: ScreenshotData
events: (TimelineEvent<TData, TMeta> & ScreenshotOverlayEvent)[]
index: number
}
export type ScreenshotOverlayRenderResult = HTMLElement | string | false
export interface ScreenshotData {
time: number
}
export interface TimelineEventOptions {
layerId: string
event: TimelineEvent
all?: boolean
}
export interface TimelineEvent<TData = any, TMeta = any> {
time: number
data: TData
logType?: 'default' | 'warning' | 'error'
meta?: TMeta
groupId?: ID
title?: string
subtitle?: string
}
export interface TimelineMarkerOptions {
id: string
time: number
color: number
label: string
all?: boolean
}
export interface CustomInspectorOptions {
id: string
label: string
icon?: string
treeFilterPlaceholder?: string
stateFilterPlaceholder?: string
noSelectionText?: string
actions?: {
icon: string
tooltip?: string
action: () => void | Promise<void>
}[]
nodeActions?: {
icon: string
tooltip?: string
action: (nodeId: string) => void | Promise<void>
}[]
}
export interface CustomInspectorNode {
id: string
label: string
children?: CustomInspectorNode[]
tags?: InspectorNodeTag[]
}
export interface InspectorNodeTag {
label: string
textColor: number
backgroundColor: number
tooltip?: string
}
export interface CustomInspectorState {
[key: string]: (StateBase | Omit<ComponentState, 'type'>)[]
}
================================================
FILE: packages/api/src/api/app.ts
================================================
export type App = any // @TODO
================================================
FILE: packages/api/src/api/component.ts
================================================
import type { InspectorNodeTag } from './api.js'
import type { ID } from './util.js'
export type ComponentInstance = any // @TODO
export interface ComponentTreeNode {
uid: ID
id: string
name: string
renderKey: string | number
inactive: boolean
isFragment: boolean
hasChildren: boolean
children: ComponentTreeNode[]
domOrder?: number[]
consoleId?: string
isRouterView?: boolean
macthedRouteSegment?: string
tags: InspectorNodeTag[]
autoOpen: boolean
meta?: any
}
export interface InspectedComponentData {
id: string
name: string
file: string
state: ComponentState[]
functional?: boolean
}
export interface StateBase {
key: string
value: any
editable?: boolean
objectType?: 'ref' | 'reactive' | 'computed' | 'other'
raw?: string
}
export interface ComponentStateBase extends StateBase {
type: string
}
export interface ComponentPropState extends ComponentStateBase {
meta?: {
type: string
required: boolean
/** Vue 1 only */
mode?: 'default' | 'sync' | 'once'
}
}
export type ComponentBuiltinCustomStateTypes = 'function' | 'map' | 'set' | 'reference' | 'component' | 'component-definition' | 'router' | 'store'
export interface ComponentCustomState extends ComponentStateBase {
value: CustomState
}
export interface CustomState {
_custom: {
type: ComponentBuiltinCustomStateTypes | string
objectType?: string
display?: string
tooltip?: string
value?: any
abstract?: boolean
file?: string
uid?: number
readOnly?: boolean
/** Configure immediate child fields */
fields?: {
abstract?: boolean
}
id?: any
actions?: {
icon: string
tooltip?: string
action: () => void | Promise<void>
}[]
/** internal */
_reviveId?: number
}
}
export type ComponentState = ComponentStateBase | ComponentPropState | ComponentCustomState
export interface ComponentDevtoolsOptions {
hide?: boolean
}
================================================
FILE: packages/api/src/api/context.ts
================================================
import type { AppRecord } from './api.js'
export interface Context {
currentTab: string
currentAppRecord: AppRecord
}
================================================
FILE: packages/api/src/api/hooks.ts
================================================
import type { ComponentDevtoolsOptions, ComponentInstance, ComponentTreeNode, InspectedComponentData } from './component.js'
import type { App } from './app.js'
import type { CustomInspectorNode, CustomInspectorState, TimelineEvent } from './api.js'
// eslint-disable-next-line no-restricted-syntax
export const enum Hooks {
TRANSFORM_CALL = 'transformCall',
GET_APP_RECORD_NAME = 'getAppRecordName',
GET_APP_ROOT_INSTANCE = 'getAppRootInstance',
REGISTER_APPLICATION = 'registerApplication',
WALK_COMPONENT_TREE = 'walkComponentTree',
VISIT_COMPONENT_TREE = 'visitComponentTree',
WALK_COMPONENT_PARENTS = 'walkComponentParents',
INSPECT_COMPONENT = 'inspectComponent',
GET_COMPONENT_BOUNDS = 'getComponentBounds',
GET_COMPONENT_NAME = 'getComponentName',
GET_COMPONENT_INSTANCES = 'getComponentInstances',
GET_ELEMENT_COMPONENT = 'getElementComponent',
GET_COMPONENT_ROOT_ELEMENTS = 'getComponentRootElements',
EDIT_COMPONENT_STATE = 'editComponentState',
GET_COMPONENT_DEVTOOLS_OPTIONS = 'getAppDevtoolsOptions',
GET_COMPONENT_RENDER_CODE = 'getComponentRenderCode',
INSPECT_TIMELINE_EVENT = 'inspectTimelineEvent',
TIMELINE_CLEARED = 'timelineCleared',
GET_INSPECTOR_TREE = 'getInspectorTree',
GET_INSPECTOR_STATE = 'getInspectorState',
EDIT_INSPECTOR_STATE = 'editInspectorState',
SET_PLUGIN_SETTINGS = 'setPluginSettings',
}
export interface ComponentBounds {
left: number
top: number
width: number
height: number
}
export interface HookPayloads {
[Hooks.TRANSFORM_CALL]: {
callName: string
inArgs: any[]
outArgs: any[]
}
[Hooks.GET_APP_RECORD_NAME]: {
app: App
name: string
}
[Hooks.GET_APP_ROOT_INSTANCE]: {
app: App
root: ComponentInstance
}
[Hooks.REGISTER_APPLICATION]: {
app: App
}
[Hooks.WALK_COMPONENT_TREE]: {
componentInstance: ComponentInstance
componentTreeData: ComponentTreeNode[]
maxDepth: number
filter: string
recursively: boolean
}
[Hooks.VISIT_COMPONENT_TREE]: {
app: App
componentInstance: ComponentInstance
treeNode: ComponentTreeNode
filter: string
}
[Hooks.WALK_COMPONENT_PARENTS]: {
componentInstance: ComponentInstance
parentInstances: ComponentInstance[]
}
[Hooks.INSPECT_COMPONENT]: {
app: App
componentInstance: ComponentInstance
instanceData: InspectedComponentData
}
[Hooks.GET_COMPONENT_BOUNDS]: {
componentInstance: ComponentInstance
bounds: ComponentBounds
}
[Hooks.GET_COMPONENT_NAME]: {
componentInstance: ComponentInstance
name: string
}
[Hooks.GET_COMPONENT_INSTANCES]: {
app: App
componentInstances: ComponentInstance[]
}
[Hooks.GET_ELEMENT_COMPONENT]: {
element: HTMLElement | any
componentInstance: ComponentInstance
}
[Hooks.GET_COMPONENT_ROOT_ELEMENTS]: {
componentInstance: ComponentInstance
rootElements: (HTMLElement | any)[]
}
[Hooks.EDIT_COMPONENT_STATE]: {
app: App
componentInstance: ComponentInstance
path: string[]
type: string
state: EditStatePayload
set: (object: any, path?: string | (string[]), value?: any, cb?: (object: any, field: string, value: any) => void) => void
}
[Hooks.GET_COMPONENT_DEVTOOLS_OPTIONS]: {
componentInstance: ComponentInstance
options: ComponentDevtoolsOptions
}
[Hooks.GET_COMPONENT_RENDER_CODE]: {
componentInstance: ComponentInstance
code: string
}
[Hooks.INSPECT_TIMELINE_EVENT]: {
app: App
layerId: string
event: TimelineEvent
all?: boolean
data: any
}
[Hooks.TIMELINE_CLEARED]: Record<string, never>
[Hooks.GET_INSPECTOR_TREE]: {
app: App
inspectorId: string
filter: string
rootNodes: CustomInspectorNode[]
}
[Hooks.GET_INSPECTOR_STATE]: {
app: App
inspectorId: string
nodeId: string
state: CustomInspectorState
}
[Hooks.EDIT_INSPECTOR_STATE]: {
app: App
inspectorId: string
nodeId: string
path: string[]
type: string
state: EditStatePayload
set: (object: any, path?: string | (string[]), value?: any, cb?: (object: any, field: string, value: any) => void) => void
}
[Hooks.SET_PLUGIN_SETTINGS]: {
app: App
pluginId: string
key: string
newValue: any
oldValue: any
settings: any
}
}
export type EditStatePayload = {
value: any
newKey?: string | null
remove?: undefined | false
} | {
value?: undefined
newKey?: undefined
remove: true
}
export type HookHandler<TPayload, TContext> = (payload: TPayload, ctx: TContext) => void | Promise<void>
export interface Hookable<TContext> {
transformCall: (handler: HookHandler<HookPayloads[Hooks.TRANSFORM_CALL], TContext>) => any
getAppRecordName: (handler: HookHandler<HookPayloads[Hooks.GET_APP_RECORD_NAME], TContext>) => any
getAppRootInstance: (handler: HookHandler<HookPayloads[Hooks.GET_APP_ROOT_INSTANCE], TContext>) => any
registerApplication: (handler: HookHandler<HookPayloads[Hooks.REGISTER_APPLICATION], TContext>) => any
walkComponentTree: (handler: HookHandler<HookPayloads[Hooks.WALK_COMPONENT_TREE], TContext>) => any
visitComponentTree: (handler: HookHandler<HookPayloads[Hooks.VISIT_COMPONENT_TREE], TContext>) => any
walkComponentParents: (handler: HookHandler<HookPayloads[Hooks.WALK_COMPONENT_PARENTS], TContext>) => any
inspectComponent: (handler: HookHandler<HookPayloads[Hooks.INSPECT_COMPONENT], TContext>) => any
getComponentBounds: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_BOUNDS], TContext>) => any
getComponentName: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_NAME], TContext>) => any
getComponentInstances: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_INSTANCES], TContext>) => any
getElementComponent: (handler: HookHandler<HookPayloads[Hooks.GET_ELEMENT_COMPONENT], TContext>) => any
getComponentRootElements: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_ROOT_ELEMENTS], TContext>) => any
editComponentState: (handler: HookHandler<HookPayloads[Hooks.EDIT_COMPONENT_STATE], TContext>) => any
getComponentDevtoolsOptions: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_DEVTOOLS_OPTIONS], TContext>) => any
getComponentRenderCode: (handler: HookHandler<HookPayloads[Hooks.GET_COMPONENT_RENDER_CODE], TContext>) => any
inspectTimelineEvent: (handler: HookHandler<HookPayloads[Hooks.INSPECT_TIMELINE_EVENT], TContext>) => any
timelineCleared: (handler: HookHandler<HookPayloads[Hooks.TIMELINE_CLEARED], TContext>) => any
getInspectorTree: (handler: HookHandler<HookPayloads[Hooks.GET_INSPECTOR_TREE], TContext>) => any
getInspectorState: (handler: HookHandler<HookPayloads[Hooks.GET_INSPECTOR_STATE], TContext>) => any
editInspectorState: (handler: HookHandler<HookPayloads[Hooks.EDIT_INSPECTOR_STATE], TContext>) => any
setPluginSettings: (handler: HookHandler<HookPayloads[Hooks.SET_PLUGIN_SETTINGS], TContext>) => any
}
================================================
FILE: packages/api/src/api/index.ts
================================================
export * from './api.js'
export * from './app.js'
export * from './component.js'
export * from './context.js'
export * from './hooks.js'
export * from './util.js'
================================================
FILE: packages/api/src/api/util.ts
================================================
export type ID = number | string
export interface WithId {
id: ID
}
================================================
FILE: packages/api/src/const.ts
================================================
export const HOOK_SETUP = 'devtools-plugin:setup'
export const HOOK_PLUGIN_SETTINGS_SET = 'plugin:settings:set'
================================================
FILE: packages/api/src/env.ts
================================================
import type { ApiProxy } from './proxy.js'
import type { PluginDescriptor, SetupFunction } from './index.js'
export interface PluginQueueItem {
pluginDescriptor: PluginDescriptor
setupFn: SetupFunction
proxy?: ApiProxy
}
interface GlobalTarget {
__VUE_DEVTOOLS_PLUGINS__?: PluginQueueItem[]
__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__?: boolean
}
export function getDevtoolsGlobalHook(): any {
return (getTarget() as any).__VUE_DEVTOOLS_GLOBAL_HOOK__
}
export function getTarget(): GlobalTarget {
// @ts-expect-error navigator and windows are not available in all environments
return (typeof navigator !== 'undefined' && typeof window !== 'undefined')
? window
: typeof globalThis !== 'undefined'
? globalThis
: {}
}
export const isProxyAvailable = typeof Proxy === 'function'
================================================
FILE: packages/api/src/index.ts
================================================
import { getDevtoolsGlobalHook, getTarget, isProxyAvailable } from './env.js'
import { HOOK_SETUP } from './const.js'
import type { DevtoolsPluginApi } from './api/index.js'
import { ApiProxy } from './proxy.js'
import type { ExtractSettingsTypes, PluginDescriptor, PluginSettingsItem } from './plugin.js'
export * from './api/index.js'
export * from './plugin.js'
export * from './time.js'
export { PluginQueueItem } from './env.js'
// https://github.com/microsoft/TypeScript/issues/30680#issuecomment-752725353
type Cast<A, B> = A extends B ? A : B
type Narrowable =
| string
| number
| bigint
| boolean
type Narrow<A> = Cast<A, | []
| (A extends Narrowable ? A : never)
| ({ [K in keyof A]: Narrow<A[K]> })>
// Prevent properties not in PluginDescriptor
// We need this because of the `extends` in the generic TDescriptor
type Exact<C, T> = {
[K in keyof C]: K extends keyof T ? T[K] : never
}
export type SetupFunction<TSettings = any> = (api: DevtoolsPluginApi<TSettings>) => void
export function setupDevtoolsPlugin<
TDescriptor extends Exact<TDescriptor, PluginDescriptor>,
TSettings = ExtractSettingsTypes<TDescriptor extends { settings: infer S } ? S extends Record<string, PluginSettingsItem> ? S : Record<string, PluginSettingsItem> : Record<string, PluginSettingsItem>>,
>(pluginDescriptor: Narrow<TDescriptor>, setupFn: SetupFunction<TSettings>) {
const descriptor = pluginDescriptor as unknown as PluginDescriptor
const target = getTarget()
const hook = getDevtoolsGlobalHook()
const enableProxy = isProxyAvailable && descriptor.enableEarlyProxy
if (hook && (target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ || !enableProxy)) {
hook.emit(HOOK_SETUP, pluginDescriptor, setupFn)
}
else {
const proxy = enableProxy ? new ApiProxy(descriptor, hook) : null
const list = target.__VUE_DEVTOOLS_PLUGINS__ = target.__VUE_DEVTOOLS_PLUGINS__ || []
list.push({
pluginDescriptor: descriptor,
setupFn,
proxy,
})
if (proxy) {
setupFn(proxy.proxiedTarget as DevtoolsPluginApi<TSettings>)
}
}
}
================================================
FILE: packages/api/src/plugin.ts
================================================
import type { App } from './api/index.js'
export interface PluginDescriptor {
id: string
label: string
app: App
packageName?: string
homepage?: string
componentStateTypes?: string[]
logo?: string
disableAppScope?: boolean
disablePluginScope?: boolean
/**
* Run the plugin setup and expose the api even if the devtools is not opened yet.
* Useful to record timeline events early.
*/
enableEarlyProxy?: boolean
settings?: Record<string, PluginSettingsItem>
}
export type PluginSettingsItem = {
label: string
description?: string
} & ({
type: 'boolean'
defaultValue: boolean
} | {
type: 'choice'
defaultValue: string | number
options: { value: string | number, label: string }[]
component?: 'select' | 'button-group'
} | {
type: 'text'
defaultValue: string
})
type InferSettingsType<
T extends PluginSettingsItem,
> = [T] extends [{ type: 'boolean' }]
? boolean
: [T] extends [{ type: 'choice' }]
? T['options'][number]['value']
: [T] extends [{ type: 'text' }]
? string
: unknown
export type ExtractSettingsTypes<
O extends Record<string, PluginSettingsItem>,
> = {
[K in keyof O]: InferSettingsType<O[K]>
}
================================================
FILE: packages/api/src/proxy.ts
================================================
import type { Context, DevtoolsPluginApi, Hookable } from './api/index.js'
import type { PluginDescriptor } from './plugin.js'
import { HOOK_PLUGIN_SETTINGS_SET } from './const.js'
import { now } from './time.js'
interface QueueItem {
method: string
args: any[]
resolve?: (value?: any) => void
}
export class ApiProxy<TTarget extends DevtoolsPluginApi<any> = DevtoolsPluginApi<any>> {
target: TTarget | null
targetQueue: QueueItem[]
proxiedTarget: TTarget
onQueue: QueueItem[]
proxiedOn: Hookable<Context>
plugin: PluginDescriptor
hook: any
fallbacks: Record<string, any>
constructor(plugin: PluginDescriptor, hook: any) {
this.target = null
this.targetQueue = []
this.onQueue = []
this.plugin = plugin
this.hook = hook
const defaultSettings: Record<string, any> = {}
if (plugin.settings) {
for (const id in plugin.settings) {
const item = plugin.settings[id]
defaultSettings[id] = item.defaultValue
}
}
const localSettingsSaveId = `__vue-devtools-plugin-settings__${plugin.id}`
let currentSettings = Object.assign({}, defaultSettings)
try {
const raw = localStorage.getItem(localSettingsSaveId)
const data = JSON.parse(raw)
Object.assign(currentSettings, data)
}
catch (e) {
// noop
}
this.fallbacks = {
getSettings() {
return currentSettings
},
setSettings(value) {
try {
localStorage.setItem(localSettingsSaveId, JSON.stringify(value))
}
catch (e) {
// noop
}
currentSettings = value
},
now() {
return now()
},
}
if (hook) {
hook.on(HOOK_PLUGIN_SETTINGS_SET, (pluginId, value) => {
if (pluginId === this.plugin.id) {
this.fallbacks.setSettings(value)
}
})
}
this.proxiedOn = new Proxy({} as Hookable<Context>, {
get: (_target, prop: string) => {
if (this.target) {
return this.target.on[prop]
}
else {
return (...args) => {
this.onQueue.push({
method: prop,
args,
})
}
}
},
})
this.proxiedTarget = new Proxy({} as TTarget, {
get: (_target, prop: string) => {
if (this.target) {
return this.target[prop]
}
else if (prop === 'on') {
return this.proxiedOn
}
else if (Object.keys(this.fallbacks).includes(prop)) {
return (...args) => {
this.targetQueue.push({
method: prop,
args,
resolve: () => { /* noop */ },
})
return this.fallbacks[prop](...args)
}
}
else {
return (...args) => {
return new Promise((resolve) => {
this.targetQueue.push({
method: prop,
args,
resolve,
})
})
}
}
},
})
}
async setRealTarget(target: TTarget) {
this.target = target
for (const item of this.onQueue) {
this.target.on[item.method](...item.args)
}
for (const item of this.targetQueue) {
item.resolve(await this.target[item.method](...item.args))
}
}
}
================================================
FILE: packages/api/src/time.ts
================================================
let supported: boolean
let perf: Performance
export function isPerformanceSupported() {
if (supported !== undefined) {
return supported
}
if (typeof window !== 'undefined' && window.performance) {
supported = true
perf = window.performance
}
else if (typeof globalThis !== 'undefined' && (globalThis as any).perf_hooks?.performance) {
supported = true
perf = (globalThis as any).perf_hooks.performance
}
else {
supported = false
}
return supported
}
export function now() {
return isPerformanceSupported() ? perf.now() : Date.now()
}
================================================
FILE: packages/api/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2017",
"lib": ["ESNext", "DOM"],
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"types": ["node", "webpack-env"],
"strictBindCallApply": true,
"strictFunctionTypes": true,
"alwaysStrict": true,
// Strict
"noImplicitAny": false,
"noImplicitThis": true,
"removeComments": false,
"sourceMap": false,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"preserveWatchOutput": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
================================================
FILE: packages/app-backend-api/package.json
================================================
{
"name": "@vue-devtools/app-backend-api",
"version": "0.0.0",
"private": true,
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"scripts": {
"build": "rimraf lib && yarn ts",
"build:watch": "yarn ts -w",
"ts": "tsc -d -outDir lib"
},
"dependencies": {
"@vue-devtools/shared-utils": "^0.0.0",
"@vue/devtools-api": "^6.0.0-beta.1"
},
"devDependencies": {
"@types/node": "^20.11.16",
"@types/webpack-env": "^1.15.1",
"typescript": "^5.3.3"
}
}
================================================
FILE: packages/app-backend-api/src/api.ts
================================================
import type {
Bridge,
} from '@vue-devtools/shared-utils'
import {
HookEvents,
PluginPermission,
StateEditor,
getPluginDefaultSettings,
getPluginSettings,
hasPluginPermission,
setPluginSettings,
} from '@vue-devtools/shared-utils'
import type {
App,
ComponentDevtoolsOptions,
ComponentInstance,
ComponentTreeNode,
CustomInspectorOptions,
DevtoolsPluginApi,
EditStatePayload,
HookPayloads,
TimelineEventOptions,
TimelineLayerOptions,
WithId,
} from '@vue/devtools-api'
import {
Hooks,
now,
} from '@vue/devtools-api'
import { DevtoolsHookable } from './hooks'
import type { BackendContext } from './backend-context'
import type { Plugin } from './plugin'
import type { DevtoolsBackend } from './backend'
import type { AppRecord } from './app-record'
const pluginOn: DevtoolsHookable[] = []
export class DevtoolsApi {
bridge: Bridge
ctx: BackendContext
backend: DevtoolsBackend
on: DevtoolsHookable
stateEditor: StateEditor = new StateEditor()
constructor(backend: DevtoolsBackend, ctx: BackendContext) {
this.backend = backend
this.ctx = ctx
this.bridge = ctx.bridge
this.on = new DevtoolsHookable(ctx)
}
async callHook<T extends Hooks>(eventType: T, payload: HookPayloads[T], ctx: BackendContext = this.ctx) {
payload = await this.on.callHandlers(eventType, payload, ctx)
for (const on of pluginOn) {
payload = await on.callHandlers(eventType, payload, ctx)
}
return payload
}
async transformCall(callName: string, ...args) {
const payload = await this.callHook(Hooks.TRANSFORM_CALL, {
callName,
inArgs: args,
outArgs: args.slice(),
})
return payload.outArgs
}
async getAppRecordName(app: App, defaultName: string): Promise<string> {
const payload = await this.callHook(Hooks.GET_APP_RECORD_NAME, {
app,
name: null,
})
if (payload.name) {
return payload.name
}
else {
return `App ${defaultName}`
}
}
async getAppRootInstance(app: App) {
const payload = await this.callHook(Hooks.GET_APP_ROOT_INSTANCE, {
app,
root: null,
})
return payload.root
}
async registerApplication(app: App) {
await this.callHook(Hooks.REGISTER_APPLICATION, {
app,
})
}
async walkComponentTree(instance: ComponentInstance, maxDepth = -1, filter: string = null, recursively = false) {
const payload = await this.callHook(Hooks.WALK_COMPONENT_TREE, {
componentInstance: instance,
componentTreeData: null,
maxDepth,
filter,
recursively,
})
return payload.componentTreeData
}
async visitComponentTree(instance: ComponentInstance, treeNode: ComponentTreeNode, filter: string = null, app: App) {
const payload = await this.callHook(Hooks.VISIT_COMPONENT_TREE, {
app,
componentInstance: instance,
treeNode,
filter,
})
return payload.treeNode
}
async walkComponentParents(instance: ComponentInstance) {
const payload = await this.callHook(Hooks.WALK_COMPONENT_PARENTS, {
componentInstance: instance,
parentInstances: [],
})
return payload.parentInstances
}
async inspectComponent(instance: ComponentInstance, app: App) {
const payload = await this.callHook(Hooks.INSPECT_COMPONENT, {
app,
componentInstance: instance,
instanceData: null,
})
return payload.instanceData
}
async getComponentBounds(instance: ComponentInstance) {
const payload = await this.callHook(Hooks.GET_COMPONENT_BOUNDS, {
componentInstance: instance,
bounds: null,
})
return payload.bounds
}
async getComponentName(instance: ComponentInstance) {
const payload = await this.callHook(Hooks.GET_COMPONENT_NAME, {
componentInstance: instance,
name: null,
})
return payload.name
}
async getComponentInstances(app: App) {
const payload = await this.callHook(Hooks.GET_COMPONENT_INSTANCES, {
app,
componentInstances: [],
})
return payload.componentInstances
}
async getElementComponent(element: HTMLElement | any) {
const payload = await this.callHook(Hooks.GET_ELEMENT_COMPONENT, {
element,
componentInstance: null,
})
return payload.componentInstance
}
async getComponentRootElements(instance: ComponentInstance) {
const payload = await this.callHook(Hooks.GET_COMPONENT_ROOT_ELEMENTS, {
componentInstance: instance,
rootElements: [],
})
return payload.rootElements
}
async editComponentState(instance: ComponentInstance, dotPath: string, type: string, state: EditStatePayload, app: App) {
const arrayPath = dotPath.split('.')
const payload = await this.callHook(Hooks.EDIT_COMPONENT_STATE, {
app,
componentInstance: instance,
path: arrayPath,
type,
state,
set: (object, path = arrayPath, value = state.value, cb?) => this.stateEditor.set(object, path, value, cb || this.stateEditor.createDefaultSetCallback(state)),
})
return payload.componentInstance
}
async getComponentDevtoolsOptions(instance: ComponentInstance): Promise<ComponentDevtoolsOptions> {
const payload = await this.callHook(Hooks.GET_COMPONENT_DEVTOOLS_OPTIONS, {
componentInstance: instance,
options: null,
})
return payload.options || {}
}
async getComponentRenderCode(instance: ComponentInstance): Promise<{
code: string
}> {
const payload = await this.callHook(Hooks.GET_COMPONENT_RENDER_CODE, {
componentInstance: instance,
code: null,
})
return {
code: payload.code,
}
}
async inspectTimelineEvent(eventData: TimelineEventOptions & WithId, app: App) {
const payload = await this.callHook(Hooks.INSPECT_TIMELINE_EVENT, {
event: eventData.event,
layerId: eventData.layerId,
app,
data: eventData.event.data,
all: eventData.all,
})
return payload.data
}
async clearTimeline() {
await this.callHook(Hooks.TIMELINE_CLEARED, {})
}
async getInspectorTree(inspectorId: string, app: App, filter: string) {
const payload = await this.callHook(Hooks.GET_INSPECTOR_TREE, {
inspectorId,
app,
filter,
rootNodes: [],
})
return payload.rootNodes
}
async getInspectorState(inspectorId: string, app: App, nodeId: string) {
const payload = await this.callHook(Hooks.GET_INSPECTOR_STATE, {
inspectorId,
app,
nodeId,
state: null,
})
return payload.state
}
async editInspectorState(inspectorId: string, app: App, nodeId: string, dotPath: string, type: string, state: EditStatePayload) {
const arrayPath = dotPath.split('.')
await this.callHook(Hooks.EDIT_INSPECTOR_STATE, {
inspectorId,
app,
nodeId,
path: arrayPath,
type,
state,
set: (object, path = arrayPath, value = state.value, cb?) => this.stateEditor.set(object, path, value, cb || this.stateEditor.createDefaultSetCallback(state)),
})
}
now() {
return now()
}
}
export class DevtoolsPluginApiInstance<TSettings = any> implements DevtoolsPluginApi<TSettings> {
bridge: Bridge
ctx: BackendContext
plugin: Plugin
appRecord: AppRecord
backendApi: DevtoolsApi
on: DevtoolsHookable
private defaultSettings: TSettings
constructor(plugin: Plugin, appRecord: AppRecord, ctx: BackendContext) {
this.bridge = ctx.bridge
this.ctx = ctx
this.plugin = plugin
this.appRecord = appRecord
this.backendApi = appRecord.backend.api
this.defaultSettings = getPluginDefaultSettings(plugin.descriptor.settings)
this.on = new DevtoolsHookable(ctx, plugin)
pluginOn.push(this.on)
}
// Plugin API
async notifyComponentUpdate(instance: ComponentInstance = null) {
if (!this.enabled || !this.hasPermission(PluginPermission.COMPONENTS)) {
return
}
if (instance) {
this.ctx.hook.emit(HookEvents.COMPONENT_UPDATED, ...await this.backendApi.transformCall(HookEvents.COMPONENT_UPDATED, instance))
}
else {
this.ctx.hook.emit(HookEvents.COMPONENT_UPDATED)
}
}
addTimelineLayer(options: TimelineLayerOptions) {
if (!this.enabled || !this.hasPermission(PluginPermission.TIMELINE)) {
return false
}
this.ctx.hook.emit(HookEvents.TIMELINE_LAYER_ADDED, options, this.plugin)
return true
}
addTimelineEvent(options: TimelineEventOptions) {
if (!this.enabled || !this.hasPermission(PluginPermission.TIMELINE)) {
return false
}
this.ctx.hook.emit(HookEvents.TIMELINE_EVENT_ADDED, options, this.plugin)
return true
}
addInspector(options: CustomInspectorOptions) {
if (!this.enabled || !this.hasPermission(PluginPermission.CUSTOM_INSPECTOR)) {
return false
}
this.ctx.hook.emit(HookEvents.CUSTOM_INSPECTOR_ADD, options, this.plugin)
return true
}
sendInspectorTree(inspectorId: string) {
if (!this.enabled || !this.hasPermission(PluginPermission.CUSTOM_INSPECTOR)) {
return false
}
this.ctx.hook.emit(HookEvents.CUSTOM_INSPECTOR_SEND_TREE, inspectorId, this.plugin)
return true
}
sendInspectorState(inspectorId: string) {
if (!this.enabled || !this.hasPermission(PluginPermission.CUSTOM_INSPECTOR)) {
return false
}
this.ctx.hook.emit(HookEvents.CUSTOM_INSPECTOR_SEND_STATE, inspectorId, this.plugin)
return true
}
selectInspectorNode(inspectorId: string, nodeId: string) {
if (!this.enabled || !this.hasPermission(PluginPermission.CUSTOM_INSPECTOR)) {
return false
}
this.ctx.hook.emit(HookEvents.CUSTOM_INSPECTOR_SELECT_NODE, inspectorId, nodeId, this.plugin)
return true
}
getComponentBounds(instance: ComponentInstance) {
return this.backendApi.getComponentBounds(instance)
}
getComponentName(instance: ComponentInstance) {
return this.backendApi.getComponentName(instance)
}
getComponentInstances(app: App) {
return this.backendApi.getComponentInstances(app)
}
highlightElement(instance: ComponentInstance) {
if (!this.enabled || !this.hasPermission(PluginPermission.COMPONENTS)) {
return false
}
this.ctx.hook.emit(HookEvents.COMPONENT_HIGHLIGHT, instance.__VUE_DEVTOOLS_UID__, this.plugin)
return true
}
unhighlightElement() {
if (!this.enabled || !this.hasPermission(PluginPermission.COMPONENTS)) {
return false
}
this.ctx.hook.emit(HookEvents.COMPONENT_UNHIGHLIGHT, this.plugin)
return true
}
getSettings(pluginId?: string) {
return getPluginSettings(pluginId ?? this.plugin.descriptor.id, this.defaultSettings)
}
setSettings(value: TSettings, pluginId?: string) {
setPluginSettings(pluginId ?? this.plugin.descriptor.id, value)
}
now() {
return now()
}
private get enabled() {
return hasPluginPermission(this.plugin.descriptor.id, PluginPermission.ENABLED)
}
private hasPermission(permission: PluginPermission) {
return hasPluginPermission(this.plugin.descriptor.id, permission)
}
}
================================================
FILE: packages/app-backend-api/src/app-record.ts
================================================
import type { App, ComponentInstance } from '@vue/devtools-api'
import type { DevtoolsBackend } from './backend'
export interface AppRecordOptions {
app: App
version: string
types: { [key: string]: string | symbol }
meta?: any
}
export interface AppRecord {
id: string
name: string
options: AppRecordOptions
backend: DevtoolsBackend
lastInspectedComponentId: string
instanceMap: Map<string, ComponentInstance>
rootInstance: ComponentInstance
componentFilter?: string
perfGroupIds: Map<string, { groupId: number, time: number }>
iframe: string
meta: any
missingInstanceQueue: Set<string>
}
/**
* Used in the frontend
*/
export interface SimpleAppRecord {
id: string
name: string
version: string
iframe: string
}
================================================
FILE: packages/app-backend-api/src/backend-context.ts
================================================
import type { Bridge } from '@vue-devtools/shared-utils'
import type {
CustomInspectorOptions,
ID,
TimelineEventOptions,
TimelineLayerOptions,
TimelineMarkerOptions,
WithId,
} from '@vue/devtools-api'
import type { AppRecord } from './app-record'
import type { Plugin } from './plugin'
import type { DevtoolsHook } from './global-hook'
import type { DevtoolsBackend } from './backend'
export interface BackendContext {
bridge: Bridge
hook: DevtoolsHook
backends: DevtoolsBackend[]
appRecords: AppRecord[]
currentTab: string
currentAppRecord: AppRecord
currentInspectedComponentId: string
plugins: Plugin[]
currentPlugin: Plugin
timelineLayers: TimelineLayer[]
nextTimelineEventId: number
timelineEventMap: Map<ID, TimelineEventOptions & WithId>
perfUniqueGroupId: number
customInspectors: CustomInspector[]
timelineMarkers: TimelineMarker[]
}
export interface TimelineLayer extends TimelineLayerOptions {
appRecord: AppRecord | null
plugin: Plugin
events: (TimelineEventOptions & WithId)[]
}
export interface TimelineMarker extends TimelineMarkerOptions {
appRecord: AppRecord | null
}
export interface CustomInspector extends CustomInspectorOptions {
appRecord: AppRecord
plugin: Plugin
treeFilter: string
selectedNodeId: string
}
export interface CreateBackendContextOptions {
bridge: Bridge
hook: DevtoolsHook
}
export function createBackendContext(options: CreateBackendContextOptions): BackendContext {
return {
bridge: options.bridge,
hook: options.hook,
backends: [],
appRecords: [],
currentTab: null,
currentAppRecord: null,
currentInspectedComponentId: null,
plugins: [],
currentPlugin: null,
timelineLayers: [],
nextTimelineEventId: 0,
timelineEventMap: new Map(),
perfUniqueGroupId: 0,
customInspectors: [],
timelineMarkers: [],
}
}
================================================
FILE: packages/app-backend-api/src/backend.ts
================================================
import type { AppRecord } from './app-record'
import { DevtoolsApi } from './api'
import type { BackendContext } from './backend-context'
export enum BuiltinBackendFeature {
/**
* @deprecated
*/
FLUSH = 'flush',
}
export interface DevtoolsBackendOptions {
frameworkVersion: 1 | 2 | 3
features: (BuiltinBackendFeature | string)[]
setup: (api: DevtoolsApi) => void
setupApp?: (api: DevtoolsApi, app: AppRecord) => void
}
export function defineBackend(options: DevtoolsBackendOptions) {
return options
}
export interface DevtoolsBackend {
options: DevtoolsBackendOptions
api: DevtoolsApi
}
export function createBackend(options: DevtoolsBackendOptions, ctx: BackendContext): DevtoolsBackend {
const backend: DevtoolsBackend = {
options,
api: null,
}
backend.api = new DevtoolsApi(backend, ctx)
options.setup(backend.api)
return backend
}
================================================
FILE: packages/app-backend-api/src/global-hook.ts
================================================
import type { AppRecordOptions } from './app-record'
export interface DevtoolsHook {
emit: (event: string, ...payload: any[]) => void
on: <T extends Function>(event: string, handler: T) => void
once: <T extends Function>(event: string, handler: T) => void
off: <T extends Function>(event?: string, handler?: T) => void
Vue?: any
apps: AppRecordOptions[]
}
================================================
FILE: packages/app-backend-api/src/hooks.ts
================================================
import { PluginPermission, SharedData, hasPluginPermission } from '@vue-devtools/shared-utils'
import type { HookHandler, HookPayloads, Hookable } from '@vue/devtools-api'
import { Hooks } from '@vue/devtools-api'
import type { BackendContext } from './backend-context'
import type { Plugin } from './plugin'
type Handler<TPayload> = HookHandler<TPayload, BackendContext>
export interface HookHandlerData<THandlerPayload> {
handler: Handler<THandlerPayload>
plugin: Plugin
}
export class DevtoolsHookable implements Hookable<BackendContext> {
private handlers: Partial<{ [eventType in Hooks]: HookHandlerData<HookPayloads[eventType]>[] }> = {}
private ctx: BackendContext
private plugin: Plugin
constructor(ctx: BackendContext, plugin: Plugin = null) {
this.ctx = ctx
this.plugin = plugin
}
private hook<T extends Hooks>(eventType: T, handler: Handler<HookPayloads[T]>, pluginPermision: PluginPermission = null) {
const handlers = (this.handlers[eventType] = this.handlers[eventType] || []) as HookHandlerData<HookPayloads[T]>[]
if (this.plugin) {
const originalHandler = handler
handler = (...args) => {
// Plugin permission
if (!hasPluginPermission(this.plugin.descriptor.id, PluginPermission.ENABLED)
|| (pluginPermision && !hasPluginPermission(this.plugin.descriptor.id, pluginPermision))
) { return }
// App scope
if (!this.plugin.descriptor.disableAppScope
&& this.ctx.currentAppRecord?.options.app !== this.plugin.descriptor.app) { return }
// Plugin scope
if (!this.plugin.descriptor.disablePluginScope
&& (args[0] as any).pluginId != null && (args[0] as any).pluginId !== this.plugin.descriptor.id) { return }
return originalHandler(...args)
}
}
handlers.push({
handler,
plugin: this.ctx.currentPlugin,
})
}
async callHandlers<T extends Hooks>(eventType: T, payload: HookPayloads[T], ctx: BackendContext) {
if (this.handlers[eventType]) {
const handlers = this.handlers[eventType] as HookHandlerData<HookPayloads[T]>[]
for (let i = 0; i < handlers.length; i++) {
const { handler, plugin } = handlers[i]
try {
await handler(payload, ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(`An error occurred in hook '${eventType}'${plugin ? ` registered by plugin '${plugin.descriptor.id}'` : ''} with payload:`, payload)
console.error(e)
}
}
}
}
return payload
}
transformCall(handler: Handler<HookPayloads[Hooks.TRANSFORM_CALL]>) {
this.hook(Hooks.TRANSFORM_CALL, handler)
}
getAppRecordName(handler: Handler<HookPayloads[Hooks.GET_APP_RECORD_NAME]>) {
this.hook(Hooks.GET_APP_RECORD_NAME, handler)
}
getAppRootInstance(handler: Handler<HookPayloads[Hooks.GET_APP_ROOT_INSTANCE]>) {
this.hook(Hooks.GET_APP_ROOT_INSTANCE, handler)
}
registerApplication(handler: Handler<HookPayloads[Hooks.REGISTER_APPLICATION]>) {
this.hook(Hooks.REGISTER_APPLICATION, handler)
}
walkComponentTree(handler: Handler<HookPayloads[Hooks.WALK_COMPONENT_TREE]>) {
this.hook(Hooks.WALK_COMPONENT_TREE, handler, PluginPermission.COMPONENTS)
}
visitComponentTree(handler: Handler<HookPayloads[Hooks.VISIT_COMPONENT_TREE]>) {
this.hook(Hooks.VISIT_COMPONENT_TREE, handler, PluginPermission.COMPONENTS)
}
walkComponentParents(handler: Handler<HookPayloads[Hooks.WALK_COMPONENT_PARENTS]>) {
this.hook(Hooks.WALK_COMPONENT_PARENTS, handler, PluginPermission.COMPONENTS)
}
inspectComponent(handler: Handler<HookPayloads[Hooks.INSPECT_COMPONENT]>) {
this.hook(Hooks.INSPECT_COMPONENT, handler, PluginPermission.COMPONENTS)
}
getComponentBounds(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_BOUNDS]>) {
this.hook(Hooks.GET_COMPONENT_BOUNDS, handler, PluginPermission.COMPONENTS)
}
getComponentName(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_NAME]>) {
this.hook(Hooks.GET_COMPONENT_NAME, handler, PluginPermission.COMPONENTS)
}
getComponentInstances(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_INSTANCES]>) {
this.hook(Hooks.GET_COMPONENT_INSTANCES, handler, PluginPermission.COMPONENTS)
}
getElementComponent(handler: Handler<HookPayloads[Hooks.GET_ELEMENT_COMPONENT]>) {
this.hook(Hooks.GET_ELEMENT_COMPONENT, handler, PluginPermission.COMPONENTS)
}
getComponentRootElements(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_ROOT_ELEMENTS]>) {
this.hook(Hooks.GET_COMPONENT_ROOT_ELEMENTS, handler, PluginPermission.COMPONENTS)
}
editComponentState(handler: Handler<HookPayloads[Hooks.EDIT_COMPONENT_STATE]>) {
this.hook(Hooks.EDIT_COMPONENT_STATE, handler, PluginPermission.COMPONENTS)
}
getComponentDevtoolsOptions(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_DEVTOOLS_OPTIONS]>) {
this.hook(Hooks.GET_COMPONENT_DEVTOOLS_OPTIONS, handler, PluginPermission.COMPONENTS)
}
getComponentRenderCode(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_RENDER_CODE]>) {
this.hook(Hooks.GET_COMPONENT_RENDER_CODE, handler, PluginPermission.COMPONENTS)
}
inspectTimelineEvent(handler: Handler<HookPayloads[Hooks.INSPECT_TIMELINE_EVENT]>) {
this.hook(Hooks.INSPECT_TIMELINE_EVENT, handler, PluginPermission.TIMELINE)
}
timelineCleared(handler: Handler<HookPayloads[Hooks.TIMELINE_CLEARED]>) {
this.hook(Hooks.TIMELINE_CLEARED, handler, PluginPermission.TIMELINE)
}
getInspectorTree(handler: Handler<HookPayloads[Hooks.GET_INSPECTOR_TREE]>) {
this.hook(Hooks.GET_INSPECTOR_TREE, handler, PluginPermission.CUSTOM_INSPECTOR)
}
getInspectorState(handler: Handler<HookPayloads[Hooks.GET_INSPECTOR_STATE]>) {
this.hook(Hooks.GET_INSPECTOR_STATE, handler, PluginPermission.CUSTOM_INSPECTOR)
}
editInspectorState(handler: Handler<HookPayloads[Hooks.EDIT_INSPECTOR_STATE]>) {
this.hook(Hooks.EDIT_INSPECTOR_STATE, handler, PluginPermission.CUSTOM_INSPECTOR)
}
setPluginSettings(handler: Handler<HookPayloads[Hooks.SET_PLUGIN_SETTINGS]>) {
this.hook(Hooks.SET_PLUGIN_SETTINGS, handler)
}
}
================================================
FILE: packages/app-backend-api/src/index.ts
================================================
export * from './api'
export * from './app-record'
export * from './backend'
export * from './backend-context'
export * from './global-hook'
export * from './hooks'
export * from './plugin'
================================================
FILE: packages/app-backend-api/src/plugin.ts
================================================
import type { PluginDescriptor, SetupFunction } from '@vue/devtools-api'
export interface Plugin {
descriptor: PluginDescriptor
setupFn: SetupFunction
error: Error
}
================================================
FILE: packages/app-backend-api/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2019",
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"types": [
"node",
"webpack-env"
],
"strictBindCallApply": true,
"strictFunctionTypes": true,
"alwaysStrict": true,
// Strict
"noImplicitAny": false,
"noImplicitThis": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"preserveWatchOutput": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}
================================================
FILE: packages/app-backend-core/package.json
================================================
{
"name": "@vue-devtools/app-backend-core",
"version": "0.0.0",
"private": true,
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"scripts": {
"build": "rimraf lib && yarn ts",
"build:watch": "yarn ts -w",
"ts": "tsc -d -outDir lib"
},
"dependencies": {
"@vue-devtools/app-backend-api": "^0.0.0",
"@vue-devtools/app-backend-vue1": "^0.0.0",
"@vue-devtools/app-backend-vue2": "^0.0.0",
"@vue-devtools/app-backend-vue3": "^0.0.0",
"@vue-devtools/shared-utils": "^0.0.0",
"@vue/devtools-api": "^6.0.0-beta.1",
"lodash": "^4.17.21",
"speakingurl": "^14.0.1"
},
"devDependencies": {
"@types/node": "^20.11.16",
"@types/webpack-env": "^1.15.1",
"typescript": "^5.3.3"
}
}
================================================
FILE: packages/app-backend-core/src/app.ts
================================================
import type {
AppRecord,
AppRecordOptions,
BackendContext,
DevtoolsBackend,
SimpleAppRecord,
} from '@vue-devtools/app-backend-api'
import { BridgeEvents, SharedData, isBrowser } from '@vue-devtools/shared-utils'
import type { App } from '@vue/devtools-api'
import slug from 'speakingurl'
import { JobQueue } from './util/queue'
import { scan } from './legacy/scan'
import { addBuiltinLayers, removeLayersForApp } from './timeline'
import { availableBackends, getBackend } from './backend'
import { hook } from './global-hook.js'
import { sendComponentTreeData, sendSelectedComponentData } from './component.js'
const jobs = new JobQueue()
let recordId = 0
type AppRecordResolver = (record: AppRecord) => void | Promise<void>
const appRecordPromises = new Map<App, AppRecordResolver[]>()
export async function registerApp(options: AppRecordOptions, ctx: BackendContext) {
return jobs.queue('regiserApp', () => registerAppJob(options, ctx))
}
async function registerAppJob(options: AppRecordOptions, ctx: BackendContext) {
// Dedupe
if (ctx.appRecords.find(a => a.options.app === options.app)) {
return
}
if (!options.version) {
throw new Error('[Vue Devtools] Vue version not found')
}
// Find correct backend
const baseFrameworkVersion = Number.parseInt(options.version.substring(0, options.version.indexOf('.')))
for (let i = 0; i < availableBackends.length; i++) {
const backendOptions = availableBackends[i]
if (backendOptions.frameworkVersion === baseFrameworkVersion) {
// Enable backend if it's not enabled
const backend = getBackend(backendOptions, ctx)
await createAppRecord(options, backend, ctx)
break
}
}
}
async function createAppRecord(options: AppRecordOptions, backend: DevtoolsBackend, ctx: BackendContext) {
const rootInstance = await backend.api.getAppRootInstance(options.app)
if (rootInstance) {
if ((await backend.api.getComponentDevtoolsOptions(rootInstance)).hide) {
options.app._vueDevtools_hidden_ = true
return
}
recordId++
const name = await backend.api.getAppRecordName(options.app, recordId.toString())
const id = getAppRecordId(options.app, slug(name))
const [el]: HTMLElement[] = await backend.api.getComponentRootElements(rootInstance)
const instanceMapRaw = new Map<string, any>()
const record: AppRecord = {
id,
name,
options,
backend,
lastInspectedComponentId: null,
instanceMap: new Proxy(instanceMapRaw, {
get(target, key: string) {
if (key === 'set') {
return (instanceId: string, instance: any) => {
target.set(instanceId, instance)
// The component was requested by the frontend before it was registered
if (record.missingInstanceQueue.has(instanceId)) {
record.missingInstanceQueue.delete(instanceId)
if (ctx.currentAppRecord === record) {
sendComponentTreeData(record, instanceId, record.componentFilter, null, false, ctx)
if (record.lastInspectedComponentId === instanceId) {
sendSelectedComponentData(record, instanceId, ctx)
}
}
}
}
}
return target[key].bind(target)
},
}),
rootInstance,
perfGroupIds: new Map(),
iframe: isBrowser && document !== el?.ownerDocument ? el?.ownerDocument?.location?.pathname : null,
meta: options.meta ?? {},
missingInstanceQueue: new Set(),
}
options.app.__VUE_DEVTOOLS_APP_RECORD__ = record
const rootId = `${record.id}:root`
record.instanceMap.set(rootId, record.rootInstance)
record.rootInstance.__VUE_DEVTOOLS_UID__ = rootId
// Timeline
addBuiltinLayers(record, ctx)
ctx.appRecords.push(record)
if (backend.options.setupApp) {
backend.options.setupApp(backend.api, record)
}
await backend.api.registerApplication(options.app)
ctx.bridge.send(BridgeEvents.TO_FRONT_APP_ADD, {
appRecord: mapAppRecord(record),
})
// Auto select first app
if (ctx.currentAppRecord == null) {
await selectApp(record, ctx)
}
if (appRecordPromises.has(options.app)) {
for (const r of appRecordPromises.get(options.app)) {
await r(record)
}
}
}
else if (SharedData.debugInfo) {
console.warn('[Vue devtools] No root instance found for app, it might have been unmounted', options.app)
}
}
export async function selectApp(record: AppRecord, ctx: BackendContext) {
ctx.currentAppRecord = record
ctx.currentInspectedComponentId = record.lastInspectedComponentId
ctx.bridge.send(BridgeEvents.TO_FRONT_APP_SELECTED, {
id: record.id,
lastInspectedComponentId: record.lastInspectedComponentId,
})
}
export function mapAppRecord(record: AppRecord): SimpleAppRecord {
return {
id: record.id,
name: record.name,
version: record.options.version,
iframe: record.iframe,
}
}
const appIds = new Set()
export function getAppRecordId(app, defaultId?: string): string {
if (app.__VUE_DEVTOOLS_APP_RECORD_ID__ != null) {
return app.__VUE_DEVTOOLS_APP_RECORD_ID__
}
let id = defaultId ?? (recordId++).toString()
if (defaultId && appIds.has(id)) {
let count = 1
while (appIds.has(`${defaultId}_${count}`)) {
count++
}
id = `${defaultId}_${count}`
}
appIds.add(id)
app.__VUE_DEVTOOLS_APP_RECORD_ID__ = id
return id
}
export async function getAppRecord(app: any, ctx: BackendContext): Promise<AppRecord> {
const record = app.__VUE_DEVTOOLS_APP_RECORD__ ?? ctx.appRecords.find(ar => ar.options.app === app)
if (record) {
return record
}
if (app._vueDevtools_hidden_) {
return null
}
return new Promise((resolve, reject) => {
let resolvers = appRecordPromises.get(app)
let timedOut = false
if (!resolvers) {
resolvers = []
appRecordPromises.set(app, resolvers)
}
let timer: any
const fn = (record) => {
if (!timedOut) {
clearTimeout(timer)
resolve(record)
}
}
resolvers.push(fn)
timer = setTimeout(() => {
timedOut = true
const index = resolvers.indexOf(fn)
if (index !== -1) {
resolvers.splice(index, 1)
}
if (SharedData.debugInfo) {
// eslint-disable-next-line no-console
console.log('Timed out waiting for app record', app)
}
reject(new Error(`Timed out getting app record for app`))
}, 60000)
})
}
export function waitForAppsRegistration() {
return jobs.queue('waitForAppsRegistrationNoop', async () => { /* NOOP */ })
}
export async function sendApps(ctx: BackendContext) {
const appRecords = []
for (const appRecord of ctx.appRecords) {
appRecords.push(appRecord)
}
ctx.bridge.send(BridgeEvents.TO_FRONT_APP_LIST, {
apps: appRecords.map(mapAppRecord),
})
}
function removeAppRecord(appRecord: AppRecord, ctx: BackendContext) {
try {
appIds.delete(appRecord.id)
const index = ctx.appRecords.indexOf(appRecord)
if (index !== -1) {
ctx.appRecords.splice(index, 1)
}
removeLayersForApp(appRecord.options.app, ctx)
ctx.bridge.send(BridgeEvents.TO_FRONT_APP_REMOVE, { id: appRecord.id })
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
}
export async function removeApp(app: App, ctx: BackendContext) {
try {
const appRecord = await getAppRecord(app, ctx)
if (appRecord) {
removeAppRecord(appRecord, ctx)
}
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
}
let scanTimeout: any
export function _legacy_getAndRegisterApps(ctx: BackendContext, clear = false) {
setTimeout(() => {
try {
if (clear) {
// Remove apps that are legacy
ctx.appRecords.forEach((appRecord) => {
if (appRecord.meta.Vue) {
removeAppRecord(appRecord, ctx)
}
})
}
const apps = scan()
clearTimeout(scanTimeout)
if (!apps.length) {
scanTimeout = setTimeout(() => _legacy_getAndRegisterApps(ctx), 1000)
}
apps.forEach((app) => {
const Vue = hook.Vue
registerApp({
app,
types: {},
version: Vue?.version,
meta: {
Vue,
},
}, ctx)
})
}
catch (e) {
if (SharedData.debugInfo) {
console.error(`Error scanning for legacy apps:`)
console.error(e)
}
}
}, 0)
}
================================================
FILE: packages/app-backend-core/src/backend.ts
================================================
import type { BackendContext, DevtoolsBackend, DevtoolsBackendOptions } from '@vue-devtools/app-backend-api'
import { createBackend } from '@vue-devtools/app-backend-api'
import { backend as backendVue1 } from '@vue-devtools/app-backend-vue1'
import { backend as backendVue2 } from '@vue-devtools/app-backend-vue2'
import { backend as backendVue3 } from '@vue-devtools/app-backend-vue3'
import { handleAddPerformanceTag } from './perf'
export const availableBackends = [
backendVue1,
backendVue2,
backendVue3,
]
const enabledBackends: Map<DevtoolsBackendOptions, DevtoolsBackend> = new Map()
export function getBackend(backendOptions: DevtoolsBackendOptions, ctx: BackendContext) {
let backend: DevtoolsBackend
if (!enabledBackends.has(backendOptions)) {
// Create backend
backend = createBackend(backendOptions, ctx)
handleAddPerformanceTag(backend, ctx)
enabledBackends.set(backendOptions, backend)
ctx.backends.push(backend)
}
else {
backend = enabledBackends.get(backendOptions)
}
return backend
}
================================================
FILE: packages/app-backend-core/src/component-pick.ts
================================================
import { BridgeEvents, isBrowser } from '@vue-devtools/shared-utils'
import type { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api'
import type { ComponentInstance } from '@vue/devtools-api'
import { highlight, unHighlight } from './highlighter'
export default class ComponentPicker {
ctx: BackendContext
selectedInstance: ComponentInstance
selectedBackend: DevtoolsBackend
constructor(ctx: BackendContext) {
this.ctx = ctx
this.bindMethods()
}
/**
* Adds event listeners for mouseover and mouseup
*/
startSelecting() {
if (!isBrowser) {
return
}
window.addEventListener('mouseover', this.elementMouseOver, true)
window.addEventListener('click', this.elementClicked, true)
window.addEventListener('mouseout', this.cancelEvent, true)
window.addEventListener('mouseenter', this.cancelEvent, true)
window.addEventListener('mouseleave', this.cancelEvent, true)
window.addEventListener('mousedown', this.cancelEvent, true)
window.addEventListener('mouseup', this.cancelEvent, true)
}
/**
* Removes event listeners
*/
stopSelecting() {
if (!isBrowser) {
return
}
window.removeEventListener('mouseover', this.elementMouseOver, true)
window.removeEventListener('click', this.elementClicked, true)
window.removeEventListener('mouseout', this.cancelEvent, true)
window.removeEventListener('mouseenter', this.cancelEvent, true)
window.removeEventListener('mouseleave', this.cancelEvent, true)
window.removeEventListener('mousedown', this.cancelEvent, true)
window.removeEventListener('mouseup', this.cancelEvent, true)
unHighlight()
}
/**
* Highlights a component on element mouse over
*/
async elementMouseOver(e: MouseEvent) {
this.cancelEvent(e)
const el = e.target
if (el) {
await this.selectElementComponent(el)
}
unHighlight()
if (this.selectedInstance) {
highlight(this.selectedInstance, this.selectedBackend, this.ctx)
}
}
async selectElementComponent(el) {
for (const backend of this.ctx.backends) {
const instance = await backend.api.getElementComponent(el)
if (instance) {
this.selectedInstance = instance
this.selectedBackend = backend
return
}
}
this.selectedInstance = null
this.selectedBackend = null
}
/**
* Selects an instance in the component view
*/
async elementClicked(e: MouseEvent) {
this.cancelEvent(e)
if (this.selectedInstance && this.selectedBackend) {
const parentInstances = await this.selectedBackend.api.walkComponentParents(this.selectedInstance)
this.ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_PICK, { id: this.selectedInstance.__VUE_DEVTOOLS_UID__, parentIds: parentInstances.map(i => i.__VUE_DEVTOOLS_UID__) })
}
else {
this.ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_PICK_CANCELED, null)
}
this.stopSelecting()
}
/**
* Cancel a mouse event
*/
cancelEvent(e: MouseEvent) {
e.stopImmediatePropagation()
e.preventDefault()
}
/**
* Bind class methods to the class scope to avoid rebind for event listeners
*/
bindMethods() {
this.startSelecting = this.startSelecting.bind(this)
this.stopSelecting = this.stopSelecting.bind(this)
this.elementMouseOver = this.elementMouseOver.bind(this)
this.elementClicked = this.elementClicked.bind(this)
}
}
================================================
FILE: packages/app-backend-core/src/component.ts
================================================
import { BridgeEvents, SharedData, createThrottleQueue, parse, stringify } from '@vue-devtools/shared-utils'
import type { AppRecord, BackendContext } from '@vue-devtools/app-backend-api'
import { BuiltinBackendFeature } from '@vue-devtools/app-backend-api'
import type { App, ComponentInstance, EditStatePayload } from '@vue/devtools-api'
import { getAppRecord } from './app'
const MAX_$VM = 10
const $vmQueue = []
export async function sendComponentTreeData(appRecord: AppRecord, instanceId: string, filter = '', maxDepth: number = null, recursively = false, ctx: BackendContext) {
if (!instanceId || appRecord !== ctx.currentAppRecord) {
return
}
// Flush will send all components in the tree
// So we skip individiual tree updates
if (
instanceId !== '_root'
&& ctx.currentAppRecord.backend.options.features.includes(BuiltinBackendFeature.FLUSH)
) {
return
}
const instance = getComponentInstance(appRecord, instanceId, ctx)
if (!instance) {
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_TREE, {
instanceId,
treeData: null,
notFound: true,
})
}
else {
if (filter) {
filter = filter.toLowerCase()
}
if (maxDepth == null) {
maxDepth = instance === ctx.currentAppRecord.rootInstance ? 2 : 1
}
const data = await appRecord.backend.api.walkComponentTree(instance, maxDepth, filter, recursively)
const payload = {
instanceId,
treeData: stringify(data),
}
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_TREE, payload)
}
}
export async function sendSelectedComponentData(appRecord: AppRecord, instanceId: string, ctx: BackendContext) {
if (!instanceId || appRecord !== ctx.currentAppRecord) {
return
}
const instance = getComponentInstance(appRecord, instanceId, ctx)
if (!instance) {
sendEmptyComponentData(instanceId, ctx)
}
else {
// Expose instance on window
if (typeof window !== 'undefined') {
const win = window as any
win.$vm = instance
// $vm0, $vm1, $vm2, ...
if ($vmQueue[0] !== instance) {
if ($vmQueue.length >= MAX_$VM) {
$vmQueue.pop()
}
for (let i = $vmQueue.length; i > 0; i--) {
win[`$vm${i}`] = $vmQueue[i] = $vmQueue[i - 1]
}
win.$vm0 = $vmQueue[0] = instance
}
}
if (SharedData.debugInfo) {
// eslint-disable-next-line no-console
console.log('[DEBUG] inspect', instance)
}
const parentInstances = await appRecord.backend.api.walkComponentParents(instance)
const payload = {
instanceId,
data: stringify(await appRecord.backend.api.inspectComponent(instance, ctx.currentAppRecord.options.app)),
parentIds: parentInstances.map(i => i.__VUE_DEVTOOLS_UID__),
}
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_SELECTED_DATA, payload)
markSelectedInstance(instanceId, ctx)
}
}
export function markSelectedInstance(instanceId: string, ctx: BackendContext) {
ctx.currentInspectedComponentId = instanceId
ctx.currentAppRecord.lastInspectedComponentId = instanceId
}
export function sendEmptyComponentData(instanceId: string, ctx: BackendContext) {
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_SELECTED_DATA, {
instanceId,
data: null,
})
}
export async function editComponentState(instanceId: string, dotPath: string, type: string, state: EditStatePayload, ctx: BackendContext) {
if (!instanceId) {
return
}
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
if (instance) {
if ('value' in state && state.value != null) {
state.value = parse(state.value, true)
}
await ctx.currentAppRecord.backend.api.editComponentState(instance, dotPath, type, state, ctx.currentAppRecord.options.app)
await sendSelectedComponentData(ctx.currentAppRecord, instanceId, ctx)
}
}
export async function getComponentId(app: App, uid: number, instance: ComponentInstance, ctx: BackendContext) {
try {
if (instance.__VUE_DEVTOOLS_UID__) {
return instance.__VUE_DEVTOOLS_UID__
}
const appRecord = await getAppRecord(app, ctx)
if (!appRecord) {
return null
}
const isRoot = appRecord.rootInstance === instance
return `${appRecord.id}:${isRoot ? 'root' : uid}`
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
return null
}
}
export function getComponentInstance(appRecord: AppRecord, instanceId: string, _ctx: BackendContext) {
if (instanceId === '_root') {
instanceId = `${appRecord.id}:root`
}
const instance = appRecord.instanceMap.get(instanceId)
if (!instance) {
appRecord.missingInstanceQueue.add(instanceId)
if (SharedData.debugInfo) {
console.warn(`Instance uid=${instanceId} not found`)
}
}
return instance
}
export async function refreshComponentTreeSearch(ctx: BackendContext) {
if (!ctx.currentAppRecord.componentFilter) {
return
}
await sendComponentTreeData(ctx.currentAppRecord, '_root', ctx.currentAppRecord.componentFilter, null, false, ctx)
}
const updateTrackingQueue = createThrottleQueue(500)
export function sendComponentUpdateTracking(instanceId: string, time: number, ctx: BackendContext) {
if (!instanceId) {
return
}
updateTrackingQueue.add(instanceId, () => {
const payload = {
instanceId,
time,
}
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_UPDATED, payload)
})
}
================================================
FILE: packages/app-backend-core/src/flash.ts
================================================
import type { DevtoolsBackend } from '@vue-devtools/app-backend-api'
import type { ComponentInstance } from '@vue/devtools-api'
export async function flashComponent(instance: ComponentInstance, backend: DevtoolsBackend) {
const bounds = await backend.api.getComponentBounds(instance)
if (bounds) {
let overlay: HTMLDivElement = instance.__VUE_DEVTOOLS_FLASH
if (!overlay) {
overlay = document.createElement('div')
instance.__VUE_DEVTOOLS_FLASH = overlay
overlay.style.border = '2px rgba(65, 184, 131, 0.7) solid'
overlay.style.position = 'fixed'
overlay.style.zIndex = '99999999999998'
overlay.style.pointerEvents = 'none'
overlay.style.borderRadius = '3px'
overlay.style.boxSizing = 'border-box'
document.body.appendChild(overlay)
}
overlay.style.opacity = '1'
overlay.style.transition = null
overlay.style.width = `${Math.round(bounds.width)}px`
overlay.style.height = `${Math.round(bounds.height)}px`
overlay.style.left = `${Math.round(bounds.left)}px`
overlay.style.top = `${Math.round(bounds.top)}px`
requestAnimationFrame(() => {
overlay.style.transition = 'opacity 1s'
overlay.style.opacity = '0'
})
clearTimeout((overlay as any)._timer)
;(overlay as any)._timer = setTimeout(() => {
document.body.removeChild(overlay)
instance.__VUE_DEVTOOLS_FLASH = null
}, 1000)
}
}
================================================
FILE: packages/app-backend-core/src/global-hook.ts
================================================
import type { DevtoolsHook } from '@vue-devtools/app-backend-api'
import { target } from '@vue-devtools/shared-utils'
// hook should have been injected before this executes.
export const hook: DevtoolsHook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__
================================================
FILE: packages/app-backend-core/src/highlighter.ts
================================================
import { isBrowser } from '@vue-devtools/shared-utils'
import type { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api'
import type { ComponentBounds, ComponentInstance } from '@vue/devtools-api'
import { JobQueue } from './util/queue'
let overlay: HTMLDivElement
let overlayContent: HTMLDivElement
let currentInstance
function createOverlay() {
if (overlay || !isBrowser) {
return
}
overlay = document.createElement('div')
overlay.style.backgroundColor = 'rgba(65, 184, 131, 0.35)'
overlay.style.position = 'fixed'
overlay.style.zIndex = '99999999999998'
overlay.style.pointerEvents = 'none'
overlay.style.borderRadius = '3px'
overlayContent = document.createElement('div')
overlayContent.style.position = 'fixed'
overlayContent.style.zIndex = '99999999999999'
overlayContent.style.pointerEvents = 'none'
overlayContent.style.backgroundColor = 'white'
overlayContent.style.fontFamily = 'monospace'
overlayContent.style.fontSize = '11px'
overlayContent.style.padding = '4px 8px'
overlayContent.style.borderRadius = '3px'
overlayContent.style.color = '#333'
overlayContent.style.textAlign = 'center'
overlayContent.style.border = 'rgba(65, 184, 131, 0.5) 1px solid'
overlayContent.style.backgroundClip = 'padding-box'
}
// Use a job queue to preserve highlight/unhighlight calls order
// This prevents "sticky" highlights that are not removed because highlight is async
const jobQueue = new JobQueue()
export async function highlight(instance: ComponentInstance, backend: DevtoolsBackend, ctx: BackendContext) {
await jobQueue.queue('highlight', async () => {
if (!instance) {
return
}
const bounds = await backend.api.getComponentBounds(instance)
if (bounds) {
createOverlay()
// Name
const name = (await backend.api.getComponentName(instance)) || 'Anonymous'
const pre = document.createElement('span')
pre.style.opacity = '0.6'
pre.textContent = '<'
const text = document.createElement('span')
text.style.fontWeight = 'bold'
text.style.color = '#09ab56'
text.textContent = name
const post = document.createElement('span')
post.style.opacity = '0.6'
post.textContent = '>'
// Size
const size = document.createElement('span')
size.style.opacity = '0.5'
size.style.marginLeft = '6px'
size.appendChild(document.createTextNode((Math.round(bounds.width * 100) / 100).toString()))
const multiply = document.createElement('span')
multiply.style.marginLeft = multiply.style.marginRight = '2px'
multiply.textContent = '×'
size.appendChild(multiply)
size.appendChild(document.createTextNode((Math.round(bounds.height * 100) / 100).toString()))
currentInstance = instance
await showOverlay(bounds, [pre, text, post, size])
}
startUpdateTimer(backend, ctx)
})
}
export async function unHighlight() {
await jobQueue.queue('unHighlight', async () => {
overlay?.parentNode?.removeChild(overlay)
overlayContent?.parentNode?.removeChild(overlayContent)
currentInstance = null
stopUpdateTimer()
})
}
function showOverlay(bounds: ComponentBounds, children: Node[] = null) {
if (!isBrowser || !children.length) {
return
}
positionOverlay(bounds)
document.body.appendChild(overlay)
overlayContent.innerHTML = ''
children.forEach(child => overlayContent.appendChild(child))
document.body.appendChild(overlayContent)
positionOverlayContent(bounds)
}
function positionOverlay({ width = 0, height = 0, top = 0, left = 0 }) {
overlay.style.width = `${Math.round(width)}px`
overlay.style.height = `${Math.round(height)}px`
overlay.style.left = `${Math.round(left)}px`
overlay.style.top = `${Math.round(top)}px`
}
function positionOverlayContent({ height = 0, top = 0, left = 0 }) {
// Content position (prevents overflow)
const contentWidth = overlayContent.offsetWidth
const contentHeight = overlayContent.offsetHeight
let contentLeft = left
if (contentLeft < 0) {
contentLeft = 0
}
else if (contentLeft + contentWidth > window.innerWidth) {
contentLeft = window.innerWidth - contentWidth
}
let contentTop = top - contentHeight - 2
if (contentTop < 0) {
contentTop = top + height + 2
}
if (contentTop < 0) {
contentTop = 0
}
else if (contentTop + contentHeight > window.innerHeight) {
contentTop = window.innerHeight - contentHeight
}
overlayContent.style.left = `${~~contentLeft}px`
overlayContent.style.top = `${~~contentTop}px`
}
async function updateOverlay(backend: DevtoolsBackend, _ctx: BackendContext) {
if (currentInstance) {
const bounds = await backend.api.getComponentBounds(currentInstance)
if (bounds) {
const sizeEl = overlayContent.children.item(3)
const widthEl = sizeEl.childNodes[0] as unknown as Text
widthEl.textContent = (Math.round(bounds.width * 100) / 100).toString()
const heightEl = sizeEl.childNodes[2] as unknown as Text
heightEl.textContent = (Math.round(bounds.height * 100) / 100).toString()
positionOverlay(bounds)
positionOverlayContent(bounds)
}
}
}
let updateTimer
function startUpdateTimer(backend: DevtoolsBackend, ctx: BackendContext) {
stopUpdateTimer()
updateTimer = setInterval(() => {
jobQueue.queue('updateOverlay', async () => {
await updateOverlay(backend, ctx)
})
}, 1000 / 30) // 30fps
}
function stopUpdateTimer() {
clearInterval(updateTimer)
}
================================================
FILE: packages/app-backend-core/src/hook.ts
================================================
// this script is injected into every page.
/**
* Install the hook on window, which is an event emitter.
* Note because Chrome content scripts cannot directly modify the window object,
* we are evaling this function by inserting a script tag. That's why we have
* to inline the whole event emitter implementation here.
*
* @param {Window|global} target
*/
export function installHook(target, isIframe = false) {
const devtoolsVersion = '6.0'
let listeners = {}
function injectIframeHook(iframe) {
if ((iframe as any).__vdevtools__injected) {
return
}
try {
(iframe as any).__vdevtools__injected = true
const inject = () => {
try {
(iframe.contentWindow as any).__VUE_DEVTOOLS_IFRAME__ = iframe
const script = iframe.contentDocument.createElement('script')
script.textContent = `;(${installHook.toString()})(window, true)`
iframe.contentDocument.documentElement.appendChild(script)
script.parentNode.removeChild(script)
}
catch (e) {
// Ignore
}
}
inject()
iframe.addEventListener('load', () => inject())
}
catch (e) {
// Ignore
}
}
let iframeChecks = 0
function injectToIframes() {
if (typeof window === 'undefined') {
return
}
const iframes = document.querySelectorAll<HTMLIFrameElement>('iframe:not([data-vue-devtools-ignore])')
for (const iframe of iframes) {
injectIframeHook(iframe)
}
}
injectToIframes()
const iframeTimer = setInterval(() => {
injectToIframes()
iframeChecks++
if (iframeChecks >= 5) {
clearInterval(iframeTimer)
}
}, 1000)
if (Object.prototype.hasOwnProperty.call(target, '__VUE_DEVTOOLS_GLOBAL_HOOK__')) {
if (target.__VUE_DEVTOOLS_GLOBAL_HOOK__.devtoolsVersion !== devtoolsVersion) {
console.error(`Another version of Vue Devtools seems to be installed. Please enable only one version at a time.`)
}
return
}
let hook
if (isIframe) {
const sendToParent = (cb) => {
try {
const hook = (window.parent as any).__VUE_DEVTOOLS_GLOBAL_HOOK__
if (hook) {
return cb(hook)
}
else {
console.warn('[Vue Devtools] No hook in parent window')
}
}
catch (e) {
console.warn('[Vue Devtools] Failed to send message to parent window', e)
}
}
hook = {
devtoolsVersion,
// eslint-disable-next-line accessor-pairs
set Vue(value) {
sendToParent((hook) => {
hook.Vue = value
})
},
// eslint-disable-next-line accessor-pairs
set enabled(value) {
sendToParent((hook) => {
hook.enabled = value
})
},
on(event, fn) {
sendToParent(hook => hook.on(event, fn))
},
once(event, fn) {
sendToParent(hook => hook.once(event, fn))
},
off(event, fn) {
sendToParent(hook => hook.off(event, fn))
},
emit(event, ...args) {
sendToParent(hook => hook.emit(event, ...args))
},
cleanupBuffer(matchArg) {
return sendToParent(hook => hook.cleanupBuffer(matchArg)) ?? false
},
}
}
else {
hook = {
devtoolsVersion,
Vue: null,
enabled: undefined,
_buffer: [],
_bufferMap: new Map(),
_bufferToRemove: new Map(),
store: null,
initialState: null,
storeModules: null,
flushStoreModules: null,
apps: [],
_replayBuffer(event) {
const buffer = this._buffer
this._buffer = []
this._bufferMap.clear()
this._bufferToRemove.clear()
for (let i = 0, l = buffer.length; i < l; i++) {
const allArgs = buffer[i].slice(1)
allArgs[0] === event
// eslint-disable-next-line prefer-spread
? this.emit.apply(this, allArgs)
: this._buffer.push(buffer[i])
}
},
on(event, fn) {
const $event = `$${event}`
if (listeners[$event]) {
listeners[$event].push(fn)
}
else {
listeners[$event] = [fn]
this._replayBuffer(event)
}
},
once(event, fn) {
const on = (...args) => {
this.off(event, on)
return fn.apply(this, args)
}
this.on(event, on)
},
off(event, fn) {
event = `$${event}`
if (!arguments.length) {
listeners = {}
}
else {
const cbs = listeners[event]
if (cbs) {
if (!fn) {
listeners[event] = null
}
else {
for (let i = 0, l = cbs.length; i < l; i++) {
const cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
}
}
}
},
emit(event, ...args) {
const $event = `$${event}`
let cbs = listeners[$event]
if (cbs) {
cbs = cbs.slice()
for (let i = 0, l = cbs.length; i < l; i++) {
try {
const result = cbs[i].apply(this, args)
if (typeof result?.catch === 'function') {
result.catch((e) => {
console.error(`[Hook] Error in async event handler for ${event} with args:`, args)
console.error(e)
})
}
}
catch (e) {
console.error(`[Hook] Error in event handler for ${event} with args:`, args)
console.error(e)
}
}
}
else {
const buffered = [Date.now(), event, ...args]
this._buffer.push(buffered)
for (let i = 2; i < args.length; i++) {
if (typeof args[i] === 'object' && args[i]) {
// Save by component instance (3rd, 4th or 5th arg)
this._bufferMap.set(args[i], buffered)
break
}
}
}
},
/**
* Remove buffered events with any argument that is equal to the given value.
* @param matchArg Given value to match.
*/
cleanupBuffer(matchArg) {
const inBuffer = this._bufferMap.has(matchArg)
if (inBuffer) {
// Mark event for removal
this._bufferToRemove.set(this._bufferMap.get(matchArg), true)
}
return inBuffer
},
_cleanupBuffer() {
const now = Date.now()
// Clear buffer events that are older than 10 seconds or marked for removal
this._buffer = this._buffer.filter(args => !this._bufferToRemove.has(args) && now - args[0] < 10_000)
this._bufferToRemove.clear()
this._bufferMap.clear()
},
}
setInterval(() => {
hook._cleanupBuffer()
}, 10_000)
hook.once('init', (Vue) => {
hook.Vue = Vue
if (Vue) {
Vue.prototype.$inspect = function () {
const fn = target.__VUE_DEVTOOLS_INSPECT__
fn && fn(this)
}
}
})
hook.on('app:init', (app, version, types) => {
const appRecord = {
app,
version,
types,
}
hook.apps.push(appRecord)
hook.emit('app:add', appRecord)
})
hook.once('vuex:init', (store) => {
hook.store = store
hook.initialState = clone(store.state)
const origReplaceState = store.replaceState.bind(store)
store.replaceState = (state) => {
hook.initialState = clone(state)
origReplaceState(state)
}
// Dynamic modules
let origRegister, origUnregister
if (store.registerModule) {
hook.storeModules = []
origRegister = store.registerModule.bind(store)
store.registerModule = (path, module, options) => {
if (typeof path === 'string') {
path = [path]
}
hook.storeModules.push({ path, module, options })
origRegister(path, module, options)
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console
console.log('early register module', path, module, options)
}
}
origUnregister = store.unregisterModule.bind(store)
store.unregisterModule = (path) => {
if (typeof path === 'string') {
path = [path]
}
const key = path.join('/')
const index = hook.storeModules.findIndex(m => m.path.join('/') === key)
if (index !== -1) {
hook.storeModules.splice(index, 1)
}
origUnregister(path)
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console
console.log('early unregister module', path)
}
}
}
hook.flushStoreModules = () => {
store.replaceState = origReplaceState
if (store.registerModule) {
store.registerModule = origRegister
store.unregisterModule = origUnregister
}
return hook.storeModules || []
}
})
}
Object.defineProperty(target, '__VUE_DEVTOOLS_GLOBAL_HOOK__', {
get() {
return hook
},
})
// Handle apps initialized before hook injection
if (target.__VUE_DEVTOOLS_HOOK_REPLAY__) {
try {
target.__VUE_DEVTOOLS_HOOK_REPLAY__.forEach(cb => cb(hook))
target.__VUE_DEVTOOLS_HOOK_REPLAY__ = []
}
catch (e) {
console.error('[vue-devtools] Error during hook replay', e)
}
}
// Clone deep utility for cloning initial state of the store
// Forked from https://github.com/planttheidea/fast-copy
// Last update: 2019-10-30
// ⚠️ Don't forget to update `./hook.js`
// utils
const { toString: toStringFunction } = Function.prototype
const {
create,
defineProperty,
getOwnPropertyDescriptor,
getOwnPropertyNames,
getOwnPropertySymbols,
getPrototypeOf,
} = Object
const { hasOwnProperty, propertyIsEnumerable } = Object.prototype
/**
* @enum
*
* @const {object} SUPPORTS
*
* @property {boolean} SYMBOL_PROPERTIES are symbol properties supported
* @property {boolean} WEAKSET is WeakSet supported
*/
const SUPPORTS = {
SYMBOL_PROPERTIES: typeof getOwnPropertySymbols === 'function',
WEAKSET: typeof WeakSet === 'function',
}
/**
* @function createCache
*
* @description
* get a new cache object to prevent circular references
*
* @returns the new cache object
*/
const createCache = () => {
if (SUPPORTS.WEAKSET) {
return new WeakSet()
}
const object = create({
add: value => object._values.push(value),
has: value => !!~object._values.indexOf(value),
})
object._values = []
return object
}
/**
* @function getCleanClone
*
* @description
* get an empty version of the object with the same prototype it has
*
* @param object the object to build a clean clone from
* @param realm the realm the object resides in
* @returns the empty cloned object
*/
const getCleanClone = (object, realm) => {
if (!object.constructor) {
return create(null)
}
// eslint-disable-next-line no-proto, no-restricted-properties
const prototype = object.__proto__ || getPrototypeOf(object)
if (object.constructor === realm.Object) {
return prototype === realm.Object.prototype ? {} : create(prototype)
}
if (~toStringFunction.call(object.constructor).indexOf('[native code]')) {
try {
return new object.constructor()
}
catch (e) {
// Error
}
}
return create(prototype)
}
/**
* @function getObjectCloneLoose
*
* @description
* get a copy of the object based on loose rules, meaning all enumerable keys
* and symbols are copied, but property descriptors are not considered
*
* @param object the object to clone
* @param realm the realm the object resides in
* @param handleCopy the function that handles copying the object
* @returns the copied object
*/
const getObjectCloneLoose = (
object,
realm,
handleCopy,
cache,
) => {
const clone = getCleanClone(object, realm)
for (const key in object) {
if (hasOwnProperty.call(object, key)) {
clone[key] = handleCopy(object[key], cache)
}
}
if (SUPPORTS.SYMBOL_PROPERTIES) {
const symbols = getOwnPropertySymbols(object)
if (symbols.length) {
for (let index = 0, symbol; index < symbols.length; index++) {
symbol = symbols[index]
if (propertyIsEnumerable.call(object, symbol)) {
clone[symbol] = handleCopy(object[symbol], cache)
}
}
}
}
return clone
}
/**
* @function getObjectCloneStrict
*
* @description
* get a copy of the object based on strict rules, meaning all keys and symbols
* are copied based on the original property descriptors
*
* @param object the object to clone
* @param realm the realm the object resides in
* @param handleCopy the function that handles copying the object
* @returns the copied object
*/
const getObjectCloneStrict = (
object,
realm,
handleCopy,
cache,
) => {
const clone = getCleanClone(object, realm)
const properties = SUPPORTS.SYMBOL_PROPERTIES
? [].concat(getOwnPropertyNames(object), getOwnPropertySymbols(object))
: getOwnPropertyNames(object)
if (properties.length) {
for (
let index = 0, property, descriptor;
index < properties.length;
index++
) {
property = properties[index]
if (property !== 'callee' && property !== 'caller') {
descriptor = getOwnPropertyDescriptor(object, property)
descriptor.value = handleCopy(object[property], cache)
defineProperty(clone, property, descriptor)
}
}
}
return clone
}
/**
* @function getRegExpFlags
*
* @description
* get the flags to apply to the copied regexp
*
* @param regExp the regexp to get the flags of
* @returns the flags for the regexp
*/
const getRegExpFlags = (regExp) => {
let flags = ''
if (regExp.global) {
flags += 'g'
}
if (regExp.ignoreCase) {
flags += 'i'
}
if (regExp.multiline) {
flags += 'm'
}
if (regExp.unicode) {
flags += 'u'
}
if (regExp.sticky) {
flags += 'y'
}
return flags
}
const { isArray } = Array
const GLOBAL_THIS = (() => {
// eslint-disable-next-line no-restricted-globals
if (typeof self !== 'undefined') {
// eslint-disable-next-line no-restricted-globals
return self
}
if (typeof window !== 'undefined') {
return window
}
if (typeof globalThis !== 'undefined') {
return globalThis
}
if (console && console.error) {
console.error('Unable to locate global object, returning "this".')
}
})()
/**
* @function clone
*
* @description
* copy an object deeply as much as possible
*
* If `strict` is applied, then all properties (including non-enumerable ones)
* are copied with their original property descriptors on both objects and arrays.
*
* The object is compared to the global constructors in the `realm` provided,
* and the native constructor is always used to ensure that extensions of native
* objects (allows in ES2015+) are maintained.
*
* @param object the object to copy
* @param [options] the options for copying with
* @param [options.isStrict] should the copy be strict
* @param [options.realm] the realm (this) object the object is copied from
* @returns the copied object
*/
function clone(object, options = null) {
// manually coalesced instead of default parameters for performance
const isStrict = !!(options && options.isStrict)
const realm = (options && options.realm) || GLOBAL_THIS
const getObjectClone = isStrict
? getObjectCloneStrict
: getObjectCloneLoose
/**
* @function handleCopy
*
* @description
* copy the object recursively based on its type
*
* @param object the object to copy
* @returns the copied object
*/
const handleCopy = (
object,
cache,
) => {
if (!object || typeof object !== 'object' || cache.has(object)) {
return object
}
// DOM objects
if (typeof HTMLElement !== 'undefined' && object instanceof HTMLElement) {
return object.cloneNode(false)
}
const Constructor = object.constructor
// plain objects
if (Constructor === realm.Object) {
cache.add(object)
return getObjectClone(object, realm, handleCopy, cache)
}
let clone
// arrays
if (isArray(object)) {
cache.add(object)
// if strict, include non-standard properties
if (isStrict) {
return getObjectCloneStrict(object, realm, handleCopy, cache)
}
clone = new Constructor()
for (let index = 0; index < object.length; index++) {
clone[index] = handleCopy(object[index], cache)
}
return clone
}
// dates
if (object instanceof realm.Date) {
return new Constructor(object.getTime())
}
// regexps
if (object instanceof realm.RegExp) {
clone = new Constructor(
object.source,
object.flags || getRegExpFlags(object),
)
clone.lastIndex = object.lastIndex
return clone
}
// maps
if (realm.Map && object instanceof realm.Map) {
cache.add(object)
clone = new Constructor()
object.forEach((value, key) => {
clone.set(key, handleCopy(value, cache))
})
return clone
}
// sets
if (realm.Set && object instanceof realm.Set) {
cache.add(object)
clone = new Constructor()
object.forEach((value) => {
clone.add(handleCopy(value, cache))
})
return clone
}
// buffers (node-only)
if (realm.Buffer && realm.Buffer.isBuffer(object)) {
clone = realm.Buffer.allocUnsafe
? realm.Buffer.allocUnsafe(object.length)
: new Constructor(object.length)
object.copy(clone)
return clone
}
// arraybuffers / dataviews
if (realm.ArrayBuffer) {
// dataviews
if (realm.ArrayBuffer.isView(object)) {
return new Constructor(object.buffer.slice(0))
}
// arraybuffers
if (object instanceof realm.ArrayBuffer) {
return object.slice(0)
}
}
// if the object cannot / should not be cloned, don't
if (
// promise-like
(hasOwnProperty.call(object, 'then') && typeof object.then === 'function')
// errors
|| object instanceof Error
// weakmaps
|| (realm.WeakMap && object instanceof realm.WeakMap)
// weaksets
|| (realm.WeakSet && object instanceof realm.WeakSet)
) {
return object
}
cache.add(object)
// assume anything left is a custom constructor
return getObjectClone(object, realm, handleCopy, cache)
}
return handleCopy(object, createCache())
}
}
================================================
FILE: packages/app-backend-core/src/index.ts
================================================
import type {
AppRecord,
BackendContext,
Plugin,
} from '@vue-devtools/app-backend-api'
import {
BuiltinBackendFeature,
createBackendContext,
} from '@vue-devtools/app-backend-api'
import type {
Bridge,
} from '@vue-devtools/shared-utils'
import {
BridgeEvents,
BridgeSubscriptions,
BuiltinTabs,
HookEvents,
SharedData,
createThrottleQueue,
getPluginSettings,
initSharedData,
isBrowser,
parse,
raf,
revive,
target,
} from '@vue-devtools/shared-utils'
import debounce from 'lodash/debounce'
import type { CustomInspectorOptions, PluginDescriptor, SetupFunction, TimelineEventOptions, TimelineLayerOptions } from '@vue/devtools-api'
import { Hooks, now } from '@vue/devtools-api'
import { hook } from './global-hook'
import { isSubscribed, subscribe, unsubscribe } from './util/subscriptions'
import { highlight, unHighlight } from './highlighter'
import { addTimelineEvent, clearTimeline, sendTimelineEventData, sendTimelineLayerEvents, sendTimelineLayers, setupTimeline } from './timeline'
import ComponentPicker from './component-pick'
import {
editComponentState,
getComponentId,
getComponentInstance,
refreshComponentTreeSearch,
sendComponentTreeData,
sendComponentUpdateTracking,
sendEmptyComponentData,
sendSelectedComponentData,
} from './component'
import { addPlugin, addPreviouslyRegisteredPlugins, addQueuedPlugins, sendPluginList } from './plugin'
import { _legacy_getAndRegisterApps, getAppRecord, registerApp, removeApp, selectApp, sendApps, waitForAppsRegistration } from './app'
import { editInspectorState, getInspector, getInspectorWithAppId, selectInspectorNode, sendCustomInspectors, sendInspectorState, sendInspectorTree } from './inspector'
import { showScreenshot } from './timeline-screenshot'
import { performanceMarkEnd, performanceMarkStart } from './perf'
import { initOnPageConfig } from './page-config'
import { addTimelineMarker, sendTimelineMarkers } from './timeline-marker'
import { flashComponent } from './flash.js'
let ctx: BackendContext = target.__vdevtools_ctx ?? null
let connected = target.__vdevtools_connected ?? false
let pageTitleObserver: MutationObserver
export async function initBackend(bridge: Bridge) {
await initSharedData({
bridge,
persist: false,
})
SharedData.isBrowser = isBrowser
initOnPageConfig()
if (!connected) {
// First connect
ctx = target.__vdevtools_ctx = createBackendContext({
bridge,
hook,
})
SharedData.legacyApps = false
if (hook.Vue) {
connect()
_legacy_getAndRegisterApps(ctx, true)
SharedData.legacyApps = true
}
hook.on(HookEvents.INIT, () => {
_legacy_getAndRegisterApps(ctx, true)
SharedData.legacyApps = true
})
hook.on(HookEvents.APP_ADD, async (app) => {
await registerApp(app, ctx)
connect()
})
// Add apps that already sent init
if (hook.apps.length) {
hook.apps.forEach((app) => {
registerApp(app, ctx)
connect()
})
}
}
else {
// Reconnect
ctx.bridge = bridge
connectBridge()
ctx.bridge.send(BridgeEvents.TO_FRONT_RECONNECTED)
}
}
async function connect() {
if (connected) {
return
}
connected = target.__vdevtools_connected = true
await waitForAppsRegistration()
connectBridge()
ctx.currentTab = BuiltinTabs.COMPONENTS
// Apps
hook.on(HookEvents.APP_UNMOUNT, async (app) => {
await removeApp(app, ctx)
})
// Components
const throttleQueue = createThrottleQueue(500)
hook.on(HookEvents.COMPONENT_UPDATED, async (app, uid, parentUid, component) => {
try {
if (!app || (typeof uid !== 'number' && !uid) || !component) {
return
}
const now = Date.now()
let id: string
let appRecord: AppRecord
if (app && uid != null) {
id = await getComponentId(app, uid, component, ctx)
appRecord = await getAppRecord(app, ctx)
}
else {
id = ctx.currentInspectedComponentId
appRecord = ctx.currentAppRecord
}
throttleQueue.add(`update:${id}`, async () => {
try {
if (SharedData.trackUpdates) {
sendComponentUpdateTracking(id, now, ctx)
}
if (SharedData.flashUpdates) {
await flashComponent(component, appRecord.backend)
}
// Update component inspector
if (ctx.currentInspectedComponentId === id) {
await sendSelectedComponentData(appRecord, ctx.currentInspectedComponentId, ctx)
}
// Update tree (tags)
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, id)) {
await sendComponentTreeData(appRecord, id, appRecord.componentFilter, 0, false, ctx)
}
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
hook.on(HookEvents.COMPONENT_ADDED, async (app, uid, parentUid, component) => {
try {
if (!app || (typeof uid !== 'number' && !uid) || !component) {
return
}
const now = Date.now()
const id = await getComponentId(app, uid, component, ctx)
throttleQueue.add(`add:${id}`, async () => {
try {
const appRecord = await getAppRecord(app, ctx)
if (component) {
if (component.__VUE_DEVTOOLS_UID__ == null) {
component.__VUE_DEVTOOLS_UID__ = id
}
if (appRecord?.instanceMap) {
if (!appRecord.instanceMap.has(id)) {
appRecord.instanceMap.set(id, component)
}
}
}
if (parentUid != null && appRecord?.instanceMap) {
const parentInstances = await appRecord.backend.api.walkComponentParents(component)
if (parentInstances.length) {
// Check two parents level to update `hasChildren
for (let i = 0; i < parentInstances.length; i++) {
const parentId = await getComponentId(app, parentUid, parentInstances[i], ctx)
if (i < 2 && isSubscribed(BridgeSubscriptions.COMPONENT_TREE, parentId)) {
raf(() => {
sendComponentTreeData(appRecord, parentId, appRecord.componentFilter, null, false, ctx)
})
}
if (SharedData.trackUpdates) {
sendComponentUpdateTracking(parentId, now, ctx)
}
}
}
}
if (ctx.currentInspectedComponentId === id) {
await sendSelectedComponentData(appRecord, id, ctx)
}
if (SharedData.trackUpdates) {
sendComponentUpdateTracking(id, now, ctx)
}
if (SharedData.flashUpdates) {
await flashComponent(component, appRecord.backend)
}
await refreshComponentTreeSearch(ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
hook.on(HookEvents.COMPONENT_REMOVED, async (app, uid, parentUid, component) => {
try {
if (!app || (typeof uid !== 'number' && !uid) || !component) {
return
}
const id = await getComponentId(app, uid, component, ctx)
throttleQueue.add(`remove:${id}`, async () => {
try {
const appRecord = await getAppRecord(app, ctx)
if (parentUid != null && appRecord) {
const parentInstances = await appRecord.backend.api.walkComponentParents(component)
if (parentInstances.length) {
const parentId = await getComponentId(app, parentUid, parentInstances[0], ctx)
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, parentId)) {
raf(async () => {
try {
const appRecord = await getAppRecord(app, ctx)
if (appRecord) {
sendComponentTreeData(appRecord, parentId, appRecord.componentFilter, null, false, ctx)
}
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
}
}
}
if (isSubscribed(BridgeSubscriptions.SELECTED_COMPONENT_DATA, id)) {
await sendEmptyComponentData(id, ctx)
}
if (appRecord) {
appRecord.instanceMap.delete(id)
}
await refreshComponentTreeSearch(ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
hook.on(HookEvents.TRACK_UPDATE, (id, ctx) => {
sendComponentUpdateTracking(id, Date.now(), ctx)
})
hook.on(HookEvents.FLASH_UPDATE, (instance, backend) => {
flashComponent(instance, backend)
})
// Component perf
hook.on(HookEvents.PERFORMANCE_START, (app, uid, vm, type, time) => {
performanceMarkStart(app, uid, vm, type, time, ctx)
})
hook.on(HookEvents.PERFORMANCE_END, (app, uid, vm, type, time) => {
performanceMarkEnd(app, uid, vm, type, time, ctx)
})
// Highlighter
hook.on(HookEvents.COMPONENT_HIGHLIGHT, (instanceId) => {
highlight(ctx.currentAppRecord.instanceMap.get(instanceId), ctx.currentAppRecord.backend, ctx)
})
hook.on(HookEvents.COMPONENT_UNHIGHLIGHT, () => {
unHighlight()
})
// Timeline
setupTimeline(ctx)
hook.on(HookEvents.TIMELINE_LAYER_ADDED, async (options: TimelineLayerOptions, plugin: Plugin) => {
const appRecord = await getAppRecord(plugin.descriptor.app, ctx)
if (appRecord) {
ctx.timelineLayers.push({
...options,
appRecord,
plugin,
events: [],
})
ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_LAYER_ADD, {})
}
})
hook.on(HookEvents.TIMELINE_EVENT_ADDED, async (options: TimelineEventOptions, plugin: Plugin) => {
await addTimelineEvent(options, plugin.descriptor.app, ctx)
})
// Custom inspectors
hook.on(HookEvents.CUSTOM_INSPECTOR_ADD, async (options: CustomInspectorOptions, plugin: Plugin) => {
const appRecord = await getAppRecord(plugin.descriptor.app, ctx)
if (appRecord) {
ctx.customInspectors.push({
...options,
appRecord,
plugin,
treeFilter: '',
selectedNodeId: null,
})
ctx.bridge.send(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_ADD, {})
}
})
hook.on(HookEvents.CUSTOM_INSPECTOR_SEND_TREE, async (inspectorId: string, plugin: Plugin) => {
const inspector = getInspector(inspectorId, plugin.descriptor.app, ctx)
if (inspector) {
await sendInspectorTree(inspector, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
hook.on(HookEvents.CUSTOM_INSPECTOR_SEND_STATE, async (inspectorId: string, plugin: Plugin) => {
const inspector = getInspector(inspectorId, plugin.descriptor.app, ctx)
if (inspector) {
await sendInspectorState(inspector, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
hook.on(HookEvents.CUSTOM_INSPECTOR_SELECT_NODE, async (inspectorId: string, nodeId: string, plugin: Plugin) => {
const inspector = getInspector(inspectorId, plugin.descriptor.app, ctx)
if (inspector) {
await selectInspectorNode(inspector, nodeId, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
// Plugins
try {
await addPreviouslyRegisteredPlugins(ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(`Error adding previously registered plugins:`)
console.error(e)
}
}
try {
await addQueuedPlugins(ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(`Error adding queued plugins:`)
console.error(e)
}
}
hook.on(HookEvents.SETUP_DEVTOOLS_PLUGIN, async (pluginDescriptor: PluginDescriptor, setupFn: SetupFunction) => {
await addPlugin({ pluginDescriptor, setupFn }, ctx)
})
target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ = true
// Legacy flush
const handleFlush = debounce(async () => {
if (ctx.currentAppRecord?.backend.options.features.includes(BuiltinBackendFeature.FLUSH)) {
await sendComponentTreeData(ctx.currentAppRecord, '_root', ctx.currentAppRecord.componentFilter, null, false, ctx)
if (ctx.currentInspectedComponentId) {
await sendSelectedComponentData(ctx.currentAppRecord, ctx.currentInspectedComponentId, ctx)
}
}
}, 500)
hook.off(HookEvents.FLUSH)
hook.on(HookEvents.FLUSH, handleFlush)
// Connect done
try {
await addTimelineMarker({
id: 'vue-devtools-init-backend',
time: now(),
label: 'Vue Devtools connected',
color: 0x41B883,
all: true,
}, ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(`Error while adding devtools connected timeline marker:`)
console.error(e)
}
}
}
function connectBridge() {
// Subscriptions
ctx.bridge.on(BridgeEvents.TO_BACK_SUBSCRIBE, ({ type, key }) => {
subscribe(type, key)
})
ctx.bridge.on(BridgeEvents.TO_BACK_UNSUBSCRIBE, ({ type, key }) => {
unsubscribe(type, key)
})
// Tabs
ctx.bridge.on(BridgeEvents.TO_BACK_TAB_SWITCH, async (tab) => {
ctx.currentTab = tab
await unHighlight()
})
// Apps
ctx.bridge.on(BridgeEvents.TO_BACK_APP_LIST, async () => {
await sendApps(ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_APP_SELECT, async (id) => {
if (id == null) {
return
}
const record = ctx.appRecords.find(r => r.id === id)
if (record) {
await selectApp(record, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`App with id ${id} not found`)
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_SCAN_LEGACY_APPS, () => {
if (hook.Vue) {
_legacy_getAndRegisterApps(ctx)
}
})
// Components
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_TREE, async ({ instanceId, filter, recursively }) => {
ctx.currentAppRecord.componentFilter = filter
subscribe(BridgeSubscriptions.COMPONENT_TREE, instanceId)
await sendComponentTreeData(ctx.currentAppRecord, instanceId, filter, null, recursively, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_SELECTED_DATA, async (instanceId) => {
await sendSelectedComponentData(ctx.currentAppRecord, instanceId, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_EDIT_STATE, async ({ instanceId, dotPath, type, value, newKey, remove }) => {
await editComponentState(instanceId, dotPath, type, { value, newKey, remove }, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_INSPECT_DOM, async ({ instanceId }) => {
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
if (instance) {
const [el] = await ctx.currentAppRecord.backend.api.getComponentRootElements(instance)
if (el) {
target.__VUE_DEVTOOLS_INSPECT_TARGET__ = el
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_INSPECT_DOM, null)
}
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_SCROLL_TO, async ({ instanceId }) => {
if (!isBrowser) {
return
}
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
if (instance) {
const [el] = await ctx.currentAppRecord.backend.api.getComponentRootElements(instance)
if (el) {
if (typeof el.scrollIntoView === 'function') {
el.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
})
}
else {
// Handle nodes that don't implement scrollIntoView
const bounds = await ctx.currentAppRecord.backend.api.getComponentBounds(instance)
const scrollTarget = document.createElement('div')
scrollTarget.style.position = 'absolute'
scrollTarget.style.width = `${bounds.width}px`
scrollTarget.style.height = `${bounds.height}px`
scrollTarget.style.top = `${bounds.top}px`
scrollTarget.style.left = `${bounds.left}px`
document.body.appendChild(scrollTarget)
scrollTarget.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
})
setTimeout(() => {
document.body.removeChild(scrollTarget)
}, 2000)
}
highlight(instance, ctx.currentAppRecord.backend, ctx)
setTimeout(() => {
unHighlight()
}, 2000)
}
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_RENDER_CODE, async ({ instanceId }) => {
if (!isBrowser) {
return
}
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
if (instance) {
const { code } = await ctx.currentAppRecord.backend.api.getComponentRenderCode(instance)
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_RENDER_CODE, {
instanceId,
code,
})
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_STATE_ACTION, async ({ value, actionIndex }) => {
const rawAction = value._custom.actions[actionIndex]
const action = revive(rawAction?.action)
if (action) {
try {
await action()
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
}
else if (SharedData.debugInfo) {
console.warn(`Couldn't revive action ${actionIndex} from`, value)
}
})
// Highlighter
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_MOUSE_OVER, async (instanceId) => {
await highlight(ctx.currentAppRecord.instanceMap.get(instanceId), ctx.currentAppRecord.backend, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_MOUSE_OUT, async () => {
await unHighlight()
})
// Component picker
const componentPicker = new ComponentPicker(ctx)
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_PICK, () => {
componentPicker.startSelecting()
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_PICK_CANCELED, () => {
componentPicker.stopSelecting()
})
// Timeline
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_LAYER_LIST, async () => {
await sendTimelineLayers(ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_SHOW_SCREENSHOT, async ({ screenshot }) => {
await showScreenshot(screenshot, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_CLEAR, async () => {
await clearTimeline(ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_EVENT_DATA, async ({ id }) => {
await sendTimelineEventData(id, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_LAYER_LOAD_EVENTS, async ({ appId, layerId }) => {
await sendTimelineLayerEvents(appId, layerId, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_LOAD_MARKERS, async () => {
await sendTimelineMarkers(ctx)
})
// Custom inspectors
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_LIST, async () => {
await sendCustomInspectors(ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_TREE, async ({ inspectorId, appId, treeFilter }) => {
const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)
if (inspector) {
inspector.treeFilter = treeFilter
sendInspectorTree(inspector, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_STATE, async ({ inspectorId, appId, nodeId }) => {
const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)
if (inspector) {
inspector.selectedNodeId = nodeId
sendInspectorState(inspector, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_EDIT_STATE, async ({ inspectorId, appId, nodeId, path, type, payload }) => {
const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)
if (inspector) {
await editInspectorState(inspector, nodeId, path, type, payload, ctx)
inspector.selectedNodeId = nodeId
await sendInspectorState(inspector, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_ACTION, async ({ inspectorId, appId, actionIndex, actionType, args }) => {
const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)
if (inspector) {
const action = inspector[actionType ?? 'actions'][actionIndex]
try {
await action.action(...(args ?? []))
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
// Misc
ctx.bridge.on(BridgeEvents.TO_BACK_LOG, (payload: { level: string, value: any, serialized?: boolean, revive?: boolean }) => {
let value = payload.value
if (payload.serialized) {
value = parse(value, payload.revive)
}
else if (payload.revive) {
value = revive(value)
}
// eslint-disable-next-line no-console
console[payload.level](value)
})
// Plugins
ctx.bridge.on(BridgeEvents.TO_BACK_DEVTOOLS_PLUGIN_LIST, async () => {
await sendPluginList(ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_DEVTOOLS_PLUGIN_SETTING_UPDATED, ({ pluginId, key, newValue, oldValue }) => {
const settings = getPluginSettings(pluginId)
ctx.hook.emit(HookEvents.PLUGIN_SETTINGS_SET, pluginId, settings)
ctx.currentAppRecord.backend.api.callHook(Hooks.SET_PLUGIN_SETTINGS, {
app: ctx.currentAppRecord.options.app,
pluginId,
key,
newValue,
oldValue,
settings,
})
})
ctx.bridge.send(BridgeEvents.TO_FRONT_TITLE, { title: document.title })
// Watch page title
const titleEl = document.querySelector('title')
if (titleEl && typeof MutationObserver !== 'undefined') {
if (pageTitleObserver) {
pageTitleObserver.disconnect()
}
pageTitleObserver = new MutationObserver((mutations) => {
const title = mutations[0].target as HTMLTitleElement
ctx.bridge.send(BridgeEvents.TO_FRONT_TITLE, { title: title.textContent })
})
pageTitleObserver.observe(titleEl, { subtree: true, characterData: true, childList: true })
}
}
================================================
FILE: packages/app-backend-core/src/inspector.ts
================================================
import type { App } from '@vue/devtools-api'
import type { BackendContext, CustomInspector } from '@vue-devtools/app-backend-api'
import { BridgeEvents, parse, stringify } from '@vue-devtools/shared-utils'
export function getInspector(inspectorId: string, app: App, ctx: BackendContext) {
return ctx.customInspectors.find(i => i.id === inspectorId && i.appRecord.options.app === app)
}
export async function getInspectorWithAppId(inspectorId: string, appId: string, ctx: BackendContext): Promise<CustomInspector> {
for (const i of ctx.customInspectors) {
if (i.id === inspectorId && i.appRecord.id === appId) {
return i
}
}
return null
}
export async function sendInspectorTree(inspector: CustomInspector, ctx: BackendContext) {
const rootNodes = await inspector.appRecord.backend.api.getInspectorTree(inspector.id, inspector.appRecord.options.app, inspector.treeFilter)
ctx.bridge.send(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_TREE, {
appId: inspector.appRecord.id,
inspectorId: inspector.id,
rootNodes,
})
}
export async function sendInspectorState(inspector: CustomInspector, ctx: BackendContext) {
const state = inspector.selectedNodeId ? await inspector.appRecord.backend.api.getInspectorState(inspector.id, inspector.appRecord.options.app, inspector.selectedNodeId) : null
ctx.bridge.send(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_STATE, {
appId: inspector.appRecord.id,
inspectorId: inspector.id,
state: stringify(state),
})
}
export async function editInspectorState(inspector: CustomInspector, nodeId: string, dotPath: string, type: string, state: any, _ctx: BackendContext) {
await inspector.appRecord.backend.api.editInspectorState(inspector.id, inspector.appRecord.options.app, nodeId, dotPath, type, {
...state,
value: state.value != null ? parse(state.value, true) : state.value,
})
}
export async function sendCustomInspectors(ctx: BackendContext) {
const inspectors = []
for (const i of ctx.customInspectors) {
inspectors.push({
id: i.id,
appId: i.appRecord.id,
pluginId: i.plugin.descriptor.id,
label: i.label,
icon: i.icon,
treeFilterPlaceholder: i.treeFilterPlaceholder,
stateFilterPlaceholder: i.stateFilterPlaceholder,
noSelectionText: i.noSelectionText,
actions: i.actions?.map(a => ({
icon: a.icon,
tooltip: a.tooltip,
})),
nodeActions: i.nodeActions?.map(a => ({
icon: a.icon,
tooltip: a.tooltip,
})),
})
}
ctx.bridge.send(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_LIST, {
inspectors,
})
}
export async function selectInspectorNode(inspector: CustomInspector, nodeId: string, ctx: BackendContext) {
ctx.bridge.send(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_SELECT_NODE, {
appId: inspector.appRecord.id,
inspectorId: inspector.id,
nodeId,
})
}
================================================
FILE: packages/app-backend-core/src/legacy/scan.ts
================================================
import { isBrowser, target } from '@vue-devtools/shared-utils'
import { getPageConfig } from '../page-config'
const rootInstances = []
/**
* Scan the page for root level Vue instances.
*/
export function scan() {
rootInstances.length = 0
let inFragment = false
let currentFragment = null
function processInstance(instance) {
if (instance) {
if (!rootInstances.includes(instance.$root)) {
instance = instance.$root
}
if (instance._isFragment) {
inFragment = true
currentFragment = instance
}
// respect Vue.config.devtools option
let baseVue = instance.constructor
while (baseVue.super) {
baseVue = baseVue.super
}
if (baseVue.config && baseVue.config.devtools) {
rootInstances.push(instance)
}
return true
}
}
if (isBrowser) {
const walkDocument = (document) => {
walk(document, (node) => {
if (inFragment) {
if (node === currentFragment._fragmentEnd) {
inFragment = false
currentFragment = null
}
return true
}
const instance = node.__vue__
return processInstance(instance)
})
}
walkDocument(document)
const iframes = document.querySelectorAll<HTMLIFrameElement>('iframe')
for (const iframe of iframes) {
try {
walkDocument(iframe.contentDocument)
}
catch (e) {
// Ignore
}
}
// Scan for Vue instances in the customTarget elements
const { customVue2ScanSelector } = getPageConfig()
const customTargets = customVue2ScanSelector ? document.querySelectorAll(customVue2ScanSelector) : []
for (const customTarget of customTargets) {
try {
walkDocument(customTarget)
}
catch (e) {
// Ignore
}
}
}
else {
if (Array.isArray(target.__VUE_ROOT_INSTANCES__)) {
target.__VUE_ROOT_INSTANCES__.map(processInstance)
}
}
return rootInstances
}
/**
* DOM walk helper
*
* @param {NodeList} nodes
* @param {Function} fn
*/
function walk(node, fn) {
if (node.childNodes) {
for (let i = 0, l = node.childNodes.length; i < l; i++) {
const child = node.childNodes[i]
const stop = fn(child)
if (!stop) {
walk(child, fn)
}
}
}
// also walk shadow DOM
if (node.shadowRoot) {
walk(node.shadowRoot, fn)
}
}
================================================
FILE: packages/app-backend-core/src/page-config.ts
================================================
import { SharedData, target } from '@vue-devtools/shared-utils'
export interface PageConfig {
openInEditorHost?: string
defaultSelectedAppId?: string
customVue2ScanSelector?: string
}
let config: PageConfig = {}
export function getPageConfig(): PageConfig {
return config
}
export function initOnPageConfig() {
// User project devtools config
if (Object.hasOwnProperty.call(target, 'VUE_DEVTOOLS_CONFIG')) {
config = SharedData.pageConfig = target.VUE_DEVTOOLS_CONFIG
// Open in editor
if (Object.hasOwnProperty.call(config, 'openInEditorHost')) {
SharedData.openInEditorHost = config.openInEditorHost
}
}
}
================================================
FILE: packages/app-backend-core/src/perf.ts
================================================
import type { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api'
import type { App, ComponentInstance } from '@vue/devtools-api'
import { BridgeSubscriptions, SharedData, raf } from '@vue-devtools/shared-utils'
import { addTimelineEvent } from './timeline'
import { getAppRecord } from './app'
import { getComponentId, sendComponentTreeData } from './component'
import { isSubscribed } from './util/subscriptions'
const markEndQueue = new Map<string, {
app: App
uid: number
instance: ComponentInstance
type: string
time: number
}>()
export async function performanceMarkStart(
app: App,
uid: number,
instance: ComponentInstance,
type: string,
time: number,
ctx: BackendContext,
) {
try {
if (!SharedData.performanceMonitoringEnabled) {
return
}
const appRecord = await getAppRecord(app, ctx)
if (!appRecord) {
return
}
const componentName = await appRecord.backend.api.getComponentName(instance)
const groupId = ctx.perfUniqueGroupId++
const groupKey = `${uid}-${type}`
appRecord.perfGroupIds.set(groupKey, { groupId, time })
await addTimelineEvent({
layerId: 'performance',
event: {
time,
data: {
component: componentName,
type,
measure: 'start',
},
title: componentName,
subtitle: type,
groupId,
},
}, app, ctx)
if (markEndQueue.has(groupKey)) {
const {
app,
uid,
instance,
type,
time,
} = markEndQueue.get(groupKey)
markEndQueue.delete(groupKey)
await performanceMarkEnd(
app,
uid,
instance,
type,
time,
ctx,
)
}
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
}
export async function performanceMarkEnd(
app: App,
uid: number,
instance: ComponentInstance,
type: string,
time: number,
ctx: BackendContext,
) {
try {
if (!SharedData.performanceMonitoringEnabled) {
return
}
const appRecord = await getAppRecord(app, ctx)
if (!appRecord) {
return
}
const componentName = await appRecord.backend.api.getComponentName(instance)
const groupKey = `${uid}-${type}`
const groupInfo = appRecord.perfGroupIds.get(groupKey)
if (!groupInfo) {
markEndQueue.set(groupKey, {
app,
uid,
instance,
type,
time,
})
return
}
const { groupId, time: startTime } = groupInfo
const duration = time - startTime
await addTimelineEvent({
layerId: 'performance',
event: {
time,
data: {
component: componentName,
type,
measure: 'end',
duration: {
_custom: {
type: 'Duration',
value: duration,
display: `${duration} ms`,
},
},
},
title: componentName,
subtitle: type,
groupId,
},
}, app, ctx)
// Mark on component
const tooSlow = duration > 10
if (tooSlow || instance.__VUE_DEVTOOLS_SLOW__) {
let change = false
if (tooSlow && !instance.__VUE_DEVTOOLS_SLOW__) {
instance.__VUE_DEVTOOLS_SLOW__ = {
duration: null,
measures: {},
}
}
const data = instance.__VUE_DEVTOOLS_SLOW__
if (tooSlow && (data.duration == null || data.duration < duration)) {
data.duration = duration
change = true
}
if (data.measures[type] == null || data.measures[type] < duration) {
data.measures[type] = duration
change = true
}
if (change) {
// Update component tree
const id = await getComponentId(app, uid, instance, ctx)
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, id)) {
raf(() => {
sendComponentTreeData(appRecord, id, ctx.currentAppRecord.componentFilter, null, false, ctx)
})
}
}
}
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
}
export function handleAddPerformanceTag(backend: DevtoolsBackend, _ctx: BackendContext) {
backend.api.on.visitComponentTree((payload) => {
if (payload.componentInstance.__VUE_DEVTOOLS_SLOW__) {
const { duration, measures } = payload.componentInstance.__VUE_DEVTOOLS_SLOW__
let tooltip = '<div class="grid grid-cols-2 gap-2 font-mono text-xs">'
for (const type in measures) {
const d = measures[type]
tooltip += `<div>${type}</div><div class="text-right text-black rounded px-1 ${d > 30 ? 'bg-red-400' : d > 10 ? 'bg-yellow-400' : 'bg-green-400'}">${Math.round(d * 1000) / 1000} ms</div>`
}
tooltip += '</div>'
payload.treeNode.tags.push({
backgroundColor: duration > 30 ? 0xF87171 : 0xFBBF24,
textColor: 0x000000,
label: `${Math.round(duration * 1000) / 1000} ms`,
tooltip,
})
}
})
}
================================================
FILE: packages/app-backend-core/src/plugin.ts
================================================
import type { PluginQueueItem } from '@vue/devtools-api'
import type { BackendContext, Plugin } from '@vue-devtools/app-backend-api'
import { DevtoolsPluginApiInstance } from '@vue-devtools/app-backend-api'
import { BridgeEvents, SharedData, target } from '@vue-devtools/shared-utils'
import { getAppRecord, getAppRecordId } from './app'
export async function addPlugin(pluginQueueItem: PluginQueueItem, ctx: BackendContext) {
const { pluginDescriptor, setupFn } = pluginQueueItem
const plugin: Plugin = {
descriptor: pluginDescriptor,
setupFn,
error: null,
}
ctx.currentPlugin = plugin
try {
const appRecord = await getAppRecord(plugin.descriptor.app, ctx)
if (!appRecord) {
return
}
const api = new DevtoolsPluginApiInstance(plugin, appRecord, ctx)
if (pluginQueueItem.proxy) {
await pluginQueueItem.proxy.setRealTarget(api)
}
else {
setupFn(api)
}
}
catch (e) {
plugin.error = e
if (SharedData.debugInfo) {
console.error(e)
}
}
ctx.currentPlugin = null
ctx.plugins.push(plugin)
ctx.bridge.send(BridgeEvents.TO_FRONT_DEVTOOLS_PLUGIN_ADD, {
plugin: await serializePlugin(plugin),
})
const targetList = target.__VUE_DEVTOOLS_REGISTERED_PLUGINS__ = target.__VUE_DEVTOOLS_REGISTERED_PLUGINS__ || []
targetList.push({
pluginDescriptor,
setupFn,
})
}
export async function addQueuedPlugins(ctx: BackendContext) {
if (target.__VUE_DEVTOOLS_PLUGINS__ && Array.isArray(target.__VUE_DEVTOOLS_PLUGINS__)) {
for (const queueItem of target.__VUE_DEVTOOLS_PLUGINS__) {
await addPlugin(queueItem, ctx)
}
target.__VUE_DEVTOOLS_PLUGINS__ = null
}
}
export async function addPreviouslyRegisteredPlugins(ctx: BackendContext) {
if (target.__VUE_DEVTOOLS_REGISTERED_PLUGINS__ && Array.isArray(target.__VUE_DEVTOOLS_REGISTERED_PLUGINS__)) {
for (const queueItem of target.__VUE_DEVTOOLS_REGISTERED_PLUGINS__) {
await addPlugin(queueItem, ctx)
}
}
}
export async function sendPluginList(ctx: BackendContext) {
ctx.bridge.send(BridgeEvents.TO_FRONT_DEVTOOLS_PLUGIN_LIST, {
plugins: await Promise.all(ctx.plugins.map(p => serializePlugin(p))),
})
}
export async function serializePlugin(plugin: Plugin) {
return {
id: plugin.descriptor.id,
label: plugin.descriptor.label,
appId: getAppRecordId(plugin.descriptor.app),
packageName: plugin.descriptor.packageName,
homepage: plugin.descriptor.homepage,
logo: plugin.descriptor.logo,
componentStateTypes: plugin.descriptor.componentStateTypes,
settingsSchema: plugin.descriptor.settings,
}
}
================================================
FILE: packages/app-backend-core/src/timeline-builtins.ts
================================================
import type { TimelineLayerOptions } from '@vue/devtools-api'
export const builtinLayers: TimelineLayerOptions[] = [
{
id: 'mouse',
label: 'Mouse',
color: 0xA451AF,
screenshotOverlayRender(event, { events }) {
const samePositionEvent = events.find(e => e !== event && e.renderMeta.textEl && e.data.x === event.data.x && e.data.y === event.data.y)
if (samePositionEvent) {
const text = document.createElement('div')
text.textContent = event.data.type
samePositionEvent.renderMeta.textEl.appendChild(text)
return false
}
const div = document.createElement('div')
div.style.position = 'absolute'
div.style.left = `${event.data.x - 4}px`
div.style.top = `${event.data.y - 4}px`
div.style.width = '8px'
div.style.height = '8px'
div.style.borderRadius = '100%'
div.style.backgroundColor = 'rgba(164, 81, 175, 0.5)'
const text = document.createElement('div')
text.textContent = event.data.type
text.style.color = '#541e5b'
text.style.fontFamily = 'monospace'
text.style.fontSize = '9px'
text.style.position = 'absolute'
text.style.left = '10px'
text.style.top = '10px'
text.style.padding = '1px'
text.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'
text.style.borderRadius = '3px'
div.appendChild(text)
event.renderMeta.textEl = text
return div
},
},
{
id: 'keyboard',
label: 'Keyboard',
color: 0x8151AF,
},
{
id: 'component-event',
label: 'Component events',
color: 0x41B883,
screenshotOverlayRender: (event, { events }) => {
if (!event.meta.bounds || events.some(e => e !== event && e.layerId === event.layerId && e.renderMeta.drawn && (e.meta.componentId === event.meta.componentId || (
e.meta.bounds.left === event.meta.bounds.left
&& e.meta.bounds.top === event.meta.bounds.top
&& e.meta.bounds.width === event.meta.bounds.width
&& e.meta.bounds.height === event.meta.bounds.height
)))) {
return false
}
const div = document.createElement('div')
div.style.position = 'absolute'
div.style.left = `${event.meta.bounds.left - 4}px`
div.style.top = `${event.meta.bounds.top - 4}px`
div.style.width = `${event.meta.bounds.width}px`
div.style.height = `${event.meta.bounds.height}px`
div.style.borderRadius = '8px'
div.style.borderStyle = 'solid'
div.style.borderWidth = '4px'
div.style.borderColor = 'rgba(65, 184, 131, 0.5)'
div.style.textAlign = 'center'
div.style.display = 'flex'
div.style.alignItems = 'center'
div.style.justifyContent = 'center'
div.style.overflow = 'hidden'
const text = document.createElement('div')
text.style.color = '#267753'
text.style.fontFamily = 'monospace'
text.style.fontSize = '9px'
text.style.padding = '1px'
text.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'
text.style.borderRadius = '3px'
text.textContent = event.data.event
div.appendChild(text)
event.renderMeta.drawn = true
return div
},
},
{
id: 'performance',
label: 'Performance',
color: 0x41B86A,
groupsOnly: true,
skipScreenshots: true,
ignoreNoDurationGroups: true,
},
]
================================================
FILE: packages/app-backend-core/src/timeline-marker.ts
================================================
import type { BackendContext, TimelineMarker } from '@vue-devtools/app-backend-api'
import { BridgeEvents, SharedData } from '@vue-devtools/shared-utils'
import type { TimelineMarkerOptions } from '@vue/devtools-api'
import { isPerformanceSupported } from '@vue/devtools-api'
import { dateThreshold, perfTimeDiff } from './timeline'
export async function addTimelineMarker(options: TimelineMarkerOptions, ctx: BackendContext) {
if (!SharedData.timelineRecording) {
return
}
if (!ctx.currentAppRecord) {
options.all = true
}
const marker: TimelineMarker = {
...options,
appRecord: options.all ? null : ctx.currentAppRecord,
}
ctx.timelineMarkers.push(marker)
ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_MARKER, {
marker: await serializeMarker(marker),
appId: ctx.currentAppRecord?.id,
})
}
export async function sendTimelineMarkers(ctx: BackendContext) {
if (!SharedData.timelineRecording) {
return
}
if (!ctx.currentAppRecord) {
return
}
const markers = ctx.timelineMarkers.filter(marker => marker.all || marker.appRecord === ctx.currentAppRecord)
const result = []
for (const marker of markers) {
result.push(await serializeMarker(marker))
}
ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_LOAD_MARKERS, {
markers: result,
appId: ctx.currentAppRecord.id,
})
}
async function serializeMarker(marker: TimelineMarker) {
let time = marker.time
if (isPerformanceSupported() && time < dateThreshold) {
time += perfTimeDiff
}
return {
id: marker.id,
appId: marker.appRecord?.id,
all: marker.all,
time: Math.round(time * 1000),
label: marker.label,
color: marker.color,
}
}
================================================
FILE: packages/app-backend-core/src/timeline-screenshot.ts
================================================
import type { BackendContext } from '@vue-devtools/app-backend-api'
import type { ID, ScreenshotOverlayRenderContext } from '@vue/devtools-api'
import { SharedData } from '@vue-devtools/shared-utils'
import { JobQueue } from './util/queue'
import { builtinLayers } from './timeline-builtins'
let overlay: HTMLDivElement
let image: HTMLImageElement
let container: HTMLDivElement
const jobQueue = new JobQueue()
interface Screenshot {
id: ID
time: number
image: string
events: ID[]
}
export async function showScreenshot(screenshot: Screenshot, ctx: BackendContext) {
await jobQueue.queue('showScreenshot', async () => {
if (screenshot) {
if (!container) {
createElements()
}
image.src = screenshot.image
image.style.visibility = screenshot.image ? 'visible' : 'hidden'
clearContent()
const events = screenshot.events.map(id => ctx.timelineEventMap.get(id)).filter(Boolean).map(eventData => ({
layer: builtinLayers.concat(ctx.timelineLayers).find(layer => layer.id === eventData.layerId),
event: {
...eventData.event,
layerId: eventData.layerId,
renderMeta: {},
},
}))
const renderContext: ScreenshotOverlayRenderContext = {
screenshot,
events: events.map(({ event }) => event),
index: 0,
}
for (let i = 0; i < events.length; i++) {
const { layer, event } = events[i]
if (layer.screenshotOverlayRender) {
renderContext.index = i
try {
const result = await layer.screenshotOverlayRender(event, renderContext)
if (result !== false) {
if (typeof result === 'string') {
container.innerHTML += result
}
else {
container.appendChild(result)
}
}
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
}
}
showElement()
}
else {
hideElement()
}
})
}
function createElements() {
overlay = document.createElement('div')
overlay.style.position = 'fixed'
overlay.style.zIndex = '9999999999999'
overlay.style.pointerEvents = 'none'
overlay.style.left = '0'
overlay.style.top = '0'
overlay.style.width = '100vw'
overlay.style.height = '100vh'
overlay.style.backgroundColor = 'rgba(0,0,0,0.5)'
overlay.style.overflow = 'hidden'
const imageBox = document.createElement('div')
imageBox.style.position = 'relative'
overlay.appendChild(imageBox)
image = document.createElement('img')
imageBox.appendChild(image)
container = document.createElement('div')
container.style.position = 'absolute'
container.style.left = '0'
container.style.top = '0'
imageBox.appendChild(container)
const style = document.createElement('style')
style.innerHTML = '.__vuedevtools_no-scroll { overflow: hidden; }'
document.head.appendChild(style)
}
function showElement() {
if (!overlay.parentNode) {
document.body.appendChild(overlay)
document.body.classList.add('__vuedevtools_no-scroll')
}
}
function hideElement() {
if (overlay && overlay.parentNode) {
overlay.parentNode.removeChild(overlay)
document.body.classList.remove('__vuedevtools_no-scroll')
clearContent()
}
}
function clearContent() {
while (container.firstChild) {
container.removeChild(container.lastChild)
}
}
================================================
FILE: packages/app-backend-core/src/timeline.ts
================================================
import type { AppRecord, BackendContext } from '@vue-devtools/app-backend-api'
import { BridgeEvents, HookEvents, SharedData, isBrowser, stringify } from '@vue-devtools/shared-utils'
import type { App, ID, TimelineEventOptions, WithId } from '@vue/devtools-api'
import { isPerformanceSupported, now } from '@vue/devtools-api'
import { hook } from './global-hook'
import { getAppRecord, getAppRecordId } from './app'
import { builtinLayers } from './timeline-builtins'
export function setupTimeline(ctx: BackendContext) {
setupBuiltinLayers(ctx)
}
export function addBuiltinLayers(appRecord: AppRecord, ctx: BackendContext) {
for (const layerDef of builtinLayers) {
ctx.timelineLayers.push({
...layerDef,
appRecord,
plugin: null,
events: [],
})
}
}
function setupBuiltinLayers(ctx: BackendContext) {
if (isBrowser) {
(['mousedown', 'mouseup', 'click', 'dblclick'] as const).forEach((eventType) => {
window.addEventListener(eventType, async (event: MouseEvent) => {
await addTimelineEvent({
layerId: 'mouse',
event: {
time: now(),
data: {
type: eventType,
x: event.clientX,
y: event.clientY,
},
title: eventType,
},
}, null, ctx)
}, {
capture: true,
passive: true,
})
})
;(['keyup', 'keydown', 'keypress'] as const).forEach((eventType) => {
window.addEventListener(eventType, async (event: KeyboardEvent) => {
await addTimelineEvent({
layerId: 'keyboard',
event: {
time: now(),
data: {
type: eventType,
key: event.key,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey,
},
title: event.key,
},
}, null, ctx)
}, {
capture: true,
passive: true,
})
})
}
hook.on(HookEvents.COMPONENT_EMIT, async (app, instance, event, params) => {
try {
if (!SharedData.componentEventsEnabled) {
return
}
const appRecord = await getAppRecord(app, ctx)
if (!appRecord) {
return
}
const componentId = `${appRecord.id}:${instance.uid}`
const componentDisplay = (await appRecord.backend.api.getComponentName(instance)) || '<i>Unknown Component</i>'
await addTimelineEvent({
layerId: 'component-event',
event: {
time: now(),
data: {
component: {
_custom: {
type: 'component-definition',
display: componentDisplay,
},
},
event,
params,
},
title: event,
subtitle: `by ${componentDisplay}`,
meta: {
componentId,
bounds: await appRecord.backend.api.getComponentBounds(instance),
},
},
}, app, ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
}
export async function sendTimelineLayers(ctx: BackendContext) {
const layers = []
for (const layer of ctx.timelineLayers) {
try {
layers.push({
id: layer.id,
label: layer.label,
color: layer.color,
appId: layer.appRecord?.id,
pluginId: layer.plugin?.descriptor.id,
groupsOnly: layer.groupsOnly,
skipScreenshots: layer.skipScreenshots,
ignoreNoDurationGroups: layer.ignoreNoDurationGroups,
})
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
}
ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_LAYER_LIST, {
layers,
})
}
export async function addTimelineEvent(options: TimelineEventOptions, app: App, ctx: BackendContext) {
if (!SharedData.timelineRecording) {
return
}
const appId = app ? getAppRecordId(app) : null
const isAllApps = options.all || !app || appId == null
const id = ctx.nextTimelineEventId++
const eventData: TimelineEventOptions & WithId = {
id,
...options,
all: isAllApps,
}
ctx.timelineEventMap.set(eventData.id, eventData)
ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_EVENT, {
appId: eventData.all ? 'all' : appId,
layerId: eventData.layerId,
event: mapTimeline
gitextract_85f6hf0f/ ├── .browserslistrc ├── .circleci/ │ └── config.yml ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── feature_request.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── commit-convention.md │ └── workflows/ │ └── create-release.yml ├── .gitignore ├── .vscode/ │ └── settings.json ├── LICENSE ├── README.md ├── babel.config.js ├── cypress/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── fixtures/ │ │ └── example.json │ ├── integration/ │ │ ├── component-data-edit.js │ │ ├── components-tab.js │ │ ├── events-tab.js │ │ ├── vuex-edit.js │ │ └── vuex-tab.js │ ├── plugins/ │ │ └── index.js │ ├── support/ │ │ ├── commands.js │ │ └── index.js │ └── utils/ │ └── suite.js ├── cypress.json ├── eslint.config.js ├── extension-zips.js ├── lerna.json ├── package.json ├── packages/ │ ├── api/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── api/ │ │ │ │ ├── api.ts │ │ │ │ ├── app.ts │ │ │ │ ├── component.ts │ │ │ │ ├── context.ts │ │ │ │ ├── hooks.ts │ │ │ │ ├── index.ts │ │ │ │ └── util.ts │ │ │ ├── const.ts │ │ │ ├── env.ts │ │ │ ├── index.ts │ │ │ ├── plugin.ts │ │ │ ├── proxy.ts │ │ │ └── time.ts │ │ └── tsconfig.json │ ├── app-backend-api/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── api.ts │ │ │ ├── app-record.ts │ │ │ ├── backend-context.ts │ │ │ ├── backend.ts │ │ │ ├── global-hook.ts │ │ │ ├── hooks.ts │ │ │ ├── index.ts │ │ │ └── plugin.ts │ │ └── tsconfig.json │ ├── app-backend-core/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.ts │ │ │ ├── backend.ts │ │ │ ├── component-pick.ts │ │ │ ├── component.ts │ │ │ ├── flash.ts │ │ │ ├── global-hook.ts │ │ │ ├── highlighter.ts │ │ │ ├── hook.ts │ │ │ ├── index.ts │ │ │ ├── inspector.ts │ │ │ ├── legacy/ │ │ │ │ └── scan.ts │ │ │ ├── page-config.ts │ │ │ ├── perf.ts │ │ │ ├── plugin.ts │ │ │ ├── timeline-builtins.ts │ │ │ ├── timeline-marker.ts │ │ │ ├── timeline-screenshot.ts │ │ │ ├── timeline.ts │ │ │ ├── toast.ts │ │ │ └── util/ │ │ │ ├── queue.ts │ │ │ └── subscriptions.ts │ │ └── tsconfig.json │ ├── app-backend-vue1/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── app-backend-vue2/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── data.ts │ │ │ │ ├── el.ts │ │ │ │ ├── perf.ts │ │ │ │ ├── tree.ts │ │ │ │ ├── update-tracking.ts │ │ │ │ └── util.ts │ │ │ ├── events.ts │ │ │ ├── index.ts │ │ │ └── plugin.ts │ │ └── tsconfig.json │ ├── app-backend-vue3/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── data.ts │ │ │ │ ├── el.ts │ │ │ │ ├── filter.ts │ │ │ │ ├── tree.ts │ │ │ │ └── util.ts │ │ │ ├── index.ts │ │ │ └── util.ts │ │ └── tsconfig.json │ ├── app-frontend/ │ │ ├── package.json │ │ └── src/ │ │ ├── app.ts │ │ ├── assets/ │ │ │ ├── github-theme/ │ │ │ │ ├── dark.json │ │ │ │ └── light.json │ │ │ └── style/ │ │ │ ├── imports.styl │ │ │ ├── index.postcss │ │ │ ├── index.styl │ │ │ ├── transitions.styl │ │ │ └── variables.styl │ │ ├── features/ │ │ │ ├── App.vue │ │ │ ├── apps/ │ │ │ │ ├── AppSelect.vue │ │ │ │ ├── AppSelectItem.vue │ │ │ │ ├── AppSelectPane.vue │ │ │ │ ├── AppSelectPaneItem.vue │ │ │ │ ├── index.ts │ │ │ │ └── vue-version-check.ts │ │ │ ├── bridge/ │ │ │ │ └── index.ts │ │ │ ├── chrome/ │ │ │ │ ├── index.ts │ │ │ │ └── pane-visibility.ts │ │ │ ├── code/ │ │ │ │ └── CodeEditor.vue │ │ │ ├── components/ │ │ │ │ ├── ComponentTreeNode.vue │ │ │ │ ├── ComponentsInspector.vue │ │ │ │ ├── RenderCode.vue │ │ │ │ ├── SelectedComponentPane.vue │ │ │ │ └── composable/ │ │ │ │ ├── components.ts │ │ │ │ ├── highlight.ts │ │ │ │ ├── index.ts │ │ │ │ ├── pick.ts │ │ │ │ └── setup.ts │ │ │ ├── connection/ │ │ │ │ ├── AppConnecting.vue │ │ │ │ ├── AppDisconnected.vue │ │ │ │ └── index.ts │ │ │ ├── error/ │ │ │ │ ├── ErrorOverlay.vue │ │ │ │ └── index.ts │ │ │ ├── header/ │ │ │ │ ├── AppHeader.vue │ │ │ │ ├── AppHeaderSelect.vue │ │ │ │ ├── AppHistoryNav.vue │ │ │ │ ├── header.ts │ │ │ │ └── tabs.ts │ │ │ ├── inspector/ │ │ │ │ ├── DataField.vue │ │ │ │ ├── StateFields.vue │ │ │ │ ├── StateInspector.vue │ │ │ │ ├── StateType.vue │ │ │ │ └── custom/ │ │ │ │ ├── CustomInspector.vue │ │ │ │ ├── CustomInspectorNode.vue │ │ │ │ ├── CustomInspectorSelectedNodePane.vue │ │ │ │ └── composable.ts │ │ │ ├── layout/ │ │ │ │ ├── EmptyPane.vue │ │ │ │ ├── SplitPane.vue │ │ │ │ └── orientation.ts │ │ │ ├── plugin/ │ │ │ │ ├── PluginDetails.vue │ │ │ │ ├── PluginHome.vue │ │ │ │ ├── PluginListItem.vue │ │ │ │ ├── PluginPermission.vue │ │ │ │ ├── PluginSettings.vue │ │ │ │ ├── PluginSettingsItem.vue │ │ │ │ ├── PluginSourceDescription.vue │ │ │ │ ├── PluginSourceIcon.vue │ │ │ │ ├── Plugins.vue │ │ │ │ └── index.ts │ │ │ ├── settings/ │ │ │ │ ├── GlobalSettings.vue │ │ │ │ └── NewTag.vue │ │ │ ├── timeline/ │ │ │ │ ├── AskScreenshotPermission.vue │ │ │ │ ├── LayerItem.vue │ │ │ │ ├── Timeline.vue │ │ │ │ ├── TimelineEventInspector.vue │ │ │ │ ├── TimelineEventList.vue │ │ │ │ ├── TimelineEventListItem.vue │ │ │ │ ├── TimelineScrollbar.vue │ │ │ │ ├── TimelineView.vue │ │ │ │ └── composable/ │ │ │ │ ├── events.ts │ │ │ │ ├── index.ts │ │ │ │ ├── layers.ts │ │ │ │ ├── markers.ts │ │ │ │ ├── reset.ts │ │ │ │ ├── screenshot.ts │ │ │ │ ├── setup.ts │ │ │ │ ├── store.ts │ │ │ │ └── time.ts │ │ │ ├── ui/ │ │ │ │ ├── components/ │ │ │ │ │ ├── VueButton.vue │ │ │ │ │ ├── VueDisable.vue │ │ │ │ │ ├── VueDropdown.vue │ │ │ │ │ ├── VueDropdownButton.vue │ │ │ │ │ ├── VueFormField.vue │ │ │ │ │ ├── VueGroup.vue │ │ │ │ │ ├── VueGroupButton.vue │ │ │ │ │ ├── VueIcon.vue │ │ │ │ │ ├── VueInput.vue │ │ │ │ │ ├── VueLoadingBar.vue │ │ │ │ │ ├── VueLoadingIndicator.vue │ │ │ │ │ ├── VueModal.vue │ │ │ │ │ ├── VueSelect.vue │ │ │ │ │ ├── VueSelectButton.vue │ │ │ │ │ ├── VueSwitch.vue │ │ │ │ │ └── icons.ts │ │ │ │ ├── composables/ │ │ │ │ │ ├── useDisableScroll.ts │ │ │ │ │ └── useDisabled.ts │ │ │ │ └── index.ts │ │ │ └── welcome/ │ │ │ └── WelcomeSlideshow.vue │ │ ├── index.ts │ │ ├── locales/ │ │ │ └── en.js │ │ ├── mixins/ │ │ │ ├── data-field-edit.js │ │ │ ├── entry-list.ts │ │ │ └── keyboard.ts │ │ ├── plugins/ │ │ │ ├── global-refs.ts │ │ │ ├── i18n.ts │ │ │ ├── index.ts │ │ │ └── responsive.ts │ │ ├── router.ts │ │ ├── shims-global.d.ts │ │ ├── shims-vue.d.ts │ │ ├── types/ │ │ │ └── vue.d.ts │ │ └── util/ │ │ ├── color.ts │ │ ├── defer.ts │ │ ├── fonts.ts │ │ ├── format/ │ │ │ ├── index.ts │ │ │ ├── time.ts │ │ │ └── value.ts │ │ ├── keyboard.ts │ │ ├── queue.ts │ │ ├── reactivity.ts │ │ ├── shared-data.ts │ │ ├── theme.ts │ │ └── time.ts │ ├── build-tools/ │ │ ├── package.json │ │ └── src/ │ │ ├── createConfig.js │ │ └── index.js │ ├── docs/ │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── src/ │ │ │ ├── .vitepress/ │ │ │ │ ├── .gitignore │ │ │ │ ├── config.js │ │ │ │ └── theme/ │ │ │ │ ├── custom.css │ │ │ │ └── index.js │ │ │ ├── assets/ │ │ │ │ └── vue-devtools-architecture.drawio │ │ │ ├── components/ │ │ │ │ ├── InstallButton.vue │ │ │ │ └── InstallButtons.vue │ │ │ ├── devtools-vue3.md │ │ │ ├── guide/ │ │ │ │ ├── contributing.md │ │ │ │ ├── custom-vue2-app-scan-selector.md │ │ │ │ ├── devtools-perf.md │ │ │ │ ├── faq.md │ │ │ │ ├── installation.md │ │ │ │ └── open-in-editor.md │ │ │ ├── index.md │ │ │ ├── plugin/ │ │ │ │ ├── api-reference.md │ │ │ │ └── plugins-guide.md │ │ │ └── release.md │ │ └── tailwind.config.cjs │ ├── shared-utils/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── backend.ts │ │ │ ├── bridge.ts │ │ │ ├── consts.ts │ │ │ ├── edit.ts │ │ │ ├── env.ts │ │ │ ├── index.ts │ │ │ ├── plugin-permissions.ts │ │ │ ├── plugin-settings.ts │ │ │ ├── raf.ts │ │ │ ├── shared-data.ts │ │ │ ├── shell.ts │ │ │ ├── storage.ts │ │ │ ├── throttle.ts │ │ │ ├── transfer.ts │ │ │ └── util.ts │ │ └── tsconfig.json │ ├── shell-chrome/ │ │ ├── devtools-background.html │ │ ├── devtools.html │ │ ├── manifest.json │ │ ├── package.json │ │ ├── popups/ │ │ │ ├── disabled.html │ │ │ ├── disabled.nuxt.html │ │ │ ├── enabled.html │ │ │ ├── enabled.nuxt.html │ │ │ ├── not-found.html │ │ │ └── popup.css │ │ ├── src/ │ │ │ ├── backend.js │ │ │ ├── detector-exec.js │ │ │ ├── detector.js │ │ │ ├── devtools-background.js │ │ │ ├── devtools.js │ │ │ ├── hook-exec.js │ │ │ ├── hook.js │ │ │ ├── proxy.js │ │ │ └── service-worker.js │ │ └── webpack.config.js │ ├── shell-dev-vue2/ │ │ ├── package.json │ │ ├── public/ │ │ │ ├── target-electron.html │ │ │ ├── target-iframe.html │ │ │ └── target.html │ │ ├── src/ │ │ │ ├── Child.vue │ │ │ ├── Counter.vue │ │ │ ├── EventChild.vue │ │ │ ├── EventChild1.vue │ │ │ ├── EventChildCond.vue │ │ │ ├── Events.vue │ │ │ ├── Functional.vue │ │ │ ├── Hidden.vue │ │ │ ├── Init.vue │ │ │ ├── MyClass.js │ │ │ ├── NativeTypes.vue │ │ │ ├── NoProp.vue │ │ │ ├── Other.vue │ │ │ ├── RefTester.vue │ │ │ ├── Target.vue │ │ │ ├── TransitionExample.vue │ │ │ ├── VuexObject.vue │ │ │ ├── dynamic-module.js │ │ │ ├── iframe-app.js │ │ │ ├── index.js │ │ │ ├── router/ │ │ │ │ ├── ChildRoute.vue │ │ │ │ ├── NamedRoute.vue │ │ │ │ ├── ParentRoute.vue │ │ │ │ ├── RouteOne.vue │ │ │ │ ├── RouteTwo.vue │ │ │ │ ├── RouteWithAlias.vue │ │ │ │ ├── RouteWithBeforeEnter.vue │ │ │ │ ├── RouteWithParams.vue │ │ │ │ ├── RouteWithProps.vue │ │ │ │ ├── RouteWithQuery.vue │ │ │ │ └── Router.vue │ │ │ ├── router.js │ │ │ └── store.js │ │ └── webpack.config.js │ ├── shell-dev-vue3/ │ │ ├── package.json │ │ ├── public/ │ │ │ ├── target-iframe.html │ │ │ └── target.html │ │ ├── src/ │ │ │ ├── Animation.vue │ │ │ ├── App.vue │ │ │ ├── App3.vue │ │ │ ├── AsyncComponent.vue │ │ │ ├── AsyncSetup.vue │ │ │ ├── Child.vue │ │ │ ├── Condition.vue │ │ │ ├── DomOrder.vue │ │ │ ├── EventEmit.vue │ │ │ ├── EventNesting.vue │ │ │ ├── Form.vue │ │ │ ├── FormSection.vue │ │ │ ├── Functional.vue │ │ │ ├── Ghost.vue │ │ │ ├── Heavy.vue │ │ │ ├── Hello.vue │ │ │ ├── IframeApp.vue │ │ │ ├── IndexComponent/ │ │ │ │ └── index.vue │ │ │ ├── Mixins.vue │ │ │ ├── NativeTypes.vue │ │ │ ├── Nested.vue │ │ │ ├── NestedMore.vue │ │ │ ├── Other.vue │ │ │ ├── Provide.vue │ │ │ ├── SetupDataLike.vue │ │ │ ├── SetupRender.js │ │ │ ├── SetupScript.vue │ │ │ ├── SetupTSScriptProps.vue │ │ │ ├── SuspenseExample.vue │ │ │ ├── VModelExample.vue │ │ │ ├── devtools-plugin/ │ │ │ │ ├── index.js │ │ │ │ └── simple.js │ │ │ ├── iframe-app.js │ │ │ ├── main.js │ │ │ ├── router/ │ │ │ │ ├── Page1.vue │ │ │ │ └── Page2.vue │ │ │ └── store.js │ │ └── webpack.config.js │ ├── shell-electron/ │ │ ├── README.md │ │ ├── app.html │ │ ├── app.js │ │ ├── bin.js │ │ ├── index.js │ │ ├── package.json │ │ ├── server.js │ │ ├── src/ │ │ │ ├── backend.js │ │ │ ├── devtools.js │ │ │ └── hook.js │ │ ├── types/ │ │ │ └── index.d.ts │ │ ├── webpack.config.js │ │ └── webpack.node.config.js │ ├── shell-firefox/ │ │ ├── .gitignore │ │ ├── copy.sh │ │ ├── manifest.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── backend.js │ │ │ ├── background.js │ │ │ ├── detector.js │ │ │ ├── devtools-background.js │ │ │ ├── devtools.js │ │ │ ├── hook.js │ │ │ └── proxy.js │ │ └── webpack.config.js │ └── shell-host/ │ ├── package.json │ ├── public/ │ │ └── index.html │ ├── src/ │ │ ├── DevIframe.vue │ │ ├── backend.js │ │ ├── devtools.js │ │ └── hook.js │ └── webpack.config.js ├── postcss.config.js ├── release.js ├── sign-firefox.js ├── tailwind.config.js ├── tsconfig.json └── vue1-test.html
SYMBOL INDEX (775 symbols across 146 files)
FILE: cypress/utils/suite.js
function suite (line 1) | function suite(description, tests) {
FILE: extension-zips.js
constant IS_CI (line 7) | const IS_CI = !!(process.env.CIRCLECI || process.env.GITHUB_ACTIONS)
constant INCLUDE_GLOBS (line 11) | const INCLUDE_GLOBS = [
constant SKIP_DIR_GLOBS (line 21) | const SKIP_DIR_GLOBS = ['node_modules', 'src']
function bytesToSize (line 23) | function bytesToSize(bytes) {
function writeZip (line 36) | async function writeZip(fileName, packageDir) {
FILE: packages/api/src/api/api.ts
type DevtoolsPluginApi (line 7) | interface DevtoolsPluginApi<TSettings> {
type AppRecord (line 29) | interface AppRecord {
type TimelineLayerOptions (line 36) | interface TimelineLayerOptions<TData = any, TMeta = any> {
type ScreenshotOverlayEvent (line 46) | interface ScreenshotOverlayEvent {
type ScreenshotOverlayRenderContext (line 51) | interface ScreenshotOverlayRenderContext<TData = any, TMeta = any> {
type ScreenshotOverlayRenderResult (line 57) | type ScreenshotOverlayRenderResult = HTMLElement | string | false
type ScreenshotData (line 59) | interface ScreenshotData {
type TimelineEventOptions (line 63) | interface TimelineEventOptions {
type TimelineEvent (line 69) | interface TimelineEvent<TData = any, TMeta = any> {
type TimelineMarkerOptions (line 79) | interface TimelineMarkerOptions {
type CustomInspectorOptions (line 87) | interface CustomInspectorOptions {
type CustomInspectorNode (line 106) | interface CustomInspectorNode {
type InspectorNodeTag (line 113) | interface InspectorNodeTag {
type CustomInspectorState (line 120) | interface CustomInspectorState {
FILE: packages/api/src/api/app.ts
type App (line 1) | type App = any // @TODO
FILE: packages/api/src/api/component.ts
type ComponentInstance (line 4) | type ComponentInstance = any // @TODO
type ComponentTreeNode (line 6) | interface ComponentTreeNode {
type InspectedComponentData (line 24) | interface InspectedComponentData {
type StateBase (line 32) | interface StateBase {
type ComponentStateBase (line 40) | interface ComponentStateBase extends StateBase {
type ComponentPropState (line 44) | interface ComponentPropState extends ComponentStateBase {
type ComponentBuiltinCustomStateTypes (line 53) | type ComponentBuiltinCustomStateTypes = 'function' | 'map' | 'set' | 're...
type ComponentCustomState (line 55) | interface ComponentCustomState extends ComponentStateBase {
type CustomState (line 59) | interface CustomState {
type ComponentState (line 85) | type ComponentState = ComponentStateBase | ComponentPropState | Componen...
type ComponentDevtoolsOptions (line 87) | interface ComponentDevtoolsOptions {
FILE: packages/api/src/api/context.ts
type Context (line 3) | interface Context {
FILE: packages/api/src/api/hooks.ts
type Hooks (line 6) | const enum Hooks {
type ComponentBounds (line 31) | interface ComponentBounds {
type HookPayloads (line 38) | interface HookPayloads {
type EditStatePayload (line 152) | type EditStatePayload = {
type HookHandler (line 162) | type HookHandler<TPayload, TContext> = (payload: TPayload, ctx: TContext...
type Hookable (line 164) | interface Hookable<TContext> {
FILE: packages/api/src/api/util.ts
type ID (line 1) | type ID = number | string
type WithId (line 3) | interface WithId {
FILE: packages/api/src/const.ts
constant HOOK_SETUP (line 1) | const HOOK_SETUP = 'devtools-plugin:setup'
constant HOOK_PLUGIN_SETTINGS_SET (line 2) | const HOOK_PLUGIN_SETTINGS_SET = 'plugin:settings:set'
FILE: packages/api/src/env.ts
type PluginQueueItem (line 4) | interface PluginQueueItem {
type GlobalTarget (line 10) | interface GlobalTarget {
function getDevtoolsGlobalHook (line 15) | function getDevtoolsGlobalHook(): any {
function getTarget (line 19) | function getTarget(): GlobalTarget {
FILE: packages/api/src/index.ts
type Cast (line 13) | type Cast<A, B> = A extends B ? A : B
type Narrowable (line 14) | type Narrowable =
type Narrow (line 19) | type Narrow<A> = Cast<A, | []
type Exact (line 25) | type Exact<C, T> = {
type SetupFunction (line 29) | type SetupFunction<TSettings = any> = (api: DevtoolsPluginApi<TSettings>...
function setupDevtoolsPlugin (line 31) | function setupDevtoolsPlugin<
FILE: packages/api/src/plugin.ts
type PluginDescriptor (line 3) | interface PluginDescriptor {
type PluginSettingsItem (line 21) | type PluginSettingsItem = {
type InferSettingsType (line 37) | type InferSettingsType<
type ExtractSettingsTypes (line 47) | type ExtractSettingsTypes<
FILE: packages/api/src/proxy.ts
type QueueItem (line 6) | interface QueueItem {
class ApiProxy (line 12) | class ApiProxy<TTarget extends DevtoolsPluginApi<any> = DevtoolsPluginAp...
method constructor (line 24) | constructor(plugin: PluginDescriptor, hook: any) {
method setRealTarget (line 125) | async setRealTarget(target: TTarget) {
FILE: packages/api/src/time.ts
function isPerformanceSupported (line 4) | function isPerformanceSupported() {
function now (line 22) | function now() {
FILE: packages/app-backend-api/src/api.ts
class DevtoolsApi (line 38) | class DevtoolsApi {
method constructor (line 45) | constructor(backend: DevtoolsBackend, ctx: BackendContext) {
method callHook (line 52) | async callHook<T extends Hooks>(eventType: T, payload: HookPayloads[T]...
method transformCall (line 60) | async transformCall(callName: string, ...args) {
method getAppRecordName (line 69) | async getAppRecordName(app: App, defaultName: string): Promise<string> {
method getAppRootInstance (line 82) | async getAppRootInstance(app: App) {
method registerApplication (line 90) | async registerApplication(app: App) {
method walkComponentTree (line 96) | async walkComponentTree(instance: ComponentInstance, maxDepth = -1, fi...
method visitComponentTree (line 107) | async visitComponentTree(instance: ComponentInstance, treeNode: Compon...
method walkComponentParents (line 117) | async walkComponentParents(instance: ComponentInstance) {
method inspectComponent (line 125) | async inspectComponent(instance: ComponentInstance, app: App) {
method getComponentBounds (line 134) | async getComponentBounds(instance: ComponentInstance) {
method getComponentName (line 142) | async getComponentName(instance: ComponentInstance) {
method getComponentInstances (line 150) | async getComponentInstances(app: App) {
method getElementComponent (line 158) | async getElementComponent(element: HTMLElement | any) {
method getComponentRootElements (line 166) | async getComponentRootElements(instance: ComponentInstance) {
method editComponentState (line 174) | async editComponentState(instance: ComponentInstance, dotPath: string,...
method getComponentDevtoolsOptions (line 187) | async getComponentDevtoolsOptions(instance: ComponentInstance): Promis...
method getComponentRenderCode (line 195) | async getComponentRenderCode(instance: ComponentInstance): Promise<{
method inspectTimelineEvent (line 207) | async inspectTimelineEvent(eventData: TimelineEventOptions & WithId, a...
method clearTimeline (line 218) | async clearTimeline() {
method getInspectorTree (line 222) | async getInspectorTree(inspectorId: string, app: App, filter: string) {
method getInspectorState (line 232) | async getInspectorState(inspectorId: string, app: App, nodeId: string) {
method editInspectorState (line 242) | async editInspectorState(inspectorId: string, app: App, nodeId: string...
method now (line 255) | now() {
class DevtoolsPluginApiInstance (line 260) | class DevtoolsPluginApiInstance<TSettings = any> implements DevtoolsPlug...
method constructor (line 269) | constructor(plugin: Plugin, appRecord: AppRecord, ctx: BackendContext) {
method notifyComponentUpdate (line 282) | async notifyComponentUpdate(instance: ComponentInstance = null) {
method addTimelineLayer (line 295) | addTimelineLayer(options: TimelineLayerOptions) {
method addTimelineEvent (line 304) | addTimelineEvent(options: TimelineEventOptions) {
method addInspector (line 313) | addInspector(options: CustomInspectorOptions) {
method sendInspectorTree (line 322) | sendInspectorTree(inspectorId: string) {
method sendInspectorState (line 331) | sendInspectorState(inspectorId: string) {
method selectInspectorNode (line 340) | selectInspectorNode(inspectorId: string, nodeId: string) {
method getComponentBounds (line 349) | getComponentBounds(instance: ComponentInstance) {
method getComponentName (line 353) | getComponentName(instance: ComponentInstance) {
method getComponentInstances (line 357) | getComponentInstances(app: App) {
method highlightElement (line 361) | highlightElement(instance: ComponentInstance) {
method unhighlightElement (line 370) | unhighlightElement() {
method getSettings (line 379) | getSettings(pluginId?: string) {
method setSettings (line 383) | setSettings(value: TSettings, pluginId?: string) {
method now (line 387) | now() {
method enabled (line 391) | private get enabled() {
method hasPermission (line 395) | private hasPermission(permission: PluginPermission) {
FILE: packages/app-backend-api/src/app-record.ts
type AppRecordOptions (line 4) | interface AppRecordOptions {
type AppRecord (line 11) | interface AppRecord {
type SimpleAppRecord (line 29) | interface SimpleAppRecord {
FILE: packages/app-backend-api/src/backend-context.ts
type BackendContext (line 15) | interface BackendContext {
type TimelineLayer (line 33) | interface TimelineLayer extends TimelineLayerOptions {
type TimelineMarker (line 39) | interface TimelineMarker extends TimelineMarkerOptions {
type CustomInspector (line 43) | interface CustomInspector extends CustomInspectorOptions {
type CreateBackendContextOptions (line 50) | interface CreateBackendContextOptions {
function createBackendContext (line 55) | function createBackendContext(options: CreateBackendContextOptions): Bac...
FILE: packages/app-backend-api/src/backend.ts
type BuiltinBackendFeature (line 5) | enum BuiltinBackendFeature {
type DevtoolsBackendOptions (line 12) | interface DevtoolsBackendOptions {
function defineBackend (line 19) | function defineBackend(options: DevtoolsBackendOptions) {
type DevtoolsBackend (line 23) | interface DevtoolsBackend {
function createBackend (line 28) | function createBackend(options: DevtoolsBackendOptions, ctx: BackendCont...
FILE: packages/app-backend-api/src/global-hook.ts
type DevtoolsHook (line 3) | interface DevtoolsHook {
FILE: packages/app-backend-api/src/hooks.ts
type Handler (line 7) | type Handler<TPayload> = HookHandler<TPayload, BackendContext>
type HookHandlerData (line 9) | interface HookHandlerData<THandlerPayload> {
class DevtoolsHookable (line 14) | class DevtoolsHookable implements Hookable<BackendContext> {
method constructor (line 19) | constructor(ctx: BackendContext, plugin: Plugin = null) {
method hook (line 24) | private hook<T extends Hooks>(eventType: T, handler: Handler<HookPaylo...
method callHandlers (line 53) | async callHandlers<T extends Hooks>(eventType: T, payload: HookPayload...
method transformCall (line 72) | transformCall(handler: Handler<HookPayloads[Hooks.TRANSFORM_CALL]>) {
method getAppRecordName (line 76) | getAppRecordName(handler: Handler<HookPayloads[Hooks.GET_APP_RECORD_NA...
method getAppRootInstance (line 80) | getAppRootInstance(handler: Handler<HookPayloads[Hooks.GET_APP_ROOT_IN...
method registerApplication (line 84) | registerApplication(handler: Handler<HookPayloads[Hooks.REGISTER_APPLI...
method walkComponentTree (line 88) | walkComponentTree(handler: Handler<HookPayloads[Hooks.WALK_COMPONENT_T...
method visitComponentTree (line 92) | visitComponentTree(handler: Handler<HookPayloads[Hooks.VISIT_COMPONENT...
method walkComponentParents (line 96) | walkComponentParents(handler: Handler<HookPayloads[Hooks.WALK_COMPONEN...
method inspectComponent (line 100) | inspectComponent(handler: Handler<HookPayloads[Hooks.INSPECT_COMPONENT...
method getComponentBounds (line 104) | getComponentBounds(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_B...
method getComponentName (line 108) | getComponentName(handler: Handler<HookPayloads[Hooks.GET_COMPONENT_NAM...
method getComponentInstances (line 112) | getComponentInstances(handler: Handler<HookPayloads[Hooks.GET_COMPONEN...
method getElementComponent (line 116) | getElementComponent(handler: Handler<HookPayloads[Hooks.GET_ELEMENT_CO...
method getComponentRootElements (line 120) | getComponentRootElements(handler: Handler<HookPayloads[Hooks.GET_COMPO...
method editComponentState (line 124) | editComponentState(handler: Handler<HookPayloads[Hooks.EDIT_COMPONENT_...
method getComponentDevtoolsOptions (line 128) | getComponentDevtoolsOptions(handler: Handler<HookPayloads[Hooks.GET_CO...
method getComponentRenderCode (line 132) | getComponentRenderCode(handler: Handler<HookPayloads[Hooks.GET_COMPONE...
method inspectTimelineEvent (line 136) | inspectTimelineEvent(handler: Handler<HookPayloads[Hooks.INSPECT_TIMEL...
method timelineCleared (line 140) | timelineCleared(handler: Handler<HookPayloads[Hooks.TIMELINE_CLEARED]>) {
method getInspectorTree (line 144) | getInspectorTree(handler: Handler<HookPayloads[Hooks.GET_INSPECTOR_TRE...
method getInspectorState (line 148) | getInspectorState(handler: Handler<HookPayloads[Hooks.GET_INSPECTOR_ST...
method editInspectorState (line 152) | editInspectorState(handler: Handler<HookPayloads[Hooks.EDIT_INSPECTOR_...
method setPluginSettings (line 156) | setPluginSettings(handler: Handler<HookPayloads[Hooks.SET_PLUGIN_SETTI...
FILE: packages/app-backend-api/src/plugin.ts
type Plugin (line 3) | interface Plugin {
FILE: packages/app-backend-core/src/app.ts
type AppRecordResolver (line 22) | type AppRecordResolver = (record: AppRecord) => void | Promise<void>
function registerApp (line 25) | async function registerApp(options: AppRecordOptions, ctx: BackendContex...
function registerAppJob (line 29) | async function registerAppJob(options: AppRecordOptions, ctx: BackendCon...
function createAppRecord (line 54) | async function createAppRecord(options: AppRecordOptions, backend: Devto...
function selectApp (line 139) | async function selectApp(record: AppRecord, ctx: BackendContext) {
function mapAppRecord (line 148) | function mapAppRecord(record: AppRecord): SimpleAppRecord {
function getAppRecordId (line 159) | function getAppRecordId(app, defaultId?: string): string {
function getAppRecord (line 179) | async function getAppRecord(app: any, ctx: BackendContext): Promise<AppR...
function waitForAppsRegistration (line 217) | function waitForAppsRegistration() {
function sendApps (line 221) | async function sendApps(ctx: BackendContext) {
function removeAppRecord (line 233) | function removeAppRecord(appRecord: AppRecord, ctx: BackendContext) {
function removeApp (line 250) | async function removeApp(app: App, ctx: BackendContext) {
function _legacy_getAndRegisterApps (line 266) | function _legacy_getAndRegisterApps(ctx: BackendContext, clear = false) {
FILE: packages/app-backend-core/src/backend.ts
function getBackend (line 18) | function getBackend(backendOptions: DevtoolsBackendOptions, ctx: Backend...
FILE: packages/app-backend-core/src/component-pick.ts
class ComponentPicker (line 6) | class ComponentPicker {
method constructor (line 11) | constructor(ctx: BackendContext) {
method startSelecting (line 19) | startSelecting() {
method stopSelecting (line 35) | stopSelecting() {
method elementMouseOver (line 53) | async elementMouseOver(e: MouseEvent) {
method selectElementComponent (line 67) | async selectElementComponent(el) {
method elementClicked (line 83) | async elementClicked(e: MouseEvent) {
method cancelEvent (line 100) | cancelEvent(e: MouseEvent) {
method bindMethods (line 108) | bindMethods() {
FILE: packages/app-backend-core/src/component.ts
function sendComponentTreeData (line 10) | async function sendComponentTreeData(appRecord: AppRecord, instanceId: s...
function sendSelectedComponentData (line 48) | async function sendSelectedComponentData(appRecord: AppRecord, instanceI...
function markSelectedInstance (line 88) | function markSelectedInstance(instanceId: string, ctx: BackendContext) {
function sendEmptyComponentData (line 93) | function sendEmptyComponentData(instanceId: string, ctx: BackendContext) {
function editComponentState (line 100) | async function editComponentState(instanceId: string, dotPath: string, t...
function getComponentId (line 114) | async function getComponentId(app: App, uid: number, instance: Component...
function getComponentInstance (line 134) | function getComponentInstance(appRecord: AppRecord, instanceId: string, ...
function refreshComponentTreeSearch (line 148) | async function refreshComponentTreeSearch(ctx: BackendContext) {
function sendComponentUpdateTracking (line 157) | function sendComponentUpdateTracking(instanceId: string, time: number, c...
FILE: packages/app-backend-core/src/flash.ts
function flashComponent (line 4) | async function flashComponent(instance: ComponentInstance, backend: Devt...
FILE: packages/app-backend-core/src/highlighter.ts
function createOverlay (line 10) | function createOverlay() {
function highlight (line 39) | async function highlight(instance: ComponentInstance, backend: DevtoolsB...
function unHighlight (line 82) | async function unHighlight() {
function showOverlay (line 92) | function showOverlay(bounds: ComponentBounds, children: Node[] = null) {
function positionOverlay (line 107) | function positionOverlay({ width = 0, height = 0, top = 0, left = 0 }) {
function positionOverlayContent (line 114) | function positionOverlayContent({ height = 0, top = 0, left = 0 }) {
function updateOverlay (line 139) | async function updateOverlay(backend: DevtoolsBackend, _ctx: BackendCont...
function startUpdateTimer (line 157) | function startUpdateTimer(backend: DevtoolsBackend, ctx: BackendContext) {
function stopUpdateTimer (line 166) | function stopUpdateTimer() {
FILE: packages/app-backend-core/src/hook.ts
function installHook (line 11) | function installHook(target, isIframe = false) {
FILE: packages/app-backend-core/src/index.ts
function initBackend (line 60) | async function initBackend(bridge: Bridge) {
function connect (line 109) | async function connect() {
function connectBridge (line 464) | function connectBridge() {
FILE: packages/app-backend-core/src/inspector.ts
function getInspector (line 5) | function getInspector(inspectorId: string, app: App, ctx: BackendContext) {
function getInspectorWithAppId (line 9) | async function getInspectorWithAppId(inspectorId: string, appId: string,...
function sendInspectorTree (line 18) | async function sendInspectorTree(inspector: CustomInspector, ctx: Backen...
function sendInspectorState (line 27) | async function sendInspectorState(inspector: CustomInspector, ctx: Backe...
function editInspectorState (line 36) | async function editInspectorState(inspector: CustomInspector, nodeId: st...
function sendCustomInspectors (line 43) | async function sendCustomInspectors(ctx: BackendContext) {
function selectInspectorNode (line 70) | async function selectInspectorNode(inspector: CustomInspector, nodeId: s...
FILE: packages/app-backend-core/src/legacy/scan.ts
function scan (line 9) | function scan() {
function walk (line 93) | function walk(node, fn) {
FILE: packages/app-backend-core/src/page-config.ts
type PageConfig (line 3) | interface PageConfig {
function getPageConfig (line 11) | function getPageConfig(): PageConfig {
function initOnPageConfig (line 15) | function initOnPageConfig() {
FILE: packages/app-backend-core/src/perf.ts
function performanceMarkStart (line 17) | async function performanceMarkStart(
function performanceMarkEnd (line 78) | async function performanceMarkEnd(
function handleAddPerformanceTag (line 172) | function handleAddPerformanceTag(backend: DevtoolsBackend, _ctx: Backend...
FILE: packages/app-backend-core/src/plugin.ts
function addPlugin (line 7) | async function addPlugin(pluginQueueItem: PluginQueueItem, ctx: BackendC...
function addQueuedPlugins (line 48) | async function addQueuedPlugins(ctx: BackendContext) {
function addPreviouslyRegisteredPlugins (line 57) | async function addPreviouslyRegisteredPlugins(ctx: BackendContext) {
function sendPluginList (line 65) | async function sendPluginList(ctx: BackendContext) {
function serializePlugin (line 71) | async function serializePlugin(plugin: Plugin) {
FILE: packages/app-backend-core/src/timeline-builtins.ts
method screenshotOverlayRender (line 8) | screenshotOverlayRender(event, { events }) {
FILE: packages/app-backend-core/src/timeline-marker.ts
function addTimelineMarker (line 7) | async function addTimelineMarker(options: TimelineMarkerOptions, ctx: Ba...
function sendTimelineMarkers (line 25) | async function sendTimelineMarkers(ctx: BackendContext) {
function serializeMarker (line 43) | async function serializeMarker(marker: TimelineMarker) {
FILE: packages/app-backend-core/src/timeline-screenshot.ts
type Screenshot (line 13) | interface Screenshot {
function showScreenshot (line 20) | async function showScreenshot(screenshot: Screenshot, ctx: BackendContex...
function createElements (line 78) | function createElements() {
function showElement (line 108) | function showElement() {
function hideElement (line 115) | function hideElement() {
function clearContent (line 125) | function clearContent() {
FILE: packages/app-backend-core/src/timeline.ts
function setupTimeline (line 9) | function setupTimeline(ctx: BackendContext) {
function addBuiltinLayers (line 13) | function addBuiltinLayers(appRecord: AppRecord, ctx: BackendContext) {
function setupBuiltinLayers (line 24) | function setupBuiltinLayers(ctx: BackendContext) {
function sendTimelineLayers (line 114) | async function sendTimelineLayers(ctx: BackendContext) {
function addTimelineEvent (line 140) | async function addTimelineEvent(options: TimelineEventOptions, app: App,...
function mapTimelineEvent (line 175) | function mapTimelineEvent(eventData: TimelineEventOptions & WithId) {
function clearTimeline (line 190) | async function clearTimeline(ctx: BackendContext) {
function sendTimelineEventData (line 200) | async function sendTimelineEventData(id: ID, ctx: BackendContext) {
function removeLayersForApp (line 219) | function removeLayersForApp(app: App, ctx: BackendContext) {
function sendTimelineLayerEvents (line 232) | function sendTimelineLayerEvents(appId: string, layerId: string, ctx: Ba...
FILE: packages/app-backend-core/src/toast.ts
function installToast (line 1) | function installToast() {
FILE: packages/app-backend-core/src/util/queue.ts
type Job (line 1) | interface Job {
class JobQueue (line 6) | class JobQueue {
method queue (line 10) | queue(id: string, fn: Job['fn']) {
FILE: packages/app-backend-core/src/util/subscriptions.ts
function getSubs (line 3) | function getSubs(type: string) {
function subscribe (line 12) | function subscribe(type: string, key: string) {
function unsubscribe (line 16) | function unsubscribe(type: string, key: string) {
function isSubscribed (line 21) | function isSubscribed(
FILE: packages/app-backend-vue1/src/index.ts
method setup (line 6) | setup(_api) {
FILE: packages/app-backend-vue2/src/components/data.ts
function getInstanceDetails (line 10) | function getInstanceDetails(instance): InspectedComponentData {
function getInstanceState (line 55) | function getInstanceState(instance): ComponentState[] {
function getFunctionalInstanceState (line 70) | function getFunctionalInstanceState(instance): ComponentState[] {
function getCustomInstanceDetails (line 74) | function getCustomInstanceDetails(instance) {
function reduceStateList (line 90) | function reduceStateList(list) {
function getInstanceName (line 105) | function getInstanceName(instance): string {
function processProps (line 120) | function processProps(instance): ComponentState[] {
function processAttrs (line 144) | function processAttrs(instance): ComponentState[] {
function getPropType (line 159) | function getPropType(type) {
function processState (line 177) | function processState(instance): ComponentState[] {
function processSetupState (line 195) | function processSetupState(instance) {
function returnError (line 241) | function returnError(cb: () => any) {
function isRef (line 250) | function isRef(raw: any): boolean {
function isComputed (line 254) | function isComputed(raw: any): boolean {
function isReactive (line 258) | function isReactive(raw: any): boolean {
function isReadOnly (line 262) | function isReadOnly(raw: any): boolean {
function toRaw (line 266) | function toRaw(value: any) {
function getSetupStateInfo (line 273) | function getSetupStateInfo(raw: any) {
function getCustomObjectDetails (line 282) | function getCustomObjectDetails(object: any, _proto: string): CustomStat...
function processRefs (line 304) | function processRefs(instance): ComponentState[] {
function processComputed (line 313) | function processComputed(instance): ComponentState[] {
function processInjected (line 352) | function processInjected(instance): ComponentState[] {
function processRouteContext (line 372) | function processRouteContext(instance): ComponentState[] {
function processVuexGetters (line 412) | function processVuexGetters(instance): ComponentState[] {
function processFirebaseBindings (line 433) | function processFirebaseBindings(instance): ComponentState[] {
function processObservables (line 452) | function processObservables(instance): ComponentState[] {
function findInstanceOrVnode (line 468) | function findInstanceOrVnode(id) {
function editState (line 477) | function editState(
FILE: packages/app-backend-vue2/src/components/el.ts
function createRect (line 3) | function createRect() {
function mergeRects (line 15) | function mergeRects(a, b) {
function getInstanceOrVnodeRect (line 34) | function getInstanceOrVnodeRect(instance) {
function getLegacyFragmentRect (line 58) | function getLegacyFragmentRect({ _fragmentStart, _fragmentEnd }) {
function getTextRect (line 79) | function getTextRect(node: Text) {
function util (line 95) | function util() {
function findRelatedComponent (line 99) | function findRelatedComponent(el) {
function getElWindow (line 106) | function getElWindow(el: HTMLElement) {
function addIframePosition (line 110) | function addIframePosition(bounds, win: any) {
function getRootElementsFromComponentInstance (line 126) | function getRootElementsFromComponentInstance(instance) {
FILE: packages/app-backend-vue2/src/components/perf.ts
constant COMPONENT_HOOKS (line 5) | const COMPONENT_HOOKS = {
function initPerf (line 16) | function initPerf(api: DevtoolsApi, app, Vue) {
function applyPerfHooks (line 28) | function applyPerfHooks(api: DevtoolsApi, vm, app) {
FILE: packages/app-backend-vue2/src/components/tree.ts
function getInstanceMap (line 12) | function getInstanceMap() {
function getFunctionalVnodeMap (line 16) | function getFunctionalVnodeMap() {
function walkTree (line 33) | async function walkTree(instance, pFilter: string, pRecursively: boolean...
function getComponentParents (line 43) | function getComponentParents(instance, api: DevtoolsApi, ctx: BackendCon...
function initCtx (line 72) | function initCtx(_api: DevtoolsApi, ctx: BackendContext) {
function findQualifiedChildrenFromList (line 94) | function findQualifiedChildrenFromList(instances: any[]): Promise<Compon...
function findQualifiedChildren (line 107) | async function findQualifiedChildren(instance): Promise<ComponentTreeNod...
function getInternalInstanceChildren (line 129) | function getInternalInstanceChildren(instance): any[] {
function isQualified (line 139) | function isQualified(instance): boolean {
function flatten (line 145) | function flatten<T>(items: any[]): T[] {
function captureChild (line 168) | function captureChild(child): Promise<ComponentTreeNode[] | ComponentTre...
function capture (line 185) | async function capture(instance, _index?: number, _list?: any[]): Promis...
function mark (line 349) | function mark(instance) {
function markFunctional (line 362) | function markFunctional(id, vnode) {
FILE: packages/app-backend-vue2/src/components/update-tracking.ts
function initUpdateTracking (line 6) | function initUpdateTracking(api: DevtoolsApi, Vue) {
constant COMPONENT_HOOKS (line 15) | const COMPONENT_HOOKS = [
function applyTrackingUpdateHook (line 20) | function applyTrackingUpdateHook(api: DevtoolsApi, vm) {
FILE: packages/app-backend-vue2/src/components/util.ts
function isBeingDestroyed (line 4) | function isBeingDestroyed(instance) {
function getInstanceName (line 11) | function getInstanceName(instance) {
function getRenderKey (line 21) | function getRenderKey(value): string {
function getUniqueId (line 43) | function getUniqueId(instance, appRecord?: AppRecord): string {
FILE: packages/app-backend-vue2/src/events.ts
function wrap (line 6) | function wrap(app, Vue, method, ctx: BackendContext) {
function wrapVueForEvents (line 28) | function wrapVueForEvents(app, Vue, ctx: BackendContext) {
FILE: packages/app-backend-vue2/src/index.ts
method setup (line 18) | setup(api) {
method setupApp (line 79) | setupApp(api, appRecord) {
function injectToUtils (line 110) | function injectToUtils() {
FILE: packages/app-backend-vue2/src/plugin.ts
constant VUEX_ROOT_PATH (line 9) | const VUEX_ROOT_PATH = '__vdt_root'
constant VUEX_MODULE_PATH_SEPARATOR (line 10) | const VUEX_MODULE_PATH_SEPARATOR = '[vdt]'
constant VUEX_MODULE_PATH_SEPARATOR_REG (line 11) | const VUEX_MODULE_PATH_SEPARATOR_REG = /\[vdt\]/g
constant BLUE_600 (line 16) | const BLUE_600 = 0x2563EB
constant LIME_500 (line 17) | const LIME_500 = 0x84CC16
constant CYAN_400 (line 18) | const CYAN_400 = 0x22D3EE
constant ORANGE_400 (line 19) | const ORANGE_400 = 0xFB923C
constant WHITE (line 20) | const WHITE = 0xFFFFFF
constant DARK (line 21) | const DARK = 0x666666
function setupPlugin (line 23) | function setupPlugin(api: DevtoolsApi, app: App, Vue) {
function formatRouteNode (line 295) | function formatRouteNode(router, route, parentPath: string, filter: stri...
function formatRouteData (line 344) | function formatRouteData(route) {
function getPathId (line 384) | function getPathId(routeMatcher) {
constant TAG_NAMESPACED (line 392) | const TAG_NAMESPACED = {
function formatStoreForInspectorTree (line 398) | function formatStoreForInspectorTree(module, moduleName: string, path: s...
function flattenStoreForInspectorTree (line 416) | function flattenStoreForInspectorTree(result: CustomInspectorNode[], mod...
function extractNameFromPath (line 429) | function extractNameFromPath(path: string) {
function formatStoreForInspectorState (line 433) | function formatStoreForInspectorState(module, getters, path): CustomInsp...
function transformPathsToObjectTree (line 476) | function transformPathsToObjectTree(getters) {
function getStoreModule (line 505) | function getStoreModule(moduleMap, path) {
function canThrow (line 527) | function canThrow(cb: () => any) {
FILE: packages/app-backend-vue3/src/components/data.ts
function getInstanceDetails (line 65) | function getInstanceDetails(instance: any, ctx: BackendContext): Inspect...
function getInstanceState (line 74) | function getInstanceState(instance) {
function processProps (line 96) | function processProps(instance) {
function getPropType (line 130) | function getPropType(type) {
function processState (line 152) | function processState(instance) {
function processSetupState (line 179) | function processSetupState(instance) {
function isRef (line 230) | function isRef(raw: any): boolean {
function isComputed (line 234) | function isComputed(raw: any): boolean {
function isReactive (line 238) | function isReactive(raw: any): boolean {
function isReadOnly (line 242) | function isReadOnly(raw: any): boolean {
function toRaw (line 246) | function toRaw(value: any) {
function getSetupStateInfo (line 253) | function getSetupStateInfo(raw: any) {
function getCustomObjectDetails (line 262) | function getCustomObjectDetails(object: any, _proto: string): CustomStat...
function processComputed (line 296) | function processComputed(instance, mergedType) {
function processAttrs (line 320) | function processAttrs(instance) {
function processProvide (line 329) | function processProvide(instance) {
function processInject (line 338) | function processInject(instance, mergedType) {
function processRefs (line 374) | function processRefs(instance) {
function processEventListeners (line 383) | function processEventListeners(instance) {
function editState (line 412) | function editState({ componentInstance, path, state, type }: HookPayload...
function reduceStateList (line 444) | function reduceStateList(list) {
function getCustomInstanceDetails (line 456) | function getCustomInstanceDetails(instance) {
function resolveMergedOptions (line 475) | function resolveMergedOptions(
function mergeOptions (line 490) | function mergeOptions(
FILE: packages/app-backend-vue3/src/components/el.ts
function getComponentInstanceFromElement (line 4) | function getComponentInstanceFromElement(element) {
function getRootElementsFromComponentInstance (line 8) | function getRootElementsFromComponentInstance(instance) {
function getFragmentRootElements (line 18) | function getFragmentRootElements(vnode): any[] {
function getInstanceOrVnodeRect (line 44) | function getInstanceOrVnodeRect(instance) {
function createRect (line 66) | function createRect() {
function mergeRects (line 78) | function mergeRects(a, b) {
function getTextRect (line 101) | function getTextRect(node) {
function getFragmentRect (line 114) | function getFragmentRect(vnode) {
function getElWindow (line 143) | function getElWindow(el: HTMLElement) {
function addIframePosition (line 147) | function addIframePosition(bounds, win: any) {
FILE: packages/app-backend-vue3/src/components/filter.ts
class ComponentFilter (line 4) | class ComponentFilter {
method constructor (line 7) | constructor(filter: string) {
method isQualified (line 17) | isQualified(instance) {
FILE: packages/app-backend-vue3/src/components/tree.ts
class ComponentWalker (line 7) | class ComponentWalker {
method constructor (line 17) | constructor(maxDepth: number, filter: string, recursively: boolean, ap...
method getComponentTree (line 25) | getComponentTree(instance: any): Promise<ComponentTreeNode[]> {
method getComponentParents (line 30) | getComponentParents(instance: any) {
method findQualifiedChildren (line 51) | private async findQualifiedChildren(instance: any, depth: number): Pro...
method findQualifiedChildrenFromList (line 76) | private async findQualifiedChildrenFromList(instances, depth: number):...
method getInternalInstanceChildren (line 90) | private getInternalInstanceChildren(subTree, suspense = null) {
method captureId (line 114) | private captureId(instance): string {
method capture (line 144) | private async capture(instance: any, list: any[], depth: number): Prom...
method mark (line 238) | private mark(instance, force = false) {
method isKeepAlive (line 245) | private isKeepAlive(instance) {
method getKeepAliveCachedInstances (line 249) | private getKeepAliveCachedInstances(instance) {
FILE: packages/app-backend-vue3/src/components/util.ts
function isBeingDestroyed (line 5) | function isBeingDestroyed(instance) {
function getAppRecord (line 9) | function getAppRecord(instance) {
function isFragment (line 15) | function isFragment(instance) {
function getInstanceName (line 28) | function getInstanceName(instance) {
function saveComponentName (line 53) | function saveComponentName(instance, key) {
function getComponentTypeName (line 58) | function getComponentTypeName(options) {
function getComponentFileName (line 62) | function getComponentFileName(options) {
function getUniqueComponentId (line 73) | function getUniqueComponentId(instance, _ctx: BackendContext) {
function getRenderKey (line 79) | function getRenderKey(value): string {
function getComponentInstances (line 98) | function getComponentInstances(app: App): ComponentInstance[] {
FILE: packages/app-backend-vue3/src/index.ts
method setup (line 13) | setup(api) {
FILE: packages/app-backend-vue3/src/util.ts
function flatten (line 1) | function flatten(items) {
function returnError (line 14) | function returnError(cb: () => any) {
FILE: packages/app-frontend/src/app.ts
function createApp (line 18) | function createApp() {
function connectApp (line 42) | function connectApp(app: VueApp, shell) {
FILE: packages/app-frontend/src/features/apps/index.ts
type AppRecord (line 8) | interface AppRecord {
function useCurrentApp (line 17) | function useCurrentApp() {
function useApps (line 28) | function useApps() {
function addApp (line 55) | function addApp(app: AppRecord) {
function removeApp (line 60) | function removeApp(appId: string) {
function getApps (line 67) | function getApps() {
function fetchApps (line 71) | function fetchApps() {
function waitForAppSelect (line 79) | function waitForAppSelect(): Promise<void> {
function scanLegacyApps (line 90) | function scanLegacyApps() {
function setupAppsBridgeEvents (line 94) | function setupAppsBridgeEvents(bridge: Bridge) {
FILE: packages/app-frontend/src/features/apps/vue-version-check.ts
function useVueVersionCheck (line 6) | function useVueVersionCheck() {
FILE: packages/app-frontend/src/features/bridge/index.ts
type Sub (line 7) | interface Sub {
function useBridge (line 12) | function useBridge() {
function setBridge (line 52) | function setBridge(b: Bridge) {
function getBridge (line 56) | function getBridge(): Bridge | null {
FILE: packages/app-frontend/src/features/chrome/pane-visibility.ts
function ensurePaneShown (line 17) | function ensurePaneShown(cb: () => void | Promise<void>) {
function onPanelShown (line 26) | function onPanelShown() {
function onPanelHidden (line 34) | function onPanelHidden() {
FILE: packages/app-frontend/src/features/components/composable/components.ts
function useComponentRequests (line 31) | function useComponentRequests() {
function useComponents (line 54) | function useComponents() {
function useComponent (line 132) | function useComponent(instance: Ref<ComponentTreeNode>) {
function setComponentOpen (line 202) | function setComponentOpen(id: ComponentTreeNode['id'], isOpen: boolean) {
function isComponentOpen (line 206) | function isComponentOpen(id: ComponentTreeNode['id']) {
function useSelectedComponent (line 210) | function useSelectedComponent() {
function resetComponents (line 282) | function resetComponents() {
function requestComponentTree (line 294) | async function requestComponentTree(instanceId: ComponentTreeNode['id'] ...
function _sendTreeRequest (line 310) | function _sendTreeRequest(instanceId: ComponentTreeNode['id'], recursive...
function _queueRetryTree (line 318) | function _queueRetryTree(instanceId: ComponentTreeNode['id'], recursivel...
function _retryRequestComponentTree (line 323) | function _retryRequestComponentTree(instanceId: ComponentTreeNode['id'],...
function ensureComponentsMapData (line 332) | function ensureComponentsMapData(data: ComponentTreeNode) {
function ensureComponentsMapChildren (line 343) | function ensureComponentsMapChildren(id: string, children: ComponentTree...
function updateComponentsMapData (line 351) | function updateComponentsMapData(data: ComponentTreeNode) {
function addToComponentsMap (line 367) | function addToComponentsMap(data: ComponentTreeNode) {
function loadComponent (line 375) | async function loadComponent(id: ComponentTreeNode['id']) {
function sortChildren (line 386) | function sortChildren(children: ComponentTreeNode[]) {
function compareIndexLists (line 404) | function compareIndexLists(a: number[], b: number[]): number {
function getAppIdFromComponentId (line 416) | function getAppIdFromComponentId(id: string) {
type ComponentUpdateTrackingEvent (line 422) | interface ComponentUpdateTrackingEvent {
function addUpdateTrackingEvent (line 428) | function addUpdateTrackingEvent(instanceId: string, time: number) {
FILE: packages/app-frontend/src/features/components/composable/highlight.ts
function useComponentHighlight (line 15) | function useComponentHighlight(id: Ref<string>) {
FILE: packages/app-frontend/src/features/components/composable/pick.ts
function useComponentPick (line 6) | function useComponentPick() {
FILE: packages/app-frontend/src/features/components/composable/setup.ts
function setupComponentsBridgeEvents (line 20) | function setupComponentsBridgeEvents(bridge: Bridge) {
FILE: packages/app-frontend/src/features/connection/index.ts
function useAppConnection (line 10) | function useAppConnection() {
function setAppConnected (line 34) | function setAppConnected(value: boolean, force = false, fromReload = fal...
function setAppInitializing (line 54) | function setAppInitializing(value: boolean) {
FILE: packages/app-frontend/src/features/error/index.ts
type ErrorMessage (line 3) | interface ErrorMessage {
function putError (line 10) | function putError(message: string, icon: string = null) {
function clearError (line 22) | function clearError() {
function useError (line 26) | function useError() {
FILE: packages/app-frontend/src/features/header/tabs.ts
function useTabs (line 4) | function useTabs() {
FILE: packages/app-frontend/src/features/inspector/custom/composable.ts
type InspectorFromBackend (line 8) | interface InspectorFromBackend {
type Inspector (line 27) | interface Inspector extends InspectorFromBackend {
constant SELECTED_NODES_STORAGE (line 37) | const SELECTED_NODES_STORAGE = 'custom-inspector-selected-nodes'
function inspectorFactory (line 40) | function inspectorFactory(options: InspectorFromBackend): Inspector {
function useInspectors (line 55) | function useInspectors() {
function useCurrentInspector (line 64) | function useCurrentInspector() {
function fetchInspectors (line 133) | function fetchInspectors() {
function fetchTree (line 137) | function fetchTree(inspector: Inspector) {
function fetchState (line 148) | function fetchState(inspector: Inspector) {
function resetInspectors (line 159) | function resetInspectors() {
function setupCustomInspectorBridgeEvents (line 164) | function setupCustomInspectorBridgeEvents(bridge: Bridge) {
FILE: packages/app-frontend/src/features/layout/orientation.ts
function useOrientation (line 5) | function useOrientation() {
function switchOrientation (line 15) | function switchOrientation(mediaQueryEvent: MediaQueryListEvent | MediaQ...
FILE: packages/app-frontend/src/features/plugin/index.ts
type Plugin (line 8) | interface Plugin {
type PluginsPerApp (line 19) | interface PluginsPerApp {
function getPlugins (line 25) | function getPlugins(appId: string) {
function fetchPlugins (line 36) | function fetchPlugins() {
function usePlugins (line 40) | function usePlugins() {
function useComponentStateTypePlugin (line 50) | function useComponentStateTypePlugin() {
function addPlugin (line 62) | function addPlugin(plugin: Plugin) {
function setupPluginsBridgeEvents (line 73) | function setupPluginsBridgeEvents(bridge: Bridge) {
FILE: packages/app-frontend/src/features/timeline/composable/events.ts
constant AUTOSCROLL_DURATION (line 27) | const AUTOSCROLL_DURATION = 10_000_000
type AddEventCb (line 29) | type AddEventCb = (event: TimelineEvent) => void
function onEventAdd (line 33) | function onEventAdd(cb: AddEventCb) {
function addEvent (line 44) | function addEvent(appId: string, eventOptions: TimelineEvent, layer: Lay...
function useSelectedEvent (line 124) | function useSelectedEvent() {
function useInspectedEvent (line 131) | function useInspectedEvent() {
function loadEvent (line 149) | function loadEvent(id: TimelineEvent['id']) {
function selectEvent (line 157) | function selectEvent(event: TimelineEvent) {
FILE: packages/app-frontend/src/features/timeline/composable/layers.ts
function layerFactory (line 22) | function layerFactory(options: LayerFromBackend): Layer {
function getLayers (line 41) | function getLayers(appId: string) {
function getHiddenLayers (line 50) | function getHiddenLayers(appId: string) {
function useLayers (line 59) | function useLayers() {
function fetchLayers (line 135) | async function fetchLayers() {
function getGroupsAroundPosition (line 140) | function getGroupsAroundPosition(layer: Layer, startPosition: number, en...
function addGroupAroundPosition (line 156) | function addGroupAroundPosition(layer: Layer, group: EventGroup, newPosi...
FILE: packages/app-frontend/src/features/timeline/composable/markers.ts
function useMarkers (line 7) | function useMarkers() {
function loadMarkers (line 22) | function loadMarkers() {
FILE: packages/app-frontend/src/features/timeline/composable/reset.ts
type ResetCb (line 23) | type ResetCb = () => void
function resetTimeline (line 27) | function resetTimeline(sync = true) {
function resetTime (line 54) | function resetTime() {
function onTimelineReset (line 60) | function onTimelineReset(cb: ResetCb) {
FILE: packages/app-frontend/src/features/timeline/composable/screenshot.ts
function takeScreenshot (line 9) | async function takeScreenshot(event: TimelineEvent) {
function useScreenshots (line 76) | function useScreenshots() {
FILE: packages/app-frontend/src/features/timeline/composable/setup.ts
function setupTimelineBridgeEvents (line 22) | function setupTimelineBridgeEvents(bridge: Bridge) {
FILE: packages/app-frontend/src/features/timeline/composable/store.ts
type TimelineEventFromBackend (line 6) | interface TimelineEventFromBackend {
type EventGroup (line 15) | interface EventGroup {
type EventScreenshot (line 27) | interface EventScreenshot {
type TimelineEvent (line 34) | interface TimelineEvent extends TimelineEventFromBackend {
type LayerFromBackend (line 47) | interface LayerFromBackend {
type Layer (line 58) | interface Layer extends LayerFromBackend {
type MarkerFromBackend (line 70) | interface MarkerFromBackend {
type TimelineMarker (line 79) | interface TimelineMarker extends MarkerFromBackend {
FILE: packages/app-frontend/src/features/timeline/composable/time.ts
function useTime (line 9) | function useTime() {
function useCursor (line 18) | function useCursor() {
FILE: packages/app-frontend/src/features/ui/components/icons.ts
method install (line 8) | install() {
function generateHtmlIcon (line 33) | function generateHtmlIcon(icon: string) {
FILE: packages/app-frontend/src/features/ui/composables/useDisableScroll.ts
function getScrollingElements (line 5) | function getScrollingElements() {
function updateScroll (line 9) | function updateScroll() {
function useDisableScroll (line 20) | function useDisableScroll() {
FILE: packages/app-frontend/src/features/ui/composables/useDisabled.ts
function useDisabledParent (line 8) | function useDisabledParent(props: { disabled?: boolean }) {
function useDisabledChild (line 27) | function useDisabledChild(props: { disabled?: boolean }) {
FILE: packages/app-frontend/src/features/ui/index.ts
method install (line 24) | install(app) {
FILE: packages/app-frontend/src/index.ts
function initDevTools (line 16) | async function initDevTools(shell: Shell) {
FILE: packages/app-frontend/src/mixins/data-field-edit.js
function numberQuickEditMod (line 9) | function numberQuickEditMod(event) {
method data (line 45) | data() {
method cssClass (line 56) | cssClass() {
method isEditable (line 62) | isEditable() {
method isValueEditable (line 75) | isValueEditable() {
method customField (line 91) | customField() {
method inputType (line 95) | inputType() {
method isSubfieldsEditable (line 102) | isSubfieldsEditable() {
method valueValid (line 106) | valueValid() {
method duplicateKey (line 119) | duplicateKey() {
method keyValid (line 123) | keyValid() {
method editValid (line 127) | editValid() {
method quickEdits (line 131) | quickEdits() {
method openEdit (line 165) | openEdit(focusKey = false) {
method cancelEdit (line 198) | cancelEdit() {
method submitEdit (line 204) | submitEdit() {
method sendEdit (line 223) | sendEdit(payload) {
method transformSpecialTokens (line 227) | transformSpecialTokens(str, display) {
method quickEdit (line 247) | quickEdit(info, event) {
method removeField (line 258) | removeField() {
method addNewValue (line 262) | addNewValue() {
method containsEdition (line 282) | containsEdition() {
method cancelCurrentEdition (line 286) | cancelCurrentEdition() {
method quickEditNumberTooltip (line 290) | quickEditNumberTooltip(operator) {
FILE: packages/app-frontend/src/mixins/entry-list.ts
method mounted (line 17) | mounted() {
method activated (line 21) | activated() {
method refreshScrollToInspected (line 26) | refreshScrollToInspected() {
function waitForFrame (line 58) | function waitForFrame() {
FILE: packages/app-frontend/src/mixins/keyboard.ts
constant LEFT (line 3) | const LEFT = 'ArrowLeft'
constant RIGHT (line 5) | const RIGHT = 'ArrowRight'
constant DOWN (line 6) | const DOWN = 'ArrowDown'
constant ENTER (line 7) | const ENTER = 'Enter'
constant DEL (line 8) | const DEL = 'Delete'
constant BACKSPACE (line 9) | const BACKSPACE = 'Backspace'
function processEvent (line 13) | function processEvent(event, type) {
method mounted (line 55) | mounted() {
method unmounted (line 61) | unmounted() {
FILE: packages/app-frontend/src/plugins/global-refs.ts
type Options (line 3) | interface Options {
method install (line 8) | install(app: App, options: Options) {
FILE: packages/app-frontend/src/plugins/i18n.ts
type StringMap (line 6) | interface StringMap { [key: string]: string | StringMap }
type ValuesMap (line 7) | interface ValuesMap { [key: string]: any }
type Replacer (line 8) | type Replacer = (text: string) => string
function translate (line 14) | function translate(path: string | string[], values: ValuesMap = {}) {
type Options (line 25) | interface Options {
method install (line 32) | install(app, options: Options) {
FILE: packages/app-frontend/src/plugins/index.ts
function setupPlugins (line 14) | function setupPlugins(app: App) {
FILE: packages/app-frontend/src/plugins/responsive.ts
type Responsive (line 4) | interface Responsive {
method install (line 14) | install(app) {
FILE: packages/app-frontend/src/router.ts
constant STORAGE_ROUTE (line 91) | const STORAGE_ROUTE = 'route'
function createRouterInstance (line 93) | function createRouterInstance() {
FILE: packages/app-frontend/src/types/vue.d.ts
type ComponentCustomProperties (line 11) | interface ComponentCustomProperties {
FILE: packages/app-frontend/src/util/color.ts
function toStrHex (line 3) | function toStrHex(color: number) {
function dimColor (line 7) | function dimColor(color: number, dark: boolean, amount = 20) {
function boostColor (line 18) | function boostColor(color: number, dark: boolean, amount = 10) {
FILE: packages/app-frontend/src/util/defer.ts
function useDefer (line 3) | function useDefer(count = 10) {
FILE: packages/app-frontend/src/util/fonts.ts
function installFonts (line 6) | async function installFonts() {
function useFonts (line 37) | function useFonts() {
FILE: packages/app-frontend/src/util/format/time.ts
type TimeFormat (line 1) | type TimeFormat = 'ms' | 'default'
function formatTime (line 3) | function formatTime(timestamp: string | number | Date, format?: TimeForm...
FILE: packages/app-frontend/src/util/format/value.ts
function valueType (line 14) | function valueType(value, raw = true) {
function formattedValue (line 57) | function formattedValue(value, quotes = true) {
function valueDetails (line 95) | function valueDetails(value: string) {
FILE: packages/app-frontend/src/util/keyboard.ts
type KeyboardHandler (line 3) | type KeyboardHandler = (event: KeyboardEvent) => boolean | void | Promis...
function handleKeyboard (line 5) | function handleKeyboard(type: 'keyup' | 'keydown', cb: KeyboardHandler, ...
function onKeyUp (line 31) | function onKeyUp(cb: KeyboardHandler, force = false) {
function onKeyDown (line 35) | function onKeyDown(cb: KeyboardHandler, force = false) {
FILE: packages/app-frontend/src/util/queue.ts
class Queue (line 1) | class Queue<T = any> {
method add (line 6) | add(value: T) {
method shift (line 23) | shift(): T | null {
method isEmpty (line 36) | isEmpty() {
method has (line 40) | has(value: T) {
type QueueItem (line 45) | interface QueueItem<T> {
FILE: packages/app-frontend/src/util/reactivity.ts
function nonReactive (line 5) | function nonReactive<T>(ref: Ref<T>) {
function addNonReactiveProperties (line 19) | function addNonReactiveProperties<T = any>(target: T, props: Partial<T>) {
function useSavedRef (line 30) | function useSavedRef<T>(ref: Ref<T>, storageKey: string) {
FILE: packages/app-frontend/src/util/shared-data.ts
function onSharedDataChange (line 4) | function onSharedDataChange(prop, handler) {
FILE: packages/app-frontend/src/util/theme.ts
function useDarkMode (line 5) | function useDarkMode() {
FILE: packages/app-frontend/src/util/time.ts
function useTimeAgo (line 10) | function useTimeAgo(time: Ref<number>) {
FILE: packages/shared-utils/src/backend.ts
function getInstanceMap (line 8) | function getInstanceMap() {
function getCustomInstanceDetails (line 12) | function getCustomInstanceDetails(instance) {
function getCustomObjectDetails (line 16) | function getCustomObjectDetails(value, proto: string) {
function isVueInstance (line 20) | function isVueInstance(value) {
function getCustomRouterDetails (line 25) | function getCustomRouterDetails(router) {
function getCustomStoreDetails (line 42) | function getCustomStoreDetails(store) {
function getCatchedGetters (line 59) | function getCatchedGetters(store) {
FILE: packages/shared-utils/src/bridge.ts
constant BATCH_DURATION (line 5) | const BATCH_DURATION = 100
class Bridge (line 7) | class Bridge extends EventEmitter {
method constructor (line 15) | constructor(wall) {
method on (line 33) | on(event: string | symbol, listener: (...args: any[]) => void): this {
method send (line 46) | send(event: string, payload?: any) {
method log (line 61) | log(message: string) {
method _flush (line 65) | _flush() {
method _emit (line 75) | _emit(message) {
method _send (line 92) | _send(messages) {
method _nextSend (line 97) | _nextSend() {
FILE: packages/shared-utils/src/consts.ts
type BuiltinTabs (line 1) | enum BuiltinTabs {
type BridgeEvents (line 8) | enum BridgeEvents {
type BridgeSubscriptions (line 98) | enum BridgeSubscriptions {
type HookEvents (line 103) | enum HookEvents {
FILE: packages/shared-utils/src/edit.ts
class StateEditor (line 3) | class StateEditor {
method set (line 4) | set(object, path, value, cb = null) {
method get (line 24) | get(object, path) {
method has (line 38) | has(object, path, parent = false) {
method createDefaultSetCallback (line 54) | createDefaultSetCallback(state: EditStatePayload) {
method isRef (line 76) | isRef(_ref: any): boolean {
method setRefValue (line 81) | setRefValue(_ref: any, _value: any): void {
method getRefValue (line 85) | getRefValue(ref: any): any {
FILE: packages/shared-utils/src/env.ts
function initEnv (line 23) | function initEnv(app: App) {
FILE: packages/shared-utils/src/plugin-permissions.ts
type PluginPermission (line 3) | enum PluginPermission {
function hasPluginPermission (line 10) | function hasPluginPermission(pluginId: string, permission: PluginPermiss...
function setPluginPermission (line 18) | function setPluginPermission(pluginId: string, permission: PluginPermiss...
FILE: packages/shared-utils/src/plugin-settings.ts
function getPluginSettings (line 4) | function getPluginSettings<TSettings extends Record<string, any> = any>(...
function setPluginSettings (line 11) | function setPluginSettings<TSettings extends Record<string, any> = any>(...
function getPluginDefaultSettings (line 18) | function getPluginDefaultSettings<TSettings extends Record<string, any> ...
FILE: packages/shared-utils/src/shared-data.ts
type TSharedData (line 39) | type TSharedData = typeof internalSharedData
type SharedDataParams (line 79) | interface SharedDataParams {
function initSharedData (line 86) | function initSharedData(params: SharedDataParams): Promise<void> {
function onSharedDataInit (line 176) | function onSharedDataInit(cb) {
function destroySharedData (line 188) | function destroySharedData() {
function setValue (line 193) | function setValue(key: string, value: any) {
function sendValue (line 208) | function sendValue(key: string, value: any) {
function watchSharedData (line 215) | function watchSharedData<
FILE: packages/shared-utils/src/shell.ts
type Shell (line 3) | interface Shell {
FILE: packages/shared-utils/src/storage.ts
function initStorage (line 10) | function initStorage(): Promise<void> {
function getStorage (line 25) | function getStorage(key: string, defaultValue: any = null) {
function setStorage (line 38) | function setStorage(key: string, val: any) {
function removeStorage (line 52) | function removeStorage(key: string) {
function clearStorage (line 66) | function clearStorage() {
function checkStorage (line 80) | function checkStorage() {
function getDefaultValue (line 86) | function getDefaultValue(value, defaultValue) {
FILE: packages/shared-utils/src/throttle.ts
type ThrottleQueueItem (line 3) | interface ThrottleQueueItem {
function createThrottleQueue (line 8) | function createThrottleQueue(wait: number) {
FILE: packages/shared-utils/src/transfer.ts
constant MAX_SERIALIZED_SIZE (line 1) | const MAX_SERIALIZED_SIZE = 512 * 1024 // 1MB
function encode (line 3) | function encode(data, replacer, list, seen) {
function decode (line 53) | function decode(list, reviver) {
function stringifyCircularAutoChunks (line 82) | function stringifyCircularAutoChunks(data: any, replacer: (this: any, ke...
function parseCircularAutoChunks (line 103) | function parseCircularAutoChunks(data: any, reviver: (this: any, key: st...
function stringifyStrictCircularAutoChunks (line 120) | function stringifyStrictCircularAutoChunks(data: any, replacer: (this: a...
FILE: packages/shared-utils/src/util.ts
function cached (line 16) | function cached(fn) {
function toUpper (line 47) | function toUpper(_, c) {
function getComponentDisplayName (line 51) | function getComponentDisplayName(originalName, style = 'class') {
function inDoc (line 63) | function inDoc(node) {
constant UNDEFINED (line 78) | const UNDEFINED = '__vue_devtool_undefined__'
constant INFINITY (line 79) | const INFINITY = '__vue_devtool_infinity__'
constant NEGATIVE_INFINITY (line 80) | const NEGATIVE_INFINITY = '__vue_devtool_negative_infinity__'
constant NAN (line 81) | const NAN = '__vue_devtool_nan__'
constant SPECIAL_TOKENS (line 83) | const SPECIAL_TOKENS = {
constant MAX_STRING_SIZE (line 93) | const MAX_STRING_SIZE = 10000
constant MAX_ARRAY_SIZE (line 94) | const MAX_ARRAY_SIZE = 5000
function specialTokenToString (line 96) | function specialTokenToString(value) {
class EncodeCache (line 122) | class EncodeCache {
method constructor (line 125) | constructor() {
method cache (line 134) | cache<TResult, TData>(data: TData, factory: (data: TData) => TResult):...
method clear (line 146) | clear() {
class ReviveCache (line 153) | class ReviveCache {
method constructor (line 159) | constructor(maxSize: number) {
method cache (line 166) | cache(value: any) {
method read (line 178) | read(id: number) {
function stringify (line 190) | function stringify(data, target: keyof typeof replacers = 'internal') {
function replacerForInternal (line 196) | function replacerForInternal(key) {
function replaceForUser (line 291) | function replaceForUser(key) {
function getCustomMapDetails (line 316) | function getCustomMapDetails(val) {
function reviveMap (line 337) | function reviveMap(val) {
function getCustomSetDetails (line 347) | function getCustomSetDetails(val) {
function reviveSet (line 359) | function reviveSet(val) {
function getCustomBigIntDetails (line 369) | function getCustomBigIntDetails(val) {
function getCustomDateDetails (line 380) | function getCustomDateDetails(val: Date) {
function basename (line 397) | function basename(filename, ext) {
function getComponentName (line 408) | function getComponentName(options) {
function getCustomComponentDefinitionDetails (line 419) | function getCustomComponentDefinitionDetails(def) {
function getCustomFunctionDetails (line 443) | function getCustomFunctionDetails(func: Function): CustomState {
function getCustomHTMLElementDetails (line 469) | function getCustomHTMLElementDetails(value: HTMLElement): CustomState {
function namedNodeMapToObject (line 499) | function namedNodeMapToObject(map: NamedNodeMap) {
function getCustomRefDetails (line 509) | function getCustomRefDetails(instance, key, ref) {
function parse (line 541) | function parse(data: any, revive = false) {
function reviver (line 550) | function reviver(key, val) {
function revive (line 554) | function revive(val) {
function sanitize (line 617) | function sanitize(data) {
function isPlainObject (line 632) | function isPlainObject(obj) {
function isPrimitive (line 636) | function isPrimitive(data) {
function searchDeepInObject (line 654) | function searchDeepInObject(obj, searchTerm) {
constant SEARCH_MAX_DEPTH (line 661) | const SEARCH_MAX_DEPTH = 10
function internalSearchObject (line 671) | function internalSearchObject(obj, searchTerm, seen, depth) {
function internalSearchArray (line 697) | function internalSearchArray(array, searchTerm, seen, depth) {
function internalSearchCheck (line 722) | function internalSearchCheck(searchTerm, key, value, seen, depth) {
function compare (line 760) | function compare(value, searchTerm) {
function sortByKey (line 764) | function sortByKey(state) {
function simpleGet (line 776) | function simpleGet(object, path) {
function focusInput (line 787) | function focusInput(el) {
function openInEditor (line 792) | function openInEditor(file) {
constant ESC (line 818) | const ESC = {
function escape (line 825) | function escape(s) {
function escapeChar (line 829) | function escapeChar(a) {
function copyToClipboard (line 833) | function copyToClipboard(state) {
function isEmptyObject (line 855) | function isEmptyObject(obj) {
function chunk (line 865) | function chunk(array: unknown[], size: number): unknown[][] {
FILE: packages/shell-chrome/src/backend.js
function sendListening (line 8) | function sendListening() {
function handshake (line 16) | function handshake(e) {
FILE: packages/shell-chrome/src/detector-exec.js
function sendMessage (line 3) | function sendMessage(message) {
function detect (line 10) | function detect() {
FILE: packages/shell-chrome/src/devtools-background.js
function createPanelIfHasVue (line 13) | function createPanelIfHasVue() {
function onPanelShown (line 42) | function onPanelShown() {
function onPanelHidden (line 46) | function onPanelHidden() {
FILE: packages/shell-chrome/src/devtools.js
method connect (line 18) | connect(cb) {
method onReload (line 87) | onReload(reloadFn) {
function injectScript (line 100) | function injectScript(scriptName, cb) {
FILE: packages/shell-chrome/src/proxy.js
function sendMessageToBackend (line 16) | function sendMessageToBackend(payload) {
function sendMessageToDevtools (line 23) | function sendMessageToDevtools(e) {
function handleDisconnect (line 34) | function handleDisconnect() {
FILE: packages/shell-chrome/src/service-worker.js
function isNumeric (line 32) | function isNumeric(str) {
function installProxy (line 36) | function installProxy(tabId) {
function doublePipe (line 53) | function doublePipe(id, one, two) {
FILE: packages/shell-dev-vue2/src/MyClass.js
class MyClass (line 1) | class MyClass {
method constructor (line 2) | constructor() {
FILE: packages/shell-dev-vue2/src/dynamic-module.js
method state (line 3) | state() {
method state (line 24) | state() {
method state (line 48) | state() {
FILE: packages/shell-dev-vue2/src/index.js
method render (line 47) | render(h) {
method render (line 71) | render(h) {
method render (line 87) | render(h) {
FILE: packages/shell-dev-vue2/src/store.js
method state (line 64) | state() {
method state (line 86) | state() {
method state (line 101) | state() {
method state (line 111) | state() {
function wait (line 123) | function wait(ms) {
FILE: packages/shell-dev-vue2/webpack.config.js
method onBeforeSetupMiddleware (line 37) | onBeforeSetupMiddleware({ app }) {
FILE: packages/shell-dev-vue3/src/SetupRender.js
method setup (line 6) | setup() {
FILE: packages/shell-dev-vue3/src/store.js
method state (line 4) | state() {
method increment (line 17) | increment(state) {
method state (line 23) | state() {
method state (line 34) | state() {
method state (line 46) | state() {
FILE: packages/shell-dev-vue3/webpack.config.js
method onBeforeSetupMiddleware (line 23) | onBeforeSetupMiddleware({ app }) {
FILE: packages/shell-electron/app.js
function createWindow (line 10) | function createWindow() {
FILE: packages/shell-electron/index.js
method connect (line 17) | connect(host, port, { io, showToast, app } = {}) {
FILE: packages/shell-electron/src/backend.js
constant MAX_DATA_CHUNK (line 11) | const MAX_DATA_CHUNK = 2000
function connectedMessage (line 13) | function connectedMessage() {
function disconnectedMessage (line 19) | function disconnectedMessage() {
method listen (line 46) | listen(fn) {
method send (line 49) | send(data) {
FILE: packages/shell-electron/src/devtools.js
method connect (line 32) | connect(callback) {
method onReload (line 49) | onReload(fn) {
FILE: packages/shell-firefox/src/backend.js
function sendListening (line 8) | function sendListening() {
function handshake (line 16) | function handshake(e) {
FILE: packages/shell-firefox/src/background.js
function isNumeric (line 32) | function isNumeric(str) {
function installProxy (line 36) | function installProxy(tabId) {
function doublePipe (line 52) | function doublePipe(id, one, two) {
FILE: packages/shell-firefox/src/detector.js
function detect (line 10) | function detect(win) {
function installScript (line 87) | function installScript(fn) {
FILE: packages/shell-firefox/src/devtools-background.js
function createPanelIfHasVue (line 13) | function createPanelIfHasVue() {
function onPanelShown (line 42) | function onPanelShown() {
function onPanelHidden (line 46) | function onPanelHidden() {
FILE: packages/shell-firefox/src/devtools.js
method connect (line 18) | connect(cb) {
method onReload (line 87) | onReload(reloadFn) {
function injectScript (line 100) | function injectScript(scriptName, cb) {
FILE: packages/shell-firefox/src/proxy.js
function sendMessageToBackend (line 16) | function sendMessageToBackend(payload) {
function sendMessageToDevtools (line 23) | function sendMessageToDevtools(e) {
function handleDisconnect (line 34) | function handleDisconnect() {
FILE: packages/shell-host/src/backend.js
method listen (line 5) | listen(fn) {
method send (line 8) | send(data) {
FILE: packages/shell-host/webpack.config.js
method onBeforeSetupMiddleware (line 16) | onBeforeSetupMiddleware({ app }) {
FILE: release.js
constant IS_CI (line 8) | const IS_CI = !!(process.env.CIRCLECI || process.env.GITHUB_ACTIONS)
function applyIcons (line 78) | function applyIcons(manifest, suffix = '') {
Condensed preview — 404 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,008K chars).
[
{
"path": ".browserslistrc",
"chars": 27,
"preview": "Chrome >= 52\nFirefox >= 48\n"
},
{
"path": ".circleci/config.yml",
"chars": 866,
"preview": "version: 2\njobs:\n build:\n docker:\n # specify the version you desire here\n - image: node:current\n - im"
},
{
"path": ".github/FUNDING.yml",
"chars": 15,
"preview": "github: Akryum\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 3380,
"preview": "name: 🐞 Bug report\ndescription: Create a report to help us improve\nbody:\n - type: markdown\n attributes:\n value:"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 669,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: I have a performance issue\n url: https://devtools.vuejs.org/guid"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 1142,
"preview": "name: 🚀 New feature proposal\ndescription: Suggest an idea for this project\nlabels: [':sparkles: feature request']\nbody:\n"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 1162,
"preview": "<!-- Thank you for contributing! -->\n\n### Description\n\n<!-- Please insert your description here and provide especially i"
},
{
"path": ".github/commit-convention.md",
"chars": 2902,
"preview": "## Git Commit Message Convention\n\n> This is adapted from [Angular's commit convention](https://github.com/conventional-c"
},
{
"path": ".github/workflows/create-release.yml",
"chars": 477,
"preview": "name: Create release\n\non:\n push:\n tags:\n - 'v*'\n\njobs:\n build:\n name: Create Release\n runs-on: ubuntu-la"
},
{
"path": ".gitignore",
"chars": 171,
"preview": "node_modules\n.DS_Store\nbuild\n/dist/*\n*.zip\n*.xpi\ntests_output\nselenium-debug.log\nTODOs.md\n.idea\n.web-extension-id\nyarn-e"
},
{
"path": ".vscode/settings.json",
"chars": 55,
"preview": "{\n \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n"
},
{
"path": "LICENSE",
"chars": 1083,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2014-present Evan You\n\nPermission is hereby granted, free of charge, to any person "
},
{
"path": "README.md",
"chars": 1951,
"preview": "# Try the next iteration of Vue Devtools!\n\nWe have a brand new version of Devtools being developed at [vuejs/devtools-ne"
},
{
"path": "babel.config.js",
"chars": 127,
"preview": "module.exports = {\n root: true,\n presets: [\n [\n '@babel/env',\n {\n modules: false,\n },\n ],\n"
},
{
"path": "cypress/.eslintrc.js",
"chars": 151,
"preview": "module.exports = {\n plugins: [\n 'cypress',\n ],\n env: {\n 'mocha': true,\n 'cypress/globals': true,\n },\n rule"
},
{
"path": "cypress/.gitignore",
"chars": 21,
"preview": "/screenshots\n/videos\n"
},
{
"path": "cypress/fixtures/example.json",
"chars": 155,
"preview": "{\n \"name\": \"Using fixtures to represent data\",\n \"email\": \"hello@cypress.io\",\n \"body\": \"Fixtures are a great way to mo"
},
{
"path": "cypress/integration/component-data-edit.js",
"chars": 4345,
"preview": "import { suite } from '../utils/suite'\n\nsuite('component data edit', () => {\n it('should edit data using the decrease b"
},
{
"path": "cypress/integration/components-tab.js",
"chars": 5297,
"preview": "import { suite } from '../utils/suite'\n\nconst baseInstanceCount = 12\n\nsuite('components tab', () => {\n beforeEach(() =>"
},
{
"path": "cypress/integration/events-tab.js",
"chars": 1383,
"preview": "import { suite } from '../utils/suite'\n\nsuite('events tab', () => {\n it('should display new events counter', () => {\n "
},
{
"path": "cypress/integration/vuex-edit.js",
"chars": 3426,
"preview": "import { suite } from '../utils/suite'\n\nsuite('vuex edit', () => {\n it('should edit state using the decrease button', ("
},
{
"path": "cypress/integration/vuex-tab.js",
"chars": 8735,
"preview": "import { suite } from '../utils/suite'\n\nsuite('vuex tab', () => {\n it('should display mutations history', () => {\n c"
},
{
"path": "cypress/plugins/index.js",
"chars": 838,
"preview": "// ***********************************************************\n// This example plugins/index.js can be used to load plug"
},
{
"path": "cypress/support/commands.js",
"chars": 1668,
"preview": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom"
},
{
"path": "cypress/support/index.js",
"chars": 670,
"preview": "// ***********************************************************\n// This example support/index.js is processed and\n// load"
},
{
"path": "cypress/utils/suite.js",
"chars": 165,
"preview": "export function suite(description, tests) {\n describe(description, () => {\n before(() => {\n cy.visit('/')\n "
},
{
"path": "cypress.json",
"chars": 83,
"preview": "{\n \"viewportWidth\": 1280,\n \"viewportHeight\": 800,\n \"chromeWebSecurity\": false\n}\n"
},
{
"path": "eslint.config.js",
"chars": 879,
"preview": "const antfu = require('@antfu/eslint-config').default\n\nmodule.exports = antfu({\n ignores: [\n '**/dist',\n ],\n}, {\n "
},
{
"path": "extension-zips.js",
"chars": 4287,
"preview": "// require modules\nconst fs = require('node:fs')\nconst path = require('node:path')\nconst process = require('node:process"
},
{
"path": "lerna.json",
"chars": 158,
"preview": "{\n \"npmClient\": \"yarn\",\n \"useWorkspaces\": true,\n \"version\": \"6.0.0-beta.2\",\n \"packages\": [\n \"packages/*\"\n ],\n \""
},
{
"path": "package.json",
"chars": 3123,
"preview": "{\n \"name\": \"vue-devtools\",\n \"version\": \"6.6.4\",\n \"private\": true,\n \"description\": \"devtools for Vue.js!\",\n \"workspa"
},
{
"path": "packages/api/package.json",
"chars": 973,
"preview": "{\n \"name\": \"@vue/devtools-api\",\n \"version\": \"6.6.4\",\n \"description\": \"Interact with the Vue devtools from the page\",\n"
},
{
"path": "packages/api/src/api/api.ts",
"chars": 3309,
"preview": "import type { ComponentBounds, Hookable } from './hooks.js'\nimport type { Context } from './context.js'\nimport type { Co"
},
{
"path": "packages/api/src/api/app.ts",
"chars": 31,
"preview": "export type App = any // @TODO\n"
},
{
"path": "packages/api/src/api/component.ts",
"chars": 1956,
"preview": "import type { InspectorNodeTag } from './api.js'\nimport type { ID } from './util.js'\n\nexport type ComponentInstance = an"
},
{
"path": "packages/api/src/api/context.ts",
"chars": 123,
"preview": "import type { AppRecord } from './api.js'\n\nexport interface Context {\n currentTab: string\n currentAppRecord: AppRecord"
},
{
"path": "packages/api/src/api/hooks.ts",
"chars": 6905,
"preview": "import type { ComponentDevtoolsOptions, ComponentInstance, ComponentTreeNode, InspectedComponentData } from './component"
},
{
"path": "packages/api/src/api/index.ts",
"chars": 163,
"preview": "export * from './api.js'\nexport * from './app.js'\nexport * from './component.js'\nexport * from './context.js'\nexport * f"
},
{
"path": "packages/api/src/api/util.ts",
"chars": 71,
"preview": "export type ID = number | string\n\nexport interface WithId {\n id: ID\n}\n"
},
{
"path": "packages/api/src/const.ts",
"chars": 112,
"preview": "export const HOOK_SETUP = 'devtools-plugin:setup'\nexport const HOOK_PLUGIN_SETTINGS_SET = 'plugin:settings:set'\n"
},
{
"path": "packages/api/src/env.ts",
"chars": 812,
"preview": "import type { ApiProxy } from './proxy.js'\nimport type { PluginDescriptor, SetupFunction } from './index.js'\n\nexport int"
},
{
"path": "packages/api/src/index.ts",
"chars": 2081,
"preview": "import { getDevtoolsGlobalHook, getTarget, isProxyAvailable } from './env.js'\nimport { HOOK_SETUP } from './const.js'\nim"
},
{
"path": "packages/api/src/plugin.ts",
"chars": 1205,
"preview": "import type { App } from './api/index.js'\n\nexport interface PluginDescriptor {\n id: string\n label: string\n app: App\n "
},
{
"path": "packages/api/src/proxy.ts",
"chars": 3337,
"preview": "import type { Context, DevtoolsPluginApi, Hookable } from './api/index.js'\nimport type { PluginDescriptor } from './plug"
},
{
"path": "packages/api/src/time.ts",
"chars": 580,
"preview": "let supported: boolean\nlet perf: Performance\n\nexport function isPerformanceSupported() {\n if (supported !== undefined) "
},
{
"path": "packages/api/tsconfig.json",
"chars": 612,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2017\",\n \"lib\": [\"ESNext\", \"DOM\"],\n \"module\": \"ESNext\",\n \"moduleResolu"
},
{
"path": "packages/app-backend-api/package.json",
"chars": 502,
"preview": "{\n \"name\": \"@vue-devtools/app-backend-api\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"main\": \"./lib/index.js\",\n \"typ"
},
{
"path": "packages/app-backend-api/src/api.ts",
"chars": 11061,
"preview": "import type {\n Bridge,\n} from '@vue-devtools/shared-utils'\nimport {\n HookEvents,\n PluginPermission,\n StateEditor,\n "
},
{
"path": "packages/app-backend-api/src/app-record.ts",
"chars": 756,
"preview": "import type { App, ComponentInstance } from '@vue/devtools-api'\nimport type { DevtoolsBackend } from './backend'\n\nexport"
},
{
"path": "packages/app-backend-api/src/backend-context.ts",
"chars": 1876,
"preview": "import type { Bridge } from '@vue-devtools/shared-utils'\nimport type {\n CustomInspectorOptions,\n ID,\n TimelineEventOp"
},
{
"path": "packages/app-backend-api/src/backend.ts",
"chars": 882,
"preview": "import type { AppRecord } from './app-record'\nimport { DevtoolsApi } from './api'\nimport type { BackendContext } from '."
},
{
"path": "packages/app-backend-api/src/global-hook.ts",
"chars": 369,
"preview": "import type { AppRecordOptions } from './app-record'\n\nexport interface DevtoolsHook {\n emit: (event: string, ...payload"
},
{
"path": "packages/app-backend-api/src/hooks.ts",
"chars": 6182,
"preview": "import { PluginPermission, SharedData, hasPluginPermission } from '@vue-devtools/shared-utils'\nimport type { HookHandler"
},
{
"path": "packages/app-backend-api/src/index.ts",
"chars": 190,
"preview": "export * from './api'\nexport * from './app-record'\nexport * from './backend'\nexport * from './backend-context'\nexport * "
},
{
"path": "packages/app-backend-api/src/plugin.ts",
"chars": 173,
"preview": "import type { PluginDescriptor, SetupFunction } from '@vue/devtools-api'\n\nexport interface Plugin {\n descriptor: Plugin"
},
{
"path": "packages/app-backend-api/tsconfig.json",
"chars": 588,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2019\",\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\",\n \"resolveJ"
},
{
"path": "packages/app-backend-core/package.json",
"chars": 750,
"preview": "{\n \"name\": \"@vue-devtools/app-backend-core\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"main\": \"./lib/index.js\",\n \"ty"
},
{
"path": "packages/app-backend-core/src/app.ts",
"chars": 8584,
"preview": "import type {\n AppRecord,\n AppRecordOptions,\n BackendContext,\n DevtoolsBackend,\n SimpleAppRecord,\n} from '@vue-devt"
},
{
"path": "packages/app-backend-core/src/backend.ts",
"chars": 1049,
"preview": "import type { BackendContext, DevtoolsBackend, DevtoolsBackendOptions } from '@vue-devtools/app-backend-api'\nimport { cr"
},
{
"path": "packages/app-backend-core/src/component-pick.ts",
"chars": 3456,
"preview": "import { BridgeEvents, isBrowser } from '@vue-devtools/shared-utils'\nimport type { BackendContext, DevtoolsBackend } fro"
},
{
"path": "packages/app-backend-core/src/component.ts",
"chars": 5439,
"preview": "import { BridgeEvents, SharedData, createThrottleQueue, parse, stringify } from '@vue-devtools/shared-utils'\nimport type"
},
{
"path": "packages/app-backend-core/src/flash.ts",
"chars": 1417,
"preview": "import type { DevtoolsBackend } from '@vue-devtools/app-backend-api'\nimport type { ComponentInstance } from '@vue/devtoo"
},
{
"path": "packages/app-backend-core/src/global-hook.ts",
"chars": 245,
"preview": "import type { DevtoolsHook } from '@vue-devtools/app-backend-api'\nimport { target } from '@vue-devtools/shared-utils'\n\n/"
},
{
"path": "packages/app-backend-core/src/highlighter.ts",
"chars": 5523,
"preview": "import { isBrowser } from '@vue-devtools/shared-utils'\nimport type { BackendContext, DevtoolsBackend } from '@vue-devtoo"
},
{
"path": "packages/app-backend-core/src/hook.ts",
"chars": 19495,
"preview": "// this script is injected into every page.\n\n/**\n * Install the hook on window, which is an event emitter.\n * Note becau"
},
{
"path": "packages/app-backend-core/src/index.ts",
"chars": 22877,
"preview": "import type {\n AppRecord,\n BackendContext,\n Plugin,\n} from '@vue-devtools/app-backend-api'\nimport {\n BuiltinBackendF"
},
{
"path": "packages/app-backend-core/src/inspector.ts",
"chars": 2875,
"preview": "import type { App } from '@vue/devtools-api'\nimport type { BackendContext, CustomInspector } from '@vue-devtools/app-bac"
},
{
"path": "packages/app-backend-core/src/legacy/scan.ts",
"chars": 2425,
"preview": "import { isBrowser, target } from '@vue-devtools/shared-utils'\nimport { getPageConfig } from '../page-config'\n\nconst roo"
},
{
"path": "packages/app-backend-core/src/page-config.ts",
"chars": 649,
"preview": "import { SharedData, target } from '@vue-devtools/shared-utils'\n\nexport interface PageConfig {\n openInEditorHost?: stri"
},
{
"path": "packages/app-backend-core/src/perf.ts",
"chars": 5014,
"preview": "import type { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api'\nimport type { App, ComponentInstanc"
},
{
"path": "packages/app-backend-core/src/plugin.ts",
"chars": 2628,
"preview": "import type { PluginQueueItem } from '@vue/devtools-api'\nimport type { BackendContext, Plugin } from '@vue-devtools/app-"
},
{
"path": "packages/app-backend-core/src/timeline-builtins.ts",
"chars": 3367,
"preview": "import type { TimelineLayerOptions } from '@vue/devtools-api'\n\nexport const builtinLayers: TimelineLayerOptions[] = [\n "
},
{
"path": "packages/app-backend-core/src/timeline-marker.ts",
"chars": 1692,
"preview": "import type { BackendContext, TimelineMarker } from '@vue-devtools/app-backend-api'\nimport { BridgeEvents, SharedData } "
},
{
"path": "packages/app-backend-core/src/timeline-screenshot.ts",
"chars": 3471,
"preview": "import type { BackendContext } from '@vue-devtools/app-backend-api'\nimport type { ID, ScreenshotOverlayRenderContext } f"
},
{
"path": "packages/app-backend-core/src/timeline.ts",
"chars": 7032,
"preview": "import type { AppRecord, BackendContext } from '@vue-devtools/app-backend-api'\nimport { BridgeEvents, HookEvents, Shared"
},
{
"path": "packages/app-backend-core/src/toast.ts",
"chars": 46,
"preview": "export function installToast() {\n // @TODO\n}\n"
},
{
"path": "packages/app-backend-core/src/util/queue.ts",
"chars": 817,
"preview": "export interface Job {\n id: string\n fn: () => Promise<void>\n}\n\nexport class JobQueue {\n jobs: Job[] = []\n currentJob"
},
{
"path": "packages/app-backend-core/src/util/subscriptions.ts",
"chars": 516,
"preview": "const activeSubs: Map<string, Map<string, boolean>> = new Map()\n\nfunction getSubs(type: string) {\n let subs = activeSub"
},
{
"path": "packages/app-backend-core/tsconfig.json",
"chars": 588,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2019\",\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\",\n \"resolveJ"
},
{
"path": "packages/app-backend-vue1/package.json",
"chars": 508,
"preview": "{\n \"name\": \"@vue-devtools/app-backend-vue1\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"main\": \"./lib/index.js\",\n \"ty"
},
{
"path": "packages/app-backend-vue1/src/index.ts",
"chars": 178,
"preview": "import { defineBackend } from '@vue-devtools/app-backend-api'\n\nexport const backend = defineBackend({\n frameworkVersion"
},
{
"path": "packages/app-backend-vue1/tsconfig.json",
"chars": 588,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2019\",\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\",\n \"resolveJ"
},
{
"path": "packages/app-backend-vue2/package.json",
"chars": 681,
"preview": "{\n \"name\": \"@vue-devtools/app-backend-vue2\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"main\": \"./lib/index.js\",\n \"ty"
},
{
"path": "packages/app-backend-vue2/src/components/data.ts",
"chars": 12204,
"preview": "import type { StateEditor } from '@vue-devtools/shared-utils'\nimport { SharedData, camelize, getComponentName, getCustom"
},
{
"path": "packages/app-backend-vue2/src/components/el.ts",
"chars": 3067,
"preview": "import { inDoc, isBrowser, target } from '@vue-devtools/shared-utils'\n\nfunction createRect() {\n const rect = {\n top:"
},
{
"path": "packages/app-backend-vue2/src/components/perf.ts",
"chars": 1609,
"preview": "import type { DevtoolsApi } from '@vue-devtools/app-backend-api'\nimport { HookEvents, SharedData } from '@vue-devtools/s"
},
{
"path": "packages/app-backend-vue2/src/components/tree.ts",
"chars": 11173,
"preview": "import type { AppRecord, BackendContext, DevtoolsApi } from '@vue-devtools/app-backend-api'\nimport { classify, kebabize "
},
{
"path": "packages/app-backend-vue2/src/components/update-tracking.ts",
"chars": 1447,
"preview": "import type { DevtoolsApi } from '@vue-devtools/app-backend-api'\nimport { HookEvents, SharedData } from '@vue-devtools/s"
},
{
"path": "packages/app-backend-vue2/src/components/util.ts",
"chars": 1277,
"preview": "import { getComponentName } from '@vue-devtools/shared-utils'\nimport type { AppRecord } from '@vue-devtools/app-backend-"
},
{
"path": "packages/app-backend-vue2/src/events.ts",
"chars": 1120,
"preview": "import type { BackendContext } from '@vue-devtools/app-backend-api'\nimport { HookEvents } from '@vue-devtools/shared-uti"
},
{
"path": "packages/app-backend-vue2/src/index.ts",
"chars": 3769,
"preview": "import { BuiltinBackendFeature, defineBackend } from '@vue-devtools/app-backend-api'\nimport { backendInjections, getComp"
},
{
"path": "packages/app-backend-vue2/src/plugin.ts",
"chars": 14794,
"preview": "import type { DevtoolsApi } from '@vue-devtools/app-backend-api'\nimport type { App, ComponentState, CustomInspectorNode,"
},
{
"path": "packages/app-backend-vue2/tsconfig.json",
"chars": 588,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2019\",\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\",\n \"resolveJ"
},
{
"path": "packages/app-backend-vue3/package.json",
"chars": 550,
"preview": "{\n \"name\": \"@vue-devtools/app-backend-vue3\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"main\": \"./lib/index.js\",\n \"ty"
},
{
"path": "packages/app-backend-vue3/src/components/data.ts",
"chars": 13583,
"preview": "import type { BackendContext } from '@vue-devtools/app-backend-api'\nimport type { StateEditor } from '@vue-devtools/shar"
},
{
"path": "packages/app-backend-vue3/src/components/el.ts",
"chars": 3575,
"preview": "import { inDoc, isBrowser } from '@vue-devtools/shared-utils'\nimport { isFragment } from './util'\n\nexport function getCo"
},
{
"path": "packages/app-backend-vue3/src/components/filter.ts",
"chars": 533,
"preview": "import { classify, kebabize } from '@vue-devtools/shared-utils'\nimport { getInstanceName } from './util'\n\nexport class C"
},
{
"path": "packages/app-backend-vue3/src/components/tree.ts",
"chars": 8385,
"preview": "import type { BackendContext, DevtoolsApi } from '@vue-devtools/app-backend-api'\nimport type { ComponentTreeNode } from "
},
{
"path": "packages/app-backend-vue3/src/components/util.ts",
"chars": 2757,
"preview": "import { basename, classify } from '@vue-devtools/shared-utils'\nimport type { App, ComponentInstance } from '@vue/devtoo"
},
{
"path": "packages/app-backend-vue3/src/index.ts",
"chars": 3621,
"preview": "import { defineBackend } from '@vue-devtools/app-backend-api'\nimport { HookEvents, backendInjections } from '@vue-devtoo"
},
{
"path": "packages/app-backend-vue3/src/util.ts",
"chars": 324,
"preview": "export function flatten(items) {\n return items.reduce((acc, item) => {\n if (Array.isArray(item)) {\n acc.push(.."
},
{
"path": "packages/app-backend-vue3/tsconfig.json",
"chars": 588,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2019\",\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\",\n \"resolveJ"
},
{
"path": "packages/app-frontend/package.json",
"chars": 903,
"preview": "{\n \"name\": \"@vue-devtools/app-frontend\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"dependencies\": {\n \"@pixi/events"
},
{
"path": "packages/app-frontend/src/app.ts",
"chars": 2585,
"preview": "import type { App as VueApp } from 'vue'\nimport { createApp as createVueApp } from 'vue'\nimport { BridgeEvents, SharedDa"
},
{
"path": "packages/app-frontend/src/assets/github-theme/dark.json",
"chars": 15172,
"preview": "{\n \"inherit\": true,\n \"base\": \"vs-dark\",\n \"colors\": {\n \"focusBorder\": \"#388bfd\",\n \"foreground\": \"#c9d1d9\",\n \""
},
{
"path": "packages/app-frontend/src/assets/github-theme/light.json",
"chars": 14959,
"preview": "{\n \"inherit\": true,\n \"base\": \"vs\",\n \"colors\": {\n \"focusBorder\": \"#0366d6\",\n \"foreground\": \"#24292e\",\n \"descr"
},
{
"path": "packages/app-frontend/src/assets/style/imports.styl",
"chars": 57,
"preview": "@import '~@vue/ui/src/style/imports'\n@import 'variables'\n"
},
{
"path": "packages/app-frontend/src/assets/style/index.postcss",
"chars": 2194,
"preview": "html, body, #app {\n @apply dark:!bg-gray-800;\n}\n\n.vue-ui-high-contrast {\n #app {\n @apply !bg-black;\n }\n}\n\n/* Poppe"
},
{
"path": "packages/app-frontend/src/assets/style/index.styl",
"chars": 2170,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@import '~@vue/ui/dist/vue-ui.css'\n\n@import 'imports'\n@impor"
},
{
"path": "packages/app-frontend/src/assets/style/transitions.styl",
"chars": 372,
"preview": ".slide-up-enter\n opacity 0\n transform translate(0, 50%)\n\n.slide-up-leave-to\n opacity 0\n transform translate(0, -50%)"
},
{
"path": "packages/app-frontend/src/assets/style/variables.styl",
"chars": 1739,
"preview": "// Colors\n$blue = #44A1FF\n$grey = #DDDDDD\n$darkGrey = #CCC\n$darkerGrey = #AAA\n$blueishGrey = #486887\n$green = #42B983\n$d"
},
{
"path": "packages/app-frontend/src/features/App.vue",
"chars": 3674,
"preview": "<script lang=\"ts\">\nimport { defineComponent, onMounted } from 'vue'\nimport {\n SharedData,\n getStorage,\n isChrome,\n o"
},
{
"path": "packages/app-frontend/src/features/apps/AppSelect.vue",
"chars": 3153,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent, watch } from 'vue'\nimport { BridgeEvents, SharedData } from '@vue"
},
{
"path": "packages/app-frontend/src/features/apps/AppSelectItem.vue",
"chars": 1808,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent } from 'vue'\nimport { useVueVersionCheck } from './vue-version-che"
},
{
"path": "packages/app-frontend/src/features/apps/AppSelectPane.vue",
"chars": 2808,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent, ref, watch } from 'vue'\nimport { BridgeEvents, SharedData } from "
},
{
"path": "packages/app-frontend/src/features/apps/AppSelectPaneItem.vue",
"chars": 2162,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent } from 'vue'\nimport { useVueVersionCheck } from './vue-version-che"
},
{
"path": "packages/app-frontend/src/features/apps/index.ts",
"chars": 2505,
"preview": "import { computed, ref } from 'vue'\nimport type { Bridge } from '@vue-devtools/shared-utils'\nimport { BridgeEvents } fro"
},
{
"path": "packages/app-frontend/src/features/apps/vue-version-check.ts",
"chars": 863,
"preview": "import { onMounted, ref } from 'vue'\nimport semver from 'semver'\n\nconst packageData = ref<any>(null)\n\nexport function us"
},
{
"path": "packages/app-frontend/src/features/bridge/index.ts",
"chars": 1154,
"preview": "import { onUnmounted } from 'vue'\nimport type { Bridge } from '@vue-devtools/shared-utils'\nimport { BridgeEvents } from "
},
{
"path": "packages/app-frontend/src/features/chrome/index.ts",
"chars": 34,
"preview": "export * from './pane-visibility'\n"
},
{
"path": "packages/app-frontend/src/features/chrome/pane-visibility.ts",
"chars": 675,
"preview": "import { isChrome } from '@vue-devtools/shared-utils'\n\nlet panelShown = !isChrome\nlet pendingAction: (() => void | Promi"
},
{
"path": "packages/app-frontend/src/features/code/CodeEditor.vue",
"chars": 3713,
"preview": "<script>\n// Fork of https://github.com/egoist/vue-monaco/\nimport * as monaco from 'monaco-editor'\nimport assign from 'lo"
},
{
"path": "packages/app-frontend/src/features/components/ComponentTreeNode.vue",
"chars": 9442,
"preview": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent, onMounted, ref, toRefs, watch"
},
{
"path": "packages/app-frontend/src/features/components/ComponentsInspector.vue",
"chars": 7993,
"preview": "<script lang=\"ts\">\nimport SplitPane from '@front/features/layout/SplitPane.vue'\nimport { defineComponent, onMounted, pro"
},
{
"path": "packages/app-frontend/src/features/components/RenderCode.vue",
"chars": 1723,
"preview": "<script>\nimport { defineAsyncComponent, reactive, watch } from 'vue'\nimport { BridgeEvents } from '@vue-devtools/shared-"
},
{
"path": "packages/app-frontend/src/features/components/SelectedComponentPane.vue",
"chars": 4440,
"preview": "<script lang=\"ts\">\nimport StateInspector from '@front/features/inspector/StateInspector.vue'\nimport EmptyPane from '@fro"
},
{
"path": "packages/app-frontend/src/features/components/composable/components.ts",
"chars": 11730,
"preview": "import type { Ref } from 'vue'\nimport { computed, onMounted, ref, watch } from 'vue'\nimport type { ComponentTreeNode, Ed"
},
{
"path": "packages/app-frontend/src/features/components/composable/highlight.ts",
"chars": 619,
"preview": "import { getBridge } from '@front/features/bridge'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\nimport type"
},
{
"path": "packages/app-frontend/src/features/components/composable/index.ts",
"chars": 104,
"preview": "export * from './components'\nexport * from './highlight'\nexport * from './pick'\nexport * from './setup'\n"
},
{
"path": "packages/app-frontend/src/features/components/composable/pick.ts",
"chars": 1185,
"preview": "import { ref } from 'vue'\nimport { BridgeEvents } from '@vue-devtools/shared-utils'\nimport { useBridge } from '@front/fe"
},
{
"path": "packages/app-frontend/src/features/components/composable/setup.ts",
"chars": 2539,
"preview": "import type { Bridge } from '@vue-devtools/shared-utils'\nimport { BridgeEvents, getStorage, parse } from '@vue-devtools/"
},
{
"path": "packages/app-frontend/src/features/connection/AppConnecting.vue",
"chars": 866,
"preview": "<template>\n <div class=\"app-connecting w-full h-full flex items-center justify-center bg-white dark:bg-gray-800\">\n <"
},
{
"path": "packages/app-frontend/src/features/connection/AppDisconnected.vue",
"chars": 194,
"preview": "<template>\n <div class=\"w-full h-full flex items-center justify-center\">\n <VueIcon\n icon=\"code_off\"\n class"
},
{
"path": "packages/app-frontend/src/features/connection/index.ts",
"chars": 1293,
"preview": "import { computed, ref } from 'vue'\nimport { useNow } from '@vueuse/core'\n\nconst isConnected = ref(false)\nconst isInitia"
},
{
"path": "packages/app-frontend/src/features/error/ErrorOverlay.vue",
"chars": 798,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport { useError } from '.'\n\nexport default defineComponent({\n"
},
{
"path": "packages/app-frontend/src/features/error/index.ts",
"chars": 539,
"preview": "import { computed, ref } from 'vue'\n\nexport interface ErrorMessage {\n message: string\n icon: string\n}\n\nconst errors = "
},
{
"path": "packages/app-frontend/src/features/header/AppHeader.vue",
"chars": 6684,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent, ref, watch } from 'vue'\nimport type { RouteLocation, RouteLocatio"
},
{
"path": "packages/app-frontend/src/features/header/AppHeaderSelect.vue",
"chars": 5923,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent, ref } from 'vue'\nimport { SharedData } from '@vue-devtools/shared"
},
{
"path": "packages/app-frontend/src/features/header/AppHistoryNav.vue",
"chars": 284,
"preview": "<template>\n <div class=\"flex\">\n <VueButton\n icon-left=\"arrow_back\"\n class=\"icon-button flat\"\n @click="
},
{
"path": "packages/app-frontend/src/features/header/header.ts",
"chars": 69,
"preview": "import { ref } from 'vue'\n\nexport const showAppsSelector = ref(true)\n"
},
{
"path": "packages/app-frontend/src/features/header/tabs.ts",
"chars": 371,
"preview": "import { computed } from 'vue'\nimport { useRoute } from 'vue-router'\n\nexport function useTabs() {\n const route = useRou"
},
{
"path": "packages/app-frontend/src/features/inspector/DataField.vue",
"chars": 19706,
"preview": "<script lang=\"ts\">\n/* eslint-disable vue/no-unused-refs */\n\nimport { defineComponent, toRaw } from 'vue'\nimport {\n Brid"
},
{
"path": "packages/app-frontend/src/features/inspector/StateFields.vue",
"chars": 3369,
"preview": "<script>\nimport DataField from './DataField.vue'\n\nexport default {\n components: {\n DataField,\n },\n\n props: {\n f"
},
{
"path": "packages/app-frontend/src/features/inspector/StateInspector.vue",
"chars": 2792,
"preview": "<script>\nimport { useDefer } from '@front/util/defer'\nimport { getStorage, setStorage } from '@vue-devtools/shared-utils"
},
{
"path": "packages/app-frontend/src/features/inspector/StateType.vue",
"chars": 3766,
"preview": "<script lang=\"ts\">\nimport PluginSourceIcon from '@front/features/plugin/PluginSourceIcon.vue'\n\nimport { computed, define"
},
{
"path": "packages/app-frontend/src/features/inspector/custom/CustomInspector.vue",
"chars": 4460,
"preview": "<script lang=\"ts\">\nimport SplitPane from '@front/features/layout/SplitPane.vue'\nimport EmptyPane from '@front/features/l"
},
{
"path": "packages/app-frontend/src/features/inspector/custom/CustomInspectorNode.vue",
"chars": 5461,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent, ref, watch } from 'vue'\nimport scrollIntoView from 'scroll-into-v"
},
{
"path": "packages/app-frontend/src/features/inspector/custom/CustomInspectorSelectedNodePane.vue",
"chars": 2557,
"preview": "<script lang=\"ts\">\nimport EmptyPane from '@front/features/layout/EmptyPane.vue'\n\nimport { defineComponent, ref, watch } "
},
{
"path": "packages/app-frontend/src/features/inspector/custom/composable.ts",
"chars": 5765,
"preview": "import { computed, ref } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { useApps } from '@front/features/apps'"
},
{
"path": "packages/app-frontend/src/features/layout/EmptyPane.vue",
"chars": 454,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n props: {\n icon: {\n "
},
{
"path": "packages/app-frontend/src/features/layout/SplitPane.vue",
"chars": 8002,
"preview": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent, ref, watch } from 'vue'\nimpor"
},
{
"path": "packages/app-frontend/src/features/layout/orientation.ts",
"chars": 441,
"preview": "import { ref } from 'vue'\n\nconst orientation = ref('landscape')\n\nexport function useOrientation() {\n return {\n orien"
},
{
"path": "packages/app-frontend/src/features/plugin/PluginDetails.vue",
"chars": 3436,
"preview": "<script lang=\"ts\">\nimport EmptyPane from '@front/features/layout/EmptyPane.vue'\nimport SplitPane from '@front/features/l"
},
{
"path": "packages/app-frontend/src/features/plugin/PluginHome.vue",
"chars": 1030,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n props: {\n noPlugins: {\n "
},
{
"path": "packages/app-frontend/src/features/plugin/PluginListItem.vue",
"chars": 1144,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent } from 'vue'\nimport { PluginPermission, hasPluginPermission } from"
},
{
"path": "packages/app-frontend/src/features/plugin/PluginPermission.vue",
"chars": 994,
"preview": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent } from 'vue'\nimport type { Plu"
},
{
"path": "packages/app-frontend/src/features/plugin/PluginSettings.vue",
"chars": 1912,
"preview": "<script lang=\"ts\">\nimport EmptyPane from '@front/features/layout/EmptyPane.vue'\n\nimport type { PropType } from 'vue'\nimp"
},
{
"path": "packages/app-frontend/src/features/plugin/PluginSettingsItem.vue",
"chars": 2418,
"preview": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent } from 'vue'\nimport type { Plu"
},
{
"path": "packages/app-frontend/src/features/plugin/PluginSourceDescription.vue",
"chars": 1286,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { usePl"
},
{
"path": "packages/app-frontend/src/features/plugin/PluginSourceIcon.vue",
"chars": 894,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport { useRouter } from 'vue-router'\nimport PluginSourceDescr"
},
{
"path": "packages/app-frontend/src/features/plugin/Plugins.vue",
"chars": 2091,
"preview": "<script lang=\"ts\">\nimport SplitPane from '@front/features/layout/SplitPane.vue'\nimport { computed, defineComponent, ref "
},
{
"path": "packages/app-frontend/src/features/plugin/index.ts",
"chars": 1998,
"preview": "import { computed, ref } from 'vue'\nimport type { PluginDescriptor } from '@vue/devtools-api'\nimport type { Bridge } fro"
},
{
"path": "packages/app-frontend/src/features/settings/GlobalSettings.vue",
"chars": 5931,
"preview": "<script lang=\"ts\" setup>\nimport { ref } from 'vue'\nimport { useLocalStorage } from '@vueuse/core'\nimport { clearStorage "
},
{
"path": "packages/app-frontend/src/features/settings/NewTag.vue",
"chars": 579,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n inject: [\n 'currentSetti"
},
{
"path": "packages/app-frontend/src/features/timeline/AskScreenshotPermission.vue",
"chars": 1576,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\nimport { SharedData } from '@vue-devtools/shared-utils'\n\nexport"
},
{
"path": "packages/app-frontend/src/features/timeline/LayerItem.vue",
"chars": 4852,
"preview": "<script lang=\"ts\">\nimport PluginSourceIcon from '@front/features/plugin/PluginSourceIcon.vue'\n\nimport type { PropType } "
},
{
"path": "packages/app-frontend/src/features/timeline/Timeline.vue",
"chars": 17321,
"preview": "<script lang=\"ts\">\nimport SplitPane from '@front/features/layout/SplitPane.vue'\nimport PluginSourceIcon from '@front/fea"
},
{
"path": "packages/app-frontend/src/features/timeline/TimelineEventInspector.vue",
"chars": 3908,
"preview": "<script lang=\"ts\">\nimport StateInspector from '@front/features/inspector/StateInspector.vue'\nimport EmptyPane from '@fro"
},
{
"path": "packages/app-frontend/src/features/timeline/TimelineEventList.vue",
"chars": 6907,
"preview": "<script lang=\"ts\">\nimport EmptyPane from '@front/features/layout/EmptyPane.vue'\n\nimport { computed, defineComponent, ref"
},
{
"path": "packages/app-frontend/src/features/timeline/TimelineEventListItem.vue",
"chars": 2525,
"preview": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent } from 'vue'\nimport { formatTi"
},
{
"path": "packages/app-frontend/src/features/timeline/TimelineScrollbar.vue",
"chars": 5168,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent, onUnmounted, ref } from 'vue'\n\nexport default defineComponent({\n "
},
{
"path": "packages/app-frontend/src/features/timeline/TimelineView.vue",
"chars": 41779,
"preview": "<script lang=\"ts\">\nimport * as PIXI from 'pixi.js-legacy'\nimport { install as installUnsafeEval } from '@pixi/unsafe-eva"
},
{
"path": "packages/app-frontend/src/features/timeline/composable/events.ts",
"chars": 4124,
"preview": "import { computed, onUnmounted, watch } from 'vue'\nimport { BridgeEvents, setStorage } from '@vue-devtools/shared-utils'"
},
{
"path": "packages/app-frontend/src/features/timeline/composable/index.ts",
"chars": 200,
"preview": "export * from './events'\nexport * from './layers'\nexport * from './markers'\nexport * from './reset'\nexport * from './scr"
},
{
"path": "packages/app-frontend/src/features/timeline/composable/layers.ts",
"chars": 4288,
"preview": "import { computed } from 'vue'\nimport { BridgeEvents, setStorage } from '@vue-devtools/shared-utils'\nimport { useApps, w"
},
{
"path": "packages/app-frontend/src/features/timeline/composable/markers.ts",
"chars": 664,
"preview": "import { useCurrentApp } from '@front/features/apps'\nimport { computed, watch } from 'vue'\nimport { getBridge } from '@f"
},
{
"path": "packages/app-frontend/src/features/timeline/composable/reset.ts",
"chars": 1442,
"preview": "import { onUnmounted } from 'vue'\nimport { BridgeEvents, getStorage } from '@vue-devtools/shared-utils'\nimport { getBrid"
},
{
"path": "packages/app-frontend/src/features/timeline/composable/screenshot.ts",
"chars": 2634,
"preview": "import { BridgeEvents, SharedData } from '@vue-devtools/shared-utils'\nimport { useApps } from '@front/features/apps'\nimp"
},
{
"path": "packages/app-frontend/src/features/timeline/composable/setup.ts",
"chars": 4280,
"preview": "import type { Bridge } from '@vue-devtools/shared-utils'\nimport { BridgeEvents, parse } from '@vue-devtools/shared-utils"
},
{
"path": "packages/app-frontend/src/features/timeline/composable/store.ts",
"chars": 2704,
"preview": "import type { Ref } from 'vue'\nimport { ref } from 'vue'\nimport type { ID } from '@vue/devtools-api'\nimport type * as PI"
},
{
"path": "packages/app-frontend/src/features/timeline/composable/time.ts",
"chars": 250,
"preview": "import {\n cursorTime,\n endTime,\n maxTime,\n minTime,\n startTime,\n} from './store'\n\nexport function useTime() {\n ret"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueButton.vue",
"chars": 2478,
"preview": "<script lang=\"ts\">\nimport type { PropType } from 'vue'\nimport { computed, defineComponent, useAttrs } from 'vue'\nimport "
},
{
"path": "packages/app-frontend/src/features/ui/components/VueDisable.vue",
"chars": 1022,
"preview": "<script>\nimport { computed, defineComponent, h } from 'vue'\nimport { useDisabledChild, useDisabledParent } from '../comp"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueDropdown.vue",
"chars": 4215,
"preview": "<script lang=\"ts\">\nimport {\n computed,\n defineComponent,\n nextTick,\n onBeforeUnmount,\n onMounted,\n ref,\n watch,\n}"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueDropdownButton.vue",
"chars": 263,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n name: 'VueDropdownButton',\n"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueFormField.vue",
"chars": 1783,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent, provide, reactive } from 'vue'\n\nconst statusIcons = {\n danger: '"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueGroup.vue",
"chars": 2558,
"preview": "<script lang=\"ts\">\nimport {\n computed,\n defineComponent,\n nextTick,\n onMounted,\n provide,\n ref,\n useSlots,\n watc"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueGroupButton.vue",
"chars": 1142,
"preview": "<script lang=\"ts\">\nimport type { Ref } from 'vue'\nimport { computed, defineComponent, inject, watch } from 'vue'\n\nexport"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueIcon.vue",
"chars": 308,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n props: {\n icon: {\n "
},
{
"path": "packages/app-frontend/src/features/ui/components/VueInput.vue",
"chars": 8832,
"preview": "<script lang=\"ts\">\nimport type {\n UnwrapNestedRefs,\n} from 'vue'\nimport {\n computed,\n defineComponent,\n inject,\n re"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueLoadingBar.vue",
"chars": 478,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n name: 'VueLoadingBar',\n pr"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueLoadingIndicator.vue",
"chars": 116,
"preview": "<template>\n <div class=\"vue-ui-loading-indicator\">\n <div class=\"animation\" />\n <slot />\n </div>\n</template>\n"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueModal.vue",
"chars": 1773,
"preview": "<script lang=\"ts\">\n/* eslint-disable vue/no-unused-refs */\n\nimport { defineComponent, nextTick, onMounted, ref } from 'v"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueSelect.vue",
"chars": 1760,
"preview": "<script lang=\"ts\">\nimport type { ComponentInternalInstance } from 'vue'\nimport { computed, defineComponent, provide, ref"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueSelectButton.vue",
"chars": 1186,
"preview": "<script lang=\"ts\">\nimport type { ComponentInternalInstance, Ref } from 'vue'\nimport { defineComponent, getCurrentInstanc"
},
{
"path": "packages/app-frontend/src/features/ui/components/VueSwitch.vue",
"chars": 1689,
"preview": "<script lang=\"ts\">\nimport { computed, defineComponent, ref } from 'vue'\nimport { useDisabledChild } from '../composables"
},
{
"path": "packages/app-frontend/src/features/ui/components/icons.ts",
"chars": 1138,
"preview": "const icons = require.context(\n '@akryum/md-icons-svg/svg/',\n true,\n /materialicons\\/24px\\.svg$/,\n)\n\nexport default {"
},
{
"path": "packages/app-frontend/src/features/ui/composables/useDisableScroll.ts",
"chars": 584,
"preview": "import { onBeforeUnmount, onMounted } from 'vue'\n\nlet count = 0\n\nfunction getScrollingElements() {\n return document.que"
},
{
"path": "packages/app-frontend/src/features/ui/composables/useDisabled.ts",
"chars": 868,
"preview": "/**\n * (Use with the DisabledChild mixin)\n * Allow disabling an entire tree of components implementing the DisabledChild"
},
{
"path": "packages/app-frontend/src/features/ui/index.ts",
"chars": 2106,
"preview": "import type { Plugin } from 'vue'\nimport FloatingVue from 'floating-vue'\nimport VueIcons from './components/icons'\nimpor"
}
]
// ... and 204 more files (download for full content)
About this extraction
This page contains the full source code of the vuejs/devtools-v6 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 404 files (910.8 KB), approximately 248.1k tokens, and a symbol index with 775 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.