2026-03-08 03:04:18
引用原作者的话:Vite,一个基于浏览器原生ESimports的开发服务器。利用浏览器去解析imports,在服务器端按需编译返回,
完全跳过了打包这个概念,服务器随起随用。同时不仅有Vue文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。
针对生产环境则可以把同一份代码用rollup打包。虽然现在还比较粗糙,但这个方向我觉得是有潜力的,
做得好可以彻底解决改一行代码等半天热更新的问题。
正如尤大说的在开发环境下,跳过了打包,直接通过ESM方式加载文件。
基于webpack开发环境加载逻辑
这样的工程可能会遇到这样的问题
vite基于ESM加载逻辑
10个相同文件vue-cliVSvite很明显,vite在开发启动时间远远领先与vue-cli。
那么vite做了什么呢?
ESModuleES2015在语言层面上实现了模块功能,且实现简单,可以替代CommonJS和AMD规范,成为在服务器和浏览器通用的解决方案。
<scripttype="module">//标识为ESMimport{test}from'./test.js'</script>浏览器会自动发起请求,请求test.js文件。
但ESM不支持“裸”模块引入,会直接报错。例如
import{defineComponent}from'san'Vite会检测到当前服务中所以的.js文件,并重写他们的路径例如/@modules/san。
Vite使用了es-module-lexer用来解析import语法,通过替换的方式将'san'替换为'/@modules/san'。
Vite1.0浅析(暂无热更新,编译模块)启动KOA服务koa-server
constKoa=require('koa');const{staticPlugin}=require('./plugins/serverPluginStatic');const{moduleRewritePlugin}=require('./plugins/serverPluginModuleRewrite');const{moduleResolvePlugin}=require('./plugins/serverPluginModuleResolve');const{htmlRewritePlugin}=require('./plugins/serverPluginHtml');const{vueComplitePlugin}=require('./plugins/serverPluginCompliteVue');functioncreateServer(){constapp=newKoa();//创建一个koa实例constroot=process.cwd();constcontext={app,root//当前根位置};//插件集合依此进行安装constresolvePlugins=[htmlRewritePlugin,//4)解析import重写路径moduleRewritePlugin,//3)解析以/@modules文件moduleResolvePlugin,//2)vue文件编译vueComplitePlugin,//1)实现静态服务的功能。最后staticPlugin];resolvePlugins.forEach(plugin=>plugin(context));returnapp;//返回app中有listen方法}module.exports=createServer;Vite通过Koa启动了一个http服务,并且加载了一些插件。通过添加插件来对不同类型的文件做不同的逻辑处理。模块的解析机制的相关插件是serverRewritePlugin和serverResolvePlugin,静态文件处理用了koa-static
模块路径重写需要先解析import语法,用来替换引入的包路径,上面我们也提到,Vite使用的是es-module-lexer用来解析import语法
中间件serverPluginModuleRewrite
moduleRewritePlugin
const{readBody}=require('./utils');const{parse}=require('es-module-lexer');//解析import语法constMagicString=require('magic-string');//字符串具有不变性functionrewriteImports(source){//letimports=parse(source);//[[],[],boolean]letimports=parse(source)[0];letmagicString=newMagicString(source);//overwrite();//n匹配到的from'vue',s=>starte=>endss=>整条语句的开始se=>整条语句结束,d=>dynamic>-1是否是动态引入//[//[//{n:'vue',s:27,e:30,ss:0,se:31,d:-1},//{n:'./App.vue',s:49,e:58,ss:32,se:59,d:-1},//{n:'./index.css',s:68,e:79,ss:60,se:80,d:-1}//],//[]//]if(imports.length){imports.forEach(i=>{let{s:start,e:end}=i;letid=source.substring(start,end);//当前开头是/或者.的不需要重写if(/^[^/.]/.test(id)){id=`/@modules/${id}`;magicString.overwrite(start,end,id);}});}returnmagicString.toString();}functionmoduleRewritePlugin({app,root}){app.use(async(ctx,next)=>{awaitnext();//静态服务if(ctx.body&&ctx.response.is('js')){letcontent=awaitreadBody(ctx.body);//重写内容将重写后的结果返回回去constresult=rewriteImports(content);ctx.body=result;}});}exports.moduleRewritePlugin=moduleRewritePlugin;模块路径引入处理/@modules/xxx/路径,返回正确的
moduleResolvePlugin
constmoduleReg=/^/@modules//;constfs=require('fs').promises;constpath=require('path');functionresolveVue(root){//vue3有几部分组成//运行时相关代码runtime-domruntime-core//compilercompiler-sfc(单文件编译部分)reactivity(:Vue3响应式部分)shared(Vue3工具库)constresolvePath=(name)=>path.resolve(root,'node_modules',`@vue/${name}/dist/${name}.esm-bundler.js`);construntimeDomPath=resolvePath('runtime-dom');construntimeCorePath=resolvePath('runtime-core');constreactivityPath=resolvePath('reactivity');constsharedPath=resolvePath('shared');return{'@vue/runtime-dom':runtimeDomPath,'@vue/runtime-core':runtimeCorePath,'@vue/reactivity':reactivityPath,'@vue/shared':sharedPath,'vue':runtimeDomPath};}functionmoduleResolvePlugin({app,root}){constvueResolved=resolveVue(root);app.use(async(ctx,next)=>{if(!moduleReg.test(ctx.path)){//处理当前请求的路径是否以/@modules/开头的returnnext();}//将/@modules/替换掉指向到当前项目中constid=ctx.path.replace(moduleReg,'');ctx.type='js';//设置响应js类型//去当前项目下查找vue对应的真实文件constcontent=awaitfs.readFile(vueResolved[id],'utf8');ctx.body=content;});}exports.moduleResolvePlugin=moduleResolvePlugin;vue文件处理vueCompilePlugin
constpath=require('path');constfs=require('fs').promises;functionvueCompilePlugin({root,app}){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){letcontent=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';letcontent=descriptor.template.content;const{code}=compileTemplate({source:content});//将app.vue中的模板转换成render函数ctx.body=code;}})};exports.vueCompilePlugin=vueCompilePlugin;参考资料:
Vite中文文档
Vite2.0升级内容
koa2-中间件原理
npminit解读
es-module-lexer
ViteGithub
Koa
ESM工作原理
ESM兼容性
原文:https://juejin.cn/post/7096374285593215012