vite-plugin-devtools-vue2 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shuoshubao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # vite-plugin-vue2-devtools
2
+
3
+ 一个**仅用于开发环境**的 Vite 插件,为 Vue 2.6 应用注入一个悬浮的组件审查面板(devtools)。
4
+
5
+ 面板基于 Shadow DOM + [lit](https://lit.dev/) 渲染,运行在页面同一上下文中,直接读取 Vue 组件实例,无需跨上下文桥接,也不依赖浏览器扩展。
6
+
7
+ ## 特性
8
+
9
+ - **组件树**:实时展示组件层级,支持搜索、展开/折叠、方向键导航
10
+ - **状态审查**:查看选中组件的 `props` / `data` / `computed` / `attrs`,值可就地编辑、一键复制
11
+ - **组件拾取器**:在页面上点选元素直接定位到对应组件;悬停高亮组件 DOM
12
+ - **源码跳转**:调用 Vite 的 `/__open-in-editor` 在编辑器中打开组件源文件
13
+ - **渲染函数**:查看组件的 `render` 函数源码
14
+ - **Vuex**:查看 state 快照、时间旅行(time-travel)、提交历史
15
+ - **悬浮面板**:可拖拽吸附到任意边缘,窗口缩放时自动保持在可视区域内
16
+ - **兼容 externals**:即使 Vue 被外部化为全局变量(`vite-plugin-externals`),组件树也能正常刷新
17
+
18
+ ## 环境要求
19
+
20
+ - Vue `2.6.x`
21
+ - Vite(`apply: 'serve'`,仅在 dev server 生效)
22
+ - 配合 [`vite-plugin-vue2`](https://github.com/vitejs/vite-plugin-vue2) 使用
23
+
24
+ ## 安装
25
+
26
+ ```bash
27
+ npm i -D vite-plugin-vue2-devtools
28
+ ```
29
+
30
+ ## 使用
31
+
32
+ 在 `vite.config.js` 中注册插件:
33
+
34
+ ```js
35
+ import { createVuePlugin } from 'vite-plugin-vue2';
36
+ import vueDevtools from 'vite-plugin-vue2-devtools';
37
+
38
+ export default {
39
+ plugins: [createVuePlugin(), vueDevtools()]
40
+ };
41
+ ```
42
+
43
+ 启动 dev server 后,页面右下角会出现一个悬浮入口,点击即可打开审查面板。插件不接受任何配置项。
44
+
45
+ > 该插件仅在 `vite serve`(开发模式)下生效,`vite build` 时会被自动跳过,不会进入生产产物。
46
+
47
+ ## 工作原理
48
+
49
+ - 插件通过 `transformIndexHtml` 把客户端脚本以 `head-prepend` 的方式注入到页面 `<head>` 最前面,**先于应用加载 Vue**,从而在 Vue 之前装好全局 devtools hook(`__VUE_DEVTOOLS_GLOBAL_HOOK__`)。
50
+ - 组件树通过遍历 DOM 上的 `el.__vue__` 反推得到,并借助 Vue 的 `flush` 事件与 `MutationObserver` 双重刷新,保证在各种构建形态下都能保持同步。
51
+ - 面板与页面处于同一 realm,可直接读取组件实例的响应式数据,无需序列化桥接。
52
+
53
+ ## License
54
+
55
+ [MIT](./LICENSE)
package/lib/hook.js ADDED
@@ -0,0 +1,41 @@
1
+ // hook.js — installs the Vue 2 global devtools hook.
2
+ //
3
+ // Vue 2.6 checks `window.__VUE_DEVTOOLS_GLOBAL_HOOK__` shortly after it loads
4
+ // (inside a setTimeout in vue.runtime.esm.js) and, when `config.devtools` is
5
+ // true (the default in dev builds), calls `hook.emit('init', Vue)`. After every
6
+ // patch the scheduler calls `hook.emit('flush')`. We implement a tiny emitter
7
+ // so we can capture the Vue constructor and subscribe to flushes for live
8
+ // refresh. This module MUST be imported before the app imports Vue.
9
+
10
+ const HOOK_KEY = '__VUE_DEVTOOLS_GLOBAL_HOOK__';
11
+
12
+ const createHook = () => {
13
+ const listeners = Object.create(null);
14
+ return {
15
+ // captured Vue constructor (set on 'init')
16
+ Vue: undefined,
17
+ on(event, fn) {
18
+ (listeners[event] || (listeners[event] = [])).push(fn);
19
+ },
20
+ off(event, fn) {
21
+ const arr = listeners[event];
22
+ if (!arr) return;
23
+ const i = arr.indexOf(fn);
24
+ if (i > -1) arr.splice(i, 1);
25
+ },
26
+ emit(event, ...args) {
27
+ const arr = listeners[event];
28
+ if (arr) arr.slice().forEach(fn => fn(...args));
29
+ }
30
+ };
31
+ };
32
+
33
+ // Reuse an existing hook if one somehow already exists, otherwise install ours.
34
+ const hook = window[HOOK_KEY] || (window[HOOK_KEY] = createHook());
35
+
36
+ // Capture the Vue constructor as soon as Vue registers itself.
37
+ hook.on('init', Vue => {
38
+ hook.Vue = Vue;
39
+ });
40
+
41
+ export default hook;
package/lib/index.js ADDED
@@ -0,0 +1,63 @@
1
+ import { dirname, resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { searchForWorkspaceRoot } from 'vite';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+
7
+ // Absolute path to the browser-side entry that gets injected into the page.
8
+ // It must run BEFORE the app imports Vue, so we inject it at the top of <head>.
9
+ const CLIENT_ENTRY = resolve(__dirname, 'main.js');
10
+
11
+ // Root of this plugin package — needed so Vite's dev server is allowed to
12
+ // serve the plugin's own files that live outside the demo project root.
13
+ const PLUGIN_ROOT = __dirname;
14
+
15
+ /**
16
+ * vite-plugin-vue2-devtools
17
+ *
18
+ * Dev-only plugin. Injects a small client bundle that hooks into the Vue 2.6
19
+ * global devtools hook, walks the component tree and renders a floating
20
+ * inspector panel (Shadow DOM + lit) living in the same page realm — so it can
21
+ * read component instances directly without any cross-context bridge.
22
+ *
23
+ * @returns {import('vite').Plugin}
24
+ */
25
+ const vueDevTools = () => {
26
+ return {
27
+ name: 'vite-plugin-vue2-devtools',
28
+ // Inspector is a development aid only; never touch the production build.
29
+ apply: 'serve',
30
+
31
+ config() {
32
+ return {
33
+ server: {
34
+ fs: {
35
+ // Setting `fs.allow` replaces Vite's default (which includes the
36
+ // project/workspace root), so we must re-add it — otherwise the
37
+ // app's own /src files get a 403. Plus this package's own dir so
38
+ // the injected client can be served from outside the demo root.
39
+ allow: [searchForWorkspaceRoot(process.cwd()), PLUGIN_ROOT]
40
+ }
41
+ }
42
+ };
43
+ },
44
+
45
+ transformIndexHtml() {
46
+ return [
47
+ {
48
+ tag: 'script',
49
+ attrs: {
50
+ type: 'module',
51
+ // `/@fs/` lets Vite serve + transform a file by absolute path.
52
+ src: `/@fs/${CLIENT_ENTRY}`
53
+ },
54
+ // head-prepend => this module script executes before the app's
55
+ // module script, so our global hook is installed before Vue loads.
56
+ injectTo: 'head-prepend'
57
+ }
58
+ ];
59
+ }
60
+ };
61
+ };
62
+
63
+ export default vueDevTools;
@@ -0,0 +1,38 @@
1
+ // inspector.js — a single reusable highlight box drawn over a component's DOM.
2
+
3
+ let box = null;
4
+
5
+ const ensureBox = () => {
6
+ if (box) return box;
7
+ box = document.createElement('div');
8
+ Object.assign(box.style, {
9
+ position: 'fixed',
10
+ zIndex: '2147483646', // just under the panel
11
+ background: 'rgba(65, 184, 131, 0.35)', // Vue green
12
+ border: '1px solid rgba(65, 184, 131, 0.9)',
13
+ borderRadius: '3px',
14
+ pointerEvents: 'none',
15
+ display: 'none',
16
+ transition: 'all 0.08s ease-out'
17
+ });
18
+ document.body.appendChild(box);
19
+ return box;
20
+ };
21
+
22
+ export const highlight = vm => {
23
+ const el = vm && vm.$el;
24
+ if (!el || !el.getBoundingClientRect) return hide();
25
+ const rect = el.getBoundingClientRect();
26
+ const b = ensureBox();
27
+ Object.assign(b.style, {
28
+ display: 'block',
29
+ top: `${rect.top}px`,
30
+ left: `${rect.left}px`,
31
+ width: `${rect.width}px`,
32
+ height: `${rect.height}px`
33
+ });
34
+ };
35
+
36
+ export const hide = () => {
37
+ if (box) box.style.display = 'none';
38
+ };
package/lib/main.js ADDED
@@ -0,0 +1,21 @@
1
+ // main.js — injected entry. Importing ./hook.js first installs the global Vue
2
+ // devtools hook *before* the app imports Vue. Then we mount the inspector
3
+ // panel once the document is ready.
4
+
5
+ import './hook.js';
6
+ import './panel.js';
7
+ import './vuex.js';
8
+
9
+ const mount = () => {
10
+ if (document.querySelector('#__vue_devtools__')) return;
11
+ const host = document.createElement('div');
12
+ host.id = '__vue_devtools__';
13
+ document.body.appendChild(host);
14
+ host.appendChild(document.createElement('vue-devtools-panel'));
15
+ };
16
+
17
+ if (document.readyState === 'loading') {
18
+ document.addEventListener('DOMContentLoaded', mount);
19
+ } else {
20
+ mount();
21
+ }