Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/vite/src/client/bundledDevClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
updateStyle,
} from './client'

declare const __HMR_PARTIAL_ACCEPT__: boolean

// keep the same public exports as `client.ts`, which this entry replaces when inlined
export {
createHotContext,
Expand Down Expand Up @@ -60,6 +62,7 @@ if (typeof DevRuntime !== 'undefined') {
runtime,
{
base,
partialAccept: __HMR_PARTIAL_ACCEPT__,
beforeApply: clearOverlayOrReloadOnFirstUpdate,
},
)
Expand Down
67 changes: 64 additions & 3 deletions packages/vite/src/client/bundledDevHmrClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { NormalizedModuleRunnerTransport } from '../shared/moduleRunnerTran
/** the subset of `__rolldown_runtime__` the HMR client uses */
export interface RolldownRuntimeLike {
getImporters(id: string): string[]
getImportedBindings?(importer: string, id: string): string[] | undefined
isExecuted(id: string): boolean
hasFactory(id: string): boolean
removeModuleCache(id: string): void
Expand All @@ -33,6 +34,7 @@ type HmrUpdate =

export interface BundledDevHMRClientOptions {
base: string
partialAccept: boolean
/** returning `'reload'` aborts the apply — the hook reloads the page itself */
beforeApply: () => 'reload' | 'continue'
}
Expand All @@ -41,6 +43,8 @@ export class BundledDevHMRClient extends HMRClient {
private applyQueue = Promise.resolve()
private lastSeq = 0
private reloadPending = false
private acceptedExportsMap = new Map<string, Set<string>>()
private partialAcceptCallbacks = new WeakSet<(...args: any[]) => void>()

constructor(
logger: HMRLogger,
Expand All @@ -57,11 +61,39 @@ export class BundledDevHMRClient extends HMRClient {

isSelfAccepted(id: string): boolean {
return (
this.hotModulesMap.get(id)?.callbacks.some((c) => c.deps.includes(id)) ??
false
this.hotModulesMap
.get(id)
?.callbacks.some(
(c) => c.deps.includes(id) && !this.partialAcceptCallbacks.has(c.fn),
) ?? false
)
}

registerAcceptedExports(
owner: string,
exportNames: string | readonly string[],
callback: (...args: any[]) => void,
): void {
const accepted = this.acceptedExportsMap.get(owner) ?? new Set<string>()
for (const name of typeof exportNames === 'string'
? [exportNames]
: exportNames) {
accepted.add(name)
}
this.acceptedExportsMap.set(owner, accepted)
this.partialAcceptCallbacks.add(callback)
}

clearAcceptedExports(owner: string): void {
this.acceptedExportsMap.delete(owner)
}

private acceptsAllExports(id: string, accepted: Set<string>): boolean {
const exports = this.runtime.loadExports(id)
if (typeof exports !== 'object' || exports == null) return false
return Object.keys(exports).every((name) => accepted.has(name))
}

acceptsDep(parent: string, id: string): boolean {
return (
this.hotModulesMap
Expand Down Expand Up @@ -113,24 +145,37 @@ export class BundledDevHMRClient extends HMRClient {
reason: `update propagated back to ${firstInvalidatedBy}, which already called \`import.meta.hot.invalidate()\``,
}
}
if (this.isSelfAccepted(id)) {
const acceptedExports = this.acceptedExportsMap.get(id)
if (
this.isSelfAccepted(id) ||
(acceptedExports && this.acceptsAllExports(id, acceptedExports))
) {
boundaries.push({
boundary: id,
acceptedVia: id,
isWithinCircularImport: this.isNodeWithinCircularImports(id, stack),
})
return
}
if (acceptedExports) {
boundaries.push({
boundary: id,
acceptedVia: id,
isWithinCircularImport: this.isNodeWithinCircularImports(id, stack),
})
}
const parents = this.runtime
.getImporters(id)
.filter((p) => this.runtime.isExecuted(p))
if (!parents.length) {
if (acceptedExports) return
return {
type: 'full-reload',
reason: `no hmr boundary found for module \`${id}\``,
}
}
for (const parent of parents) {
if (acceptedExports && parent === id) continue
const subChain = [...stack, parent]
if (this.acceptsDep(parent, id)) {
boundaries.push({
Expand All @@ -143,6 +188,12 @@ export class BundledDevHMRClient extends HMRClient {
})
continue
}
if (acceptedExports && this.options.partialAccept) {
const bindings = this.runtime.getImportedBindings?.(parent, id)
if (bindings && bindings.every((name) => acceptedExports.has(name))) {
continue
}
}
if (!stack.includes(parent)) {
const fullReload = this.bubble(
parent,
Expand Down Expand Up @@ -425,6 +476,16 @@ export class BundledDevHMRContext extends HMRContext {
private owner: string,
) {
super(bundledDevClient, owner)
bundledDevClient.clearAcceptedExports(owner)
}

override acceptExports(
exportNames: string | readonly string[],
callback?: (data: any) => void,
): void {
const fn = ([mod]: any[]) => callback?.(mod)
this.bundledDevClient.registerAcceptedExports(this.owner, exportNames, fn)
this.acceptDeps([this.owner], fn)
}

override invalidate(message: string): void {
Expand Down
4 changes: 4 additions & 0 deletions packages/vite/src/node/plugins/clientInjections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ async function createClientConfigValueReplacer(
const hmrTimeoutReplacement = escapeReplacement(timeout)
const hmrEnableOverlayReplacement = escapeReplacement(overlay)
const hmrConfigNameReplacement = escapeReplacement(hmrConfigName)
const hmrPartialAcceptReplacement = escapeReplacement(
config.experimental.hmrPartialAccept,
)
const wsTokenReplacement = escapeReplacement(config.webSocketToken)
const serverForwardConsoleReplacement = escapeReplacement(
config.server.forwardConsole as any,
Expand All @@ -140,6 +143,7 @@ async function createClientConfigValueReplacer(
.replace(`__HMR_TIMEOUT__`, hmrTimeoutReplacement)
.replace(`__HMR_ENABLE_OVERLAY__`, hmrEnableOverlayReplacement)
.replace(`__HMR_CONFIG_NAME__`, hmrConfigNameReplacement)
.replace(`__HMR_PARTIAL_ACCEPT__`, hmrPartialAcceptReplacement)
.replace(`__WS_TOKEN__`, wsTokenReplacement)
.replace(`__SERVER_FORWARD_CONSOLE__`, serverForwardConsoleReplacement)
}
Expand Down
6 changes: 3 additions & 3 deletions packages/vite/src/shared/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ export class HMRContext implements ViteHotContext {
}
}

// export names (first arg) are irrelevant on the client side, they're
// extracted in the server for propagation
// in unbundled dev the export names (first arg) are extracted by the server;
// the bundled-dev context overrides this method and reads them itself
acceptExports(
_: string | readonly string[],
callback?: (data: any) => void,
Expand Down Expand Up @@ -151,7 +151,7 @@ export class HMRContext implements ViteHotContext {
this.hmrClient.send({ type: 'custom', event, data })
}

private acceptDeps(
protected acceptDeps(
deps: string[],
callback: HotCallback['fn'] = () => {},
): void {
Expand Down
Loading
Loading