From eedc02444e004b0bb57078622d423681051bdfff Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 12 Sep 2024 15:20:35 +0200 Subject: [PATCH 1/5] fix(vite): refactor module runner to module graph --- packages/vite/src/module-runner/hmrHandler.ts | 11 +- packages/vite/src/module-runner/index.ts | 3 +- .../vite/src/module-runner/moduleCache.ts | 171 +++++++++--------- packages/vite/src/module-runner/runner.ts | 126 ++++++------- .../module-runner/sourcemap/interceptor.ts | 12 +- packages/vite/src/module-runner/types.ts | 11 +- packages/vite/src/node/ssr/fetchModule.ts | 3 +- .../__tests__/server-source-maps.spec.ts | 2 +- packages/vite/src/node/ssr/ssrModuleLoader.ts | 15 +- 9 files changed, 178 insertions(+), 176 deletions(-) diff --git a/packages/vite/src/module-runner/hmrHandler.ts b/packages/vite/src/module-runner/hmrHandler.ts index eeed2f521cd55a..2ef39e53ccf3f1 100644 --- a/packages/vite/src/module-runner/hmrHandler.ts +++ b/packages/vite/src/module-runner/hmrHandler.ts @@ -54,7 +54,7 @@ export async function handleHotPayload( hmrClient.logger.debug(`program reload`) await hmrClient.notifyListeners('vite:beforeFullReload', payload) - runner.moduleCache.clear() + runner.moduleGraph.clear() for (const id of clearEntrypoints) { await runner.import(id) @@ -122,7 +122,7 @@ class Queue { function getModulesByFile(runner: ModuleRunner, file: string) { const modules: string[] = [] - for (const [id, mod] of runner.moduleCache.entries()) { + for (const [id, mod] of runner.moduleGraph.idToModuleMap.entries()) { if (mod.meta && 'file' in mod.meta && mod.meta.file === file) { modules.push(id) } @@ -139,7 +139,10 @@ function getModulesEntrypoints( for (const moduleId of modules) { if (visited.has(moduleId)) continue visited.add(moduleId) - const module = runner.moduleCache.getByModuleId(moduleId) + const module = runner.moduleGraph.getModuleById(moduleId) + if (!module) { + continue + } if (module.importers && !module.importers.size) { entrypoints.add(moduleId) continue @@ -155,7 +158,7 @@ function findAllEntrypoints( runner: ModuleRunner, entrypoints = new Set(), ): Set { - for (const [id, mod] of runner.moduleCache.entries()) { + for (const [id, mod] of runner.moduleGraph.idToModuleMap.entries()) { if (mod.importers && !mod.importers.size) { entrypoints.add(id) } diff --git a/packages/vite/src/module-runner/index.ts b/packages/vite/src/module-runner/index.ts index 836b261a8b8687..3ba0b8b90b1dd6 100644 --- a/packages/vite/src/module-runner/index.ts +++ b/packages/vite/src/module-runner/index.ts @@ -1,6 +1,6 @@ // this file should re-export only things that don't rely on Node.js or other runner features -export { ModuleCacheMap } from './moduleCache' +export { ModuleRunnerGraph, type ModuleRunnerNode } from './moduleCache' export { ModuleRunner } from './runner' export { ESModulesEvaluator } from './esmEvaluator' export { RemoteRunnerTransport } from './runnerTransport' @@ -10,7 +10,6 @@ export type { HMRLogger, HMRConnection } from '../shared/hmr' export type { ModuleEvaluator, ModuleRunnerContext, - ModuleCache, FetchResult, FetchFunction, FetchFunctionOptions, diff --git a/packages/vite/src/module-runner/moduleCache.ts b/packages/vite/src/module-runner/moduleCache.ts index 9c5c188e3da8cc..c46b51a9b82dde 100644 --- a/packages/vite/src/module-runner/moduleCache.ts +++ b/packages/vite/src/module-runner/moduleCache.ts @@ -1,85 +1,109 @@ -import { isWindows, slash, withTrailingSlash } from '../shared/utils' +import { + cleanUrl, + isWindows, + slash, + unwrapId, + withTrailingSlash, +} from '../shared/utils' import { SOURCEMAPPING_URL } from '../shared/constants' -import { decodeBase64 } from './utils' +import { decodeBase64, posixResolve } from './utils' import { DecodedMap } from './sourcemap/decoder' -import type { ModuleCache } from './types' +import type { ResolvedResult } from './types' const MODULE_RUNNER_SOURCEMAPPING_REGEXP = new RegExp( `//# ${SOURCEMAPPING_URL}=data:application/json;base64,(.+)`, ) -export class ModuleCacheMap extends Map { - private root: string - - constructor(root: string, entries?: [string, ModuleCache][]) { - super(entries) - this.root = withTrailingSlash(root) - } - - normalize(fsPath: string): string { - return normalizeModuleId(fsPath, this.root) +export class ModuleRunnerNode { + public importers = new Set() + public imports = new Set() + public lastInvalidationTimestamp = 0 + public evaluated = false + public meta: ResolvedResult | undefined + public promise: Promise | undefined + public exports: any | undefined + public file: string + public map: DecodedMap | undefined + + constructor( + public id: string, + public url: string, + ) { + this.file = cleanUrl(url) } +} - /** - * Assign partial data to the map - */ - update(fsPath: string, mod: ModuleCache): this { - fsPath = this.normalize(fsPath) - if (!super.has(fsPath)) this.setByModuleId(fsPath, mod) - else Object.assign(super.get(fsPath)!, mod) - return this - } +export class ModuleRunnerGraph { + private root: string - setByModuleId(modulePath: string, mod: ModuleCache): this { - return super.set(modulePath, mod) - } + public idToModuleMap = new Map() + public fileToModuleMap = new Map() - override set(fsPath: string, mod: ModuleCache): this { - return this.setByModuleId(this.normalize(fsPath), mod) + constructor(root: string) { + this.root = withTrailingSlash(root) } - getByModuleId(modulePath: string): ModuleCache { - if (!super.has(modulePath)) this.setByModuleId(modulePath, {}) - - const mod = super.get(modulePath)! - if (!mod.imports) { - Object.assign(mod, { - imports: new Set(), - importers: new Set(), - timestamp: 0, - }) - } - return mod + public getModuleById(id: string): ModuleRunnerNode | undefined { + return this.idToModuleMap.get(id) } - override get(fsPath: string): ModuleCache { - return this.getByModuleId(this.normalize(fsPath)) + public getModulesByFile(file: string): ModuleRunnerNode[] { + return this.fileToModuleMap.get(file) || [] } - deleteByModuleId(modulePath: string): boolean { - return super.delete(modulePath) + public getModuleByUrl(url: string): ModuleRunnerNode | undefined { + url = unwrapId(url) + if (url.startsWith('/')) { + const id = posixResolve(this.root, url.slice(1)) + return this.idToModuleMap.get(id) + } + return this.idToModuleMap.get(url) } - override delete(fsPath: string): boolean { - return this.deleteByModuleId(this.normalize(fsPath)) - } + public ensureModule(id: string, url: string): ModuleRunnerNode { + id = normalizeModuleId(id) + if (this.idToModuleMap.has(id)) { + return this.idToModuleMap.get(id)! + } + const moduleNode = new ModuleRunnerNode(id, url) + this.idToModuleMap.set(id, moduleNode) - invalidateUrl(id: string): void { - const module = this.get(id) - this.invalidateModule(module) + const fileModules = this.fileToModuleMap.get(moduleNode.file) || [] + fileModules.push(moduleNode) + this.fileToModuleMap.set(moduleNode.file, fileModules) + return moduleNode } - invalidateModule(module: ModuleCache): void { - module.evaluated = false - module.meta = undefined - module.map = undefined - module.promise = undefined - module.exports = undefined + public invalidateModule(node: ModuleRunnerNode): void { + node.evaluated = false + node.meta = undefined + node.map = undefined + node.promise = undefined + node.exports = undefined // remove imports in case they are changed, // don't remove the importers because otherwise it will be empty after evaluation // this can create a bug when file was removed but it still triggers full-reload // we are fine with the bug for now because it's not a common case - module.imports?.clear() + node.imports?.clear() + } + + getModuleSourceMapById(id: string): null | DecodedMap { + const mod = this.getModuleById(id) + if (!mod) return null + if (mod.map) return mod.map + if (!mod.meta || !('code' in mod.meta)) return null + const mapString = MODULE_RUNNER_SOURCEMAPPING_REGEXP.exec( + mod.meta.code, + )?.[1] + if (!mapString) return null + const baseFile = mod.file + mod.map = new DecodedMap(JSON.parse(decodeBase64(mapString)), baseFile) + return mod.map + } + + public clear(): void { + this.idToModuleMap.clear() + this.fileToModuleMap.clear() } /** @@ -90,12 +114,12 @@ export class ModuleCacheMap extends Map { invalidated = new Set(), ): Set { for (const _id of ids) { - const id = this.normalize(_id) + const id = normalizeModuleId(_id) if (invalidated.has(id)) continue invalidated.add(id) - const mod = super.get(id) + const mod = this.getModuleById(id) if (mod?.importers) this.invalidateDepTree(mod.importers, invalidated) - this.invalidateUrl(id) + if (mod) this.invalidateModule(mod) } return invalidated } @@ -108,32 +132,20 @@ export class ModuleCacheMap extends Map { invalidated = new Set(), ): Set { for (const _id of ids) { - const id = this.normalize(_id) + const id = normalizeModuleId(_id) if (invalidated.has(id)) continue invalidated.add(id) - const subIds = Array.from(super.entries()) + const subIds = Array.from(this.idToModuleMap.entries()) .filter(([, mod]) => mod.importers?.has(id)) .map(([key]) => key) if (subIds.length) { this.invalidateSubDepTree(subIds, invalidated) } - super.delete(id) + const mod = this.getModuleById(id) + if (mod) this.invalidateModule(mod) } return invalidated } - - getSourceMap(moduleId: string): null | DecodedMap { - const mod = this.get(moduleId) - if (mod.map) return mod.map - if (!mod.meta || !('code' in mod.meta)) return null - const mapString = MODULE_RUNNER_SOURCEMAPPING_REGEXP.exec( - mod.meta.code, - )?.[1] - if (!mapString) return null - const baseFile = mod.meta.file || moduleId.split('?')[0] - mod.map = new DecodedMap(JSON.parse(decodeBase64(mapString)), baseFile) - return mod.map - } } // unique id that is not available as "$bare_import" like "test" @@ -146,20 +158,15 @@ const prefixedBuiltins = new Set(['node:test', 'node:sqlite']) // /root/id.js -> /id.js // C:/root/id.js -> /id.js // C:\root\id.js -> /id.js -function normalizeModuleId(file: string, root: string): string { +function normalizeModuleId(file: string): string { if (prefixedBuiltins.has(file)) return file // unix style, but Windows path still starts with the drive letter to check the root - let unixFile = slash(file) + const unixFile = slash(file) .replace(/^\/@fs\//, isWindows ? '' : '/') .replace(/^node:/, '') .replace(/^\/+/, '/') - if (unixFile.startsWith(root)) { - // keep slash - unixFile = unixFile.slice(root.length - 1) - } - // if it's not in the root, keep it as a path, not a URL return unixFile.replace(/^file:\//, '/') } diff --git a/packages/vite/src/module-runner/runner.ts b/packages/vite/src/module-runner/runner.ts index 4c132ec4117072..5aa71e721f2e24 100644 --- a/packages/vite/src/module-runner/runner.ts +++ b/packages/vite/src/module-runner/runner.ts @@ -1,10 +1,10 @@ import type { ViteHotContext } from 'types/hot' import { HMRClient, HMRContext } from '../shared/hmr' -import { cleanUrl, isPrimitive, isWindows, unwrapId } from '../shared/utils' +import { cleanUrl, isPrimitive, isWindows } from '../shared/utils' import { analyzeImportedModDifference } from '../shared/ssrTransform' -import { ModuleCacheMap } from './moduleCache' +import type { ModuleRunnerNode } from './moduleCache' +import { ModuleRunnerGraph } from './moduleCache' import type { - ModuleCache, ModuleEvaluator, ModuleRunnerContext, ModuleRunnerImportMeta, @@ -40,11 +40,11 @@ export class ModuleRunner { * Holds the cache of modules * Keys of the map are ids */ - public moduleCache: ModuleCacheMap + public moduleGraph: ModuleRunnerGraph public hmrClient?: HMRClient - private readonly urlToIdMap = new Map() - private readonly fileToIdMap = new Map() + // private readonly urlToIdMap = new Map() + // private readonly fileToIdMap = new Map() private readonly envProxy = new Proxy({} as any, { get(_, p) { throw new Error( @@ -55,7 +55,10 @@ export class ModuleRunner { private readonly transport: RunnerTransport private readonly resetSourceMapSupport?: () => void private readonly root: string - private readonly moduleInfoCache = new Map>() + private readonly moduleInfoCache = new Map< + string, + Promise + >() private destroyed = false @@ -66,7 +69,8 @@ export class ModuleRunner { ) { const root = this.options.root this.root = root[root.length - 1] === '/' ? root : `${root}/` - this.moduleCache = options.moduleCache ?? new ModuleCacheMap(options.root) + this.moduleGraph = + options.moduleGraph ?? new ModuleRunnerGraph(options.root) this.transport = options.transport if (typeof options.hmr === 'object') { this.hmrClient = new HMRClient( @@ -95,8 +99,7 @@ export class ModuleRunner { * Clear all caches including HMR listeners. */ public clearCache(): void { - this.moduleCache.clear() - this.urlToIdMap.clear() + this.moduleGraph.clear() this.hmrClient?.clear() } @@ -132,7 +135,7 @@ export class ModuleRunner { return exports } - private isCircularModule(mod: Required) { + private isCircularModule(mod: ModuleRunnerNode) { for (const importedFile of mod.imports) { if (mod.importers.has(importedFile)) { return true @@ -154,10 +157,9 @@ export class ModuleRunner { if (importer === moduleUrl) { return true } - const mod = this.moduleCache.getByModuleId( - importer, - ) as Required + const mod = this.moduleGraph.getModuleById(importer) if ( + mod && mod.importers.size && this.isCircularImport(mod.importers, moduleUrl, visited) ) { @@ -168,14 +170,13 @@ export class ModuleRunner { } private async cachedRequest( - id: string, - mod_: ModuleCache, + url: string, + mod: ModuleRunnerNode, callstack: string[] = [], metadata?: SSRImportMetadata, ): Promise { - const mod = mod_ as Required const meta = mod.meta! - const moduleUrl = meta.url + const moduleId = meta.id const { importers } = mod @@ -185,9 +186,9 @@ export class ModuleRunner { // check circular dependency if ( - callstack.includes(moduleUrl) || + callstack.includes(moduleId) || this.isCircularModule(mod) || - this.isCircularImport(importers, moduleUrl) + this.isCircularImport(importers, moduleId) ) { if (mod.exports) return this.processImport(mod.exports, meta, metadata) } @@ -196,13 +197,13 @@ export class ModuleRunner { if (this.debug) { debugTimer = setTimeout(() => { const getStack = () => - `stack:\n${[...callstack, moduleUrl] + `stack:\n${[...callstack, moduleId] .reverse() .map((p) => ` - ${p}`) .join('\n')}` this.debug!( - `[module runner] module ${moduleUrl} takes over 2s to load.\n${getStack()}`, + `[module runner] module ${moduleId} takes over 2s to load.\n${getStack()}`, ) }, 2000) } @@ -212,7 +213,7 @@ export class ModuleRunner { if (mod.promise) return this.processImport(await mod.promise, meta, metadata) - const promise = this.directRequest(id, mod, callstack) + const promise = this.directRequest(url, mod, callstack) mod.promise = promise mod.evaluated = false return this.processImport(await promise, meta, metadata) @@ -222,14 +223,13 @@ export class ModuleRunner { } } - private async cachedModule(url: string, importer?: string) { + private async cachedModule( + url: string, + importer?: string, + ): Promise { url = normalizeAbsoluteUrl(url, this.root) - const normalized = this.urlToIdMap.get(url) - let cachedModule = normalized && this.moduleCache.getByModuleId(normalized) - if (!cachedModule) { - cachedModule = this.moduleCache.getByModuleId(url) - } + const cachedModule = this.moduleGraph.getModuleById(url) let cached = this.moduleInfoCache.get(url) if (!cached) { @@ -249,8 +249,8 @@ export class ModuleRunner { private async getModuleInformation( url: string, importer: string | undefined, - cachedModule: ModuleCache | undefined, - ): Promise { + cachedModule: ModuleRunnerNode | undefined, + ): Promise { if (this.destroyed) { throw new Error(`Vite module runner has been destroyed.`) } @@ -277,60 +277,48 @@ export class ModuleRunner { return cachedModule } - // base moduleId on "file" and not on id - // if `import(variable)` is called it's possible that it doesn't have an extension for example - // if we used id for that, then a module will be duplicated - const idQuery = url.split('?')[1] - const query = idQuery ? `?${idQuery}` : '' - const file = 'file' in fetchedModule ? fetchedModule.file : undefined - const fileId = file ? `${file}${query}` : url - const moduleUrl = this.moduleCache.normalize(fileId) - const mod = this.moduleCache.getByModuleId(moduleUrl) + const moduleId = + 'externalize' in fetchedModule + ? fetchedModule.externalize + : fetchedModule.id + const moduleUrl = 'url' in fetchedModule ? fetchedModule.url : url + const module = this.moduleGraph.ensureModule(moduleId, moduleUrl) if ('invalidate' in fetchedModule && fetchedModule.invalidate) { - this.moduleCache.invalidateModule(mod) + this.moduleGraph.invalidateModule(module) } fetchedModule.url = moduleUrl - mod.meta = fetchedModule + fetchedModule.id = moduleId + module.meta = fetchedModule - if (file) { - const fileModules = this.fileToIdMap.get(file) || [] - fileModules.push(moduleUrl) - this.fileToIdMap.set(file, fileModules) - } - - this.urlToIdMap.set(url, moduleUrl) - this.urlToIdMap.set(unwrapId(url), moduleUrl) - return mod + return module } // override is allowed, consider this a public API protected async directRequest( - id: string, - mod: ModuleCache, + url: string, + mod: ModuleRunnerNode, _callstack: string[], ): Promise { const fetchResult = mod.meta! - const moduleUrl = fetchResult.url - const callstack = [..._callstack, moduleUrl] + const moduleId = fetchResult.id + const callstack = [..._callstack, moduleId] const request = async (dep: string, metadata?: SSRImportMetadata) => { - const importer = ('file' in fetchResult && fetchResult.file) || moduleUrl - const fetchedModule = await this.cachedModule(dep, importer) - const resolvedId = fetchedModule.meta!.url - const depMod = this.moduleCache.getByModuleId(resolvedId) - depMod.importers!.add(moduleUrl) - mod.imports!.add(resolvedId) - - return this.cachedRequest(dep, fetchedModule, callstack, metadata) + const importer = ('file' in fetchResult && fetchResult.file) || moduleId + const depMod = await this.cachedModule(dep, importer) + depMod.importers!.add(moduleId) + mod.imports!.add(depMod.id) + + return this.cachedRequest(dep, depMod, callstack, metadata) } const dynamicRequest = async (dep: string) => { // it's possible to provide an object with toString() method inside import() dep = String(dep) if (dep[0] === '.') { - dep = posixResolve(posixDirname(id), dep) + dep = posixResolve(posixDirname(url), dep) } return request(dep, { isDynamicImport: true }) } @@ -348,13 +336,13 @@ export class ModuleRunner { if (code == null) { const importer = callstack[callstack.length - 2] throw new Error( - `[module runner] Failed to load "${id}"${ + `[module runner] Failed to load "${url}"${ importer ? ` imported from ${importer}` : '' }`, ) } - const modulePath = cleanUrl(file || moduleUrl) + const modulePath = cleanUrl(file || moduleId) // disambiguate the `:/` on windows: see nodejs/node#31710 const href = posixPathToFileHref(modulePath) const filename = modulePath @@ -391,8 +379,8 @@ export class ModuleRunner { if (!this.hmrClient) { throw new Error(`[module runner] HMR client was destroyed.`) } - this.debug?.('[module runner] creating hmr context for', moduleUrl) - hotContext ||= new HMRContext(this.hmrClient, moduleUrl) + this.debug?.('[module runner] creating hmr context for', moduleId) + hotContext ||= new HMRContext(this.hmrClient, moduleId) return hotContext }, set: (value) => { @@ -411,7 +399,7 @@ export class ModuleRunner { this.debug?.('[module runner] executing', href) - await this.evaluator.runInlinedModule(context, code, id) + await this.evaluator.runInlinedModule(context, code, url) return exports } diff --git a/packages/vite/src/module-runner/sourcemap/interceptor.ts b/packages/vite/src/module-runner/sourcemap/interceptor.ts index 1041e6b7276459..88117d7b8b62b8 100644 --- a/packages/vite/src/module-runner/sourcemap/interceptor.ts +++ b/packages/vite/src/module-runner/sourcemap/interceptor.ts @@ -1,7 +1,7 @@ import type { OriginalMapping } from '@jridgewell/trace-mapping' import type { ModuleRunner } from '../runner' import { posixDirname, posixResolve } from '../utils' -import type { ModuleCacheMap } from '../moduleCache' +import type { ModuleRunnerGraph } from '../moduleCache' import { slash } from '../../shared/utils' import { DecodedMap, getOriginalPosition } from './decoder' @@ -21,7 +21,7 @@ export interface InterceptorOptions { const sourceMapCache: Record = {} const fileContentsCache: Record = {} -const moduleGraphs = new Set() +const moduleGraphs = new Set() const retrieveFileHandlers = new Set() const retrieveSourceMapHandlers = new Set() @@ -46,7 +46,7 @@ let overridden = false const originalPrepare = Error.prepareStackTrace function resetInterceptor(runner: ModuleRunner, options: InterceptorOptions) { - moduleGraphs.delete(runner.moduleCache) + moduleGraphs.delete(runner.moduleGraph) if (options.retrieveFile) retrieveFileHandlers.delete(options.retrieveFile) if (options.retrieveSourceMap) retrieveSourceMapHandlers.delete(options.retrieveSourceMap) @@ -64,7 +64,7 @@ export function interceptStackTrace( Error.prepareStackTrace = prepareStackTrace overridden = true } - moduleGraphs.add(runner.moduleCache) + moduleGraphs.add(runner.moduleGraph) if (options.retrieveFile) retrieveFileHandlers.add(options.retrieveFile) if (options.retrieveSourceMap) retrieveSourceMapHandlers.add(options.retrieveSourceMap) @@ -102,8 +102,8 @@ function supportRelativeURL(file: string, url: string) { } function getRunnerSourceMap(position: OriginalMapping): CachedMapEntry | null { - for (const moduleCache of moduleGraphs) { - const sourceMap = moduleCache.getSourceMap(position.source!) + for (const moduleGraph of moduleGraphs) { + const sourceMap = moduleGraph.getModuleSourceMapById(position.source!) if (sourceMap) { return { url: position.source, diff --git a/packages/vite/src/module-runner/types.ts b/packages/vite/src/module-runner/types.ts index 71857ba5964007..9684e5346ff6b9 100644 --- a/packages/vite/src/module-runner/types.ts +++ b/packages/vite/src/module-runner/types.ts @@ -5,7 +5,7 @@ import type { DefineImportMetadata, SSRImportMetadata, } from '../shared/ssrTransform' -import type { ModuleCacheMap } from './moduleCache' +import type { ModuleRunnerGraph } from './moduleCache' import type { ssrDynamicImportKey, ssrExportAllKey, @@ -119,7 +119,11 @@ export interface ViteFetchResult { /** * Module ID in the server module graph. */ - serverId: string + id: string + /** + * Module URL used in the import. + */ + url: string /** * Invalidate module on the client side. */ @@ -128,6 +132,7 @@ export interface ViteFetchResult { export type ResolvedResult = (ExternalFetchResult | ViteFetchResult) & { url: string + id: string } export type FetchFunction = ( @@ -177,7 +182,7 @@ export interface ModuleRunnerOptions { /** * Custom module cache. If not provided, creates a separate module cache for each ModuleRunner instance. */ - moduleCache?: ModuleCacheMap + moduleGraph?: ModuleRunnerGraph } export interface ImportMetaEnv { diff --git a/packages/vite/src/node/ssr/fetchModule.ts b/packages/vite/src/node/ssr/fetchModule.ts index 663fff6bd121da..3d5e812d17870f 100644 --- a/packages/vite/src/node/ssr/fetchModule.ts +++ b/packages/vite/src/node/ssr/fetchModule.ts @@ -137,7 +137,8 @@ export async function fetchModule( return { code: result.code, file: mod.file, - serverId: mod.id!, + id: mod.id!, + url: mod.url, invalidate: !cached, } } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/server-source-maps.spec.ts b/packages/vite/src/node/ssr/runtime/__tests__/server-source-maps.spec.ts index ece2fc12242753..1c4925550e299e 100644 --- a/packages/vite/src/node/ssr/runtime/__tests__/server-source-maps.spec.ts +++ b/packages/vite/src/node/ssr/runtime/__tests__/server-source-maps.spec.ts @@ -52,7 +52,7 @@ describe('module runner initialization', async () => { resolvePath(import.meta.url, './fixtures/throws-error-method.ts'), (code) => '\n\n\n\n\n' + code + '\n', ) - runner.moduleCache.clear() + runner.moduleGraph.clear() server.environments.ssr.moduleGraph.invalidateAll() const methodErrorNew = await getError(async () => { diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index aa155ad35d29bb..423329f4ab18b5 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -1,5 +1,5 @@ import colors from 'picocolors' -import type { ModuleCache } from 'vite/module-runner' +import type { ModuleRunnerNode } from 'vite/module-runner' import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner' import type { ViteDevServer } from '../server' import { unwrapId } from '../../shared/utils' @@ -73,18 +73,17 @@ class SSRCompatModuleRunner extends ModuleRunner { } protected override async directRequest( - id: string, - mod: ModuleCache, + url: string, + mod: ModuleRunnerNode, _callstack: string[], ): Promise { - const serverId = mod.meta && 'serverId' in mod.meta && mod.meta.serverId + const id = mod.meta && 'id' in mod.meta && mod.meta.id // serverId doesn't exist for external modules - if (!serverId) { - return super.directRequest(id, mod, _callstack) + if (!id) { + return super.directRequest(url, mod, _callstack) } - const viteMod = - this.server.environments.ssr.moduleGraph.getModuleById(serverId) + const viteMod = this.server.environments.ssr.moduleGraph.getModuleById(id) if (!viteMod) { return super.directRequest(id, mod, _callstack) From b5d96bb477f96857a19deb95ff5d86714eeb3311 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 12 Sep 2024 15:22:57 +0200 Subject: [PATCH 2/5] chore: cleanup --- .../vite/src/module-runner/moduleCache.ts | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/packages/vite/src/module-runner/moduleCache.ts b/packages/vite/src/module-runner/moduleCache.ts index c46b51a9b82dde..50ba1ec5aeffad 100644 --- a/packages/vite/src/module-runner/moduleCache.ts +++ b/packages/vite/src/module-runner/moduleCache.ts @@ -105,47 +105,6 @@ export class ModuleRunnerGraph { this.idToModuleMap.clear() this.fileToModuleMap.clear() } - - /** - * Invalidate modules that dependent on the given modules, up to the main entry - */ - invalidateDepTree( - ids: string[] | Set, - invalidated = new Set(), - ): Set { - for (const _id of ids) { - const id = normalizeModuleId(_id) - if (invalidated.has(id)) continue - invalidated.add(id) - const mod = this.getModuleById(id) - if (mod?.importers) this.invalidateDepTree(mod.importers, invalidated) - if (mod) this.invalidateModule(mod) - } - return invalidated - } - - /** - * Invalidate dependency modules of the given modules, down to the bottom-level dependencies - */ - invalidateSubDepTree( - ids: string[] | Set, - invalidated = new Set(), - ): Set { - for (const _id of ids) { - const id = normalizeModuleId(_id) - if (invalidated.has(id)) continue - invalidated.add(id) - const subIds = Array.from(this.idToModuleMap.entries()) - .filter(([, mod]) => mod.importers?.has(id)) - .map(([key]) => key) - if (subIds.length) { - this.invalidateSubDepTree(subIds, invalidated) - } - const mod = this.getModuleById(id) - if (mod) this.invalidateModule(mod) - } - return invalidated - } } // unique id that is not available as "$bare_import" like "test" From 9fb8e7dc8d46e776074e9999f021ea3683a45151 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 12 Sep 2024 15:26:05 +0200 Subject: [PATCH 3/5] chore: add proposal --- packages/vite/src/module-runner/runner.ts | 6 ++++++ packages/vite/src/module-runner/types.ts | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/module-runner/runner.ts b/packages/vite/src/module-runner/runner.ts index 5aa71e721f2e24..c74dcb8bb2b704 100644 --- a/packages/vite/src/module-runner/runner.ts +++ b/packages/vite/src/module-runner/runner.ts @@ -361,6 +361,12 @@ export class ModuleRunner { glob() { throw new Error('[module runner] "import.meta.glob" is not supported.') }, + + // TODO: this is a proposal to start a discussion + environment: { + module: mod, + moduleGraph: this.moduleGraph, + }, } const exports = Object.create(null) Object.defineProperty(exports, Symbol.toStringTag, { diff --git a/packages/vite/src/module-runner/types.ts b/packages/vite/src/module-runner/types.ts index 9684e5346ff6b9..7d219628f83afc 100644 --- a/packages/vite/src/module-runner/types.ts +++ b/packages/vite/src/module-runner/types.ts @@ -5,7 +5,7 @@ import type { DefineImportMetadata, SSRImportMetadata, } from '../shared/ssrTransform' -import type { ModuleRunnerGraph } from './moduleCache' +import type { ModuleRunnerGraph, ModuleRunnerNode } from './moduleCache' import type { ssrDynamicImportKey, ssrExportAllKey, @@ -31,6 +31,12 @@ export interface ModuleRunnerImportMeta extends ImportMeta { url: string env: ImportMetaEnv hot?: ViteHotContext + + // TODO: this is a proposal to start a discussion + environment: { + module: ModuleRunnerNode + moduleGraph: ModuleRunnerGraph + } [key: string]: any } From 1cbe5f1cad71f94b20c847f4ea0fa4112d8ed8fa Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 12 Sep 2024 15:37:42 +0200 Subject: [PATCH 4/5] fix: correct id in the source maps --- packages/vite/src/module-runner/moduleCache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/module-runner/moduleCache.ts b/packages/vite/src/module-runner/moduleCache.ts index 50ba1ec5aeffad..c19d177fa9c9c1 100644 --- a/packages/vite/src/module-runner/moduleCache.ts +++ b/packages/vite/src/module-runner/moduleCache.ts @@ -29,7 +29,7 @@ export class ModuleRunnerNode { public id: string, public url: string, ) { - this.file = cleanUrl(url) + this.file = cleanUrl(id) } } From e4264550bde0f738f22a0ddba6cc97caed2a5355 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 12 Sep 2024 15:45:24 +0200 Subject: [PATCH 5/5] fix: use mod.url for HMR --- packages/vite/src/module-runner/hmrHandler.ts | 10 +++++----- packages/vite/src/module-runner/runner.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/vite/src/module-runner/hmrHandler.ts b/packages/vite/src/module-runner/hmrHandler.ts index 2ef39e53ccf3f1..7a273b35a0421f 100644 --- a/packages/vite/src/module-runner/hmrHandler.ts +++ b/packages/vite/src/module-runner/hmrHandler.ts @@ -122,9 +122,9 @@ class Queue { function getModulesByFile(runner: ModuleRunner, file: string) { const modules: string[] = [] - for (const [id, mod] of runner.moduleGraph.idToModuleMap.entries()) { + for (const [_, mod] of runner.moduleGraph.idToModuleMap.entries()) { if (mod.meta && 'file' in mod.meta && mod.meta.file === file) { - modules.push(id) + modules.push(mod.url) } } return modules @@ -144,7 +144,7 @@ function getModulesEntrypoints( continue } if (module.importers && !module.importers.size) { - entrypoints.add(moduleId) + entrypoints.add(module.url) continue } for (const importer of module.importers || []) { @@ -158,9 +158,9 @@ function findAllEntrypoints( runner: ModuleRunner, entrypoints = new Set(), ): Set { - for (const [id, mod] of runner.moduleGraph.idToModuleMap.entries()) { + for (const [_, mod] of runner.moduleGraph.idToModuleMap.entries()) { if (mod.importers && !mod.importers.size) { - entrypoints.add(id) + entrypoints.add(mod.url) } } return entrypoints diff --git a/packages/vite/src/module-runner/runner.ts b/packages/vite/src/module-runner/runner.ts index c74dcb8bb2b704..b42554db2fbaf7 100644 --- a/packages/vite/src/module-runner/runner.ts +++ b/packages/vite/src/module-runner/runner.ts @@ -385,8 +385,8 @@ export class ModuleRunner { if (!this.hmrClient) { throw new Error(`[module runner] HMR client was destroyed.`) } - this.debug?.('[module runner] creating hmr context for', moduleId) - hotContext ||= new HMRContext(this.hmrClient, moduleId) + this.debug?.('[module runner] creating hmr context for', mod.url) + hotContext ||= new HMRContext(this.hmrClient, mod.url) return hotContext }, set: (value) => {