diff --git a/packages/vite/src/node/server/moduleGraph.ts b/packages/vite/src/node/server/moduleGraph.ts index 804e38bc2c3cc7..461c182bb69c55 100644 --- a/packages/vite/src/node/server/moduleGraph.ts +++ b/packages/vite/src/node/server/moduleGraph.ts @@ -24,7 +24,8 @@ export class ModuleNode { info?: ModuleInfo meta?: Record importers = new Set() - importedModules = new Set() + clientImportedModules = new Set() + ssrImportedModules = new Set() acceptedHmrDeps = new Set() acceptedHmrExports: Set | null = null importedBindings: Map> | null = null @@ -46,6 +47,14 @@ export class ModuleNode { this.isSelfAccepting = false } } + + get importedModules(): Set { + const importedModules = new Set(this.clientImportedModules) + for (const module of this.ssrImportedModules) { + importedModules.add(module) + } + return importedModules + } } export type ResolvedUrl = [ @@ -169,7 +178,7 @@ export class ModuleGraph { ssr?: boolean, ): Promise | undefined> { mod.isSelfAccepting = isSelfAccepting - const prevImports = mod.importedModules + const prevImports = ssr ? mod.ssrImportedModules : mod.clientImportedModules let noLongerImported: Set | undefined let resolvePromises = [] @@ -195,11 +204,19 @@ 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.clientImportedModules = nextImports + } // remove the importer from deps that were imported but no longer are. prevImports.forEach((dep) => { - if (!nextImports.has(dep)) { + if ( + !mod.clientImportedModules.has(dep) && + !mod.ssrImportedModules.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..45dc6aa35566a2 100644 --- a/playground/ssr-deps/index.html +++ b/playground/ssr-deps/index.html @@ -12,5 +12,15 @@

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