Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: test ModuleRunnerTransport invoke API #18865

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// @ts-check

import { BroadcastChannel, parentPort } from 'node:worker_threads'
import { fileURLToPath } from 'node:url'
import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner'

if (!parentPort) {
throw new Error('File "worker.js" must be run in a worker thread')
}

/** @type {import('vite/module-runner').ModuleRunnerTransport} */
const transport = {
async invoke(/** @type {import('vite').HotPayload} */ event) {
const hotPayloadData = event['data']

const id = hotPayloadData['data'][0]

if (id === 'test_invalid_error') {
return {
error: 'a string instead of an error'
}
}

if (id !== 'virtual:invoke-default-string') {
return {
error: new Error(`error, module not found: ${id}`)
}
}

return {
result: {
"code": "__vite_ssr_exports__.default = 'hello invoke world'",
"id": "\0virtual:invoke-default-string",
}
};
},
}

const runner = new ModuleRunner(
{
root: fileURLToPath(new URL('./', import.meta.url)),
transport,
hmr: false,
},
new ESModulesEvaluator(),
)

const channel = new BroadcastChannel('vite-worker')
channel.onmessage = async (message) => {
try {
const mod = await runner.import(message.data.id)
channel.postMessage({ result: mod.default })
} catch (e) {
channel.postMessage({ error: e.stack })
}
}
parentPort.postMessage('ready')
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { BroadcastChannel, Worker } from 'node:worker_threads'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import type { HotChannel, HotChannelListener, HotPayload } from 'vite'
import { DevEnvironment } from '../../..'
import { type ViteDevServer, createServer } from '../../../server'

const createWorkerTransport = (w: Worker): HotChannel => {
const handlerToWorkerListener = new WeakMap<
HotChannelListener,
(value: HotPayload) => void
>()

return {
send: (data) => w.postMessage(data),
on: (event: string, handler: HotChannelListener) => {
if (event === 'connection') return

const listener = (value: HotPayload) => {
if (value.type === 'custom' && value.event === event) {
const client = {
send(payload: HotPayload) {
w.postMessage(payload)
},
}
handler(value.data, client)
}
}
handlerToWorkerListener.set(handler, listener)
w.on('message', listener)
},
off: (event, handler: HotChannelListener) => {
if (event === 'connection') return
const listener = handlerToWorkerListener.get(handler)
if (listener) {
w.off('message', listener)
handlerToWorkerListener.delete(handler)
}
},
}
}

describe('running module runner inside a worker and using the ModuleRunnerTransport#invoke API', () => {
let worker: Worker
let server: ViteDevServer

beforeAll(async () => {
worker = new Worker(
new URL('./fixtures/worker.invoke.mjs', import.meta.url),
{
stdout: true,
},
)
await new Promise<void>((resolve, reject) => {
worker.on('message', () => resolve())
worker.on('error', reject)
})
server = await createServer({
root: __dirname,
logLevel: 'error',
server: {
middlewareMode: true,
watch: null,
hmr: {
port: 9610,
},
},
environments: {
worker: {
dev: {
createEnvironment: (name, config) => {
return new DevEnvironment(name, config, {
hot: false,
transport: createWorkerTransport(worker),
})
},
},
},
},
})
})

afterAll(() => {
server.close()
worker.terminate()
})

it('correctly runs ssr code', async () => {
const channel = new BroadcastChannel('vite-worker')
return new Promise<void>((resolve, reject) => {
channel.onmessage = (event) => {
try {
expect((event as MessageEvent).data).toEqual({
result: 'hello invoke world',
})
} catch (e) {
reject(e)
} finally {
resolve()
}
}
channel.postMessage({ id: 'virtual:invoke-default-string' })
})
})

it('triggers an error', async () => {
const channel = new BroadcastChannel('vite-worker')
return new Promise<void>((resolve, reject) => {
channel.onmessage = (event) => {
try {
expect((event as MessageEvent).data.error).toContain(
'Error: error, module not found: test_error',
)
} catch (e) {
reject(e)
} finally {
resolve()
}
}
channel.postMessage({ id: 'test_error' })
})
})

it('triggers an unknown error', async () => {
const channel = new BroadcastChannel('vite-worker')
return new Promise<void>((resolve, reject) => {
channel.onmessage = (event) => {
try {
expect((event as MessageEvent).data.error).toContain(
'Error: Unknown invoke error',
)
} catch (e) {
reject(e)
} finally {
resolve()
}
}
channel.postMessage({ id: 'test_invalid_error' })
})
})
})
Loading