可以看到,涉及到js的一共三块:

  • client,请求路径为/@vite/client,请注意这个路径,这是vite本身的依赖的路径;

  • react-refresh的模块代码,这是插件react-refresh注入的代码;代码内部又请求了@react-refresh,这是插件react-refresh的sdk的请求;

  • main,请求路径为/src/main/tsc,这是与咱们项目中的真实代码相关的;

除了上面的三个外,还有一个env,请求路径为/@vite/env.js,这个就是@vite/client内部发出的对env依赖的请求:import '/node_modules/vite/dist/client/env.js';;

当然还有@react-refresh的sdk请求;

除了上面所提到的js之外,其他的请求其实就是我们项目代码里面的请求了;

client第一步要做的事情就是建立websocket通信通道,可以看到上面的websocket类型的localhost请求,这就是client与server端通信,进行热更新等的管道。

vite- server

============

说完了client,我们回到server部分,入口文件为packages/vite/src/node/serve.ts,最主要的逻辑其实是在packages/vite/src/node/server/index.ts;我们暂且把server端称为node端,node端主要包含几种类型文件的处理,毕竟这只是个代理服务器;

image.png

我们从几个部分来看看这几种类型的处理

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’),

“pages”: path.resolve(__dirname, ‘src/pages’),

“types”: path.resolve(__dirname, ‘src/types’),

“utils”: path.resolve(__dirname, ‘src/utils’),

},

},

这样我们不用改动里面的引用,就可以让vite知道去哪里找哪个文件了。引用中有对process.env.***类似的引用,用此来判断一些环境相关的逻辑,在vite中是没有了,vite的环境变量是通过import.meta.env.***;

改完这些执行npm run start,是可以正常跑起来的。

坑1

在执行npm run build后,我们在进行预览的时候,执行npm run preview,出现了下面的画面:

出现了这种没见过的错误,然后我们的解决办法是什么呢?

首先,把压缩给干掉,别压缩了,压缩后的代码全都是abcd,啥也看不出来;干掉的方式是改vite的配置:

build: {

minify: false, // 是否进行压缩,boolean | ‘terser’ | ‘esbuild’,默认使用terser

manifest: false, // 是否产出maifest.json

sourcemap: false, // 是否产出soucemap.json

outDir: ‘build’, // 产出目录

},

我们把minify改为了false,再重新执行build和preview命令,可以看到了精确的行,到底是哪里进行了报错.

关于最后是怎么解决的呢?TMD,竟然是一个object-inspect的库,引用了一个util的包,然后咱们的node_modules里面没有util的包。

这些个中缘由,就不多说了,折腾了两三个小时,解决办法就是一个命令:npm i -S util

重新执行build和preview后,正常了。

坑2

本地开发启动start,build+preview都OK了,接下来,就得试试单测了。执行npm run test。果不其然,报错了,原因是没有babel-preset-react-app的babel配置。

那我们增加上配置那不就好了嘛?

我们在package.json里面增加了babel的配置:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

前15.PNG

前16.PNG

由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处免费获取

易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-80WLOyuN-1712213272316)]

[外链图片转存中…(img-GQab9LNU-1712213272316)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-o4Pw7p3N-1712213272317)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

[外链图片转存中…(img-EsbwYu6F-1712213272317)]

[外链图片转存中…(img-VqJlOtuG-1712213272318)]

由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处免费获取

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐