Skip to content

Commit

Permalink
fix(hmr): avoid fetching stale modules on nested hmr updates
Browse files Browse the repository at this point in the history
+ clear stale accept callbacks on hmr context re-creation
  • Loading branch information
yyx990803 committed May 27, 2020
1 parent 42526f4 commit 0554f06
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 28 deletions.
11 changes: 7 additions & 4 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,6 @@ async function updateModule(
? deps.some((dep) => modulesToUpdate.has(dep))
: modulesToUpdate.has(deps)
})
// reset callbacks on self update since they are going to be registered again
if (isSelfUpdate) {
mod.callbacks = []
}

await Promise.all(
Array.from(modulesToUpdate).map(async (dep) => {
Expand Down Expand Up @@ -247,6 +243,13 @@ export const createHotContext = (id: string) => {
dataMap.set(id, {})
}

// when a file is hot updated, a new context is created
// clear its stale callbacks
const mod = hotModulesMap.get(id)
if (mod) {
mod.callbacks = []
}

const hot = {
get data() {
return dataMap.get(id)
Expand Down
41 changes: 25 additions & 16 deletions src/node/server/serverPluginHmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,17 @@ import { resolveImport } from './serverPluginModuleRewrite'
import { FSWatcher } from 'chokidar'
import MagicString from 'magic-string'
import { parse } from '../utils/babelParse'
import { Node, StringLiteral, Statement, Expression } from '@babel/types'
import { InternalResolver } from '../resolver'
import LRUCache from 'lru-cache'
import slash from 'slash'
import { cssPreprocessLangRE } from '../utils/cssUtils'
import {
Node,
StringLiteral,
Statement,
Expression,
IfStatement
} from '@babel/types'

export const debugHmr = require('debug')('vite:hmr')

Expand All @@ -57,6 +63,7 @@ export const importeeMap: HMRStateMap = new Map()
// files that are dirty (i.e. in the import chain between the accept boundrary
// and the actual changed file) for an hmr update at a given timestamp.
export const hmrDirtyFilesMap = new LRUCache<string, Set<string>>({ max: 10 })
export const latestVersionsMap = new Map<string, string>()

// client and node files are placed flat in the dist folder
export const hmrClientFilePath = path.resolve(__dirname, '../client.js')
Expand Down Expand Up @@ -98,6 +105,9 @@ export const hmrPlugin: ServerPlugin = ({
ctx.status = 200
ctx.body = hmrClient
} else {
if (ctx.query.t) {
latestVersionsMap.set(ctx.path, ctx.query.t)
}
return next()
}
})
Expand Down Expand Up @@ -267,7 +277,8 @@ export function rewriteFileWithHMR(
resolver: InternalResolver,
s: MagicString
) {
let hasDeclined
let hasDeclined = false
let importMetaConditional: IfStatement | undefined

const registerDep = (e: StringLiteral) => {
const deps = ensureMapEntry(hmrAcceptanceMap, importer)
Expand All @@ -291,7 +302,7 @@ export function rewriteFileWithHMR(
if (isTopLevel) {
console.warn(
chalk.yellow(
`[vite warn] HMR syntax error in ${importer}: import.meta.hot.accept() ` +
`[vite] HMR syntax error in ${importer}: import.meta.hot.accept() ` +
`should be wrapped in \`if (import.meta.hot) {}\` conditional ` +
`blocks so that they can be tree-shaken in production.`
)
Expand Down Expand Up @@ -384,18 +395,13 @@ export function rewriteFileWithHMR(
if (node.type === 'ExpressionStatement') {
// top level hot.accept() call
checkHotCall(node.expression, isTopLevel, isDevBlock)
// import.meta.hot && import.meta.hot.accept()
if (
node.expression.type === 'LogicalExpression' &&
node.expression.operator === '&&' &&
isMetaHot(node.expression.left)
) {
checkHotCall(node.expression.right, false, isDevBlock)
}
}
// if (import.meta.hot) ...
if (node.type === 'IfStatement') {
const isDevBlock = isMetaHot(node.test)
if (isDevBlock) {
importMetaConditional = node
}
if (node.consequent.type === 'BlockStatement') {
node.consequent.body.forEach((s) =>
checkStatements(s, false, isDevBlock)
Expand All @@ -410,11 +416,14 @@ export function rewriteFileWithHMR(
const ast = parse(source)
ast.forEach((s) => checkStatements(s, true, false))

// inject import.meta.hot
s.prepend(`
import { createHotContext } from "${hmrClientPublicPath}"
import.meta.hot = createHotContext(${JSON.stringify(importer)})
`)
if (importMetaConditional) {
// inject import.meta.hot
s.prependLeft(
importMetaConditional.start!,
`import { createHotContext } from "${hmrClientPublicPath}"; ` +
`import.meta.hot = createHotContext(${JSON.stringify(importer)}); `
)
}

// clear decline state
if (!hasDeclined) {
Expand Down
16 changes: 8 additions & 8 deletions src/node/server/serverPluginModuleRewrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
ensureMapEntry,
rewriteFileWithHMR,
hmrClientPublicPath,
hmrDirtyFilesMap
hmrDirtyFilesMap,
latestVersionsMap
} from './serverPluginHmr'
import {
readBody,
Expand Down Expand Up @@ -273,14 +274,13 @@ export const resolveImport = (
// 5. force re-fetch dirty imports by appending timestamp
if (timestamp) {
const dirtyFiles = hmrDirtyFilesMap.get(timestamp)
// only force re-fetch if this is a marked dirty file (in the import
// chain of the changed file) or a vue part request (made by a dirty
// vue main request)
if (
(dirtyFiles && dirtyFiles.has(pathname)) ||
/\?type=(template|style)/.test(id)
) {
// only rewrite if:
if (dirtyFiles && dirtyFiles.has(pathname)) {
// 1. this is a marked dirty file (in the import chain of the changed file)
query += `${query ? `&` : `?`}t=${timestamp}`
} else if (latestVersionsMap.has(pathname)) {
// 2. this file was previously hot-updated and has an updated version
query += `${query ? `&` : `?`}t=${latestVersionsMap.get(pathname)}`
}
}
return pathname + query
Expand Down

0 comments on commit 0554f06

Please sign in to comment.