转载请联系唯一大学前端技术公众号。前言vite是一套基于浏览器支持ESM模块解决大型应用本地开发环境打包和长期热更新的解决方案。目前支持vue、react、Svelte、Solid等主流框架。相信很多同学都已经开始使用vite,体验“飞”的体验了。让我们看看vite支持如何反应。1.启动首先,从github上拉下vite的源码,准备gitclonehttps://github.com/vitejs/vite.gitcdviteyarncdpackages/viteyarnbuildyarnlink用脚手架搭建vite-react项目yarncreate@vitejs/appmy-react-app--templatereactyarnlinkviteyarndev加上node浏览器调试脚本"debug":"node--inspect-brknode_modules/vite/dist/node/cli.js"启动服务,可以看到多了一段vite/clinet和@react/的代码refreshinindex.html比源码,脚本的类型是module类型,下面我们根据源码来分析一下vite是怎么做到这一层改造的。2、中间件(middleware)Vite2.x之后,抛弃了原来1.x的koa模型,采用了node原生http模块+connect中间件模型。在请求localhost的过程中,首先会通过connect-history-api-fallback定位到index.html,然后进入下一个中间件indexHtmlMiddleware,这里会真正执行createDevHtmlTransformFn函数//packages\vite\src\node\server\middlewares\indexHtml.tsexportfunctioncreateDevHtmlTransformFn(server:ViteDevServer):(url:string,html:string,originalUrl:string)=>Promise<字符串>{const[preHooks,postHooks]=resolveHtmlTransforms(server.config.plugins)返回(url:字符串,html:字符串,originalUrl:字符串):承诺<字符串>=>{returnapplyHtmlTransforms(html,[...preHooks,devHtmlHook,...postHooks],{路径:url,文件名:getHtmlFilename(url,server),server,originalUrl})}}这里会导出两个hooks,function是devHtmlHook,把@/vite/client.js插入headerreact-refresh,把一堆react-refresh代码插入headerhereexplains截图中的两个脚本来自哪里,@/vite/client简单的说,.js就是一些支持热更新的代码vite-hmr的e,@react-refresh是vite支持react的热更新插件代码。3.从入口文件(index.html)发起的Transform资源请求会进入transformMiddleware//packages\vite\src\node\server\index.ts文件转换核心middlewares.use(transformMiddleware(server))//packages\vite\src\node\server\transformRequest.tsexportasyncfunctiontransformRequest(url{config,pluginContainer,moduleGraph,watcher}options){...constloadResult=awaitpluginContainer.load(id,ssr)code=loadResult.codemap=loadResult.map//代码转换,调用一系列插件进行代码转换consttransformStart=isDebug?Date.now():0consttransformResult=awaitpluginContainer.transform(code,id,map,ssr)code=transformResult.code!map=transformResult.mapreturn(mod.transformResult={code,map,etag:getEtag(code,{weak:true})}asTransformResult)}pluginContainer的transform函数会在初始化时调用vite内置的一系列插件对源码进行转换。以src/main.jsx文件为例,首先源码importReactfrom'react'importReactDOMfrom'react-dom'import'./index.css'importAppfrom'./App'ReactDOM.render(,document.getElementById('root'))会被名为vite:esbuild的插件识别使用内置的esbuildapitransform,将jsx语法翻译成React.createE元素,替换了babel的一部分.StrictMode,null,/*@__PURE__*/React.createElement(App,null)),document.getElementById("root"));然后会进入标识名为vite:import-analysis的插件原生ES导入支持裸模块导入,Vite会在服务的所有源文件中检测此类裸模块导入,预构建并重写importlegalurlimport{someMethod}from'my-dep'由于浏览器不支持直接导入裸模块,所以需要将模块地址改写成真实的资源文件地址。import-analysis使用es-module-lexer包动态分析当前代码中import语法涉及的依赖关系。比如上面的react和react-dom解析成依赖文件的本地地址(/node_modules/.vite文件夹),然后调用内置的transformCjsImport函数转换Commonjs类型包的import语句,比如importReactfrom"react"会被翻译成import__vite__cjsImport0_reactfrom"/node_modules/.vite/react.js?v=21227a2f";constReact=__vite__cjsImport0_react.__esModule?__vite__cjsImport0_react.default:__vite__cjsImport0_react我总觉得这部分有类似的实现网页包。有兴趣的小伙伴也可以找找看,同时说几句,vue3一样的转换逻辑,只是针对单个文件需要@vitejs/plugin-vue的支持,后面的加载逻辑形式类似,不再赘述。替换@babel/preset-react原有的功能,另外一个是封装官方的react-refresh,支持react的热更新。接下来,让我们看看它做了什么。转码其实所有的文件资源都会被@react-refresh处理,所有的jsx文件都会通过@babel/core被@react-refresh转译,但是只有真正需要热更新的react组件才会输出constresult=transformSync(代码,{babelrc:false,configFile:false,filename:id,parserOpts:{sourceType:'module',allowAwaitOutsideFunction:true,plugins:parserPlugins},generatorOpts:{decoratorsBeforeExport:true},plugins:[require('@babel/plugin-transform-react-jsx-self'),require('@babel/plugin-transform-react-jsx-source'),[require('react-refresh/babel'),{skipEnvCheck:true}]],ast:!isReasonReact,sourceMaps:true,sourceFileName:id})if(!/\$RefreshReg\$\(/.test(result.code)){//这里会用正则来分析代码块是否是a需要热更新支持的reactcomponent,否则返回源码returncode}提供额外的运行时代码//index.html插入到这串初始化代码importRefreshRuntimefrom"/@react-refresh"RefreshRuntime.injectIntoGlobalHook(window)window.$RefreshReg$=()=>{}window.$RefreshSig$=()=>(type)=>typewindow.__vite_plugin_react_preamble_installed__=trueimport{createHotContextas__vite__createHotContext}from"/@vite/客户”;import.meta.hot=__vite__createHotContext("/src/App.jsx");importRefreshRuntimefrom"/@react-refresh";if(import.meta.hot){window.$RefreshReg$=(type,id)=>{RefreshRuntime.register(type,"D:/xxx/vite-react/src/App.jsx"+id);};window.$RefreshSig$=RefreshRuntime.createSignatureFunctionForTransform;}//这里插入组件转换后的代码如果(import.meta.hot){import.meta.hot.accept();if(!window.__vite_plugin_react_timeout){window.__vite_plugin_react_timeout=setTimeout(()=>{window.__vite_plugin_react_timeout=0;RefreshRuntime.performReactReact_timeout();},30);}}importAnalysis会在jsx文件中动态插入createHotContext的代码。createHotContext是vite提供的缓存机制contextRefreshRuntime.register是react-refresh提供的注册组件的api。第二个参数是组件的文件路径加上id,用于标识需要热替换的组件。RefreshRuntime.performReactRefresh触发反应渲染。五、小结下面用一张图来总结下vite为支持react做了什么。实际上,在启动服务的时候,vite会从入口文件和预构建开始扫描所有的依赖关系,生成一个模块依赖关系moduleGraph,类似于一棵树,方便管理缓存。由于本文主要是对react的解读,就不一一深入了。以后会有其他同学更深入的解读。欢迎感兴趣的同学继续关注。姐妹们加油!