vite-plugin-vscode-jumper
Version:
A Vite plugin to enable element picker and jump to Vue SFC source files in VSCode via shortcut
1 lines • 12.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/enableElementPicker.ts","../src/client.ts"],"sourcesContent":["import type { Plugin } from 'vite'\r\nimport { parse } from '@vue/compiler-sfc'\r\nimport clientCode from './client'\r\n\r\nexport default function vscodeJumper(): Plugin {\r\n return {\r\n name: 'vite-plugin-vscode-jumper',\r\n enforce: 'pre',\r\n\r\n transform(code, id) {\r\n if (!id.endsWith('.vue') || process.env.NODE_ENV === 'production') return null\r\n\r\n const { descriptor } = parse(code)\r\n const { template } = descriptor\r\n if (!template) return null\r\n\r\n if (template.content.includes('code-path=')) {\r\n // 已经注入过了,避免重复注入\r\n return null\r\n }\r\n\r\n const absPath = `vscode://file/${id.replace(/\\\\/g, '/')}`\r\n const newTemplate = template.content.replace(\r\n /<([a-zA-Z0-9\\-]+)([^>]*)>/,\r\n `<$1$2 code-path=\"${absPath}\">`\r\n )\r\n\r\n const newCode = code.replace(template.content, newTemplate)\r\n return {\r\n code: newCode,\r\n map: null,\r\n }\r\n },\r\n\r\n configureServer(server) {\r\n // 注入浏览器端代码到页面\r\n server.middlewares.use((req, res, next) => {\r\n if (req.url === '/@vscode-jumper-client') {\r\n res.setHeader('Content-Type', 'application/javascript')\r\n res.end(clientCode)\r\n } else {\r\n next()\r\n }\r\n })\r\n },\r\n\r\n transformIndexHtml(html) {\r\n // 在 index.html 注入浏览器端脚本\r\n const scriptTag = `<script type=\"module\" src=\"/@vscode-jumper-client\"></script>`\r\n return html.replace(/<\\/head>/, `${scriptTag}\\n</head>`)\r\n },\r\n }\r\n}\r\n","// utils/enableElementPicker.ts\r\n\r\nexport const createHighlightOverlay = (): HTMLDivElement => {\r\n const overlay = document.createElement('div')\r\n overlay.id = '__element_selector_overlay__'\r\n Object.assign(overlay.style, {\r\n position: 'absolute',\r\n pointerEvents: 'none',\r\n border: '2px solid #00a8ff',\r\n zIndex: '999999',\r\n background: 'rgba(0, 168, 255, 0.1)',\r\n display: 'none',\r\n })\r\n document.body.appendChild(overlay)\r\n return overlay\r\n}\r\n\r\nexport function enableElementPicker(\r\n onPick: (el: HTMLElement) => void,\r\n isActiveGetter: () => boolean,\r\n ESC?: () => void,\r\n) {\r\n let overlay: HTMLDivElement | null = null\r\n\r\n const addGlobalPickingStyle = () => {\r\n document.body.style.cursor = 'pointer'\r\n document.body.style.userSelect = 'none'\r\n }\r\n\r\n const removeGlobalPickingStyle = () => {\r\n document.body.style.cursor = ''\r\n document.body.style.userSelect = ''\r\n }\r\n\r\n const moveHandler = (e: MouseEvent) => {\r\n if (!isActiveGetter()) return\r\n const target = e.target as HTMLElement\r\n if (!target || target === overlay) return\r\n\r\n const rect = target.getBoundingClientRect()\r\n if (!overlay) {\r\n overlay = createHighlightOverlay()\r\n addGlobalPickingStyle()\r\n }\r\n\r\n Object.assign(overlay.style, {\r\n top: `${rect.top + window.scrollY}px`,\r\n left: `${rect.left + window.scrollX}px`,\r\n width: `${rect.width}px`,\r\n height: `${rect.height}px`,\r\n display: 'block',\r\n })\r\n }\r\n\r\n const clickHandler = (e: MouseEvent) => {\r\n if (!isActiveGetter() || !overlay) return\r\n e.preventDefault()\r\n e.stopPropagation()\r\n const target = e.target as HTMLElement\r\n onPick(target)\r\n cleanup()\r\n }\r\n\r\n const keydownHandler = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') {\r\n cleanup()\r\n if (ESC) ESC()\r\n }\r\n }\r\n\r\n const cleanup = () => {\r\n document.removeEventListener('mousemove', moveHandler, true)\r\n document.removeEventListener('click', clickHandler, true)\r\n document.removeEventListener('keydown', keydownHandler, true)\r\n overlay?.remove()\r\n overlay = null\r\n removeGlobalPickingStyle()\r\n }\r\n\r\n document.addEventListener('mousemove', moveHandler, true)\r\n document.addEventListener('click', clickHandler, true)\r\n document.addEventListener('keydown', keydownHandler, true)\r\n\r\n return cleanup\r\n}\r\n","import { enableElementPicker } from './enableElementPicker'\r\ntype KeyHandler = (el: HTMLElement) => void\r\n\r\n// 这里把 enableElementPicker 代码也写进来,或者用字符串拼接的方式注入\r\n\r\nfunction findElementWithCodePath(el) {\r\n while (el && el !== document.body) {\r\n if (el.hasAttribute('code-path')) return el\r\n el = el.parentElement\r\n }\r\n return null\r\n}\r\n\r\nfunction parseShortcut(shortcut: string) {\r\n const parts = shortcut.toLowerCase().split('+')\r\n const keySet = new Set(parts)\r\n return {\r\n ctrl: keySet.has('ctrl'),\r\n shift: keySet.has('shift'),\r\n alt: keySet.has('alt'),\r\n meta: keySet.has('meta'),\r\n key: parts.find(k => !['ctrl', 'shift', 'alt', 'meta'].includes(k)) || '',\r\n allKeys: parts,\r\n }\r\n}\r\n\r\nexport function useGlobalElementShortcut(shortcut: string, callback: KeyHandler) {\r\n let isPicking = false\r\n let cleanupPicker: (() => void) | null = null\r\n\r\n const { ctrl, shift, alt, meta, key, allKeys } = parseShortcut(shortcut)\r\n\r\n const isShortcutPressed = (e: KeyboardEvent) => {\r\n return (\r\n (ctrl === e.ctrlKey) &&\r\n (shift === e.shiftKey) &&\r\n (alt === e.altKey) &&\r\n (meta === e.metaKey) &&\r\n (key ? e.key.toLowerCase() === key : true)\r\n )\r\n }\r\n\r\n const keydownHandler = (e: KeyboardEvent) => {\r\n if (isShortcutPressed(e) && !isPicking) {\r\n isPicking = true\r\n cleanupPicker = enableElementPicker(\r\n (el: HTMLElement) => {\r\n callback(el)\r\n cleanupPicker?.()\r\n cleanupPicker = null\r\n isPicking = false\r\n\r\n if (document.activeElement instanceof HTMLElement) {\r\n document.activeElement.blur()\r\n }\r\n },\r\n () => isPicking,\r\n )\r\n }\r\n }\r\n\r\n const keyupHandler = (e: KeyboardEvent) => {\r\n if (!isPicking) return\r\n\r\n const releasedKey = e.key.toLowerCase()\r\n const keyMap = new Map<string, boolean>([\r\n ['ctrl', e.ctrlKey],\r\n ['shift', e.shiftKey],\r\n ['alt', e.altKey],\r\n ['meta', e.metaKey],\r\n ])\r\n\r\n const comboKeyReleased = allKeys.some(k => {\r\n if (['ctrl', 'shift', 'alt', 'meta'].includes(k)) {\r\n return !keyMap.get(k)\r\n } else {\r\n return k === releasedKey\r\n }\r\n })\r\n\r\n if (comboKeyReleased) {\r\n cleanupPicker?.()\r\n cleanupPicker = null\r\n isPicking = false\r\n\r\n if (document.activeElement instanceof HTMLElement) {\r\n document.activeElement.blur()\r\n }\r\n }\r\n }\r\n\r\n window.addEventListener('keydown', keydownHandler, true)\r\n window.addEventListener('keyup', keyupHandler, true)\r\n\r\n return () => {\r\n window.removeEventListener('keydown', keydownHandler, true)\r\n window.removeEventListener('keyup', keyupHandler, true)\r\n cleanupPicker?.()\r\n cleanupPicker = null\r\n isPicking = false\r\n }\r\n}\r\n\r\n\r\nif (import.meta.env.DEV) {\r\n useGlobalElementShortcut('alt+shift', (el) => {\r\n const target = findElementWithCodePath(el)\r\n if (target) {\r\n const url = target.getAttribute('code-path')\r\n if (url?.startsWith('vscode://file/')) {\r\n window.location.href = url\r\n }\r\n }\r\n })\r\n}\r\n\r\nexport default `\r\n// 这里是上面写的脚本内容,注意转义和打包方式\r\n`\r\n"],"mappings":"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","names":[]}