我们从几个部分来看看这几种类型的处理
node watcher
watcher的主要作用是对于文件变化的监听,然后与client端进行通信:
监听的目录为整个项目的根目录,watchOptions为vite.config.js
里面的server.watch配置,初始化代码如下:
// 使用chokidar进行对文件目录的监听,
const watcher = chokidar.watch(path.resolve(root), {
ignored: [‘/node_modules/’, ‘/.git/’, …ignored],
ignoreInitial: true,
ignorePermissionErrors: true,
…watchOptions
}) as FSWatcher
启动对文件的监听:
// 如果发生改变,调用handleHMRUpdate,
watcher.on(‘change’, async (file) => {
file = normalizePath(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)
})
}
}
})
// 增加文件连接
watcher.on(‘add’, (file) => {
handleFileAddUnlink(normalizePath(file), server)
})
// 减少文件连接
watcher.on(‘unlink’, (file) => {
handleFileAddUnlink(normalizePath(file), server, true)
})
监听对应的事件所对应的处理函数在packages/vite/src/node/server/hmr.ts
文件里面。再细节的处理,我们不做说明了,其实里面逻辑是差不太多的,最后都是调用了websocket,发送到client端。
node 依赖类型
依赖类型,其实也就是node_modules下面的依赖包,例如:
这些包属于基本不会变的类型,vite的做法是把这些依赖,在服务启动的时候放到.vite
目录下面,收到的请求直接去.vite下面获取,然后返回。
node 静态资源
静态资源其实也就是我们了解和熟悉的public/下面的或者static/下面的内容,这些资源属于静态文件,例如:
这样的数据,vite不做任何处理,直接返回。
node html
对于入口文件index.html
,我们这里暂且只讲单入口文件,多入口文件vite也是支持的,详情可见多页面应用;
// 删减后的代码如下
// @file packages/vite/src/node/server/middlewares/indexHtml.ts
export function indexHtmlMiddleware(server){
return async (req, res, next) => {
const url = req.url && cleanUrl(req.url)
const filename = getHtmlFilename(url, server)
try {
// 从本地读取index.html的内容
let html = fs.readFileSync(filename, ‘utf-8’)
// dev模式下调用createDevHtmlTransformFn转换html的内容,插入两个script
html = await server.transformIndexHtml(url, html)
// 把html的内容返回。
return send(req, res, html, ‘html’)
} catch (e) {
return next(e)
}
}
}
对于入口文件index.html,vite首先会从硬盘上读取文件的内容,经过一系列操作后,把操作后的内容进行返回,我们来看看这个一系列操作:
- 调用createDevHtmlTransformFn去获取处理函数:
// @file packages/vite/src/node/plugins/html.ts
export function resolveHtmlTransforms(plugins: readonly Plugin[]) {
const preHooks: IndexHtmlTransformHook[] = []
const postHooks: IndexHtmlTransformHook[] = []
for (const plugin of plugins) {
const hook = plugin.transformIndexHtml
if (hook) {
if (typeof hook === ‘function’) {
postHooks.push(hook)
} else if (hook.enforce === ‘pre’) {
preHooks.push(hook.transform)
} else {
postHooks.push(hook.transform)
}
}
}
return [preHooks, postHooks]
}
// @file packages/vite/src/node/server/middlewares/indexHtml.ts
export function createDevHtmlTransformFn(server: ViteDevServer) {
const [preHooks, postHooks] = resolveHtmlTransforms(server.config.plugins)
return (url: string, html: string): Promise => {
return applyHtmlTransforms(
html,
url,
getHtmlFilename(url, server),
[…preHooks, devHtmlHook, …postHooks],
server
)
}
}
此处,我们还是拿react项目为例,react-refresh的插件被插入到了postHooks里面;最后其实是返回了一个无名的promise类型的函数;此处也就是闭包了。无名函数里面调用的是applyHtmlTransforms
,我们来看下参数:
-
html为根目录下面的index.html的内容
-
url为/index.html,
-
第三个参数的执行结果为/index.html
-
第四个参数为一个大数组,prehooks是空的,第二个为是vite自己的/@vite/client链接的返回函数,第三个是有一个react-refresh的插件在里面的
-
第五个参数为当前server
接下来是applyHtmlTransforms的调用时刻,此处会改写html内容,然后返回。
image.png
最后处理好的html的内容,就是我们上面看到的html的内容。
node 其他类型
暂时把其他类型都算为其他类型,包括@vite开头的/@vite/client和业务相关的请求;这些请求都会走同一个transformMiddleware
中间件。此中间件所做的工作如下:
// @file packages/vite/src/node/server/middlewares/transform.ts
image.png
其实上面的逻辑正常走下来,是会到命中缓存和未命中缓存中的,二选一,命中就直接返回了,没有命中的话,就是走到了transform,接下来我们看下调用transform
的过程:
// @file packages/vite/src/node/server/transformRequest.ts
// 调用插件获取当前请求的id,如/@react-refresh,当然也有获取不到的情况;
const id = (await pluginContainer.resolveId(url))?.id || url
// 调用插件获取插件返回的内容,如/@react-refresh,肯定有不是插件返回的情况,
const loadResult = await pluginContainer.load(id, ssr)
// 接下来是重点
// 如果没有获取到结果,也就是不是插件类型的请求,如我们的入口文件/src/main.tsx
if (loadResult == null) {
// 从硬盘读取非插件提供的返回结果
code = await fs.readFile(file, ‘utf-8’)
} else {
if (typeof loadResult === ‘object’) {
code = loadResult.code
map = loadResult.map
} else {
code = loadResult
}
}
}
// 启动文件监听,调用watcher,和上面讲到的watcher遥相呼应
ensureWatchedFile(watcher, mod.file, root)
// 代码运行到这里,是获取到内容了不假,不过code还是源文件,也就是编写的文件内容
// 下面的transform是开始进行替换
const transformResult = await pluginContainer.transform(code, id, map, ssr)
code = transformResult.code!
map = transformResult.map
return (mod.transformResult = {
code,
map,
etag: getEtag(code, { weak: true })
} as TransformResult)
大体的流程如下:
image.png
async transform(code, id, inMap, ssr) {
const ctx = new TransformContext(id, code, inMap as SourceMap)
ctx.ssr = !!ssr
for (const plugin of plugins) {
if (!plugin.transform) continue
ctx._activePlugin = plugin
ctx._activeId = id
ctx._activeCode = code
let result
try {
result = await plugin.transform.call(ctx as any, code, id, ssr)
} catch (e) {
ctx.error(e)
}
if (!result) continue
if (typeof result === ‘object’) {
code = result.code || ‘’
if (result.map) ctx.sourcemapChain.push(result.map)
} else {
code = result
}
}
return {
code,
map: ctx._getCombinedSourcemap()
}
},
image.png
其实到这里,我们对于vite server所实现的功能基本是已经清楚了,代理服务器,然后对引用修改为自己的规则,对自己的规则进行解析处理。尤为重要的其实是vite:import-analysis
这个插件。
vite + react
开始之前先附上地址:github:vite-react-concent-pro【1】;这个项目是由github:webpack-react-concent-pro项目改过来的,业务逻辑代码模块没动,只改动了编译打包部分。
在这里说下由webpack改为vite的过程和其中遇到的一些问题。
项目的改动其实是不大的,基本就是clone下项目下来后,把webpack相关的依赖去掉,然后换成vite,记得加上react的vite插件:@vitejs/plugin-react-refresh;换完以后,因为我们项目中的引用路径是在src文件夹下面的,所以我们需要为vite提供下别名:
resolve: {
alias: { // 别名
“configs”: path.resolve(__dirname, ‘src/configs’),
“components”: path.resolve(__dirname, ‘src/components’),
“services”: path.resolve(__dirname, ‘src/services’),
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
最后
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
❤️ 谢谢支持,喜欢的话别忘了 关注、点赞哦。
板技术停滞不前!**
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-fm1LtbgJ-1710840739927)]
[外链图片转存中…(img-Oh7ZrT7T-1710840739927)]
[外链图片转存中…(img-pWGkbW2m-1710840739928)]
[外链图片转存中…(img-IE4NRU0x-1710840739929)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
[外链图片转存中…(img-KaMMCx8y-1710840739929)]
最后
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
❤️ 谢谢支持,喜欢的话别忘了 关注、点赞哦。