From 16c6e97bac2f14e2ee70abc717367a12c46d2049 Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 26 Oct 2026 12:09:10 +0800 Subject: [PATCH 1/2] fix: skip full reload in workers --- client-src/index.ts | 6 +++- tests/client-reload.test.ts | 62 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 tests/client-reload.test.ts diff --git a/client-src/index.ts b/client-src/index.ts index 8df68c0..8fc2502 100644 --- a/client-src/index.ts +++ b/client-src/index.ts @@ -278,7 +278,7 @@ const reloadApp = ( } } // allow refreshing the page only if liveReload isn't disabled - else if (liveReload && allowToLiveReload) { + else if (liveReload && allowToLiveReload && typeof document !== 'undefined') { let rootWindow = self; // use parent window for reload (in case we're in an iframe with no valid src) @@ -414,6 +414,10 @@ const onSocketMessage = { reloadApp(options, status); }, 'static-changed': function staticChanged(file: string) { + if (typeof document === 'undefined') { + return; + } + log.info( `${ file ? `"${file}"` : 'Content' diff --git a/tests/client-reload.test.ts b/tests/client-reload.test.ts new file mode 100644 index 0000000..f5eca93 --- /dev/null +++ b/tests/client-reload.test.ts @@ -0,0 +1,62 @@ +import { rstest } from 'rstack/test'; +import type socket from '../client-src/socket.js'; + +rstest.mock('../client-src/socket.js', () => ({ default: rstest.fn() })); +rstest.mock('@rspack/core/hot/emitter.js', () => ({ + emitter: { emit: rstest.fn() }, +})); + +let handlers: Parameters[1]; + +beforeEach(async () => { + rstest.resetModules(); + rstest.clearAllMocks(); + rstest.useFakeTimers(); + rstest.stubGlobal('BUILD_HASH', 'initial'); + rstest.stubGlobal( + 'RESOURCE_QUERY', + '?hostname=localhost&protocol=ws:&port=8080&live-reload=true&logging=none', + ); + rstest.stubGlobal('document', undefined); + rstest.stubGlobal('self', { + location: new URL('http://localhost/worker.js'), + addEventListener: rstest.fn(), + postMessage: rstest.fn(), + setInterval, + }); + await import('../client-src/index'); + const { default: socket } = await import('../client-src/socket.js'); + handlers = rstest.mocked(socket).mock.calls[0][1]; +}); + +afterEach(() => { + rstest.useRealTimers(); + rstest.unstubAllGlobals(); +}); + +describe.each(['ok', 'static-changed'])('%s', (message) => { + it('skips full reload in a Worker without location.reload', () => { + handlers.hash('updated'); + expect(() => { + handlers[message](); + rstest.runAllTimers(); + }).not.toThrow(); + }); + + it('still reloads the page', () => { + rstest.stubGlobal('document', {}); + self.location.reload = rstest.fn(); + handlers.hash('updated'); + handlers[message](); + rstest.runAllTimers(); + expect(self.location.reload).toHaveBeenCalledTimes(1); + }); +}); + +it('still emits hot updates in a Worker', async () => { + const { emitter: hotEmitter } = await import('@rspack/core/hot/emitter.js'); + handlers.hot(); + handlers.hash('updated'); + handlers.ok(); + expect(hotEmitter.emit).toHaveBeenCalledWith('webpackHotUpdate', 'updated'); +}); From ca867944b4b27e1ecc480a737235c303549f19ea Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 26 Oct 2026 13:23:00 +0800 Subject: [PATCH 2/2] test: remove mocked client reload tests --- tests/client-reload.test.ts | 62 ------------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 tests/client-reload.test.ts diff --git a/tests/client-reload.test.ts b/tests/client-reload.test.ts deleted file mode 100644 index f5eca93..0000000 --- a/tests/client-reload.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { rstest } from 'rstack/test'; -import type socket from '../client-src/socket.js'; - -rstest.mock('../client-src/socket.js', () => ({ default: rstest.fn() })); -rstest.mock('@rspack/core/hot/emitter.js', () => ({ - emitter: { emit: rstest.fn() }, -})); - -let handlers: Parameters[1]; - -beforeEach(async () => { - rstest.resetModules(); - rstest.clearAllMocks(); - rstest.useFakeTimers(); - rstest.stubGlobal('BUILD_HASH', 'initial'); - rstest.stubGlobal( - 'RESOURCE_QUERY', - '?hostname=localhost&protocol=ws:&port=8080&live-reload=true&logging=none', - ); - rstest.stubGlobal('document', undefined); - rstest.stubGlobal('self', { - location: new URL('http://localhost/worker.js'), - addEventListener: rstest.fn(), - postMessage: rstest.fn(), - setInterval, - }); - await import('../client-src/index'); - const { default: socket } = await import('../client-src/socket.js'); - handlers = rstest.mocked(socket).mock.calls[0][1]; -}); - -afterEach(() => { - rstest.useRealTimers(); - rstest.unstubAllGlobals(); -}); - -describe.each(['ok', 'static-changed'])('%s', (message) => { - it('skips full reload in a Worker without location.reload', () => { - handlers.hash('updated'); - expect(() => { - handlers[message](); - rstest.runAllTimers(); - }).not.toThrow(); - }); - - it('still reloads the page', () => { - rstest.stubGlobal('document', {}); - self.location.reload = rstest.fn(); - handlers.hash('updated'); - handlers[message](); - rstest.runAllTimers(); - expect(self.location.reload).toHaveBeenCalledTimes(1); - }); -}); - -it('still emits hot updates in a Worker', async () => { - const { emitter: hotEmitter } = await import('@rspack/core/hot/emitter.js'); - handlers.hot(); - handlers.hash('updated'); - handlers.ok(); - expect(hotEmitter.emit).toHaveBeenCalledWith('webpackHotUpdate', 'updated'); -});