Skip to content

Commit e5f5301

Browse files
dario-piotrowiczsapphi-redbluwy
authored
test: test ModuleRunnerTransport invoke API (#18865)
Co-authored-by: sapphi-red <[email protected]> Co-authored-by: bluwy <[email protected]>
1 parent 7a0758c commit e5f5301

File tree

4 files changed

+148
-1
lines changed

4 files changed

+148
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// @ts-check
2+
3+
import { BroadcastChannel, parentPort } from 'node:worker_threads'
4+
import { fileURLToPath } from 'node:url'
5+
import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner'
6+
import { createBirpc } from 'birpc'
7+
8+
if (!parentPort) {
9+
throw new Error('File "worker.js" must be run in a worker thread')
10+
}
11+
12+
/** @type {import('worker_threads').MessagePort} */
13+
const pPort = parentPort
14+
15+
/** @type {import('birpc').BirpcReturn<{ invoke: (data: any) => any }>} */
16+
const rpc = createBirpc({}, {
17+
post: (data) => pPort.postMessage(data),
18+
on: (data) => pPort.on('message', data),
19+
})
20+
21+
const runner = new ModuleRunner(
22+
{
23+
root: fileURLToPath(new URL('./', import.meta.url)),
24+
transport: {
25+
invoke(data) { return rpc.invoke(data) }
26+
},
27+
hmr: false,
28+
},
29+
new ESModulesEvaluator(),
30+
)
31+
32+
const channel = new BroadcastChannel('vite-worker:invoke')
33+
channel.onmessage = async (message) => {
34+
try {
35+
const mod = await runner.import(message.data.id)
36+
channel.postMessage({ result: mod.default })
37+
} catch (e) {
38+
channel.postMessage({ error: e.stack })
39+
}
40+
}
41+
parentPort.postMessage('ready')

packages/vite/src/node/ssr/runtime/__tests__/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"dependencies": {
99
"@vitejs/cjs-external": "link:./fixtures/cjs-external",
1010
"@vitejs/esm-external": "link:./fixtures/esm-external",
11-
"tinyspy": "2.2.0"
11+
"tinyspy": "2.2.0",
12+
"birpc": "^0.2.19"
1213
}
1314
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { BroadcastChannel, Worker } from 'node:worker_threads'
2+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
3+
import type { BirpcReturn } from 'birpc'
4+
import { createBirpc } from 'birpc'
5+
import { DevEnvironment } from '../../..'
6+
import { type ViteDevServer, createServer } from '../../../server'
7+
8+
describe('running module runner inside a worker and using the ModuleRunnerTransport#invoke API', () => {
9+
let worker: Worker
10+
let server: ViteDevServer
11+
let rpc: BirpcReturn<
12+
unknown,
13+
{ invoke: (data: any) => Promise<{ result: any } | { error: any }> }
14+
>
15+
let handleInvoke: (data: any) => Promise<{ result: any } | { error: any }>
16+
17+
beforeAll(async () => {
18+
worker = new Worker(
19+
new URL('./fixtures/worker.invoke.mjs', import.meta.url),
20+
{
21+
stdout: true,
22+
},
23+
)
24+
await new Promise<void>((resolve, reject) => {
25+
worker.on('message', () => resolve())
26+
worker.on('error', reject)
27+
})
28+
server = await createServer({
29+
root: __dirname,
30+
logLevel: 'error',
31+
server: {
32+
middlewareMode: true,
33+
watch: null,
34+
hmr: {
35+
port: 9610,
36+
},
37+
},
38+
environments: {
39+
worker: {
40+
dev: {
41+
createEnvironment: (name, config) => {
42+
return new DevEnvironment(name, config, {
43+
hot: false,
44+
})
45+
},
46+
},
47+
},
48+
},
49+
})
50+
handleInvoke = (data: any) => server.environments.ssr.hot.handleInvoke(data)
51+
rpc = createBirpc(
52+
{
53+
invoke: (data: any) => handleInvoke(data),
54+
},
55+
{
56+
post: (data) => worker.postMessage(data),
57+
on: (data) => worker.on('message', data),
58+
},
59+
)
60+
})
61+
62+
afterAll(() => {
63+
server.close()
64+
worker.terminate()
65+
rpc.$close()
66+
})
67+
68+
async function run(id: string) {
69+
const channel = new BroadcastChannel('vite-worker:invoke')
70+
return new Promise<any>((resolve, reject) => {
71+
channel.onmessage = (event) => {
72+
try {
73+
resolve((event as MessageEvent).data)
74+
} catch (e) {
75+
reject(e)
76+
}
77+
}
78+
channel.postMessage({ id })
79+
})
80+
}
81+
82+
it('correctly runs ssr code', async () => {
83+
const output = await run('./fixtures/default-string.ts')
84+
expect(output).toStrictEqual({
85+
result: 'hello world',
86+
})
87+
})
88+
89+
it('triggers an error', async () => {
90+
handleInvoke = async () => ({ error: new Error('This is an Invoke Error') })
91+
const output = await run('dummy')
92+
expect(output).not.toHaveProperty('result')
93+
expect(output.error).toContain('Error: This is an Invoke Error')
94+
})
95+
96+
it('triggers an unknown error', async () => {
97+
handleInvoke = async () => ({ error: 'a string instead of an error' })
98+
const output = await run('dummy')
99+
expect(output).not.toHaveProperty('result')
100+
expect(output.error).toContain('Error: Unknown invoke error')
101+
})
102+
})

pnpm-lock.yaml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)