├── README.md ├── README.zh-CN.md ├── css ├── highlight.css └── index.css ├── images ├── icon128.png ├── icon32.png └── icon48.png ├── index.js ├── manifest.json └── vue-component-finder.crx /README.md: -------------------------------------------------------------------------------- 1 | # vue-component-finder 2 | vue-component-finder is a Chrome plugin for Vue project, which help developer preview component code module and locate code file in IDE quickly. 3 | 4 | [中文README](https://github.com/csonlai/vue-component-finder/blob/master/README.zh-CN.md) 5 | 6 | # Example 7 | ![插件展示][1] 8 | 9 | 1. "template","script" and "style" tag of the component in code file and line number 10 | 11 | 2. File path with line number the component was created 12 | 13 | 3. Component code file preview 14 | 15 | 4. Position of the component in html page 16 | 17 | Open IDE with path clicked and locate the component file and code line: 18 | 19 | ![代码定位][2] 20 | 21 | # Usage 22 | 23 | 1.Install loader and webpack plugin: 24 | ``` 25 | npm install vue-component-finder-loader && npm install vue-component-finder-plugin 26 | ``` 27 | 2.Use loader and webpack plugin in Vue project dev build: 28 | 29 | Use loader: 30 | 31 | webpack 2.x: 32 | ``` js 33 | module: { 34 | rules: [{ 35 | test: /\.(vue)$/, 36 | loader: 'vue-component-finder-loader', 37 | enforce: "pre", 38 | include: ['src'] 39 | }] 40 | } 41 | ``` 42 | webpack 1.x: 43 | ``` js 44 | module: { 45 | preLoaders: [{ 46 | test: /\.(vue)$/, 47 | loader: 'vue-component-finder-loader', 48 | include: ['src'] 49 | }] 50 | } 51 | ``` 52 | Use webpack plugin and config IDE type and IDE path: 53 | ``` js 54 | var VueComponentFinderPlugin = require('vue-component-finder-plugin'); 55 | 56 | plugins: [ 57 | new VueComponentFinderPlugin({ 58 | editor: 'sublime', 59 | cmd: 'E:\\Sublime Text 2\\sublime_text.exe' 60 | }); 61 | ] 62 | ``` 63 | 64 | 3.Install Chrome plugin vue-component-finder.crx,You can download from github or from chrome app store:https://chrome.google.com/webstore/detail/vue-component-finder/maldlhiallkfciipjnedanjjpnfaljpl 65 | 66 | 4.npm run dev, and open html page, when hover the Vue component, it will show code module info of component and locate component Vue file after path clicked 67 | 68 | 69 | 70 | [1]: http://p.qpic.cn/pic_wework/3832524150/beb84ab606969bfaf48d8997b870cfa549817938e8657f98/0 71 | [2]: http://p.qpic.cn/pic_wework/3832524150/b3b547bb07efdf6682e4d13f9bdd5c939537ac9915842d7d/0 72 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # vue-component-finder 2 | vue-component-finder是一款用于Vue项目的代码模块预览与快速定位的chrome插件,对于文件目录繁多的vue项目,可以使用该插件快速查看组件对应的代码模块,以及快速打开IDE修改组件代码。 3 | 4 | [English README](https://github.com/csonlai/vue-component-finder/blob/master/README.md) 5 | 6 | # 插件展示 7 | ![插件展示][1] 8 | 9 | 10 | 1. 组件的template,script,style对应所在的文件以及起始行数 11 | 12 | 2. 组件被创建的文件以及行数 13 | 14 | 3. 文件代码预览 15 | 16 | 4. 组件所在页面位置 17 | 18 | 点击自动打开IDE,并定位到对应文件和对应的代码行: 19 | 20 | ![代码定位][2] 21 | 22 | # 如何使用 23 | 24 | 1.安装对应的loader与webpack plugin: 25 | ``` 26 | npm install vue-component-finder-loader && npm install vue-component-finder-plugin 27 | ``` 28 | 2.在项目的dev构建中引入loader与plugin: 29 | 30 | 引入loader: 31 | 32 | webpack 2.x: 33 | ``` js 34 | module: { 35 | rules: [{ 36 | test: /\.(vue)$/, 37 | loader: 'vue-component-finder-loader', 38 | enforce: "pre", 39 | include: ['src'] 40 | }] 41 | } 42 | ``` 43 | webpack 1.x: 44 | ``` js 45 | module: { 46 | preLoaders: [{ 47 | test: /\.(vue)$/, 48 | loader: 'vue-component-finder-loader', 49 | include: ['src'] 50 | }] 51 | } 52 | ``` 53 | 引入plugin并配置对应IDE类型以及文件路径(sublime为例): 54 | ``` js 55 | var VueComponentFinderPlugin = require('vue-component-finder-plugin'); 56 | 57 | plugins: [ 58 | new VueComponentFinderPlugin({ 59 | editor: 'sublime', 60 | cmd: 'E:\\Sublime Text 2\\sublime_text.exe' 61 | }); 62 | ] 63 | ``` 64 | 65 | 3.安装chrome插件vue-component-finder.crx 66 | 67 | 4.运行项目开发构建npm run dev,打开页面,鼠标移动到组件区域即可展示对应模块详情,点击自动打开IDE展示对应组件文件内容。 68 | 69 | 70 | [1]: http://p.qpic.cn/pic_wework/3832524150/beb84ab606969bfaf48d8997b870cfa549817938e8657f98/0 71 | [2]: http://p.qpic.cn/pic_wework/3832524150/b3b547bb07efdf6682e4d13f9bdd5c939537ac9915842d7d/0 -------------------------------------------------------------------------------- /css/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | IR_Black style (c) Vasily Mikhailitchenko 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | background: #000; 10 | color: #f8f8f8; 11 | } 12 | 13 | .hljs-comment, 14 | .hljs-quote, 15 | .hljs-meta { 16 | color: #7c7c7c; 17 | } 18 | 19 | .hljs-keyword, 20 | .hljs-selector-tag, 21 | .hljs-tag, 22 | .hljs-name { 23 | color: #96cbfe; 24 | } 25 | 26 | .hljs-attribute, 27 | .hljs-selector-id { 28 | color: #ffffb6; 29 | } 30 | 31 | .hljs-string, 32 | .hljs-selector-attr, 33 | .hljs-selector-pseudo, 34 | .hljs-addition { 35 | color: #a8ff60; 36 | } 37 | 38 | .hljs-subst { 39 | color: #daefa3; 40 | } 41 | 42 | .hljs-regexp, 43 | .hljs-link { 44 | color: #e9c062; 45 | } 46 | 47 | .hljs-title, 48 | .hljs-section, 49 | .hljs-type, 50 | .hljs-doctag { 51 | color: #ffffb6; 52 | } 53 | 54 | .hljs-symbol, 55 | .hljs-bullet, 56 | .hljs-variable, 57 | .hljs-template-variable, 58 | .hljs-literal { 59 | color: #c6c5fe; 60 | } 61 | 62 | .hljs-number, 63 | .hljs-deletion { 64 | color:#ff73fd; 65 | } 66 | 67 | .hljs-emphasis { 68 | font-style: italic; 69 | } 70 | 71 | .hljs-strong { 72 | font-weight: bold; 73 | } 74 | -------------------------------------------------------------------------------- /css/index.css: -------------------------------------------------------------------------------- 1 | .vfinder-info a, 2 | .vfinder-info div { 3 | color: #dedede; 4 | } 5 | .vfinder-info *, 6 | .vfinder-code * 7 | { 8 | margin: 0; 9 | padding: 0; 10 | border-width: 0; 11 | background: transparent; 12 | } 13 | .vfinder-info > div { 14 | padding: 4px 12px; 15 | } 16 | .vfinder-info > div:hover{ 17 | background: rgba(140, 140, 140, 0.36); 18 | } 19 | .vfinder-info { 20 | position: fixed; 21 | border-radius: 4px; 22 | padding: 5px 0; 23 | background: rgba(64, 64, 64, 0.9); 24 | 25 | /* padding: 10px 18px;*/ 26 | z-index: 9999999; 27 | border: 1px solid rgb(143, 143, 143); 28 | font-size: 13px; 29 | } 30 | .vfinder-info:after{ 31 | position: absolute; 32 | content: " "; 33 | width: 10px; 34 | height: 10px; 35 | -webkit-transform: rotate(45deg); 36 | background: rgba(64, 64, 64, 0.9); 37 | left: -5px; 38 | top: 50%; 39 | margin-top: -5px; 40 | } 41 | .vfinder-info-name-template{ 42 | color: #ff7474; 43 | } 44 | .vfinder-info-name-style{ 45 | color: #17ceff; 46 | } 47 | .vfinder-info-name-script{ 48 | color: #f281ff; 49 | } 50 | .vfinder-info-name-created{ 51 | color: #40c22a; 52 | } 53 | .vfinder-info-name-template, 54 | .vfinder-info-name-style, 55 | .vfinder-info-name-script, 56 | .vfinder-info-name-created{ 57 | display: inline-block; 58 | width: 75px; 59 | } 60 | .vfinder-mask { 61 | position: fixed; 62 | z-index: 9999998; 63 | background-color: rgba(1, 165, 255, 0.2); 64 | pointer-events: none; 65 | } 66 | .vfinder-code{ 67 | position: fixed; 68 | /* padding: 10px;*/ 69 | /* background: rgba(254, 255, 221, 0.9);*/ 70 | background: rgba(64, 64, 64, 0.9); 71 | max-width: 400px; 72 | font-size: 10px; 73 | z-index: 10000000; 74 | word-wrap: break-word; 75 | border-radius: 4px; 76 | /* color: #747474;*/ 77 | color: #dedede; 78 | border: 1px solid rgba(222, 222, 222, 0.34); 79 | } 80 | .vfinder-path::-webkit-scrollbar, 81 | .vfinder-code-content::-webkit-scrollbar { 82 | height: 8px; 83 | width: 8px; 84 | } 85 | .vfinder-path::-webkit-scrollbar-thumb, 86 | .vfinder-code-content::-webkit-scrollbar-thumb { 87 | background: rgba(103, 103, 103, 0.9); 88 | border-radius: 4px; 89 | } 90 | .vfinder-path::-webkit-scrollbar-track, 91 | .vfinder-code-content::-webkit-scrollbar-track { 92 | background: #ddd; 93 | border-radius: 4px; 94 | } 95 | 96 | 97 | .vfinder-code-content{ 98 | padding: 0 10px; 99 | overflow: auto; 100 | max-height: 260px; 101 | } 102 | .vfinder-code-content p { 103 | padding: 2px 6px 2px 0; 104 | } 105 | .vfinder-code-content > pre { 106 | float: left; 107 | clear: left; 108 | white-space: nowrap; 109 | } 110 | .vfinder-current-code-line{ 111 | background: rgba(234, 102, 223, 0.25); 112 | border-radius: 2px; 113 | } 114 | .vfinder-path{ 115 | padding: 10px; 116 | border-bottom: 1px solid rgba(222, 222, 222, 0.34); 117 | background: rgba(140, 140, 140, 0.36); 118 | white-space:nowrap; 119 | overflow-x: auto; 120 | } 121 | .vfinder-line-count{ 122 | color: #dedede; 123 | min-width: 25px; 124 | display: inline-block; 125 | } 126 | 127 | 128 | -------------------------------------------------------------------------------- /images/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csonlai/vue-component-finder/99d1d43723fc99db288dd873a4f51003447c71d5/images/icon128.png -------------------------------------------------------------------------------- /images/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csonlai/vue-component-finder/99d1d43723fc99db288dd873a4f51003447c71d5/images/icon32.png -------------------------------------------------------------------------------- /images/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csonlai/vue-component-finder/99d1d43723fc99db288dd873a4f51003447c71d5/images/icon48.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var mask; 3 | var maskRect; 4 | var info; 5 | var infoRect; 6 | var code; 7 | var currentTemplatePath; 8 | var currentLineIndex; 9 | var offsetCount = 5; 10 | var port = 8493; 11 | var protocol = window.location.protocol; 12 | var timeId; 13 | 14 | 15 | function request(url,cb){ 16 | var xmlHttpReq = new XMLHttpRequest(); 17 | xmlHttpReq.open('GET', url); 18 | xmlHttpReq.send(null); 19 | xmlHttpReq.onreadystatechange = function () { 20 | if(xmlHttpReq.readyState === 4){ 21 | cb && cb(xmlHttpReq.responseText); 22 | } 23 | } 24 | } 25 | 26 | function fectchContent(path,type){ 27 | var url = chrome.extension.getURL(path); 28 | request(url,function(content){ 29 | var s = document.createElement(type); 30 | s.innerHTML = content; 31 | document.getElementsByTagName('head')[0].appendChild(s); 32 | }); 33 | } 34 | 35 | fectchContent('./css/index.css','style'); 36 | fectchContent('./css/highlight.css','style'); 37 | 38 | function getCodeContent (contentList , viewLineCount, viewLineLastCount) { 39 | if (!contentList) return ''; 40 | var dCount = 0; 41 | var startCount, endCount; 42 | 43 | startCount = Math.max(1, viewLineCount - offsetCount); 44 | 45 | endCount = startCount + offsetCount * 2 - 1; 46 | 47 | var codeArr = []; 48 | for (var i = 0; i < contentList.length; i++) { 49 | var lineCount = i + 1; 50 | var newCodeLine; 51 | var lineText; 52 | var lineElement = document.createElement('p'); 53 | 54 | if (lineCount >= startCount && lineCount <= endCount){ 55 | lineText = '' + lineCount + ':' + contentList[i]; 56 | // 当前行 57 | if (lineCount >= viewLineCount && (viewLineLastCount == null || lineCount <= viewLineLastCount)) { 58 | lineElement.className = 'vfinder-current-code-line'; 59 | } 60 | lineElement.innerHTML = lineText; 61 | codeArr.push('
' + lineElement.outerHTML + '
'); 62 | } 63 | } 64 | return codeArr.join(''); 65 | } 66 | 67 | 68 | function setCode (path, linkElement) { 69 | if (!code) { 70 | code = document.createElement('div'); 71 | code.className = 'vfinder-code'; 72 | document.body.appendChild(code); 73 | } 74 | code.style.display = 'block'; 75 | request(protocol + '//localhost:' + port + '?view=' + encodeURIComponent(path) + '&count=' + (offsetCount * 2), function(data) { 76 | var contentList; 77 | var codeRect; 78 | var viewLineCount; 79 | var codeContent; 80 | var lastIndex = linkElement.getAttribute('data-last-index'); 81 | var codeElement = document.createElement('div'); 82 | codeElement.className = 'vfinder-code-content'; 83 | var dataType = linkElement.getAttribute('data-type'); 84 | if (data) { 85 | data = JSON.parse(data); 86 | contentList = data.contentList; 87 | viewLineCount = data.viewLineCount; 88 | } 89 | 90 | infoRect = info.getBoundingClientRect(); 91 | // 显示的代码片段内容 92 | codeContent = getCodeContent(contentList , viewLineCount, lastIndex === '' ? null : Number(lastIndex) + 1); 93 | 94 | 95 | codeElement.innerHTML = codeContent; 96 | code.innerHTML = `
${path}
`; 97 | code.appendChild(codeElement); 98 | 99 | codeRect = code.getBoundingClientRect(); 100 | 101 | var linkRect = linkElement.getBoundingClientRect(); 102 | 103 | code.style.left = infoRect.left + (infoRect.width - codeRect.width) / 2 + 'px'; 104 | 105 | if (infoRect.right + codeRect.width > window.innerWidth) { 106 | code.style.left = infoRect.left - codeRect.width + 20 + 'px'; 107 | } 108 | else { 109 | code.style.left = infoRect.left + infoRect.width - 20 + 'px'; 110 | } 111 | if (infoRect.top + codeRect.height > window.innerHeight) { 112 | code.style.top = window.innerHeight - codeRect.height - 20 + 'px'; 113 | } 114 | else { 115 | code.style.top = linkRect.top - 30 + 'px'; 116 | } 117 | }); 118 | } 119 | 120 | 121 | function setMask (target) { 122 | var windowWidth = window.innerWidth; 123 | var windowHeight = window.innerHeight; 124 | var top; 125 | var left; 126 | 127 | if (!mask) { 128 | mask = document.createElement('div'); 129 | mask.className = 'vfinder-mask'; 130 | document.body.appendChild(mask); 131 | } 132 | maskRect = target.getBoundingClientRect(); 133 | 134 | mask.style.display = 'block'; 135 | mask.style.left = maskRect.left + 'px'; 136 | mask.style.top = maskRect.top + 'px'; 137 | mask.style.width = maskRect.width + 'px'; 138 | mask.style.height = maskRect.height + 'px'; 139 | 140 | 141 | if (!info) { 142 | info = document.createElement('div'); 143 | info.className = 'vfinder-info'; 144 | bindInfoClick(info); 145 | bindInfoHover(info); 146 | document.body.appendChild(info); 147 | } 148 | 149 | info.style.display = 'block'; 150 | 151 | 152 | info.innerHTML = createSingleLink('template', target) 153 | + createSingleLink('script', target) 154 | + createSingleLink('style', target) 155 | + createSingleLink('created', target, currentLineIndex); 156 | 157 | infoRect = info.getBoundingClientRect(); 158 | // 超出右边界 159 | if (maskRect.right + infoRect.width > windowWidth) { 160 | info.style.right = 20 + 'px'; 161 | info.style.left = 'auto'; 162 | } 163 | else { 164 | info.style.left = maskRect.right - Math.min(30, (maskRect.width * 0.1)) + 'px'; 165 | info.style.right = 'auto'; 166 | } 167 | if (infoRect.height > maskRect.height) { 168 | top = maskRect.top + ((maskRect.height - infoRect.height) / 2); 169 | } 170 | else { 171 | top = maskRect.top + (Math.min(30, maskRect.height * 0.1)); 172 | } 173 | if (top + infoRect.height > windowHeight) { 174 | top = windowHeight - infoRect.height - 10; 175 | } 176 | else if (top < 0) { 177 | top = 10; 178 | } 179 | info.style.top = top + 'px'; 180 | 181 | } 182 | 183 | function bindInfoHover (info) { 184 | info.addEventListener('mouseover', function(e){ 185 | var target = e.target; 186 | if (target.tagName === 'A') { 187 | setCode(target.getAttribute("data-path"), target); 188 | } 189 | }); 190 | } 191 | 192 | function createSingleLink (name, target) { 193 | var path = target.getAttribute('data-' + name + '-path'); 194 | var lineIndex; 195 | 196 | if (!path) { 197 | return ''; 198 | } 199 | 200 | if (name === 'created') { 201 | lineIndex = target.getAttribute('data-created-index'); 202 | } 203 | var lastLineIndex = target.getAttribute('data-' + name + '-last-index'); 204 | 205 | path = lineIndex == null ? path : path + ':' + (Number(lineIndex) + 1); 206 | var fileName = path.replace(/^.*[\\\/]/, ''); 207 | return `
${name}:${fileName}
`; 208 | } 209 | 210 | function bindInfoClick (info) { 211 | info.addEventListener('mousedown', function(e){ 212 | var target = e.target; 213 | var path = target.getAttribute('data-path'); 214 | if (path) { 215 | request(protocol + '//localhost:8493?open=' + encodeURIComponent(path)); 216 | } 217 | }); 218 | } 219 | 220 | 221 | function getComponentElement (target) { 222 | if (!target) return; 223 | while(target && target.getAttribute) { 224 | var templatePath = target.getAttribute('data-template-path'); 225 | var parentTemplatePath = target.getAttribute('data-parent-template-path'); 226 | if (templatePath || parentTemplatePath) { 227 | currentTemplatePath = templatePath; 228 | 229 | return target; 230 | } 231 | target = target.parentNode; 232 | } 233 | } 234 | 235 | 236 | document.addEventListener('mouseover',function(e){ 237 | var target = e.target; 238 | if ((info && info.contains(target)) || (code && code.contains(target))) { 239 | return; 240 | } 241 | if (code) { 242 | code.style.display = 'none'; 243 | } 244 | target = getComponentElement(target); 245 | if (!target) { 246 | if (mask) { 247 | currentTemplatePath = null; 248 | mask.style.display = info.style.display = 'none'; 249 | } 250 | return; 251 | } 252 | setMask(target); 253 | }); 254 | })(); 255 | 256 | 257 | 258 | 259 | 260 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vue Component Finder", 3 | "version": "1.0", 4 | "manifest_version": 2, 5 | "description": "Tool for locate Vue component code.", 6 | "content_scripts": [ 7 | { 8 | "matches": ["http://*/*","https://*/*"], 9 | "js": ["index.js"], 10 | "run_at":"document_end" 11 | } 12 | ], 13 | "icons": { 14 | "32": "./images/icon32.png", 15 | "48": "./images/icon48.png", 16 | "128": "./images/icon128.png" 17 | }, 18 | "web_accessible_resources": [ 19 | "css/index.css", 20 | "css/highlight.css" 21 | ] 22 | } -------------------------------------------------------------------------------- /vue-component-finder.crx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csonlai/vue-component-finder/99d1d43723fc99db288dd873a4f51003447c71d5/vue-component-finder.crx --------------------------------------------------------------------------------