diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts
index fdd28e6be42278..df05a4eccb276c 100644
--- a/packages/vite/src/client/client.ts
+++ b/packages/vite/src/client/client.ts
@@ -388,7 +388,12 @@ export function removeStyle(id: string): void {
}
}
-async function fetchUpdate({ path, acceptedPath, timestamp }: Update) {
+async function fetchUpdate({
+ path,
+ acceptedPath,
+ timestamp,
+ explicitImportRequired
+}: Update) {
const mod = hotModulesMap.get(path)
if (!mod) {
// In a code-splitting project,
@@ -415,7 +420,9 @@ async function fetchUpdate({ path, acceptedPath, timestamp }: Update) {
/* @vite-ignore */
base +
path.slice(1) +
- `?import&t=${timestamp}${query ? `&${query}` : ''}`
+ `?${explicitImportRequired ? 'import&' : ''}t=${timestamp}${
+ query ? `&${query}` : ''
+ }`
)
moduleMap.set(dep, newMod)
} catch (e) {
diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts
index 77b24c2a35da68..f1356d5c7f879e 100644
--- a/packages/vite/src/node/plugins/importAnalysis.ts
+++ b/packages/vite/src/node/plugins/importAnalysis.ts
@@ -76,7 +76,7 @@ export const canSkipImportAnalysis = (id: string): boolean =>
const optimizedDepChunkRE = /\/chunk-[A-Z0-9]{8}\.js/
const optimizedDepDynamicRE = /-[A-Z0-9]{8}\.js/
-function isExplicitImportRequired(url: string) {
+export function isExplicitImportRequired(url: string): boolean {
return !isJSRequest(cleanUrl(url)) && !isCSSRequest(url)
}
diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts
index f3298944a5e0b0..3493d370ab0b65 100644
--- a/packages/vite/src/node/server/hmr.ts
+++ b/packages/vite/src/node/server/hmr.ts
@@ -9,6 +9,7 @@ import { createDebugger, normalizePath, unique } from '../utils'
import type { ViteDevServer } from '..'
import { isCSSRequest } from '../plugins/css'
import { getAffectedGlobModules } from '../plugins/importMetaGlob'
+import { isExplicitImportRequired } from '../plugins/importAnalysis'
import type { ModuleNode } from './moduleGraph'
export const debugHmr = createDebugger('vite:hmr')
@@ -151,9 +152,13 @@ export function updateModules(
updates.push(
...[...boundaries].map(({ boundary, acceptedVia }) => ({
- type: `${boundary.type}-update` as Update['type'],
+ type: `${boundary.type}-update` as const,
timestamp,
path: boundary.url,
+ explicitImportRequired:
+ boundary.type === 'js'
+ ? isExplicitImportRequired(acceptedVia.url)
+ : undefined,
acceptedPath: acceptedVia.url
}))
)
diff --git a/packages/vite/types/hmrPayload.d.ts b/packages/vite/types/hmrPayload.d.ts
index 2fbed3a821466f..9ddb1a48c78072 100644
--- a/packages/vite/types/hmrPayload.d.ts
+++ b/packages/vite/types/hmrPayload.d.ts
@@ -20,6 +20,10 @@ export interface Update {
path: string
acceptedPath: string
timestamp: number
+ /**
+ * @internal
+ */
+ explicitImportRequired: boolean | undefined
}
export interface PrunePayload {
diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts
index d0635fc04db9ee..3858719b772a37 100644
--- a/playground/hmr/__tests__/hmr.spec.ts
+++ b/playground/hmr/__tests__/hmr.spec.ts
@@ -218,6 +218,32 @@ if (!isBuild) {
expect(await btn.textContent()).toBe('Counter 1')
})
+ // #2255
+ test('importing reloaded', async () => {
+ await page.goto(viteTestUrl)
+ const outputEle = await page.$('.importing-reloaded')
+ const getOutput = () => {
+ return outputEle.innerHTML()
+ }
+
+ await untilUpdated(getOutput, ['a.js: a0', 'b.js: b0,a0'].join('
'))
+
+ editFile('importing-updated/a.js', (code) => code.replace("'a0'", "'a1'"))
+ await untilUpdated(
+ getOutput,
+ ['a.js: a0', 'b.js: b0,a0', 'a.js: a1'].join('
')
+ )
+
+ editFile('importing-updated/b.js', (code) =>
+ code.replace('`b0,${a}`', '`b1,${a}`')
+ )
+ // note that "a.js: a1" should not happen twice after "b.js: b0,a0'"
+ await untilUpdated(
+ getOutput,
+ ['a.js: a0', 'b.js: b0,a0', 'a.js: a1', 'b.js: b1,a1'].join('
')
+ )
+ })
+
describe('acceptExports', () => {
const HOT_UPDATED = /hot updated/
const CONNECTED = /connected/
diff --git a/playground/hmr/hmr.ts b/playground/hmr/hmr.ts
index f2d21b9bc78884..97330f05f29f64 100644
--- a/playground/hmr/hmr.ts
+++ b/playground/hmr/hmr.ts
@@ -1,4 +1,5 @@
import { foo as depFoo, nestedFoo } from './hmrDep'
+import './importing-updated'
export const foo = 1
text('.app', foo)
diff --git a/playground/hmr/importing-updated/a.js b/playground/hmr/importing-updated/a.js
new file mode 100644
index 00000000000000..4b08229417e4c3
--- /dev/null
+++ b/playground/hmr/importing-updated/a.js
@@ -0,0 +1,8 @@
+const val = 'a0'
+document.querySelector('.importing-reloaded').innerHTML += `a.js: ${val}
`
+
+export default val
+
+if (import.meta.hot) {
+ import.meta.hot.accept()
+}
diff --git a/playground/hmr/importing-updated/b.js b/playground/hmr/importing-updated/b.js
new file mode 100644
index 00000000000000..87c4a065061fea
--- /dev/null
+++ b/playground/hmr/importing-updated/b.js
@@ -0,0 +1,8 @@
+import a from './a.js'
+
+const val = `b0,${a}`
+document.querySelector('.importing-reloaded').innerHTML += `b.js: ${val}
`
+
+if (import.meta.hot) {
+ import.meta.hot.accept()
+}
diff --git a/playground/hmr/importing-updated/index.js b/playground/hmr/importing-updated/index.js
new file mode 100644
index 00000000000000..0cc74268d385de
--- /dev/null
+++ b/playground/hmr/importing-updated/index.js
@@ -0,0 +1,2 @@
+import './a'
+import './b'
diff --git a/playground/hmr/index.html b/playground/hmr/index.html
index 7857ef818a911c..aafeaea5b565d4 100644
--- a/playground/hmr/index.html
+++ b/playground/hmr/index.html
@@ -25,3 +25,4 @@