【webpack+loader+plugin】帮女同事做了一个代码定位功能,结果......😭
事情是这样的
我叫A。
多年母胎solo,女人只会影响我拔刀的速度,我的♥就像在大润发宰了10年🐟一样。我认为我自己的一生不会为任何事物所动心。
直到前两天公司来个很好看的前端妹子,我俩对视第一眼,我的道心差点就动摇了。
还好还好,毕竟不是加入我的项目组,领导安排她加入在隔壁的项目组,一切都还好。
当天一切如初。
第二天早上,经过妹子的座位时,发现妹子眉头紧锁,我猜测应该是遇到了困难,作为一个友好青年,平时看见老奶奶过马路都会去扶的,我立马就过去打探打探,问问是不是项目上遇到什么困难了,有没有什么需要帮忙的之类。
没想到这个妹子好像发现了救星一样,连忙和我说:“帅哥你过来了正好,这个项目结构太复杂了,我要找个页面入口都找半天,看着头痛,有项目文档什么的吗?”
进行了简单的交流之后,我回到座位上帮她寻找项目文档。经过我一番搜寻,发现这个项目根本就没有文档,这没办法啊。
正准备起身告诉妹子我爱莫能助的时候,突然脑子里有一个想法。
不就是找不到代码入口嘛,我做个功能让你在网页上能快速找到入口就好了。
说干就干了
既然想法有了,那就立马动手。
妹子他们的项目是一个vue2+vue-cli4
的项目,vue-cli
也就是整webpack
那一套,那么行,那我就用webpack
的插件功能和loader功能配合吧
思路:先通在网页标签上添加上文件地址和代码行数属性,然后通过nodejs来定位到文件并打开
既然要写loader
和plugin
那么大家要先了解好这两个玩意是什么,可以去webpack的 官网 了解一下,这一文章不陈述这些东西。
loader
按照上面的思路,我们如何在网页的标签上添加文件路径和代码行数呢?
答案就是:在编译过程中添加上。(原理不说这么多)
开始干活。
在项目文件夹下创建一个loaders
的文件夹,并新建一个叫position-loader.js
的文件
首先写一个简单的loader
// position-loader.js
module.exports = function (source) {
return source
}
这样子,其实就是一个loader
了,当然我们要做的不是这么简单。在loader
的这个方法里面,有一个参数source
,在我们这个loader
里面,即将接受到的是每个vue
文件的代码,一串字符串(这个为什么vue文件的代码,后面会说)。
那我按照思路,拿到了源码,我们接下来要做的就是将源码中的标签添加一个属性来记录源文件路径和当前代码行数就行了。
// 切割源代码
const codeLineTrack = (code, path) => {
const lineList = code.split('\n')
const newList = []
lineList.forEach((item, index) => {
// 为每个源码添加属性
newList.push(addLineAttr(item, index + 1, path))
})
return newList.join('\n')
}
// 添加标签属性
const addLineAttr = (lineStr, line, path) => {
if (!/^\s+</.test(lineStr)) {
return lineStr
}
// 利用一个正则避开一些不需要添加属性的标签
const reg = /((((^(\s)+\<))|(^\<))[\w-]+)|(<\/template)/g
let leftTagList = lineStr.match(reg)
if (leftTagList) {
leftTagList = Array.from(new Set(leftTagList))
leftTagList.forEach((item) => {
const skip = [
'KeepAlive',
'template',
'keep-alive',
'transition',
'router-view',
]
// 为符合的标签元素添加 code-location属性
if (item && !skip.some((i) => item.indexOf(i) > -1)) {
const reg = new RegExp(`${item}`)
const location = `${item} code-location="${path}:${line}"`
lineStr = lineStr.replace(reg, location)
}
})
}
return lineStr
}
上面代码中有两个函数,一个是codeLineTrack
函数,接受两个参数源代码
和当前文件地址
,将源码字符串用换行符切割,以行为单位分个一个个元素,然后遍历用addLineAttr
为符合要求的标签添加上code-position
属性。
之后就在刚刚我们一开始新建的loader
函数中调用,整个loader的代码如下
module.exports = function (source) {
// this.resourcePath为当前编译文件的源路径
const res = codeLineTrack(source, this.resourcePath)
return res
}
const codeLineTrack = (code, path) => {
const lineList = code.split('\n')
const newList = []
lineList.forEach((item, index) => {
newList.push(addLineAttr(item, index + 1, path)) // 添加位置属性,index+1为具体的代码行号
})
return newList.join('\n')
}
const addLineAttr = (lineStr, line, path) => {
if (!/^\s+</.test(lineStr)) {
return lineStr
}
const reg = /((((^(\s)+\<))|(^\<))[\w-]+)|(<\/template)/g
let leftTagList = lineStr.match(reg)
if (leftTagList) {
leftTagList = Array.from(new Set(leftTagList))
leftTagList.forEach((item) => {
const skip = [
'KeepAlive',
'template',
'keep-alive',
'transition',
'router-view',
]
if (item && !skip.some((i) => item.indexOf(i) > -1)) {
const reg = new RegExp(`${item}`)
const location = `${item} code-location="${path}:${line}"`
lineStr = lineStr.replace(reg, location)
}
})
}
return lineStr
}
既然loader
写完了,那我们就赶紧用上,在项目的vue.config.js
文件下添加如下配置。
module.exports = {
configureWebpack: {
resolveLoader: {
//找loader的时候,先去loaders目录下找,找不到再去node_modules下面找
modules: ["loaders", "node_modules"],
}
},
// 我们在vue-loader之前先编译用我们的loader编译一遍
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.end()
.use('position-loader')
.loader('position-loader')
.end()
},
}
在上面的配置中,我们自己写的loader
放在vue-loader
之前,先利用我们的loader
添加完属性之后,在利用vue-loader
编译,vue-loader
是专门用来编译vue
文件的,所以之前我们loader
接收到的参数是vue
文件的代码,就是这个原因。
那我们来看看添加我们的loader
之后,我们的标签发生了什么变化。
nice 🚀🚀🚀🚀🚀,我们已经成功了第一步了。
plugin
我们成功的在标签上加上了文件的源路径和代码行数有什么用呢?难道就只有提示功能吗,还要我自己打开vscode
去找对应文件,这也太low
了吧。
要是我们到了这一步就完事了,妹子也看不起我,所以我们还要弄一个更智能的功能,就是思路里说的通过nodejs找到文件并打开。
接下来我们需要编写一个简单的webpack-plugin来实现这个功能,插件的编写方法可以自己去官网看。
我们要编写的插件功能是,启动一个nodejs的服务,然后前端给这个服务发送标签的code-location属性,然后通过这个属性来打开我们的vscode
// position-plugin.js
const http = require('http')
const child_process = require('child_process')
class codePositionServer {
constructor(options) {
this.options = options
this.server = null
}
apply (compiler) {
// 挂在一个钩子
compiler.hooks.done.tap('codePositionServer', compilation => {
const { port } = this.options
if (this.server) return
this.server = http.createServer((req, res) => {
const code = req.url.split('?')[1]
if (code.length) {
const path = code.split('=')[1]
if (path) {
child_process.exec('code -r -g ' + path)
}
}
})
this.server.listen(port, () => {
console.log(`codePositionServer started on port ${port}`);
})
})
}
}
module.exports = codePositionServer
上面的代码整体意思是,创建一个webpack插件,然后通过插件启动一个node服务,然后接受一个get请求,获取get请求上的参数,拿到地址和行数,然后通过chin_process.exec('code -r -g xxx')打开你的vscode工具
插件写完了,就去vue.config.js
挂载吧。
// vue.config.js
const codePositionServer = require('./plugins/position')
module.exports = {
configureWebpack: {
// 其他配置
plugins: [new codePositionServer({ port: 8999 })]
},
// 其他配置
}
做完上面的操作之后,我们距离这个功能只剩一下一步了,就是前端发送一个get请求给我们的node服务。
那我们应该怎么做呢?
我们实现一个方法,当我们点击网页元素的时候,获取元素的
code-location
属性,然后发送一个fetch
给我们服务
有完没完了
好了,直接上代码
// position.js
const initDom = () => {
document.onmousedown = (e) => {
e = e || window.event
if (e.shiftKey && e.button === 0) {
e.preventDefault()
sendRequestToOpenFileInEditor(getFilePath(e))
}
}
}
const getFilePath = (e) => {
let element = e
if (e.target) {
element = e.target
}
if (!element || !element.getAttribute) return null
if (element.getAttribute('code-location')) {
return element.getAttribute('code-location')
}
return this.getFilePath(element.parentNode)
}
const sendRequestToOpenFileInEditor = (filePath) => {
const protocol = window.location.protocol
? window.location.protocol
: 'http:'
const hostname = window.location.hostname
? window.location.hostname
: 'localhost'
const port = 8999
fetch(`${protocol}//${hostname}:${port}?filePath=${filePath}`)
.catch((error) => {
console.log(error)
})
}
export default {
initDom
}
代码中,initDome
函数监听了用户的操作,当用户按住shift
+ 鼠标左键
点击元素后,会通过getFilePath
函数获取到标签的code-location
属性,之后会发送一个fetch
请求到我们的服务,服务接收到参数之后就会打开我们的vscode了。
然后我们直接在main.js
中注册函数
// main.js
import initDom from './position.js'
initDom()
然后再启动我们的项目,现在按住shift
+ 鼠标左键,我们的vscode就可以自动帮我们打开对应的文件,而且定位到代码函数了~~~
最后结局
当我写完这个功能,并植入他们的项目之后,我就去告诉了妹子同事这个好消息。
等待。。。。。。
在煎熬的时间里,我一直等着我的微信消息,终于。
叮~~~,我的微信来消息了。
妹子同事:“哇,帅哥,你真的好厉害啊,真的谢谢你了”。
得到妹子的夸奖,那么接下来就要趁热打铁,乘胜追击!!!
我直接就一个直球,很快啊,她来不及闪的,一条信息发过去。
我以为我的技术这么好,她肯定没办法拒绝......
结果!!!
原来,我只能是一个朋友。
四月又要到了,我只是她的友人A 💔💔💔