You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
watcher.on('change',async(file)=>{file=normalizePath(file)if(file.endsWith('/package.json')){returninvalidatePackageData(packageCache,file)}// invalidate module graph cache on file changemoduleGraph.onFileChange(file)if(serverConfig.hmr!==false){try{awaithandleHMRUpdate(file,server)}catch(err){ws.send({type: 'error',err: prepareError(err)})}}})
handleHMRUpdate 触发编译更新文件
handleHMRUpdate 会通过 webSocket 向客户端发送更新消息:
full-reload: 页面更新
update: 部分更新
插件容器(pluginContainer): 用于执行各个插件的相关钩子
// container.buildStart()if(!middlewareMode&&httpServer){letisOptimized=false// overwrite listen to init optimizer before server startconstlisten=httpServer.listen.bind(httpServer)httpServer.listen=(async(port: number, ...args: any[])=>{if(!isOptimized){try{awaitcontainer.buildStart({})initOptimizer()isOptimized=true}catch(e){httpServer.emit('error',e)return}}returnlisten(port, ...args)})asany}else{awaitcontainer.buildStart({})initOptimizer()}
exportfunctionresolveCompiler(root: string): typeof_compiler{// resolve from project root first, then fallback to peer dep (if any)constcompiler=tryRequire('vue/compiler-sfc',root)||tryRequire('vue/compiler-sfc')if(!compiler){thrownewError(`Failed to resolve vue/compiler-sfc.\n`+`@vitejs/plugin-vue requires vue (>=3.2.25) `+`to be present in the dependency tree.`)}returncompiler}
可以看出,其用的是 compiler-sfc。
load
在 load 钩子执行前,vite 已经启动了。
此时用浏览器或其他方式访问 vite 在控制台提示的访问路径,如 http://localhost:3000/
本文发表在 https://github.com/hoperyy/blog
(0)概述
总体而言,vite 利用「中间件」和「暴露一系列生命周期的插件」,完成其主要功能。
「中间件」负责 server 端的对请求 url、文件修改、代码转译等方面的工作;「插件」通过暴露 vite 各个生命周期钩子,通过代码转译等操作,实现对 vite 流转过程的影响,从功能上它是 webpack plugin 和 loader 的结合体。
对源码的拆解是个相对繁杂的工程,我也还未对 vite 源码逐行分解,目前主要的解读方向是「主要工具」和「值得学习的技巧」。
(1)工程化
(2)cli
cac
(3)vite dev/serve
3.1 解析用户配置
定义文件
vite/packages/vite/src/node/config.ts
配置收集(
resolveConfig
)configFile
: 配置文件,默认读取根目录下的 vite.config.js 配置文件envFile
: 环境变量配置文件,默认读取根目录下的 .env 环境变量配置文件root
: 项目根目录,默认process.cwd()
base
: 类似于 webpack 中的 publicPath,资源的公共基础路径server
: 本地服务的配置,如port
、host
build
: 构建产物配置preview
: 预览配置。执行 build 后,可以用 vite preview 执行预览publicDir
: 静态资源目录,用于放置不需要编译的静态资源,默认值是 public 目录cacheDir
: 缓存目录,用于存放预编译的依赖等,如 .vite/depsmode
:development | production
,编译模式define
: 定义全局变量。不同的是,开发环境是定义在全局,生产环境是静态替换plugins
: 配置插件resolve
:css
:json
:esbuild
:assetsInclude
:optimizeDeps
: 依赖优化配置ssr
: ssr 相关配置logLevel
: 控制台日志级别,默认info
clearScreen
: 重复编译后,是否清除之前的日志,默认是true
envDir
: 加载 .env 环境变量配置文件的目录,默认是当前根目录envPrefix
: 环境变量的前缀,带前缀的环境变量会被注入项目worker
: 配置 bundle 输出类型、rollup 配置、plugins 等插件排序
插件执行顺序:
enforce: pre
> normal >enforce: post
。处理 alias
合并用户配置的 alias 配置。
读取 env 环境变量文件
loadEnv()
方法定义。依次读取
.env.{mode}.local
/.env.{mode}
/.env.local
/.env
。默认返回一个空对象。
初始化构建配置(
build
)resolveBuildOptions()
方法。收尾
publicDir
、optimizeDeps
等后3.2 启动本地服务 createServer
定义文件
vite/packages/vite/src/node/server/index.ts
收集 httpServer 相关配置
config
对象中拿到config.server.https / config.cacheDir
等配置resolveHttpsConfig()
生成httpsConfig
从
config.server.middlewareMode
获取是否是ssr
利用
connect()
的中间件能力生成 middleware 容器如果不是 ssr 模式,启动 webSocket 服务
文件监听 + 热重载
利用 chokidar 对项目文件进行监听
handleHMRUpdate 触发编译更新文件
handleHMRUpdate 会通过 webSocket 向客户端发送更新消息:
full-reload
: 页面更新update
: 部分更新插件容器(pluginContainer): 用于执行各个插件的相关钩子
3.3 中间件
技巧
所有的中间件都返回了具名方法,便于 debug 调试
中间件列表
3.4 预构建依赖
(4)插件
4.1 生命周期定义
插件所有的生命周期钩子定义在
vite/packages/vite/src/node/plugin.ts
并且生命周期继承自 RollupPlugin 钩子。
4.2 生命周期钩子
name
: 插件名称handleHotUpdate
: 执行自定义 HMR(模块热替换)更新处理config
: 在解析 Vite 配置前调用。可以自定义配置,会与 vite 基础配置进行合并configResolved
: 在解析 Vite 配置后调用。可以读取 vite 的配置,进行一些操作configureServer
: 用于配置开发服务器的钩子。最常见的用例是在内部 connect 应用程序中添加自定义中间件transformIndexHtml
: 转换 index.html 的专用钩子options
: 在收集 rollup 配置前,vite (本地)服务启动时调用,可以和 rollup 配置进行合并buildStart
: 在 rollup 构建中,vite (本地)服务启动时调用,在这个函数中可以访问 rollup 的配置resolveId
: 在解析模块时调用,可以返回一个特殊的 resolveId 来指定某个 import 语句加载特定的模块load
: 在解析模块时调用,可以返回代码块来指定某个 import 语句加载特定的模块transform
: 在解析模块时调用,将源代码进行转换,输出转换后的结果,类似于 webpack 的 loaderbuildEnd
: 在 vite 本地服务关闭前,rollup 输出文件到目录前调用closeBundle
: 在 vite 本地服务关闭前,rollup 输出文件到目录前调用4.3 内置插件
4.4 plugin-vue 插件详解
config
定义了两个全局变量
__VUE_OPTIONS_API__
/__VUE_PROD_DEVTOOLS__
。设置了要为 SSR 强制外部化的依赖。
configResolved
在 config 钩子执行完成后,下一个调用的是 configResolved 钩子。
该钩子读取了 config 对象的
config.root
、config.isProduction
配置插件内部配置项。另外,sourceMap 生成有一定的逻辑,本地开发环境会一直生成,生产环境会有条件判断。
configureServer
只是将 vite 钩子中的 server 对象放到了内部配置项里。
buildStart
设置了 compiler 编译对象,优先使用用户配置的 compiler。
内置的 compiler 生成逻辑位于:
vite/packages/plugin-vue/src/compiler.ts
可以看出,其用的是 compiler-sfc。
load
在
load
钩子执行前,vite 已经启动了。此时用浏览器或其他方式访问 vite 在控制台提示的访问路径,如
http://localhost:3000/
打开服务后,会触发
load
钩子执行。解析 id(也就是请求链接)
过滤非 vue 请求
解析目标 vue 文件(
const descriptor = getDescriptor(filename, options)!
)getDescriptor
内部调用了compiler.parse
,也就是用vue/compiler-sfc
解析出 vue 文件的 template / style / script 部分transform
在 load 返回了对应的代码片段后,进入到 transform 钩子。
transform 做了 3 件事:
transformMain 函数内部主要做了几件事情:
解构 vue 文件的 script、template、style
解析 vue 文件中的 script 代码
内部使用
genScriptCode()
方法转译。会解析 scripts 中定义的变量、定义的 import,最终转译为
解析 vue 文件中的 template 代码
内部使用
genTemplateCode()
方法转译。template 转译后,template 部分代码会通过 AST 解析、节点操作等,转译为:
解析 vue 文件中的 style 标签
会解析为一段
import 'xxx.vue?vue&type=style&index=0&scoped=true&lang.less'
。接下来会触发请求,并由 transform 钩子的 transformStyle 方法处理样式的编译。
解析 vue 文件中的 自定义模块 代码
处理 HMR(模块热重载)的逻辑
处理 ssr 的逻辑
处理 sourcemap 的逻辑
处理 ts 的转换,转成成 es
内部通过 esbuild 完成 ts 到 es 的转译。
最终,transform 会将 vue 的 template、script、style 解析为一段 es 的代码。
handleHotUpdate
文件模块热重载。
当 vue 文件修改后,会触发
handleHotUpdate
钩子。钩子内部会继续使用compiler.parse
解析 vue 文件内容,并检测发生变更的部分。该钩子会返回一个变更模块的数组,交给 vite 处理。
(5)client
connected
update
custom
full-reload
prune
error
(6)日志
silent
error
warn
info
clearScreen
(7)打开浏览器
vite/packages/vite/src/node/server/openBrowser.ts
open
打开)(8)参考资料
The text was updated successfully, but these errors were encountered: