@@ -7,7 +7,7 @@ import type { IncomingMessage, RequestListener, ServerResponse } from 'node:http
7
7
import type { AddressInfo } from 'node:net'
8
8
9
9
import EventEmitter from 'node:events'
10
- import { existsSync , watch } from 'node:fs'
10
+ import { existsSync , statSync , watch } from 'node:fs'
11
11
import { mkdir } from 'node:fs/promises'
12
12
import process from 'node:process'
13
13
import { pathToFileURL } from 'node:url'
@@ -73,6 +73,28 @@ interface NuxtDevServerOptions {
73
73
// https://regex101.com/r/7HkR5c/1
74
74
const RESTART_RE = / ^ (?: n u x t \. c o n f i g \. [ a - z 0 - 9 ] + | \. n u x t i g n o r e | \. n u x t r c | \. c o n f i g \/ n u x t (?: \. c o n f i g ) ? \. [ a - z 0 - 9 ] + ) $ /
75
75
76
+ export class FileChangeTracker {
77
+ private mtimes = new Map < string , number > ( )
78
+
79
+ shouldEmitChange ( filePath : string ) : boolean {
80
+ try {
81
+ const stats = statSync ( filePath )
82
+ const currentMtime = stats . mtimeMs
83
+ const lastMtime = this . mtimes . get ( filePath )
84
+
85
+ this . mtimes . set ( filePath , currentMtime )
86
+
87
+ // emit change for new file or mtime has changed
88
+ return lastMtime === undefined || currentMtime !== lastMtime
89
+ }
90
+ catch {
91
+ // remove from cache if it has been deleted or is inaccessible
92
+ this . mtimes . delete ( filePath )
93
+ return true
94
+ }
95
+ }
96
+ }
97
+
76
98
type NuxtWithServer = Omit < Nuxt , 'server' > & { server ?: NitroDevServer }
77
99
78
100
interface DevServerEventMap {
@@ -89,6 +111,7 @@ export class NuxtDevServer extends EventEmitter<DevServerEventMap> {
89
111
private _currentNuxt ?: NuxtWithServer
90
112
private _loadingMessage ?: string
91
113
private _loadingError ?: Error
114
+ private _fileChangeTracker = new FileChangeTracker ( )
92
115
private cwd : string
93
116
94
117
loadDebounced : ( reload ?: boolean , reason ?: string ) => void
@@ -310,7 +333,11 @@ export class NuxtDevServer extends EventEmitter<DevServerEventMap> {
310
333
const distDir = resolve ( this . _currentNuxt . options . buildDir , 'dist' )
311
334
await mkdir ( distDir , { recursive : true } )
312
335
this . _distWatcher = watch ( distDir )
313
- this . _distWatcher . on ( 'change' , ( ) => {
336
+ this . _distWatcher . on ( 'change' , ( _event , file : string ) => {
337
+ if ( ! this . _fileChangeTracker . shouldEmitChange ( resolve ( distDir , file || '' ) ) ) {
338
+ return
339
+ }
340
+
314
341
this . loadDebounced ( true , '.nuxt/dist directory has been removed' )
315
342
} )
316
343
@@ -374,8 +401,13 @@ function createConfigWatcher(cwd: string, dotenvFileName: string | string[] = '.
374
401
const configWatcher = watch ( cwd )
375
402
let configDirWatcher = existsSync ( resolve ( cwd , '.config' ) ) ? createConfigDirWatcher ( cwd , onReload ) : undefined
376
403
const dotenvFileNames = new Set ( Array . isArray ( dotenvFileName ) ? dotenvFileName : [ dotenvFileName ] )
404
+ const fileWatcher = new FileChangeTracker ( )
377
405
378
406
configWatcher . on ( 'change' , ( _event , file : string ) => {
407
+ if ( ! fileWatcher . shouldEmitChange ( resolve ( cwd , file ) ) ) {
408
+ return
409
+ }
410
+
379
411
if ( dotenvFileNames . has ( file ) ) {
380
412
onRestart ( )
381
413
}
@@ -397,9 +429,14 @@ function createConfigWatcher(cwd: string, dotenvFileName: string | string[] = '.
397
429
398
430
function createConfigDirWatcher ( cwd : string , onReload : ( file : string ) => void ) {
399
431
const configDir = resolve ( cwd , '.config' )
432
+ const fileWatcher = new FileChangeTracker ( )
400
433
401
434
const configDirWatcher = watch ( configDir )
402
435
configDirWatcher . on ( 'change' , ( _event , file : string ) => {
436
+ if ( ! fileWatcher . shouldEmitChange ( resolve ( configDir , file ) ) ) {
437
+ return
438
+ }
439
+
403
440
if ( RESTART_RE . test ( file ) ) {
404
441
onReload ( file )
405
442
}
0 commit comments