Skip to content

Commit 15bbbf8

Browse files
authoredJan 4, 2024
fix(vite-node): correctly return cached result (#4870)
1 parent 6088b37 commit 15bbbf8

File tree

3 files changed

+205
-13
lines changed

3 files changed

+205
-13
lines changed
 

‎packages/vite-node/src/server.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,16 @@ export class ViteNodeServer {
211211

212212
const { path: filePath } = toFilePath(id, this.server.config.root)
213213

214-
const module = this.server.moduleGraph.getModuleById(id)
215-
const timestamp = module ? module.lastHMRTimestamp : null
214+
const moduleNode = this.server.moduleGraph.getModuleById(id) || this.server.moduleGraph.getModuleById(filePath)
216215
const cache = this.fetchCaches[transformMode].get(filePath)
217-
if (timestamp && cache && cache.timestamp >= timestamp)
216+
217+
// lastUpdateTimestamp is the timestamp that marks the last time the module was changed
218+
// if lastUpdateTimestamp is 0, then the module was not changed since the server started
219+
// we test "timestamp === 0" for expressiveness, but it's not necessary
220+
const timestamp = moduleNode
221+
? Math.max(moduleNode.lastHMRTimestamp, moduleNode.lastInvalidationTimestamp)
222+
: 0
223+
if (cache && (timestamp === 0 || cache.timestamp >= timestamp))
218224
return cache.result
219225

220226
const time = Date.now()

‎test/vite-node/test/server.test.ts

+192-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { resolve } from 'pathe'
1+
import { join, resolve } from 'pathe'
22
import { ViteNodeServer } from 'vite-node/server'
33
import { describe, expect, test, vi } from 'vitest'
4-
import { createServer } from 'vite'
4+
import { type Plugin, type ViteDevServer, createServer } from 'vite'
55
import { extractSourceMap } from '../../../packages/vite-node/src/source-map'
66

77
describe('server works correctly', async () => {
@@ -29,19 +29,201 @@ describe('server works correctly', async () => {
2929
await vnServer.resolveId('/ssr', '/ssr path')
3030
expect(resolveId).toHaveBeenCalledWith('/ssr', '/ssr path', { ssr: true })
3131
})
32-
test('fetchModule with id, and got sourcemap source in absolute path', async () => {
33-
const server = await createServer({
34-
logLevel: 'error',
35-
root: resolve(__dirname, '../'),
36-
})
37-
const vnServer = new ViteNodeServer(server)
32+
})
3833

39-
// fetchModule in not a valid filesystem path
40-
const fetchResult = await vnServer.fetchModule('/src/foo.js')
34+
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
35+
36+
describe('server correctly caches data', () => {
37+
const it = test.extend<{
38+
root: string
39+
plugin: Plugin
40+
ssrFiles: string[]
41+
webFiles: string[]
42+
server: ViteDevServer
43+
viteNode: ViteNodeServer
44+
}>({
45+
ssrFiles: async ({}, use) => {
46+
await use([])
47+
},
48+
webFiles: async ({}, use) => {
49+
await use([])
50+
},
51+
root: resolve(__dirname, '../'),
52+
plugin: async ({ ssrFiles, webFiles }, use) => {
53+
const plugin: Plugin = {
54+
name: 'test',
55+
transform(code, id, options) {
56+
// this should be called only once if cached is configured correctly
57+
if (options?.ssr)
58+
ssrFiles.push(id)
59+
else
60+
webFiles.push(id)
61+
},
62+
}
63+
await use(plugin)
64+
},
65+
server: async ({ root, plugin }, use) => {
66+
const server = await createServer({
67+
configFile: false,
68+
root,
69+
server: {
70+
middlewareMode: true,
71+
watch: null,
72+
},
73+
optimizeDeps: {
74+
disabled: true,
75+
},
76+
plugins: [plugin],
77+
})
78+
await use(server)
79+
await server.close()
80+
},
81+
viteNode: async ({ server }, use) => {
82+
const vnServer = new ViteNodeServer(server)
83+
await use(vnServer)
84+
},
85+
})
86+
87+
it('fetchModule with id, and got sourcemap source in absolute path', async ({ viteNode }) => {
88+
const fetchResult = await viteNode.fetchModule('/src/foo.js')
4189

4290
const sourceMap = extractSourceMap(fetchResult.code!)
4391

4492
// expect got sourcemap source in a valid filesystem path
4593
expect(sourceMap?.sources[0]).toBe('foo.js')
4694
})
95+
96+
it('correctly returns cached and invalidated ssr modules', async ({ root, viteNode, ssrFiles, webFiles, server }) => {
97+
await viteNode.fetchModule('/src/foo.js', 'ssr')
98+
99+
const fsPath = join(root, './src/foo.js')
100+
101+
expect(viteNode.fetchCaches.web.has(fsPath)).toBe(false)
102+
expect(viteNode.fetchCache.has(fsPath)).toBe(true)
103+
expect(viteNode.fetchCaches.ssr.has(fsPath)).toBe(true)
104+
105+
expect(webFiles).toHaveLength(0)
106+
expect(ssrFiles).toHaveLength(1)
107+
expect(ssrFiles).toContain(fsPath)
108+
109+
await viteNode.fetchModule('/src/foo.js', 'ssr')
110+
111+
expect(ssrFiles).toHaveLength(1)
112+
113+
server.moduleGraph.invalidateModule(
114+
server.moduleGraph.getModuleById(fsPath)!,
115+
new Set(),
116+
Date.now(),
117+
false,
118+
)
119+
120+
// wait so TS are different
121+
await wait(10)
122+
123+
await viteNode.fetchModule('/src/foo.js', 'ssr')
124+
125+
expect(ssrFiles).toHaveLength(2)
126+
127+
// another fetch after invalidation returns cached result
128+
await viteNode.fetchModule('/src/foo.js', 'ssr')
129+
130+
expect(ssrFiles).toHaveLength(2)
131+
132+
server.moduleGraph.invalidateModule(
133+
server.moduleGraph.getModuleById(fsPath)!,
134+
new Set(),
135+
Date.now(),
136+
true,
137+
)
138+
139+
// wait so TS are different
140+
await wait(10)
141+
142+
await viteNode.fetchModule('/src/foo.js', 'ssr')
143+
144+
expect(ssrFiles).toHaveLength(3)
145+
146+
// another fetch after invalidation returns cached result
147+
await viteNode.fetchModule('/src/foo.js', 'ssr')
148+
149+
expect(ssrFiles).toHaveLength(3)
150+
expect(webFiles).toHaveLength(0)
151+
})
152+
153+
it('correctly returns cached and invalidated web modules', async ({ root, viteNode, webFiles, ssrFiles, server }) => {
154+
await viteNode.fetchModule('/src/foo.js', 'web')
155+
156+
const fsPath = join(root, './src/foo.js')
157+
158+
expect(viteNode.fetchCaches.ssr.has(fsPath)).toBe(false)
159+
expect(viteNode.fetchCache.has(fsPath)).toBe(true)
160+
expect(viteNode.fetchCaches.web.has(fsPath)).toBe(true)
161+
162+
expect(ssrFiles).toHaveLength(0)
163+
expect(webFiles).toHaveLength(1)
164+
expect(webFiles).toContain(fsPath)
165+
166+
await viteNode.fetchModule('/src/foo.js', 'web')
167+
168+
expect(webFiles).toHaveLength(1)
169+
170+
server.moduleGraph.invalidateModule(
171+
server.moduleGraph.getModuleById(fsPath)!,
172+
new Set(),
173+
Date.now(),
174+
false,
175+
)
176+
177+
// wait so TS are different
178+
await wait(10)
179+
180+
await viteNode.fetchModule('/src/foo.js', 'web')
181+
182+
expect(webFiles).toHaveLength(2)
183+
184+
// another fetch after invalidation returns cached result
185+
await viteNode.fetchModule('/src/foo.js', 'web')
186+
187+
expect(webFiles).toHaveLength(2)
188+
189+
server.moduleGraph.invalidateModule(
190+
server.moduleGraph.getModuleById(fsPath)!,
191+
new Set(),
192+
Date.now(),
193+
true,
194+
)
195+
196+
// wait so TS are different
197+
await wait(10)
198+
199+
await viteNode.fetchModule('/src/foo.js', 'web')
200+
201+
expect(webFiles).toHaveLength(3)
202+
203+
// another fetch after invalidation returns cached result
204+
await viteNode.fetchModule('/src/foo.js', 'web')
205+
206+
expect(webFiles).toHaveLength(3)
207+
expect(ssrFiles).toHaveLength(0)
208+
})
209+
210+
it('correctly processes the same file with both transform modes', async ({ viteNode, ssrFiles, webFiles, root }) => {
211+
await viteNode.fetchModule('/src/foo.js', 'ssr')
212+
await viteNode.fetchModule('/src/foo.js', 'web')
213+
214+
const fsPath = join(root, './src/foo.js')
215+
216+
expect(viteNode.fetchCaches.ssr.has(fsPath)).toBe(true)
217+
expect(viteNode.fetchCache.has(fsPath)).toBe(true)
218+
expect(viteNode.fetchCaches.web.has(fsPath)).toBe(true)
219+
220+
expect(ssrFiles).toHaveLength(1)
221+
expect(webFiles).toHaveLength(1)
222+
223+
await viteNode.fetchModule('/src/foo.js', 'ssr')
224+
await viteNode.fetchModule('/src/foo.js', 'web')
225+
226+
expect(ssrFiles).toHaveLength(1)
227+
expect(webFiles).toHaveLength(1)
228+
})
47229
})

‎test/vite-node/vitest.config.ts

+4
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@ export default defineConfig({
44
test: {
55
clearMocks: true,
66
testTimeout: process.env.CI ? 120_000 : 5_000,
7+
onConsoleLog(log) {
8+
if (log.includes('Port is already'))
9+
return false
10+
},
711
},
812
})

0 commit comments

Comments
 (0)
Please sign in to comment.