Vite原理实现

Vite原理实现
最新回答
欺凌不断上演

2026-03-08 03:04:18

Vite简介

引用原作者的话:Vite,一个基于浏览器原生ESimports的开发服务器。利用浏览器去解析imports,在服务器端按需编译返回,

完全跳过了打包这个概念,服务器随起随用。同时不仅有Vue文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。

针对生产环境则可以把同一份代码用rollup打包。虽然现在还比较粗糙,但这个方向我觉得是有潜力的,

做得好可以彻底解决改一行代码等半天热更新的问题。

正如尤大说的在开发环境下,跳过了打包,直接通过ESM方式加载文件。

基于webpack开发环境加载逻辑

这样的工程可能会遇到这样的问题

vite基于ESM加载逻辑

10个相同文件vue-cliVSvite

很明显,vite在开发启动时间远远领先与vue-cli。

那么vite做了什么呢?

ESModule

ES2015在语言层面上实现了模块功能,且实现简单,可以替代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