From e4d4998a87e733f05fb8c814a70094da83a91a0b Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 5 Jan 2024 14:54:30 +0100 Subject: [PATCH] test: more tests --- packages/vite/src/node/server/ws.ts | 14 + packages/vite/src/node/ssr/runtime/runtime.ts | 16 + playground/hmr-ssr/__tests__/hmr.spec.ts | 606 ++++++++++-------- .../accept-exports/main-accepted/index.html | 1 - .../accept-exports/main-accepted/index.ts | 1 + playground/hmr-ssr/counter/index.ts | 5 +- 6 files changed, 359 insertions(+), 284 deletions(-) delete mode 100644 playground/hmr-ssr/accept-exports/main-accepted/index.html create mode 100644 playground/hmr-ssr/accept-exports/main-accepted/index.ts diff --git a/packages/vite/src/node/server/ws.ts b/packages/vite/src/node/server/ws.ts index d9a67e4934c42f..5be4f243ba9896 100644 --- a/packages/vite/src/node/server/ws.ts +++ b/packages/vite/src/node/server/ws.ts @@ -66,6 +66,8 @@ export interface WebSocketServer { off: WebSocketTypes.Server['off'] & { (event: string, listener: Function): void } + + emit(...args: any[]): void } export interface WebSocketClient { @@ -280,6 +282,18 @@ export function createWebSocketServer( }) }, + // TODO: remove when multiple hmr cliens are merged + emit(event, payload) { + customListeners.get(event)?.forEach((fn) => + fn(payload.data, { + socket: {} as any, + send: () => { + // + }, + }), + ) + }, + close() { // should remove listener if hmr.server is set // otherwise the old listener swallows all WebSocket connections diff --git a/packages/vite/src/node/ssr/runtime/runtime.ts b/packages/vite/src/node/ssr/runtime/runtime.ts index ddc42dfb6e3f33..5057e559ef1e37 100644 --- a/packages/vite/src/node/ssr/runtime/runtime.ts +++ b/packages/vite/src/node/ssr/runtime/runtime.ts @@ -91,6 +91,22 @@ export class ViteRuntime { this.hmrClient?.clear() } + // TODO - what if it's a virtual module(?) + public resolveIdToFilepath(id: string): string { + if (this.moduleCache.has(id)) { + return id + } + const file = this.idToFileMap.get(id) + if (file && this.moduleCache.has(file)) { + return file + } + const resolved = posixResolve( + this.options.root, + id[0] === '/' ? id.slice(1) : id, + ) + return resolved + } + private async cachedRequest( id: string, fetchedModule: FetchResult, diff --git a/playground/hmr-ssr/__tests__/hmr.spec.ts b/playground/hmr-ssr/__tests__/hmr.spec.ts index adc084758c2e25..cd80f6fce6ef5e 100644 --- a/playground/hmr-ssr/__tests__/hmr.spec.ts +++ b/playground/hmr-ssr/__tests__/hmr.spec.ts @@ -4,7 +4,7 @@ import { dirname, resolve } from 'node:path' import { Writable } from 'node:stream' import { Console } from 'node:console' import { - afterEach, + afterAll, beforeAll, beforeEach, describe, @@ -12,9 +12,8 @@ import { onTestFailed, test, } from 'vitest' -import type { InlineConfig, ViteDevServer, ViteRuntime } from 'vite' +import type { ViteDevServer, ViteRuntime } from 'vite' import { createServer, createViteRuntime } from 'vite' -import { hasWindowsUnicodeFsBug } from '../../hasWindowsUnicodeFsBug' import { addFile, getBg, @@ -39,12 +38,18 @@ let logs: { originalConsole: Console } -beforeAll(async () => { +async function setupViteRuntime(entrypoint: string) { + if (server) { + await server.close() + logs.restore() + runtime.clearCache() + } + globalThis.__HMR__ = {} as any server = await createServer({ - configFile: resolvePath(import.meta.url, '../vite.config.ts'), - root: resolvePath(import.meta.url, '..'), + configFile: resolvePath('../vite.config.ts'), + root: resolvePath('..'), logLevel: 'error', server: { middlewareMode: true, @@ -63,6 +68,9 @@ beforeAll(async () => { const stderr: string[] = [] let _onLog: (msg: string) => void = () => {} + const originalConsole = globalThis.console + globalThis._console = originalConsole + const streams = { stdout: new Writable({ write(chunk, _, callback) { @@ -70,6 +78,7 @@ beforeAll(async () => { stdout.push(str) callback() _onLog(str) + originalConsole.log(str) }, }), stderr: new Writable({ @@ -82,7 +91,6 @@ beforeAll(async () => { }), } - const originalConsole = globalThis.console const originalStdoutWrite = process.stdout.write const originalStderrWrite = process.stderr.write @@ -129,21 +137,24 @@ beforeAll(async () => { await waitForWatcher(server) - return async () => { - await server.close() - logs.restore() + await runtime.executeEntrypoints([entrypoint]) + + return { + logs, + runtime, + server, } -}) +} beforeEach(() => { onTestFailed(() => { - logs.printLogs() + // logs.printLogs() }) }) const originalFiles = new Map() const createdFiles = new Set() -afterEach(() => { +afterAll(() => { originalFiles.forEach((content, file) => { fs.writeFileSync(file, content, 'utf-8') }) @@ -154,291 +165,317 @@ afterEach(() => { createdFiles.clear() }) -test('should connect', async () => { - expect(logs.stdout).toContain('[vite] connected.') -}) - -test.only('self accept', async () => { - await runtime.executeEntrypoints(['./hmr.ts']) - - await untilConsoleLogAfter( - () => - editFile('hmr.ts', (code) => - code.replace('const foo = 1', 'const foo = 2'), - ), - [ - '>>> vite:beforeUpdate -- update', - 'foo was: 1', - '(self-accepting 1) foo is now: 2', - '(self-accepting 2) foo is now: 2', - `[vite] hot updated: ${resolvePath(import.meta.url, '../hmr.ts')}`, - '>>> vite:afterUpdate -- update', - ], - true, - ) - // await untilUpdated(() => el.textContent(), '2') - - // await untilConsoleLogAfter( - // () => - // editFile('hmr.ts', (code) => - // code.replace('const foo = 2', 'const foo = 3'), - // ), - // [ - // '>>> vite:beforeUpdate -- update', - // 'foo was: 2', - // '(self-accepting 1) foo is now: 3', - // '(self-accepting 2) foo is now: 3', - // '[vite] hot updated: /hmr.ts', - // '>>> vite:afterUpdate -- update', - // ], - // true, - // ) - // await untilUpdated(() => el.textContent(), '3') -}) - -test('accept dep', async () => { - const el = await page.$('.dep') - await untilConsoleLogAfter( - () => - editFile('hmrDep.js', (code) => - code.replace('const foo = 1', 'const foo = 2'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 1', - '(dep) foo from dispose: 1', - '(single dep) foo is now: 2', - '(single dep) nested foo is now: 1', - '(multi deps) foo is now: 2', - '(multi deps) nested foo is now: 1', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '2') +const hmr = (key: string) => (globalThis.__HMR__[key] as string) || '' - await untilConsoleLogAfter( - () => - editFile('hmrDep.js', (code) => - code.replace('const foo = 2', 'const foo = 3'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 2', - '(dep) foo from dispose: 2', - '(single dep) foo is now: 3', - '(single dep) nested foo is now: 1', - '(multi deps) foo is now: 3', - '(multi deps) nested foo is now: 1', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '3') -}) - -test('nested dep propagation', async () => { - const el = await page.$('.nested') - await untilConsoleLogAfter( - () => - editFile('hmrNestedDep.js', (code) => - code.replace('const foo = 1', 'const foo = 2'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 3', - '(dep) foo from dispose: 3', - '(single dep) foo is now: 3', - '(single dep) nested foo is now: 2', - '(multi deps) foo is now: 3', - '(multi deps) nested foo is now: 2', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '2') - - await untilConsoleLogAfter( - () => - editFile('hmrNestedDep.js', (code) => - code.replace('const foo = 2', 'const foo = 3'), - ), - [ - '>>> vite:beforeUpdate -- update', - '(dep) foo was: 3', - '(dep) foo from dispose: 3', - '(single dep) foo is now: 3', - '(single dep) nested foo is now: 3', - '(multi deps) foo is now: 3', - '(multi deps) nested foo is now: 3', - '[vite] hot updated: /hmrDep.js via /hmr.ts', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), '3') -}) - -test('invalidate', async () => { - const el = await page.$('.invalidation') - await untilConsoleLogAfter( - () => - editFile('invalidation/child.js', (code) => - code.replace('child', 'child updated'), - ), - [ - '>>> vite:beforeUpdate -- update', - '>>> vite:invalidate -- /invalidation/child.js', - '[vite] invalidate /invalidation/child.js', - '[vite] hot updated: /invalidation/child.js', - '>>> vite:afterUpdate -- update', - '>>> vite:beforeUpdate -- update', - '(invalidation) parent is executing', - '[vite] hot updated: /invalidation/parent.js', - '>>> vite:afterUpdate -- update', - ], - true, - ) - await untilUpdated(() => el.textContent(), 'child updated') -}) - -test('soft invalidate', async () => { - const el = await page.$('.soft-invalidation') - expect(await el.textContent()).toBe( - 'soft-invalidation/index.js is transformed 1 times. child is bar', - ) - editFile('soft-invalidation/child.js', (code) => - code.replace('bar', 'updated'), - ) - await untilUpdated( - () => el.textContent(), - 'soft-invalidation/index.js is transformed 1 times. child is updated', - ) -}) +const updated = (file: string, via?: string) => { + const filepath = resolvePath('..', file) + if (via) { + return `[vite] hot updated: ${filepath} via ${resolvePath('..', via)}` + } + return `[vite] hot updated: ${filepath}` +} -test('plugin hmr handler + custom event', async () => { - const el = await page.$('.custom') - editFile('customFile.js', (code) => code.replace('custom', 'edited')) - await untilUpdated(() => el.textContent(), 'edited') -}) +const invalidated = (file: string) => { + return `[vite] invalidate ${resolvePath('..', file)}` +} -test('plugin hmr remove custom events', async () => { - const el = await page.$('.toRemove') - editFile('customFile.js', (code) => code.replace('custom', 'edited')) - await untilUpdated(() => el.textContent(), 'edited') - editFile('customFile.js', (code) => code.replace('edited', 'custom')) - await untilUpdated(() => el.textContent(), 'edited') -}) +describe('hmr works correctly', () => { + beforeAll(async () => { + await setupViteRuntime('./hmr.ts') -test('plugin client-server communication', async () => { - const el = await page.$('.custom-communication') - await untilUpdated(() => el.textContent(), '3') -}) + return async () => { + await server.close() + logs.restore() + } + }) -test.skipIf(hasWindowsUnicodeFsBug)('full-reload encodeURI path', async () => { - await page.goto( - viteTestUrl + '/unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html', - ) - const el = await page.$('#app') - expect(await el.textContent()).toBe('title') - editFile('unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html', (code) => - code.replace('title', 'title2'), - ) - await page.waitForEvent('load') - await untilUpdated(async () => (await page.$('#app')).textContent(), 'title2') -}) + test('should connect', async () => { + expect(logs.stdout).toContain('[vite] connected.') + }) -test('CSS update preserves query params', async () => { - await page.goto(viteTestUrl) + test.only('self accept', async () => { + const el = () => hmr('.app') + await untilConsoleLogAfter( + () => + editFile('hmr.ts', (code) => + code.replace('const foo = 1', 'const foo = 2'), + ), + [ + '>>> vite:beforeUpdate -- update', + 'foo was: 1', + '(self-accepting 1) foo is now: 2', + '(self-accepting 2) foo is now: 2', + updated('hmr.ts'), + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el(), '2') - editFile('global.css', (code) => code.replace('white', 'tomato')) - - const elprev = await page.$('.css-prev') - const elpost = await page.$('.css-post') - await untilUpdated(() => elprev.textContent(), 'param=required') - await untilUpdated(() => elpost.textContent(), 'param=required') - const textprev = await elprev.textContent() - const textpost = await elpost.textContent() - expect(textprev).not.toBe(textpost) - expect(textprev).not.toMatch('direct') - expect(textpost).not.toMatch('direct') -}) + await untilConsoleLogAfter( + () => + editFile('hmr.ts', (code) => + code.replace('const foo = 2', 'const foo = 3'), + ), + [ + '>>> vite:beforeUpdate -- update', + 'foo was: 2', + '(self-accepting 1) foo is now: 3', + '(self-accepting 2) foo is now: 3', + updated('hmr.ts'), + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el(), '3') + }) -test('it swaps out link tags', async () => { - await page.goto(viteTestUrl) + test.only('accept dep', async () => { + const el = () => hmr('.dep') + await untilConsoleLogAfter( + () => + editFile('hmrDep.js', (code) => + code.replace('const foo = 1', 'const foo = 2'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 1', + '(dep) foo from dispose: 1', + '(single dep) foo is now: 2', + '(single dep) nested foo is now: 1', + '(multi deps) foo is now: 2', + '(multi deps) nested foo is now: 1', + updated('hmrDep.js', 'hmr.ts'), + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el(), '2') - editFile('global.css', (code) => code.replace('white', 'tomato')) + await untilConsoleLogAfter( + () => + editFile('hmrDep.js', (code) => + code.replace('const foo = 2', 'const foo = 3'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 2', + '(dep) foo from dispose: 2', + '(single dep) foo is now: 3', + '(single dep) nested foo is now: 1', + '(multi deps) foo is now: 3', + '(multi deps) nested foo is now: 1', + updated('hmrDep.js', 'hmr.ts'), + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el(), '3') + }) - let el = await page.$('.link-tag-added') - await untilUpdated(() => el.textContent(), 'yes') + test.only('nested dep propagation', async () => { + const el = () => hmr('.nested') + await untilConsoleLogAfter( + () => + editFile('hmrNestedDep.js', (code) => + code.replace('const foo = 1', 'const foo = 2'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 3', + '(dep) foo from dispose: 3', + '(single dep) foo is now: 3', + '(single dep) nested foo is now: 2', + '(multi deps) foo is now: 3', + '(multi deps) nested foo is now: 2', + updated('hmrDep.js', 'hmr.ts'), + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el(), '2') - el = await page.$('.link-tag-removed') - await untilUpdated(() => el.textContent(), 'yes') + await untilConsoleLogAfter( + () => + editFile('hmrNestedDep.js', (code) => + code.replace('const foo = 2', 'const foo = 3'), + ), + [ + '>>> vite:beforeUpdate -- update', + '(dep) foo was: 3', + '(dep) foo from dispose: 3', + '(single dep) foo is now: 3', + '(single dep) nested foo is now: 3', + '(multi deps) foo is now: 3', + '(multi deps) nested foo is now: 3', + updated('hmrDep.js', 'hmr.ts'), + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el(), '3') + }) - expect((await page.$$('link')).length).toBe(1) -}) + test.only('invalidate', async () => { + const el = () => hmr('.invalidation') + await untilConsoleLogAfter( + () => + editFile('invalidation/child.js', (code) => + code.replace('child', 'child updated'), + ), + [ + '>>> vite:beforeUpdate -- update', + `>>> vite:invalidate -- ${resolvePath('../invalidation/child.js')}`, + invalidated('invalidation/child.js'), + updated('invalidation/child.js'), + '>>> vite:afterUpdate -- update', + '>>> vite:beforeUpdate -- update', + '(invalidation) parent is executing', + updated('invalidation/parent.js'), + '>>> vite:afterUpdate -- update', + ], + true, + ) + await untilUpdated(() => el(), 'child updated') + }) -test('not loaded dynamic import', async () => { - await page.goto(viteTestUrl + '/counter/index.html', { waitUntil: 'load' }) + test.only('soft invalidate', async () => { + const el = () => hmr('.soft-invalidation') + expect(el()).toBe( + 'soft-invalidation/index.js is transformed 1 times. child is bar', + ) + editFile('soft-invalidation/child.js', (code) => + code.replace('bar', 'updated'), + ) + await untilUpdated( + () => el(), + 'soft-invalidation/index.js is transformed 1 times. child is updated', + ) + }) - let btn = await page.$('button') - expect(await btn.textContent()).toBe('Counter 0') - await btn.click() - expect(await btn.textContent()).toBe('Counter 1') + test.only('plugin hmr handler + custom event', async () => { + const el = () => hmr('.custom') + editFile('customFile.js', (code) => code.replace('custom', 'edited')) + await untilUpdated(() => el(), 'edited') + }) - // Modifying `index.ts` triggers a page reload, as expected - const indexTsLoadPromise = page.waitForEvent('load') - editFile('counter/index.ts', (code) => code) - await indexTsLoadPromise - btn = await page.$('button') - expect(await btn.textContent()).toBe('Counter 0') + test.only('plugin hmr remove custom events', async () => { + const el = () => hmr('.toRemove') + editFile('customFile.js', (code) => code.replace('custom', 'edited')) + await untilUpdated(() => el(), 'edited') + editFile('customFile.js', (code) => code.replace('edited', 'custom')) + await untilUpdated(() => el(), 'edited') + }) - await btn.click() - expect(await btn.textContent()).toBe('Counter 1') - - // #7561 - // `dep.ts` defines `import.module.hot.accept` and has not been loaded. - // Therefore, modifying it has no effect (doesn't trigger a page reload). - // (Note that, a dynamic import that is never loaded and that does not - // define `accept.module.hot.accept` may wrongfully trigger a full page - // reload, see discussion at #7561.) - const depTsLoadPromise = page.waitForEvent('load', { timeout: 1000 }) - editFile('counter/dep.ts', (code) => code) - await expect(depTsLoadPromise).rejects.toThrow( - /page\.waitForEvent: Timeout \d+ms exceeded while waiting for event "load"/, - ) + // TODO: should be somehow initiated in the multiple clients PR + test.skip('plugin client-server communication', async () => { + const el = () => hmr('.custom-communication') + await untilUpdated(() => el(), '3') + }) - btn = await page.$('button') - expect(await btn.textContent()).toBe('Counter 1') -}) + // TODO + // test.skipIf(hasWindowsUnicodeFsBug)('full-reload encodeURI path', async () => { + // await page.goto( + // viteTestUrl + '/unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html', + // ) + // const el = () => hmr('#app') + // expect(await el()).toBe('title') + // editFile('unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html', (code) => + // code.replace('title', 'title2'), + // ) + // await page.waitForEvent('load') + // await untilUpdated(async () => el(), 'title2') + // }) + + // TODO: css is not supported in SSR (yet?) + // test('CSS update preserves query params', async () => { + // await page.goto(viteTestUrl) + + // editFile('global.css', (code) => code.replace('white', 'tomato')) + + // const elprev = () => hmr('.css-prev') + // const elpost = () => hmr('.css-post') + // await untilUpdated(() => elprev(), 'param=required') + // await untilUpdated(() => elpost(), 'param=required') + // const textprev = elprev() + // const textpost = elpost() + // expect(textprev).not.toBe(textpost) + // expect(textprev).not.toMatch('direct') + // expect(textpost).not.toMatch('direct') + // }) + + // test('it swaps out link tags', async () => { + // await page.goto(viteTestUrl) + + // editFile('global.css', (code) => code.replace('white', 'tomato')) + + // let el = () => hmr('.link-tag-added') + // await untilUpdated(() => el(), 'yes') + + // el = () => hmr('.link-tag-removed') + // await untilUpdated(() => el(), 'yes') + + // expect((await page.$$('link')).length).toBe(1) + // }) + + test.todo('not loaded dynamic import', async () => { + const [{ increment, getCount }] = await runtime.executeEntrypoints([ + './counter/index', + ]) + + expect(getCount()).toBe(0) + increment() + expect(getCount()).toBe(1) + + // Modifying `index.ts` triggers a page reload, as expected + // const indexTsLoadPromise = page.waitForEvent('load') + editFile('counter/index.ts', (code) => code) + // await indexTsLoadPromise + const newCounter = await runtime.executeId('./counter/index', true) + expect(newCounter.getCount()).toBe(0) + + newCounter.increment() + expect(newCounter.getCount()).toBe(1) + + // #7561 + // `dep.ts` defines `import.module.hot.accept` and has not been loaded. + // Therefore, modifying it has no effect (doesn't trigger a page reload). + // (Note that, a dynamic import that is never loaded and that does not + // define `accept.module.hot.accept` may wrongfully trigger a full page + // reload, see discussion at #7561.) + + // TODO: how to test this in SSR context? + // const depTsLoadPromise = page.waitForEvent('load', { timeout: 1000 }) + // editFile('counter/dep.ts', (code) => code) + // await expect(depTsLoadPromise).rejects.toThrow( + // /page\.waitForEvent: Timeout \d+ms exceeded while waiting for event "load"/, + // ) + + // btn = await page.$('button') + // 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() - } + // #2255 + test.only('importing reloaded', async () => { + const outputEle = () => hmr('.importing-reloaded') - await untilUpdated(getOutput, ['a.js: a0', 'b.js: b0,a0'].join('
')) + await untilUpdated(outputEle, ['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/a.js', (code) => code.replace("'a0'", "'a1'")) + await untilUpdated( + outputEle, + ['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('
'), - ) + 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( + outputEle, + ['a.js: a0', 'b.js: b0,a0', 'a.js: a1', 'b.js: b1,a1'].join('
'), + ) + }) }) describe('acceptExports', () => { @@ -458,13 +495,18 @@ describe('acceptExports', () => { beforeAll(async () => { await untilConsoleLogAfter( - () => page.goto(`${viteTestUrl}/${testDir}/`), - [CONNECTED, />>>>>>/], + () => setupViteRuntime(`./${testDir}/index`), + [CONNECTED], (logs) => { expect(logs).toContain(`<<<<<< A0 B0 D0 ; ${dep}`) expect(logs).toContain('>>>>>> A0 D0') }, ) + + return async () => { + await server.close() + logs.restore() + } }) test('the callback is called with the new version the module', async () => { @@ -1053,14 +1095,14 @@ export function editFile( file: string, callback: (content: string) => string, ): void { - const filepath = resolvePath(import.meta.url, '..', file) + const filepath = resolvePath('..', file) const content = fs.readFileSync(filepath, 'utf-8') if (!originalFiles.has(filepath)) originalFiles.set(filepath, content) fs.writeFileSync(filepath, callback(content), 'utf-8') } -export function resolvePath(baseUrl: string, ...segments: string[]): string { - const filename = fileURLToPath(baseUrl) +export function resolvePath(...segments: string[]): string { + const filename = fileURLToPath(import.meta.url) return resolve(dirname(filename), ...segments).replace(/\\/g, '/') } diff --git a/playground/hmr-ssr/accept-exports/main-accepted/index.html b/playground/hmr-ssr/accept-exports/main-accepted/index.html deleted file mode 100644 index 8d576b0e135457..00000000000000 --- a/playground/hmr-ssr/accept-exports/main-accepted/index.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/playground/hmr-ssr/accept-exports/main-accepted/index.ts b/playground/hmr-ssr/accept-exports/main-accepted/index.ts new file mode 100644 index 00000000000000..2e798337101607 --- /dev/null +++ b/playground/hmr-ssr/accept-exports/main-accepted/index.ts @@ -0,0 +1 @@ +import './main-accepted' diff --git a/playground/hmr-ssr/counter/index.ts b/playground/hmr-ssr/counter/index.ts index 35a4710252d6c0..06ac6c462b72b0 100644 --- a/playground/hmr-ssr/counter/index.ts +++ b/playground/hmr-ssr/counter/index.ts @@ -1,7 +1,10 @@ -export let count = 0 +let count = 0 export function increment() { count++ } +export function getCount() { + return count +} function neverCalled() { import('./dep') }