本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
前语
上篇咱们说到webpack相关的规划和完成,Vite的呈现确实动摇了一下老大哥webpack的江湖地位,从2021年2月份Vite2更新,到2022年7月份Vite3发布,到今年2022年11月份Vite 4.0.0-alpha.0 (2022-11-07)开端更新,不得不说,Vite在改善前端开发体会的道路上越来越卷了,下面咱们还是带着问题去探究Vite的中心规划和完成:
一、Vite号称下一代的前端工具链,处理了什么问题?
1、改进了前端开发服务器发动时刻
Vite 经过在一开端将运用中的模块区分为依靠和源码两类,改进了传统工具,在开发服务器发动时刻
依靠,Vite 将会运用esbuild预构建依靠,esbuild 运用 Go 编写,而且比以 JavaScript 编写的打包器预构建依靠快 10-100 倍
源码,这儿通常是指一些需求转化的文件(比方JSX,CSS 或者 Vue/Svelte 组件),Vite 以原生 ESM办法提供源码,让浏览器接收打包程序的作业,并按需提供源码。
二、Vite发动都做了什么?
注: 本文vite源码版本为 v3.2.4
因为在之前的文章有写类似检查源码的办法,这儿就快速定位了 packages/vite/src/node/cli.ts
可以看到这儿创立server,是经过引证packagesvitesrcnodeserverindex.ts
里的createServer()
办法,咱们要点看看这个办法首要干了什么事情
咱们看着这段代码往下读:
...
const config = await resolveConfig(inlineConfig, 'serve', 'development')
...
(1)首要会经过resolveConfig
函数解析发动服务时分需求的装备,这包括plugins
用户插件和内建插件、cacheDir
npm 依靠预构建之后的缓存目录、在之后浏览器按需获取文件时对恳求进行截获,回来相对应内容的处理函数createResolve
,以及界说在vite.config.js
里边的resolve
,包括用户自界说的一些alias
文件的处理等。
...
const middlewares = connect() as Connect.Server
const httpServer = middlewareMode
? null
: await resolveHttpServer(serverConfig, middlewares, httpsOptions)
const ws = createWebSocketServer(httpServer, config, httpsOptions)
...
(2)创立httpServer
、ws
服务,创立watcher
,设置代码文件监听,创立server
目标,
文件监听改变,websocket
向前端通讯。然后注册一系列中间件用于处理浏览器恳求,包括对/
、js/css/vue
的恳求等
...
const container = await createPluginContainer(config, moduleGraph, watcher)
...
(3)创立插件处理中心container
...
const server: ViteDevServer = {
...
}
server.transformIndexHtml = createDevHtmlTransformFn(server)
...
(4)处理html
进行转化,在html
文件中注入咱们在localhost
network 面板中看到的<script type="module" src="https://juejin.im/@vite/client"></script>
脚本,运转vite
相关的client
脚本内容。
(5)在服务发动前,首要履行container.buildStart({})
调用一切注册插件的buildStart
钩子函数,然后运转initDepsOptimizer()
优化预构建的依靠,接收来自浏览器的恳求。
三、解析经过源码vite的中的一些原理
3.1 Vite
为什么要做预构建?
个人认为这是Vite
在追求极速的服务发动,牺牲首屏渲染速度之间的一个挑选。完成Vite 针对用户项目中的各种文件都是不做打包处理的,而是在浏览器运转时按需恳求,并进行转化处理,相比其他构建工具需求打包依靠再发动服务来说,在耗时上是质的腾跃。
前面咱们对vite
发动进程的剖析知道,发动Vite
它会搜集处理config
、注册各种中间件、初始化一些之后会用到的插件容器container
以及模块依靠图moduleGraph
等事情都不耗费时刻的,最耗费时刻的就依靠预构建。咱们可以简略看一下Vite的预构建做了那些事情
(1) 缓存判别,node_modules.vite_metadata.json
看看是不是有前次缓存的预构建成果,经过判别存储缓存信息文件hash值是否与最新的hash值相同,要是相同则回来前次缓存的预构建成果
(2)依靠扫描,假如没有缓存成果和缓存hash值不一致,经过discoverProjectDependencies()
进行依靠扫描,从入口开端搜集依靠,得到deps,将这些依靠项添加到已发现的列表中,preAliasPlugin用来支撑别号和优化的deps。
(3)构建依靠,经过esbuildDepPlugin()
在这个进程中它将将非ESM
标准的代码转化为契合ESM
标准的代码,将第三方依靠内部的多个文件合并为单一的可缓存文件,削减http
恳求数量。
网上有人专门拿出import { debounce } from "lodash-es"
做试验,直接运用会时浏览器会导入600+个文件,需求1秒多,而经过依靠预构建之后,浏览器只需求导入一个文件,且只需 20 ms
(4)最后,履行writeFile
,再将相关信息保存到_metadata.json
,咱们可以看看它的内容
{
"hash": "1a547ddf",
"browserHash": "2065b8ab",
"optimized": {
"@vue/runtime-core": {
"file": "E:/codeWorlk/xxx/node_modules/.vite/@vue_runtime-core.js",
"src": "E:/codeWorlk/xxx/node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js",
"needsInterop": false
},
"vue": {
"file": "E:/codeWorlk/xxx/node_modules/.vite/vue.js",
"src": "E:/codeWorlk/xxx/node_modules/vue/dist/vue.runtime.esm-bundler.js",
"needsInterop": false
},
"vue-router": {
"file": "E:/codeWorlk/xxx/node_modules/.vite/vue-router.js",
"src": "E:/codeWorlk/xxx/node_modules/vue-router/dist/vue-router.esm-bundler.js",
"needsInterop": false
},
"@vue/reactivity": {
"file": "E:/codeWorlk/xxx/node_modules/.vite/@vue_reactivity.js",
"src": "E:/codeWorlk/xxx/node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js",
"needsInterop": false
}
}
}
从上面咱们可以看到 Vite 只对 npm 依靠进行预构建,关于用户编写的文件不进行预处理,而是经过浏览器支撑的 ES Module 来进行按需读取,所以假如用户文件过多,且没有进行必定的 Code Spliting 等操作。这时分首屏加载渲染是十分慢的。
3.2 一个恳求到Vite服务的进程是怎样样的?
<script type="module" src="/src/main.js"></script>
// or
import { get } from './utils'
咱们经过上文Vite创立服务的进程源码看到 经过transformMiddleware()
函数去处理恳求
// main transform middleware
middlewares.use(transformMiddleware(server))
(1)首要它会判别是否恳求, 假如是恳求,就会进入运用插件容器解析、加载和转化,不是恳求就交给其他插件处理
(2)transformRequest()进行解析、加载和转化恳求
(3)判别依靠图谱中是否现已存在,存在的话直接回来module
(4)没有话调用插件的路径解析钩子(关于不同的资源会有不同的插件去处理),创立module并保存
(5)最后调用esbuildPlugin插件的transfrom钩子,回来编译后的code,并缓存到依靠map里
(3)
Vite
是怎样完成快速的热重载(HMR)?
HMR的原理大多差不多,首要是经过WebSocket创立浏览器和服务器的通讯监听文件的改动,当文件被修正时,服务端发送音讯告诉客户端修正相应的代码,客户端对应不同的文件进行不同的操作的更新。
咱们回顾发动Vite服务的(createServer)时分
const ws = createWebSocketServer(httpServer, config, httpsOptions)
就发动了WebSocketServer服务,然后监听文件的改变
watcher.on('change', async (file) => {
file = normalizePath(file)
if (file.endsWith('/package.json')) {
return invalidatePackageData(packageCache, file)
}
// invalidate module graph cache on file change
moduleGraph.onFileChange(file)
if (serverConfig.hmr !== false) {
try {
await handleHMRUpdate(file, server)
} catch (err) {
ws.send({
type: 'error',
err: prepareError(err)
})
}
}
})
假如有改变,则则履行监听回调函数handleHMRUpdate()
,从头打包文件,并告诉到客户端,客户端接收到信息,从头加载到更新后的模块。
最后
Vite4.0.0都悄然在“卷”出来了,期待它在一次次更新中对前端人愈加的友好,本文也是浅读源码其间的中心流程,更多细节,比方Vite的插件机制是怎样样的,具体是怎样样做文件扫描的,都值得各位前端人探究!
下一篇: 前端工程化基建探究(6)前端人对前端财物建造的思考和实践