网站建设丶金手指下拉15,用模板怎么做网站,如何创建自己的网店,梵客家装和业之峰家装哪个好大家好#xff0c;我是若川。持续组织了8个月源码共读活动#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与#xff0c;每周大家一起学习200行左右的源码#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列前言这… 大家好我是若川。持续组织了8个月源码共读活动感兴趣的可以点此加我微信 ruochuan12 参与每周大家一起学习200行左右的源码共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列前言这两天肝了个Vite插件,本文主要跟大家分享一下它的功能和实现思路.如果你觉得它对你有帮助,请给一个star支持作者 .介绍vite-plugin-vue-inspector的功能是点击页面元素,自动打开本地IDE并跳转到对应的Vue组件.类似于Vue DevTools的 Open component in editor功能。若川批注关于原理可以看我写过的文章据说 99% 的人不知道 vue-devtools 还能直接打开对应组件文件本文原理揭秘用法vite-plugin-vue-inspector支持Vue2 Vue3,并且只需要进行简单的配置就可以使用.Vue2// vite.config.tsimport { defineConfig } from vite
import { createVuePlugin } from vite-plugin-vue2
import Inspector from vite-plugin-vue-inspectorexport default defineConfig({plugins: [createVuePlugin(),Inspector({vue: 2,}),],
})Vue3// vite.config.tsimport { defineConfig } from vite
import Vue from vitejs/plugin-vue
import Inspector from vite-plugin-vue-inspectorexport default defineConfig({plugins: [Vue(), Inspector()],
})IDE也要进行配置,这里就不啰嗦了, 传送门.实现思路看到这里,如果你觉得这个插件索然无味的话先别跑,插件没意思,看看怎么写插件还是有点意思的嘛 ! 接下来跟大家介绍一下这个插件的实现思路.我们先来分析一下实现这个功能我们需要有哪些元素 :Open IDE: 打开编辑器功能.Web层: 提供该功能所需的页面元素及交互功能.Server层: 用户交互时传递数据到Server层,由Server层调用Open IDE功能.DOMVue SFC映射关系: 告诉OPen IDE打开哪个文件并定位到对应的行列.明确我们需要什么元素,我们就可以进一步来梳理它的实现方式,直接晒图:vite-plugin-step.drawio (2).png实现细节接下来,我们来看具体的实现细节.在这之前,我们先简单看下我们需要用到的几个Vite插件API:function VitePluginInspector(): Plugin {return {name: vite-plugin-vue-inspector,// 应用顺序enforce: pre,// 应用模式 (只在开发模式应用)apply: serve,// 含义: 转换钩子,接收每个传入请求模块的内容和文件路径// 应用: 在这个钩子对SFC模版进行解析并注入自定义属性transform(code, id) {},// 含义: 配置开发服务器钩子,可以添加自定义中间件// 应用: 在这个钩子实现Open Editor调用服务configureServer(server) {},// 含义: 转换index.html的专用钩子,接收当前HTML字符串和转换上下文// 应用: 在这个钩子注入交互功能transformIndexHtml(html) {},}
}解析SFC模版 注入自定义属性这部分的实现主要分为两步:SFC Template AST获取元素所在组件的行和列的编号获取自定义属性插入的位置注入自定义属性file (SFC路径,用于跳转到指定文件)line (元素所在行编号,用于跳转到指定行)column (元素所在列编号,用于跳转到指定列)title (SFC名称,用于展示)// vite.config.tsfunction VitePluginInspector(): Plugin {return {name: vite-plugin-vue-inspector,transform(code, id) {const { filename, query } parseVueRequest(id)// 只处理SFC文件if (filename.endsWith(.vue) query.type ! style) return compileSFCTemplate(code, filename)return code},}
}// compiler.tsimport path from path
import MagicString from magic-string
import { parse, transform } from vue/compiler-domconst EXCLUDE_TAG [template, script, style]export async function compileSFCTemplate(code: string,id: string,
) {// MagicString是一个非常好用的字符串操作库,也如它的名字一样,非常的神奇 !// 有了它,我们可以直接操作字符串,避免操作AST,换来更好的性能. Vue3的实现也大量的用到了它.const s new MagicString(code)// SFC ASTconst ast parse(code, { comments: true })const result await new Promise((resolve) {transform(ast, {// ast node节点访问器nodeTransforms: [(node) {if (node.type 1) {// 只解析html标签 if (node.tagType 0 !EXCLUDE_TAG.includes(node.tag)) {const { base } path.parse(id)// 获取到相关信息,并进行自定义属性注入!node.loc.source.includes(data-v-inspecotr-file) s.prependLeft(node.loc.start.offset node.tag.length 1, data-v-inspecotr-file${id} data-v-inspecotr-line${node.loc.start.line} data-v-inspecotr-column${node.loc.start.column} data-v-inspecotr-title${base},)}}},],})resolve(s.toString())})return result
}注入后的DOM元素长这样 :h3 data-v-inspector-file/xxx/src/Hi.vue data-v-inspector-line3 data-v-inspector-column5 data-v-inspector-titleHi.vue
/h3Open Editor Server服务前面我们提到了创建Server服务的思路是在vite的configureServer的钩子函数注入中间件:// vite.config.tsfunction VitePluginInspector(): Plugin {return {name: vite-plugin-vue-inspector,configureServer(server) {// 注册中间件// 请求Query参数解析中间件 server.middlewares.use(queryParserMiddleware)// Open Edito服务中间件server.middlewares.use(launchEditorMiddleware)},}
}// middleware.ts// 请求Query参数解析中间件
export const queryParserMiddleware: Connect.NextHandleFunction (req: RequestMessage {query?: object},_,next,
) {if (!req.query req.url?.startsWith(SERVER_URL)) {const url new URL(req.url, http://domain.inspector)req.query Object.fromEntries(url.searchParams.entries())}next()
}// Open Editor服务中间件
export const launchEditorMiddleware: Connect.NextHandleFunction (req: RequestMessage {query?: { line: number; column: number; file: string }},res,next,
) {// 只处理Open Editor接口if (req.url.startsWith(SERVER_URL)) {// 解析SFC路径,行号,列号const { file, line, column } req.queryif (!file) {res.statusCode 500res.end(launch-editor-middleware: required query param \file\ is missing.)}const lineNumber line || 1const columnNumber column || 1// 见下方链接launchEditor(file, lineNumber, columnNumber)res.end()}else {next()}
}关于launchEditor的具体逻辑我直接fork了react-dev-utils的实现,它支持很多IDE (vscode,atom,webstorm...),它的大致原理就是通过维护一些进程映射表和环境变量,然后通过调用Node.js的子进程唤醒IDE:child_process.spawn(editor, args, { stdio: inherit });交互功能注入这个功能的实现原理其实就在transformIndexHtml注入功能所需要的html,scripts,styles.// vite.config.tsfunction VitePluginInspector(): Plugin {return {transformIndexHtml(html) {return {html,tags: [{tag: script,children: ...,injectTo: body,}, {tag: script,attrs: {type: module,},children: scripts,injectTo: body,}, {tag: style,children: styles,injectTo: head,}],}}}
}关于交互的页面实现有很多种,最简单的无非就是编写原生js,这样我们无需任何编译就可以直接注入到html中,但是用原生js来写页面真的是慢又不好维护,于是我选择了Vue进行开发,使用Vue就意味着要进行编译才能在浏览器中跑起来.为了这个所谓的研发体验,又折腾了一波,大概过程就是通过compile-sfc等包编译出render函数,样式代码等,为了兼容Vue2,我又引入了祖传的vue-template-compiler...噼里啪啦噼里啪啦..感兴趣的童鞋可以点传送门详看. (u1s1,还是有点意思的!!) 当然了,这部分的编译都是在插件打包时完成的,用户在使用插件的时候并不会有这部分的运行时开销.致谢这个项目的灵感来自于react-dev-inspector,使用React的童鞋可以看看.结语在做这个插件的时候也踩了一些坑,通过查看vue,vite等源码排查解决.这里给想看源码的童鞋一个建议,从实践和带着问题的角度出发,也许会有更好的效果和更深刻的印象 (教训) :)················· 若川简介 ·················你好我是若川毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇在知乎、掘金收获超百万阅读。从2014年起每年都会写一篇年度总结已经坚持写了8年点击查看年度总结。同时最近组织了源码共读活动帮助3000前端人学会看源码。公众号愿景帮助5年内前端人走向前列。扫码加我微信 ruochuan02、拉你进源码共读群今日话题略。分享、收藏、点赞、在看我的文章就是对我最大的支持~