diff --git a/packages/vite/src/node/server/moduleGraph.ts b/packages/vite/src/node/server/moduleGraph.ts index 4c3b8ad966388a..06ae22a9f74219 100644 --- a/packages/vite/src/node/server/moduleGraph.ts +++ b/packages/vite/src/node/server/moduleGraph.ts @@ -25,6 +25,7 @@ export class ModuleNode { meta?: Record importers = new Set() importedModules = new Set() + ssrImportedModules = new Set() acceptedHmrDeps = new Set() acceptedHmrExports: Set | null = null importedBindings: Map> | null = null @@ -147,7 +148,7 @@ export class ModuleGraph { ssr?: boolean, ): Promise | undefined> { mod.isSelfAccepting = isSelfAccepting - const prevImports = mod.importedModules + const prevImports = ssr ? mod.ssrImportedModules : mod.importedModules let noLongerImported: Set | undefined let resolvePromises = [] @@ -173,11 +174,16 @@ export class ModuleGraph { await Promise.all(resolvePromises) } - const nextImports = (mod.importedModules = new Set(resolveResults)) + const nextImports = new Set(resolveResults) + if (ssr) { + mod.ssrImportedModules = nextImports + } else { + mod.importedModules = nextImports + } // remove the importer from deps that were imported but no longer are. prevImports.forEach((dep) => { - if (!nextImports.has(dep)) { + if (!mod.ssrImportedModules.has(dep) && !mod.importedModules.has(dep)) { dep.importers.delete(mod) if (!dep.importers.size) { // dependency no longer imported diff --git a/playground/ssr-deps/__tests__/ssr-deps.spec.ts b/playground/ssr-deps/__tests__/ssr-deps.spec.ts index a6d8973977e7a3..68d069c5964b94 100644 --- a/playground/ssr-deps/__tests__/ssr-deps.spec.ts +++ b/playground/ssr-deps/__tests__/ssr-deps.spec.ts @@ -1,6 +1,6 @@ -import { expect, test } from 'vitest' +import { describe, expect, test } from 'vitest' import { port } from './serve' -import { getColor, page } from '~utils' +import { editFile, getColor, isServe, page, untilUpdated } from '~utils' const url = `http://localhost:${port}` @@ -121,3 +121,46 @@ test('import css library', async () => { await page.goto(url) expect(await page.textContent('.module-condition')).toMatch('[success]') }) + +test('msg from isomorphic module (server)', async () => { + await page.goto(url) + expect(await page.textContent('.isomorphic-module-server')).toMatch( + '[server]', + ) +}) + +test('msg from isomorphic module (browser)', async () => { + await page.goto(url) + expect(await page.textContent('.isomorphic-module-browser')).toMatch( + '[browser]', + ) +}) + +describe.runIf(isServe)('hmr', () => { + test('handle isomorphic module updates', async () => { + await page.goto(url) + + expect(await page.textContent('.isomorphic-module-server')).toMatch( + '[server]', + ) + expect(await page.textContent('.isomorphic-module-browser')).toMatch( + '[browser]', + ) + + editFile('src/isomorphic-module-browser.js', (code) => + code.replace('[browser]', '[browser-hmr]'), + ) + await page.waitForNavigation() + await untilUpdated(async () => { + return page.textContent('.isomorphic-module-browser') + }, '[browser-hmr]') + + editFile('src/isomorphic-module-server.js', (code) => + code.replace('[server]', '[server-hmr]'), + ) + await page.waitForNavigation() + await untilUpdated(async () => { + return page.textContent('.isomorphic-module-server') + }, '[server-hmr]') + }) +}) diff --git a/playground/ssr-deps/index.html b/playground/ssr-deps/index.html index 72287c441c80b0..228adf234711f3 100644 --- a/playground/ssr-deps/index.html +++ b/playground/ssr-deps/index.html @@ -12,5 +12,10 @@

SSR Dependencies

// hydration scripts import '@vitejs/test-css-lib' + diff --git a/playground/ssr-deps/server.js b/playground/ssr-deps/server.js index 3f973a9afec001..05e86a863ea744 100644 --- a/playground/ssr-deps/server.js +++ b/playground/ssr-deps/server.js @@ -64,6 +64,23 @@ export async function createServer(root = process.cwd(), hmrPort) { } }, }, + { + name: 'virtual-isomorphic-module', + resolveId(id) { + if (id === 'virtual:isomorphic-module') { + return '\0virtual:isomorphic-module' + } + }, + load(id, { ssr }) { + if (id === '\0virtual:isomorphic-module') { + if (ssr) { + return 'export { default } from "/src/isomorphic-module-server.js";' + } else { + return 'export { default } from "/src/isomorphic-module-browser.js";' + } + } + }, + }, ], }) // use vite's connect instance as middleware diff --git a/playground/ssr-deps/src/app.js b/playground/ssr-deps/src/app.js index 0af57b094d76e0..9942dec47d7401 100644 --- a/playground/ssr-deps/src/app.js +++ b/playground/ssr-deps/src/app.js @@ -29,6 +29,7 @@ import optimizedCjsWithNestedExternal from '@vitejs/test-optimized-cjs-with-nest import { setMessage } from '@vitejs/test-external-entry/entry' setMessage('Hello World!') import externalUsingExternalEntry from '@vitejs/test-external-using-external-entry' +import isomorphicModuleMessage from 'virtual:isomorphic-module' export async function render(url, rootDir) { let html = '' @@ -90,5 +91,9 @@ export async function render(url, rootDir) { html += `\n

${moduleConditionMessage}

` + html += `\n

${isomorphicModuleMessage}

` + + html += `\n

` + return html + '\n' } diff --git a/playground/ssr-deps/src/isomorphic-module-browser.js b/playground/ssr-deps/src/isomorphic-module-browser.js new file mode 100644 index 00000000000000..95ab03d26f7d7b --- /dev/null +++ b/playground/ssr-deps/src/isomorphic-module-browser.js @@ -0,0 +1,3 @@ +const message = 'message from isomorphic-module (browser): [browser]' + +export default message diff --git a/playground/ssr-deps/src/isomorphic-module-server.js b/playground/ssr-deps/src/isomorphic-module-server.js new file mode 100644 index 00000000000000..def23eb4caa384 --- /dev/null +++ b/playground/ssr-deps/src/isomorphic-module-server.js @@ -0,0 +1,3 @@ +const message = 'message from isomorphic-module (server): [server]' + +export default message