继上篇文章来分析下vite启动完服务器之后客户端请求的流程,浏览器请求localhost:3000/时匹配到了D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\middlewares\spaFallback.ts中的viteSpaFallbackMiddleware这个中间件,中间件方法如下:
代码1
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\middlewares\spaFallback.ts
export function spaFallbackMiddleware(
root: string
): Connect.NextHandleFunction {
const historySpaFallbackMiddleware = history({
logger: createDebugger('vite:spa-fallback'),
// support /dir/ without explicit index.html
rewrites: [
{
from: /\/$/,
to({ parsedUrl }: any) {
const rewritten =
decodeURIComponent(parsedUrl.pathname) + 'index.html'
if (fs.existsSync(path.join(root, rewritten))) {
return rewritten
} else {
return `/index.html`
}
}
}
]
})
return function viteSpaFallbackMiddleware(req, res, next) {
return historySpaFallbackMiddleware(req, res, next)
}
}
URL重定向是客户端操作,指示客户端访问另一个地址的资源。这需要往返服务器。 客户端对资源发出新请求时,返回客户端的重定向URL会出现在浏览器地址栏。
URL重写是服务器端操作,提供来自不同资源地址的资源。重写URL不需要往返服务器。 重写的URL不会返回客户端,也不会出现在浏览器地址栏。例如/resource 重写到 /different-resource 时,客户端会请求 /resource ,并且服务器会在内部提取 /different-resource 处的资源。
然后匹配到D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\middlewares\indexHtml.ts中的indexHtmlMiddleware中间件
代码2
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\middlewares\indexHtml.ts
export function indexHtmlMiddleware(
server: ViteDevServer
): Connect.NextHandleFunction {
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
return async function viteIndexHtmlMiddleware(req, res, next) {
if (res.writableEnded) {
return next()
}
//url='/index.html'
const url = req.url && cleanUrl(req.url)
// spa-fallback always redirects to /index.html
if (url?.endsWith('.html') && req.headers['sec-fetch-dest'] !== 'script') {
//filename='D:\\workspace\\vue3\\vitelearn\\index.html'
const filename = getHtmlFilename(url, server)
if (fs.existsSync(filename)) {
try {
//读取index.html文件的内容
let html = fs.readFileSync(filename, 'utf-8')
html = await server.transformIndexHtml(url, html, req.originalUrl)
return send(req, res, html, 'html', {
headers: server.config.server.headers
})
} catch (e) {
return next(e)
}
}
}
next()
}
}
来看下server.transformIndexHtml方法,transformIndexHtml方法是在createServer时赋值的,方法如下:
代码3
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\index.ts
export async function createServer(
inlineConfig: InlineConfig = {}
): Promise<ViteDevServer> {
...省略代码
server.transformIndexHtml = createDevHtmlTransformFn(server)
...省略代码
}
所以代码2中调用server.transformIndexHtml,其实就是调用的createDevHtmlTransformFn的return处的方法,来看下createDevHtmlTransformFn的return处的方法
代码4
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\middlewares\indexHtml.ts
export function createDevHtmlTransformFn(
server: ViteDevServer
): (url: string, html: string, originalUrl: string) => Promise<string> {
const [preHooks, postHooks] = resolveHtmlTransforms(server.config.plugins)
//url='/index.html' html='index.html文件的内容' originalUrl='/'
return (url: string, html: string, originalUrl: string): Promise<string> => {
return applyHtmlTransforms(html, [...preHooks, devHtmlHook, ...postHooks], {
path: url,
filename: getHtmlFilename(url, server),
server,
originalUrl
})
}
}
来看下applyHtmlTransforms方法
代码5
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\html.ts
//html='index.html文件的内容'
async function applyHtmlTransforms(html, hooks, ctx) {
const headTags = [];
const headPrependTags = [];
const bodyTags = [];
const bodyPrependTags = [];
for (const hook of hooks) {
const res = await hook(html, ctx);
if (!res) {
continue;
}
if (typeof res === 'string') {
html = res;
}
else {
let tags;
if (Array.isArray(res)) {
tags = res;
}
else {
html = res.html || html;
tags = res.tags;
}
for (const tag of tags) {
if (tag.injectTo === 'body') {
bodyTags.push(tag);
}
else if (tag.injectTo === 'body-prepend') {
bodyPrependTags.push(tag);
}
else if (tag.injectTo === 'head') {
headTags.push(tag);
}
else {
headPrependTags.push(tag);
}
}
}
}
// inject tags
if (headPrependTags.length) {
html = injectToHead(html, headPrependTags, true);
}
if (headTags.length) {
html = injectToHead(html, headTags);
}
if (bodyPrependTags.length) {
html = injectToBody(html, bodyPrependTags, true);
}
if (bodyTags.length) {
html = injectToBody(html, bodyTags);
}
return html;
}
然后循环调用前置插件、devHtmlHook插件、后置插件的函数,因为前置插件、后置插件都为空数组,所以只剩了devHtmlHook插件,来看下devHtmlHook插件的实现:
代码6
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\middlewares\indexHtml.ts
//html='index.html文件的内容'
//htmlPath='/index.html'
//originalUrl='/'
const devHtmlHook: IndexHtmlTransformHook = async (
html,
{ path: htmlPath, server, originalUrl }
) => {
...省略代码
await traverseHtml(html, htmlPath, (node) => {
...省略代码
})
html = s.toString()
return {
html,
tags: [
{
tag: 'script',
attrs: {
type: 'module',
//src='/@vite/client'
src: path.posix.join(base, CLIENT_PUBLIC_PATH)
},
injectTo: 'head-prepend'
}
]
}
}
代码6中traverseHtml其实就是@vue/compiler-dom中的parse方法对index.html的文件内容进行抽象语法树分析,然后调用@vue/compiler-dom中的transform方法对抽象语法树进行遍历。再回到代码5处,调用injectToHead方法将<script type="module" src="/@vite/client">插入到index.html文件的<head>标签中,然后将index.html文件的内容返回给浏览器。通过ESM的模块化规范,浏览器会自动向服务器发起获取/src/main.ts和/@vite/client的GET请求,当浏览器发起/@vite/client的GET请求时会匹配到transformMiddleware这个中间件,transformMiddleware返回了viteTransformMiddleware方法,来看下viteTransformMiddleware方法
代码7
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\middlewares\transform.ts
export function transformMiddleware(
server: ViteDevServer
): Connect.NextHandleFunction {
...省略代码
//url='/@vite/client'
return async function viteTransformMiddleware(req, res, next) {
...省略代码
if (
//匹配到了isJSRequest方法
isJSRequest(url) ||
isImportRequest(url) ||
isCSSRequest(url) ||
isHTMLProxy(url)
) {
...省略代码
//判断能不能让客户端直接读取缓存,因为是首次请求/@vite/client所以肯定没有缓存存在
const ifNoneMatch = req.headers['if-none-match']
if (
ifNoneMatch &&
(await moduleGraph.getModuleByUrl(url))?.transformResult?.etag ===
ifNoneMatch
) {
isDebug && debugCache(`[304] ${prettifyUrl(url, root)}`)
res.statusCode = 304
return res.end()
}
const result = await transformRequest(url, server, {
html: req.headers.accept?.includes('text/html')
})
...省略代码
}
...省略代码
}
}
来看下transformRequest方法
代码8
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\transformRequest.ts
export function transformRequest(
url: string,
server: ViteDevServer,
options: TransformOptions = {}
): Promise<TransformResult | null> {
const cacheKey = (options.ssr ? 'ssr:' : options.html ? 'html:' : '') + url
let request = server._pendingRequests.get(cacheKey)
if (!request) {
request = doTransform(url, server, options)
server._pendingRequests.set(cacheKey, request)
const done = () => server._pendingRequests.delete(cacheKey)
request.then(done, done)
}
return request
}
transformRequest方法主要判断如果是在请求中的请求那么不进行doTransform,不在请求中的请求才进行doTransform,doTransform实现如下:
代码9
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\transformRequest.ts
async function doTransform(
url: string,
server: ViteDevServer,
options: TransformOptions
) {
url = removeTimestampQuery(url)
const { config, pluginContainer, moduleGraph, watcher } = server
const { root, logger } = config
const prettyUrl = isDebug ? prettifyUrl(url, root) : ''
const ssr = !!options.ssr
//url='/@vite/client'
//ssr=false
const module = await server.moduleGraph.getModuleByUrl(url, ssr)
...省略代码
}
来看下getModuleByUrl方法
代码10
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\moduleGraph.ts
export class ModuleGraph {
...省略代码
async getModuleByUrl(
rawUrl: string,
ssr?: boolean
): Promise<ModuleNode | undefined> {
const [url] = await this.resolveUrl(rawUrl, ssr)
return this.urlToModuleMap.get(url)
}
...省略代码
}
看下this.resolveUrl方法
代码11
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\moduleGraph.ts
export class ModuleGraph {
...省略代码
//url='/@vite/client'
async resolveUrl(url: string): Promise<ResolvedUrl> {
url = removeImportQuery(removeTimestampQuery(url))
const resolved = await this.resolveId(url, !!ssr)
const resolvedId = resolved?.id || url
const ext = extname(cleanUrl(resolvedId))
const { pathname, search, hash } = parseUrl(url)
if (ext && !pathname!.endsWith(ext)) {
url = pathname + ext + (search || '') + (hash || '')
}
return [url, resolvedId, resolved?.meta]
}
...省略代码
}
再看下this.resolveId方法,resolveId是在createServer时实例化ModuleGraph类时传进来的,具体如下:
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\index.ts
export async function createServer(
inlineConfig: InlineConfig = {}
): Promise<ViteDevServer> {
...省略代码
const moduleGraph: ModuleGraph = new ModuleGraph((url, ssr) =>
container.resolveId(url, undefined, { ssr })
)
...省略代码
}
所以代码11处的this.resolveId其实调用的是container.resolveId方法,container.resolveId方法实现如下:
代码12
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts
class TransformContext extends Context {
...省略代码
//rawId='/@vite/client'
//importer='D:\\workspace\\vue3\\vitelearn\\index.html'
//options=undefined
async resolveId(rawId, importer = join(root, 'index.html'), options) {
...省略代码
const ctx = new Context();
...省略代码
for (const plugin of plugins) {
...省略代码
const result = await plugin.resolveId.call(ctx, rawId, importer, { ssr });
if (!result)
continue;
if (typeof result === 'string') {
id = result;
}
else {
id = result.id;
Object.assign(partial, result);
}
...省略代码
break;
}
...省略代码
if (id) {
partial.id = isExternalUrl(id) ? id : normalizePath$4(id);
return partial;
}
else {
return null;
}
}
...省略代码
}
循环调用每个插件的resolveId方法,plugins结构如图:
先匹配到了name:alias插件,alias插件实现如下:
代码13
D:\workspace\vue3\vite-2.7.2\node_modules\.pnpm\@rollup+plugin-alias@3.1.8_rollup@2.59.0\node_modules\@rollup\plugin-alias\dist\index.es.js的alias方法
function alias(options = {}) {
...省略代码
return {
name:'alias',
...省略代码
//importee='/@vite/client'
//importer='D:\\workspace\\vue3\\vitelearn\\index.html'
//resolveOptions={ssr:undefined}
resolveId(importee, importer, resolveOptions) {
...省略代码
//updatedId='D:\\workspace\\vue3\\vite-2.7.2\\packages\\vite\\dist\\client\\client.mjs'
const updatedId = normalizeId(importeeId.replace(matchedEntry.find, matchedEntry.replacement));
...省略代码
return this.resolve(updatedId, importer, Object.assign({ skipSelf: true }, resolveOptions)).then((resolved) => {
let finalResult = resolved;
if (!finalResult) {
finalResult = { id: updatedId };
}
return finalResult;
});
}
}
}
然后调用了this.resolve方法,this此时指向的是D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts的Context对象,来看下Context的resovle方法
代码14
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts
class Context implements PluginContext {
...省略代码
//id='D:\\workspace\\vue3\\vitelearn\\node_modules\\vite\\packages\\vite\\dist\\client\\client.mjs'
//importer='D:\\workspace\\vue3\\vitelearn\\index.html'
//options={skipSelf:true,ssr:false}
async resolve(
id: string,
importer?: string,
options?: { skipSelf?: boolean }
) {
let skip: Set<Plugin> | undefined
if (options?.skipSelf && this._activePlugin) {
skip = new Set(this._resolveSkips)
skip.add(this._activePlugin)
}
let out = await container.resolveId(id, importer, { skip, ssr: this.ssr })
if (typeof out === 'string') out = { id: out }
return out as ResolvedId | null
}
...省略代码
}
然后看下container.resolveId方法
代码15
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts
class TransformContext extends Context {
...省略代码
//rawId='D:\\workspace\\vue3\\vite-2.7.2\\packages\\vite\\dist\\client\\client.mjs'
//importer='D:\\workspace\\vue3\\vitelearn\\index.html'
//options={skip:Set对象,ssr:false}
async resolveId(rawId, importer = join(root, 'index.html'), options) {
...省略代码
const ctx = new Context();
...省略代码
for (const plugin of plugins) {
...省略代码
const result = await plugin.resolveId.call(ctx, rawId, importer, { ssr });
if (!result)
continue;
if (typeof result === 'string') {
id = result;
}
else {
id = result.id;
Object.assign(partial, result);
}
...省略代码
break;
}
...省略代码
if (id) {
partial.id = isExternalUrl(id) ? id : normalizePath$4(id);
return partial;
}
else {
return null;
}
}
...省略代码
}
参数options如图:
循环调用每个插件的resovleId方法,匹配到了resolvePlugin插件
代码16
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts
export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
...省略代码
return {
name:'vite:resolve',
...省略代码
//id='D:\\workspace\\vue3\\vite-2.7.2\\packages\\vite\\dist\\client\\client.mjs'
//importer='D:\\workspace\\vue3\\vitelearn\\index.html'
//resolveOpts={ssr:false}
resolveId(id, importer, resolveOpts) {
...省略代码
// absolute fs paths
if (path.isAbsolute(id) && (res = tryFsResolve(id, options))) {
isDebug && debug(`[fs] ${colors.cyan(id)} -> ${colors.dim(res)}`)
return res
}
...省略代码
}
}
}
也就是代码10返回了
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\moduleGraph.ts
export class ModuleGraph {
...省略代码
async getModuleByUrl(
rawUrl: string,
ssr?: boolean
): Promise<ModuleNode | undefined> {
//url='/@vite/client.mjs'
const [url] = await this.resolveUrl(rawUrl, ssr)
//urlToModuleMap此时为空,所以返回undefined,只在调用ensureEntryFromUrl时才会写入内容
return this.urlToModuleMap.get(url)
}
...省略代码
}
回到代码9,继续往下执行
代码17
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\transformRequest.ts
async function doTransform(
url: string,
server: ViteDevServer,
options: TransformOptions
) {
...省略代码
//module=undefined
const module = await server.moduleGraph.getModuleByUrl(url, ssr)
...省略代码
//url='/@vite/client'
const id = (await pluginContainer.resolveId(url, undefined, { ssr }))?.id || url
const file = cleanUrl(id)
...省略代码
}
调用pluginContainer.resolveId方法
代码18
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts
class TransformContext extends Context {
...省略代码
//rawId='/@vite/client'
//importer='D:\\workspace\\vue3\\vitelearn\\index.html'
//options=undefined
async resolveId(rawId, importer = join(root, 'index.html'), options) {
...省略代码
const ctx = new Context()
...省略代码
for (const plugin of plugins) {
...省略代码
const result = await plugin.resolveId.call(
ctx as any,
rawId,
importer,
{ ssr }
)
if (!result) continue
if (typeof result === 'string') {
id = result
} else {
id = result.id
Object.assign(partial, result)
}
...省略代码
break
}
...省略代码
if (id) {
partial.id = isExternalUrl(id) ? id : normalizePath(id)
return partial as PartialResolvedId
} else {
return null
}
}
...省略代码
}
重复以上的步骤,先匹配上D:\workspace\vue3\vite-2.7.2\node_modules\.pnpm\@rollup+plugin-alias@3.1.8_rollup@2.59.0\node_modules\@rollup\plugin-alias\dist\index.es.js的alias插件,变更/@vite/client为D:\\workspace\\vue3\\vite-2.7.2\\packages\\vite\\dist\\client\\client.mjs,然后匹配上D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts的resolvePlugin的resolveId,最终代码17处id='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs',代码17继续往下执行
代码19
node_modules\vite\src\node\server\transformRequest.ts
async function doTransform(
url: string,
server: ViteDevServer,
options: TransformOptions
) {
...省略代码
//id=''
const id =
(await pluginContainer.resolveId(url, undefined, { ssr }))?.id || url
...省略代码
const loadResult = await pluginContainer.load(id, { ssr })
...省略代码
}
看下pluginContainer的load方法
代码20
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts
const container: PluginContainer = {
...省略代码
//id='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs'
//options={ssr:false}
async load(id, options) {
const ssr = options?.ssr
const ctx = new Context()
ctx.ssr = !!ssr
for (const plugin of plugins) {
if (!plugin.load) continue
ctx._activePlugin = plugin
const result = await plugin.load.call(ctx as any, id, { ssr })
if (result != null) {
if (isObject(result)) {
updateModuleInfo(id, result)
}
return result
}
}
return null
},
...省略代码
}
没有匹配到任何一个插件的load规则,所以代码19处loadResult=null,代码19继续往下执行,看下后续代码
代码21
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\transformRequest.ts
async function doTransform(
url: string,
server: ViteDevServer,
options: TransformOptions
) {
...省略代码
//id='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs'
const loadResult = await pluginContainer.load(id, { ssr })
if (loadResult == null) {
...省略代码
if (options.ssr || isFileServingAllowed(file, server)) {
try {
//读取client.mjs文件的内容
code = await fs.readFile(file, 'utf-8')
isDebug && debugLoad(`${timeFrom(loadStart)} [fs] ${prettyUrl}`)
} catch (e) {
if (e.code !== 'ENOENT') {
throw e
}
}
}
}
...省略代码
//url='/@vite/client'
//ssr=false
const mod = await moduleGraph.ensureEntryFromUrl(url, ssr);
...省略代码
}
来看下 moduleGraph.ensureEntryFromUrl方法
代码22
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\moduleGraph.ts
export class ModuleGraph {
...省略代码
//rawUrl='/@vite/client'
//ssr=false
async ensureEntryFromUrl(rawUrl: string): Promise<ModuleNode> {
const [url, resolvedId, meta] = await this.resolveUrl(rawUrl, ssr)
let mod = this.urlToModuleMap.get(url)
if (!mod) {
mod = new ModuleNode(url)
if (meta) mod.meta = meta
this.urlToModuleMap.set(url, mod)
mod.id = resolvedId
this.idToModuleMap.set(resolvedId, mod)
const file = (mod.file = cleanUrl(resolvedId))
let fileMappedModules = this.fileToModulesMap.get(file)
if (!fileMappedModules) {
fileMappedModules = new Set()
this.fileToModulesMap.set(file, fileMappedModules)
}
fileMappedModules.add(mod)
}
return mod
}
...省略代码
}
看下this.resolveUrl方法
代码23
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\moduleGraph.ts
export class ModuleGraph {
...省略代码
//url='/@vite/client'
async resolveUrl(url: string, ssr?: boolean): Promise<ResolvedUrl> {
url = removeImportQuery(removeTimestampQuery(url))
const resolved = await this.resolveId(url, !!ssr)
const resolvedId = resolved?.id || url
const ext = extname(cleanUrl(resolvedId))
const { pathname, search, hash } = parseUrl(url)
if (ext && !pathname!.endsWith(ext)) {
url = pathname + ext + (search || '') + (hash || '')
}
return [url, resolvedId, resolved?.meta]
}
...省略代码
}
resolveId在createServer时创建ModuleGraph实例传递进来的,如下:
export async function createServer(
inlineConfig: InlineConfig = {}
): Promise<ViteDevServer> {
...省略代码
const moduleGraph: ModuleGraph = new ModuleGraph((url, ssr) =>
container.resolveId(url, undefined, { ssr })
)
...省略代码
}
所以代码23处的this.resolveId就是调用container.resolveId方法,看下container的resolveId方法
代码24
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts
class TransformContext extends Context {
...省略代码
//rawId='/@vite/client'
//importer='D:\\workspace\\vue3\\vitelearn\\index.html'
//options=undefined
async resolveId(rawId, importer = join(root, 'index.html'), options) {
...省略代码
const ctx = new Context()
...省略代码
for (const plugin of plugins) {
...省略代码
const result = await plugin.resolveId.call(
ctx as any,
rawId,
importer,
{ ssr }
)
if (!result) continue
if (typeof result === 'string') {
id = result
} else {
id = result.id
Object.assign(partial, result)
}
...省略代码
break
}
if (id) {
partial.id = isExternalUrl(id) ? id : normalizePath(id)
return partial as PartialResolvedId
} else {
return null
}
}
...省略代码
}
经过与以上同样的步骤,最终代码24返回
return {
id:'D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs'
}
在代码代码22处的ensureEntryFromUrl方法将rawUrl与resolveId以及File缓存起来,这样下次直接从缓存中读取,D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\moduleGraph.ts的ensureEntryFromUrl方法中字段含义:
urlToModuleMap:Map<string, ModuleNode>:key是rawUrl,value是ModuleNode对象
idToModuleMap:Map<string, ModuleNode>:key是rawUrl经过resolve之后的路径,也就是绝对路径(路径后面可能会带有参数),value是ModuleNode对象
fileToModulesMap:Map<string, Set<ModuleNode>>:key是rawUrl经过resolve之后的路径,也就是绝对路径(路径后面不带任何参数)
代码21继续往下执行
代码25
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\transformRequest.ts
async function doTransform(
url: string,
server: ViteDevServer,
options: TransformOptions
) {
...省略代码
const mod = await moduleGraph.ensureEntryFromUrl(url, ssr)
ensureWatchedFile(watcher, mod.file, root)
const transformStart = isDebug ? performance.now() : 0
//code='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs文件的内容'
//id='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs'
const transformResult = await pluginContainer.transform(code, id, {
inMap: map,
ssr
})
...省略代码
}
来看下pluginContainer.transform方法
代码26
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts
class TransformContext extends Context {
...省略代码
//code='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs文件的内容'
//id='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs'
async transform(code, id, options) {
const inMap = options?.inMap
const ssr = options?.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
const start = isDebug ? performance.now() : 0
let result: TransformResult | string | undefined
try {
result = await plugin.transform.call(ctx as any, code, id, { ssr })
} catch (e) {
ctx.error(e)
}
if (!result) continue
isDebug &&
debugPluginTransform(
timeFrom(start),
plugin.name,
prettifyUrl(id, root)
)
if (isObject(result)) {
if (result.code !== undefined) {
code = result.code
if (result.map) {
ctx.sourcemapChain.push(result.map)
}
}
updateModuleInfo(id, result)
} else {
code = result
}
}
return {
code,
map: ctx._getCombinedSourcemap()
}
}
...省略代码
}
循环调用每个插件的transform方法,并将this指向为D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts的TransformContext对象,最终匹配到了clientInjectionsPlugin插件
代码27
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\clientInjections.ts
export function clientInjectionsPlugin(config: ResolvedConfig): Plugin {
return {
name: 'vite:client-inject',
//id='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs'
transform(code, id) {
if (id === normalizedClientEntry || id === normalizedEnvEntry) {
...省略代码
return code
.replace(`__MODE__`, JSON.stringify(config.mode))
.replace(`__BASE__`, JSON.stringify(config.base))
.replace(`__DEFINES__`, serializeDefine(config.define || {}))
.replace(`__HMR_PROTOCOL__`, JSON.stringify(protocol))
.replace(`__HMR_HOSTNAME__`, JSON.stringify(host))
.replace(`__HMR_PORT__`, JSON.stringify(port))
.replace(`__HMR_TIMEOUT__`, JSON.stringify(timeout))
.replace(`__HMR_ENABLE_OVERLAY__`, JSON.stringify(overlay))
} else if (code.includes('process.env.NODE_ENV')) {
...省略代码
}
}
}
}
主要是对 D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs文件中的一些值做替换,例如__MODE__替换为config.mode等。然后又匹配上了importAnalysisPlugin插件
代码28
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\importAnalysis.ts
export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
...省略代码
return {
name: 'vite:import-analysis',
...省略代码
//source='经过上个插件处理过之后的文件内容'
//importer='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs'
//options={ssr:false}
async transform(source, importer, options) {
...省略代码
try {
//调用es-module-lexer的parseImports分析client.mjs的依赖
imports = parseImports(source)[0]
} catch (e: any) {
...省略代码
}
...省略代码
//读取缓存中的ModuleNode对象
const importerModule = moduleGraph.getModuleById(importer);
}
}
}
imports结构如图:
调用了moduleGraph的getModuleById方法
代码29
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\moduleGraph.ts
export class ModuleGraph {
...省略代码
//id='D:/workspace/vue3/vitelearn/node_modules/vite/dist/client/client.mjs'
getModuleById(id: string): ModuleNode | undefined {
return this.idToModuleMap.get(removeTimestampQuery(id))
}
...省略代码
}
this.idToModuleMap在代码22处的ensureEntryFromUrl方法已经缓存了起来,所以这里直接根据id取出ModuleNode对象返回,回到代码28,继续往下执行
代码30
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\importAnalysis.ts
export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
...省略代码
return {
name: 'vite:import-analysis',
async transform(source, importer, options) {
...省略代码
const importerModule = moduleGraph.getModuleById(importer)!
...省略代码
for (let index = 0; index < imports.length; index++) {
...省略代码
//rawUrl='@vite/env'
const rawUrl = source.slice(start, end)
...省略代码
//specifier='@vite/env'
if (specifier) {
...省略代码
const [normalizedUrl, resolvedId] = await normalizeUrl(specifier,start)
...省略代码
}
}
},
...省略代码
}
}
来看下normalizeUrl方法
代码31
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\importAnalysis.ts
export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
...省略代码
return {
name: 'vite:import-analysis',
...省略代码
async transform(source, importer, options) {
...省略代码
//url='@vite/env'
const normalizeUrl = async (url: string,pos: number): Promise<[string, string]> => {
...省略代码
//importerFile='D:/workspace/vue3/vitelearn/node_modules/vite/dist/client/client.mjs'
const resolved = await this.resolve(url, importerFile)
...省略代码
}
...省略代码
}
}
}
看下this.resolve方法,this此时指向的是D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts的TransformContext对象,TransformContext继承自D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts的Context对象,看下Context的resolve方法
代码32
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts
class Context implements PluginContext {
...省略代码
//id='@vite/env'
//importer='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs'
//options=undefined
async resolve(id: string, importer?: string, options?: { skipSelf?: boolean }) {
let skip: Set<Plugin> | undefined
if (options?.skipSelf && this._activePlugin) {
skip = new Set(this._resolveSkips)
skip.add(this._activePlugin)
}
let out = await container.resolveId(id, importer, { skip, ssr: this.ssr })
if (typeof out === 'string') out = { id: out }
return out as ResolvedId | null
}
...省略代码
}
container.resolveId方法就与/@vite/client解析过程类似了,匹配上了D:\workspace\vue3\vite-2.7.2\node_modules\.pnpm\@rollup+plugin-alias@3.1.8_rollup@2.59.0\node_modules\@rollup\plugin-alias\dist\index.es.js插件的resolveId方法,将@vite/env转为D:\\workspace\\vue3\\vite-2.7.2\\packages\\vite\\dist\\client\\env.mjs,然后D:\\workspace\\vue3\\vite-2.7.2\\packages\\vite\\dist\\client\\env.mjs又匹配上了D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts的resolvePlugin插件的resolveId方法,esolvePlugin插件的resolveId方法最后返回D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs,回到代码31处,代码31处返回了D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs,代码31处继续往下执行
代码33
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\importAnalysis.ts
const normalizeUrl = async (url: string, pos: number): Promise<[string, string]> => {
...省略代码
//resolved={id:'D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'}
const resolved = await this.resolve(url, importerFile)
...省略代码
//root='D:/workspace/vue3/vitelearn'
if (resolved.id.startsWith(root + '/')) {
// in root: infer short absolute path from root
...省略代码
} else if (fs.existsSync(cleanUrl(resolved.id))) {
// exists but out of root: rewrite to absolute /@fs/ paths
//url='/@fs/D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'
url = path.posix.join(FS_PREFIX + resolved.id)
} else {
...省略代码
}
...省略代码
if (!ssr) {
...省略代码
try {
const depModule = await moduleGraph.ensureEntryFromUrl(url)
if (depModule.lastHMRTimestamp > 0) {
url = injectQuery(url, `t=${depModule.lastHMRTimestamp}`)
}
} catch (e:any){
...省略代码
}
url = base + url.replace(/^\//, '')
}
return [url, resolved.id]
}
来看下moduleGraph.ensureEntryFromUrl方法
代码34
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\moduleGraph.ts
export class ModuleGraph {
...省略代码
//rawUrl='/@fs/D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'
async ensureEntryFromUrl(rawUrl: string): Promise<ModuleNode> {
const [url, resolvedId, meta] = await this.resolveUrl(rawUrl)
let mod = this.urlToModuleMap.get(url)
if (!mod) {
mod = new ModuleNode(url)
if (meta) mod.meta = meta
this.urlToModuleMap.set(url, mod)
mod.id = resolvedId
this.idToModuleMap.set(resolvedId, mod)
const file = (mod.file = cleanUrl(resolvedId))
let fileMappedModules = this.fileToModulesMap.get(file)
if (!fileMappedModules) {
fileMappedModules = new Set()
this.fileToModulesMap.set(file, fileMappedModules)
}
fileMappedModules.add(mod)
}
return mod
}
...省略代码
}
this.resolveUrl调用流程就与以上一样了,不重复分析了,最终匹配到了resolvePlugin插件的resolveId
代码35
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts
export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
...省略代码
return {
name: 'vite:resolve',
...省略代码
//id='/@fs/D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'
//importer='D:\\workspace\\vue3\\vitelearn\\index.html'
resolveId(id, importer, resolveOpts) {
...省略代码
//asSrc=true
if (asSrc && id.startsWith(FS_PREFIX)) {
//fsPath='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'
const fsPath = fsPathFromId(id)
//res='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'
res = tryFsResolve(fsPath, options)
isDebug && debug(`[@fs] ${chalk.cyan(id)} -> ${chalk.dim(res)}`)
// always return here even if res doesn't exist since /@fs/ is explicit
// if the file doesn't exist it should be a 404
return res || fsPath
}
...省略代码
}
}
}
回到代码33,代码继续往下执行
代码36
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\importAnalysis.ts
const normalizeUrl = async (url: string, pos: number): Promise<[string, string]> => {
...省略代码
//resolved={id:'D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'}
const resolved = await this.resolve(url, importerFile)
...省略代码
//root='D:/workspace/vue3/vitelearn'
if (resolved.id.startsWith(root + '/')) {
// in root: infer short absolute path from root
...省略代码
} else if (fs.existsSync(cleanUrl(resolved.id))) {
// exists but out of root: rewrite to absolute /@fs/ paths
//url='/@fs/D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'
url = path.posix.join(FS_PREFIX + resolved.id)
} else {
...省略代码
}
...省略代码
if (!ssr) {
...省略代码
try {
const depModule = await moduleGraph.ensureEntryFromUrl(url)
if (depModule.lastHMRTimestamp > 0) {
url = injectQuery(url, `t=${depModule.lastHMRTimestamp}`)
}
} catch (e:any){
...省略代码
}
//base='/'
//url='/@fs/D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'
url = base + url.replace(/^\//, '')
}
//resolve.id='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'
return [url, resolved.id]
}
回到代码30,代码继续往下执行
代码37
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\importAnalysis.ts
export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
...省略代码
return {
name: 'vite:import-analysis',
async transform(source, importer, options) {
...省略代码
const importerModule = moduleGraph.getModuleById(importer)!
...省略代码
for (let index = 0; index < imports.length; index++) {
...省略代码
//rawUrl='@vite/env'
const rawUrl = source.slice(start, end)
...省略代码
//specifier='@vite/env'
if (specifier) {
...省略代码
//normalizedUrl='/@fs/D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'
//resolvedId='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'
const [normalizedUrl, resolvedId] = await normalizeUrl(specifier,start)
...省略代码
if (url !== specifier) {
if (resolvedId.endsWith(`&es-interop`)) {
...省略代码
} else {
//对D:\workspace\vue3\vitelearn\node_modules\vite\dist\client\client.mjs
//源文件中的import '@vite/env';进行改写,
//改为import '/@fs/D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'
str().overwrite(start, end, isDynamicImport ? `'${url}'` : url)
}
}
...省略代码
}
}
...省略代码
//importer='D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/client.mjs'
if (!isCSSRequest(importer)) {
...省略代码
}
//staticImportedUrls=Set<>("/@fs/D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs")
if (staticImportedUrls.size) {
staticImportedUrls.forEach((url) => {
//异步的将每个依赖都进行transformRequest加载对应依赖的源码
transformRequest(unwrapId(removeImportQuery(url)), server, { ssr })
})
}
if (s) {
//返回经过改写的代码给浏览器
return s.toString()
} else {
return source
}
}
}
}
异步的对D:\workspace\vue3\vitelearn\node_modules\vite\dist\client\client.mjs文件中引入的依赖进行transformRequest,将经过改写的D:\workspace\vue3\vitelearn\node_modules\vite\dist\client\client.mjs的文件的内容同步的返回给浏览器。 分析过程与url='/@vite/client'的过程一样,只是此时url='/@fs/D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs',匹配到D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts插件的resolveId方法,返回url='/@fs/D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs'的路径为:D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs,然后调用pluginContainer的load方法加载,没有匹配到任何插件的load方法,然后用node的fs读取D:/workspace/vue3/vite-2.7.2/packages/vite/dist/client/env.mjs文件的源码,然后去transform env.mjs文件的内容,进行transform时匹配到了D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\clientInjections.ts的clientInjectionsPlugin插件的transform方法,对env.mjs文件的内容进行一些变量的替换,然后又匹配到D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\importAnalysis.ts的importAnalysisPlugin插件的transform对env.mjs文件进行依赖分析,发下env.mjs文件并没有依赖任何文件,至此index.html去请求/@vite/client,以及/@vite/client所依赖的依赖都解析结束,index.html请求/@vite/client的同时还请求了/src/main.ts,来看下请求/src/main.ts的流程,transform时匹配到了D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\esbuild.ts的esbuildPlugin插件的transform方法,对/src/main.ts进行esbuild得到其源码跟sourcemap,然后匹配到D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\importAnalysis.ts的importAnalysisPlugin插件的transform方法,对/src/main.ts进行依赖分析,发现/src/main.ts文件依赖了vue与./App.vue两个文件,D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\importAnalysis.ts的importAnalysisPlugin插件的transform方法对/src/main.ts的内容进行修改,改为
import { createApp } from "/node_modules/.vite/vue.js?v=c86b8604";
import App from "/src/App.vue";
createApp(App).mount("#app");
返回给浏览器,然后在异步的解析/src/main.ts依赖的依赖,解析过程与以上就一样了。