Skip to content

Commit cc920c0

Browse files
committed
Ignore duplicate lib registrations in Next dev
Avoid false-positive multiple-instance warnings caused by Next.js Fast Refresh re-executing module code. Add an isNextjsDev() helper and only skip exact duplicate registrations when running in Next.js development mode (process.env.NODE_ENV === 'development' and global __NEXT_DATA__ present). Update tests to stub __NEXT_DATA__, toggle NODE_ENV and restore it, and add a new test that verifies duplicates still trigger a warning outside Next.js dev mode. Files changed: packages/utils/src/lib/version.ts, packages/utils/src/lib/version.test.ts.
1 parent dc9de5c commit cc920c0

File tree

2 files changed

+41
-8
lines changed

2 files changed

+41
-8
lines changed

packages/utils/src/lib/version.test.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,17 +294,35 @@ describe('version utilities', () => {
294294
expect(mockConsoleLog).not.toHaveBeenCalled()
295295
})
296296

297-
it('should ignore duplicate registrations of the same library', () => {
298-
// Re-registering the same library with the same version and module type
299-
// (e.g. due to module re-evaluation in Next.js dev mode) should not
300-
// trigger a warning
297+
it('should ignore duplicate registrations in Next.js dev mode', () => {
298+
// In Next.js dev mode, Fast Refresh re-executes module-level code which causes
299+
// the same library to register multiple times, producing a false positive warning.
300+
vi.stubGlobal('__NEXT_DATA__', {})
301+
const originalNodeEnv = process.env.NODE_ENV
302+
process.env.NODE_ENV = 'development'
303+
301304
registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
302305
registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
303306
registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
304307

305308
vi.runAllTimers()
306309

307310
expect(mockConsoleLog).not.toHaveBeenCalled()
311+
312+
process.env.NODE_ENV = originalNodeEnv
313+
})
314+
315+
it('should detect duplicate registrations outside Next.js dev mode', () => {
316+
// Outside Next.js dev, duplicate exact registrations should still be
317+
// tracked and trigger the multiple instances warning
318+
registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
319+
registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
320+
321+
vi.runAllTimers()
322+
323+
expect(mockConsoleLog).toHaveBeenCalled()
324+
const logMessage = mockConsoleLog.mock.calls[0][0]
325+
expect(logMessage).toContain('multiple instances')
308326
})
309327

310328
it('should handle bundler misconfiguration scenario', () => {

packages/utils/src/lib/version.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,17 @@ export function registerTldrawLibraryVersion(name?: string, version?: string, mo
9393
}
9494

9595
const info = getLibraryVersions()
96-
const isDuplicate = info.versions.some(
97-
(v) => v.name === name && v.version === version && v.modules === modules
98-
)
99-
if (isDuplicate) return
96+
97+
// In Next.js dev mode, Fast Refresh re-executes module-level code which causes
98+
// the same library to register multiple times, producing a false positive warning.
99+
// We skip exact duplicates only in Next.js dev to avoid hiding genuine issues.
100+
if (isNextjsDev()) {
101+
const isDuplicate = info.versions.some(
102+
(v) => v.name === name && v.version === version && v.modules === modules
103+
)
104+
if (isDuplicate) return
105+
}
106+
100107
info.versions.push({ name, version, modules })
101108

102109
if (!info.scheduledNotice) {
@@ -223,6 +230,14 @@ function format(value: string, formatters: (keyof typeof formats)[] = []) {
223230
return `\x1B[${formatters.map((f) => formats[f]).join(';')}m${value}\x1B[m`
224231
}
225232

233+
function isNextjsDev(): boolean {
234+
try {
235+
return process.env.NODE_ENV === 'development' && '__NEXT_DATA__' in globalThis
236+
} catch {
237+
return false
238+
}
239+
}
240+
226241
function entry<K, V>(map: Map<K, V>, key: K, defaultValue: V): V {
227242
if (map.has(key)) {
228243
return map.get(key)!

0 commit comments

Comments
 (0)