diff --git a/npm/vite-dev-server/cypress/components/circular-dependencies/a.spec.ts b/npm/vite-dev-server/cypress/components/circular-dependencies/a.spec.ts
new file mode 100644
index 000000000000..7986eadf5a6a
--- /dev/null
+++ b/npm/vite-dev-server/cypress/components/circular-dependencies/a.spec.ts
@@ -0,0 +1,7 @@
+///
+
+import { a } from './a'
+
+it('handles circular dependencies', () => {
+ expect(a()).to.eq('This is the message')
+})
diff --git a/npm/vite-dev-server/cypress/components/circular-dependencies/a.ts b/npm/vite-dev-server/cypress/components/circular-dependencies/a.ts
new file mode 100644
index 000000000000..75b069630fb2
--- /dev/null
+++ b/npm/vite-dev-server/cypress/components/circular-dependencies/a.ts
@@ -0,0 +1,9 @@
+import { b } from './b'
+
+export function a () {
+ const msg = b()
+
+ return msg
+}
+
+export const message = 'This is the message'
diff --git a/npm/vite-dev-server/cypress/components/circular-dependencies/b.ts b/npm/vite-dev-server/cypress/components/circular-dependencies/b.ts
new file mode 100644
index 000000000000..31d8ed470dfa
--- /dev/null
+++ b/npm/vite-dev-server/cypress/components/circular-dependencies/b.ts
@@ -0,0 +1,5 @@
+import { c } from './c'
+
+export function b () {
+ return c()
+}
diff --git a/npm/vite-dev-server/cypress/components/circular-dependencies/c.ts b/npm/vite-dev-server/cypress/components/circular-dependencies/c.ts
new file mode 100644
index 000000000000..e0639bd3d969
--- /dev/null
+++ b/npm/vite-dev-server/cypress/components/circular-dependencies/c.ts
@@ -0,0 +1,5 @@
+import { message } from './a'
+
+export function c () {
+ return message
+}
diff --git a/npm/vite-dev-server/src/makeCypressPlugin.ts b/npm/vite-dev-server/src/makeCypressPlugin.ts
index 277b87275214..93344f28a645 100644
--- a/npm/vite-dev-server/src/makeCypressPlugin.ts
+++ b/npm/vite-dev-server/src/makeCypressPlugin.ts
@@ -1,7 +1,10 @@
import { resolve, posix, sep } from 'path'
import { readFile } from 'fs'
import { promisify } from 'util'
-import { Plugin, ViteDevServer } from 'vite'
+import Debug from 'debug'
+import { ModuleNode, Plugin, ViteDevServer } from 'vite'
+
+const debug = Debug('cypress:vite-dev-server:plugin')
const read = promisify(readFile)
@@ -16,13 +19,18 @@ function convertPathToPosix (path: string): string {
const INIT_FILEPATH = resolve(__dirname, '../client/initCypressTests.js')
+const HMR_DEPENDENCY_LOOKUP_MAX_ITERATION = 50
+
export const makeCypressPlugin = (
projectRoot: string,
supportFilePath: string,
devServerEvents: EventEmitter,
+ specs: {absolute: string, relative: string}[],
): Plugin => {
let base = '/'
+ const specsPathsSet = new Set(specs.map((spec) => spec.absolute))
+
const posixSupportFilePath = supportFilePath ? convertPathToPosix(resolve(projectRoot, supportFilePath)) : undefined
const normalizedSupportFilePath = posixSupportFilePath ? `${base}@fs/${posixSupportFilePath}` : undefined
@@ -44,6 +52,8 @@ export const makeCypressPlugin = (
base = config.base
},
transformIndexHtml () {
+ debug('transformIndexHtml with base', base)
+
return [
// load the script at the end of the body
// script has to be loaded when the vite client is connected
@@ -62,11 +72,46 @@ export const makeCypressPlugin = (
server.middlewares.use(`${base}index.html`, (req, res) => res.end(transformedIndexHtml))
},
- handleHotUpdate: () => {
- // restart tests when code is updated
- devServerEvents.emit('dev-server:compile:success')
+ handleHotUpdate: ({ server, file }) => {
+ debug('handleHotUpdate - file', file)
+ // get the graph node for the file that just got updated
+ let moduleImporters = server.moduleGraph.fileToModulesMap.get(file)
+ let iterationNumber = 0
+
+ // until we reached a point where the current module is imported by no other
+ while (moduleImporters && moduleImporters.size) {
+ if (iterationNumber > HMR_DEPENDENCY_LOOKUP_MAX_ITERATION) {
+ debug(`max hmr iteration reached: ${HMR_DEPENDENCY_LOOKUP_MAX_ITERATION}; Rerun will not happen on this file change.`)
+
+ return []
+ }
+
+ // as soon as we find one of the specs, we trigger the re-run of tests
+ for (const mod of moduleImporters.values()) {
+ if (specsPathsSet.has(mod.file)) {
+ debug('handleHotUpdate - compile success')
+ devServerEvents.emit('dev-server:compile:success')
+
+ return []
+ }
+ }
+
+ // get all the modules that import the current one
+ moduleImporters = getImporters(moduleImporters)
+ iterationNumber += 1
+ }
return []
},
}
}
+
+function getImporters (modules: Set): Set {
+ const allImporters = new Set()
+
+ modules.forEach((m) => {
+ m.importers.forEach((imp) => allImporters.add(imp))
+ })
+
+ return allImporters
+}
diff --git a/npm/vite-dev-server/src/startServer.ts b/npm/vite-dev-server/src/startServer.ts
index 20f0d5efdbfc..36e5c7a23f25 100644
--- a/npm/vite-dev-server/src/startServer.ts
+++ b/npm/vite-dev-server/src/startServer.ts
@@ -37,7 +37,7 @@ const resolveServerConfig = async ({ viteConfig, options }: StartDevServer): Pro
const finalConfig: InlineConfig = { ...viteConfig, ...requiredOptions }
- finalConfig.plugins = [...(viteConfig.plugins || []), makeCypressPlugin(projectRoot, supportFile, options.devServerEvents)]
+ finalConfig.plugins = [...(viteConfig.plugins || []), makeCypressPlugin(projectRoot, supportFile, options.devServerEvents, options.specs)]
// This alias is necessary to avoid a "prefixIdentifiers" issue from slots mounting
// only cjs compiler-core accepts using prefixIdentifiers in slots which vue test utils use.