写在开头是因为在vue3上门使用了vite这个构建工具,其构建思路优于webpack,而且底层还使用了esbuild,性能更好所以为了照顾一些没学过vite的朋友,一起来看看什么是vite,什么是viteVite,一个基于浏览器原生ES导入的开发服务器。在服务端使用浏览器解析导入,按需编译返回,完全跳过打包的概念,服务端随时可用,支持热更新,热更新速度不会随着增加模块。对于生产环境,同样的代码可以用rollup打包。vite的天然优势:快速冷启动服务器即时热模块替换(HMR)真正的按需编译vite工作原理当声明一个script标签类型为module时,如:浏览器会像服务端一样向main.js文件发起GEThttp://localhost:3000/src/main.js请求:///src/main.js:import{createApp}from'vue'importAppfrom'./App.vue'createApp(App).mount('#app')浏览器请求main.js文件,检测到包含import导入的包会发起HTTP请求为其内部导入引用获取模块的内容文件如:GEThttp://localhost:3000/@modules/vue.js如:GEThttp://localhost:3000/src/App的主要功能.vue的vite就是从浏览器劫持这些请求,在后端进行相应的处理,对项目中使用的文件进行简单的分解整合,然后返回给浏览器渲染页面,整个过程vite文件是没有打包编译,所以它的运行速度比原来的webpack开发编译速度要快很多。vite的简单实现是因为代码量有点大,所以就不自己写了,直接拿别人的代码。原文地址是:https://juejin.cn/post/689811...首先,koa开启访问热更新服务的监听端口functioncreateServer(){letapp=newKoa()constcontext={//创建一个直接Context为不同插件app共享函数,root:process.cwd()//执行node命令的命令路径}//运行koa中间件(就是我们的vite插件)resolvePlugin.forEach(plugin=>plugin(上下文))返回应用程序}createServer().listen(4000,()=>{})先写相应的插件处理模块的引用,因为浏览器只有相对路径和绝对路径。这里readBody其实是一个读取文件流的方法,被封装了。可以看做是一个普通的读流方法。koa中间件进程首先处理重写路径,因为浏览器只有绝对路径和相对路径。app.use(async(ctx,next)=>{awaitnext();//静态服务//默认先执行静态服务中间件,结果放在ctx.body//需要转换将stream转化为字符串,只需要处理js中的引用问题if(ctx.body&&ctx.response.is('js')){letr=awaitreadBody(ctx.body);//vue=>/@modulesconstresult=rewriteImports(r);ctx.body=result;}})},重写路径后,需要拦截.vue文件和@module(重写路径前node_modules中的文件)//2.拦截包含/@modules/vue的请求,去node_modules导入对应模块并返回({app,root})=>{constreg=/^\/@modules\//app.use(async(ctx,next)=>{//如果没有匹配到/@modules/vue,就往下执行if(!reg.test(ctx.path)){returnnext();}constid=ctx.path.replace(reg,'');让mapping={vue:path.resolve(root,'node_modules','@vue/runtime-dom/dist/runtime-dom.esm-browser.js'),}constcontent=awaitfs.readFile(映射[id],'utf8');ctx.type='js';//返回的文件是jsctx.body=content;})},解析处理完路径后,我们需要解析vue模板文件,(如果是reactjsx代码,同理)//3.解析.vue文件({app,root})=>{app.use(async(ctx,next)=>{if(!ctx.path.endsWith('.vue')){returnnext();}constfilePath=path.join(root,ctx.path);constcontent=awaitfs.readFile(filePath,'utf8');//导入.vue文件解析模板const{compileTemplate,parse}=require(path.resolve(root,'node_modules','@vue/compiler-sfc/dist/compiler-sfc.cjs'))let{descriptor}=parse(content);if(!ctx.query.type){//App.vueletcode=''if(descriptor.script){让content=descriptor.script.content;code+=content.replace(/((?:^|\n|;)\s*)exportdefault/,'$1const__script=');}if(descriptor.template){constrequestPath=ctx.path+`?type=template`;code+=`\nimport{renderas__render}from"${requestPath}"`;code+=`\n__script.render=__render`}code+=`\nexportdefault__script`ctx.type='js';ctx.body=code}if(ctx.query.type=='template'){ctx.type='js';让内容=descriptor.template。contentconst{code}=compileTemplate({source:content});//将app.vue中的模板转换为渲染函数ctx.body=code;}})},//4.静态服务插件实现可以返回文件function({app,root})=>{app.use(static(root))app.use(static(path.resolve(root,'public')))}]functioncreateServer(){letapp=newKoa()constcontext={//直接创建一个context与不同的插件共享函数app,root:process.cwd()//C:\Users\...\my-vite-vue3}//运行中间件resolvePlugin.forEach(plugin=>plugin(context))returnapp}以下是两个实用函数:一个它一个是stream的读取,另一个是重写路径的函数//readbodymethodasyncfunctionreadBody(stream){if(streaminstanceofReadable){returnnewPromise((resolve)=>{letres=''stream.on('data',function(chunk){res+=chunk});stream.on('end',function(){resolve(res)})})}else{returnstream;}}重写中间路径ComponentconstresolvePlugin=[//1.在导入模块路径前添加/@modules/vue,重写后浏览器会再次发送请求({app,root})=>{functionrewriteImports(source){letimports=parse(source)[0];让ms=newMagicString(源);如果(导入.length>0){for(leti=0;i
