Skip to content

Commit 49383f1

Browse files
committed
feat: change runtime config using ipc signal
1 parent 235755b commit 49383f1

File tree

6 files changed

+92
-18
lines changed

6 files changed

+92
-18
lines changed

examples/module/test/basic.test.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,48 @@
11
import { fileURLToPath } from 'node:url'
22
import { describe, expect, it } from 'vitest'
3-
import { $fetch, setRuntimeConfig, setup } from '@nuxt/test-utils/e2e'
3+
import { $fetch, getBrowser, setRuntimeConfig, setup, url } from '@nuxt/test-utils/e2e'
44

55
describe('ssr', async () => {
66
await setup({
77
rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)),
8+
browser: true,
89
})
910

1011
it('renders the index page', async () => {
1112
// Get response to a server-rendered page with `$fetch`.
1213
const html = await $fetch('/')
13-
expect(html).toContain('<div>basic <span>original value</span></div>')
14+
expect(html).toContain('<span id="runtime">original value</span></div>')
1415
})
1516

16-
it('changes runtime config and restarts', async () => {
17-
const restoreConfig = await setRuntimeConfig({ public: { myValue: 'overwritten by test!' } })
17+
it('changes runtime config client-side', async () => {
18+
const browser = await getBrowser()
19+
const page = await browser.newPage()
20+
await page.goto(url('/'))
1821

19-
const html = await $fetch('/')
20-
expect(html).toContain('<div>basic <span>overwritten by test!</span></div>')
22+
const el = page.locator('#runtime')
23+
expect(await el.innerText()).to.equal('original value')
24+
25+
await page.evaluate(() => {
26+
window.__NUXT_TEST_RUNTIME_CONFIG_SETTER__({ public: { myValue: 'overwritten by test!' } })
27+
})
28+
29+
expect(await el.innerText()).to.equal('overwritten by test!')
30+
})
31+
32+
it('changes runtime config in server route', async () => {
33+
const originalConfig = await $fetch('/api/config')
34+
expect(originalConfig.public.myValue).to.equal('original value')
2135

22-
await restoreConfig()
23-
const htmlRestored = await $fetch('/')
24-
expect(htmlRestored).toContain('<div>basic <span>original value</span></div>')
36+
await setRuntimeConfig({ public: { myValue: 'overwritten by test!' } })
37+
38+
const newConfig = await $fetch('/api/config')
39+
expect(newConfig.public.myValue).to.equal('overwritten by test!')
40+
})
41+
42+
it('changes runtime config', async () => {
43+
await setRuntimeConfig({ public: { myValue: 'overwritten by test!' } })
44+
45+
const html = await $fetch('/')
46+
expect(html).toContain('<span id="runtime">overwritten by test!</span></div>')
2547
})
2648
})
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<template>
2-
<div>basic <span>{{ config.public.myValue }}</span></div>
2+
<div>
3+
basic <span id="runtime">{{ config.public.myValue }}</span>
4+
</div>
35
</template>
46

57
<script setup>
6-
const config = useRuntimeConfig();
8+
const config = useRuntimeConfig()
79
</script>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import defu from 'defu'
2+
import { defineNuxtPlugin } from 'nuxt/app'
3+
4+
declare global {
5+
interface Window {
6+
__NUXT_TEST_RUNTIME_CONFIG_SETTER__: (env: { public: Record<string, unknown> }) => void
7+
}
8+
}
9+
10+
export default defineNuxtPlugin(() => {
11+
const config = useRuntimeConfig()
12+
13+
if (process.client) {
14+
window.__NUXT_TEST_RUNTIME_CONFIG_SETTER__ ??= (env: { public: Record<string, unknown> }) => {
15+
config.public = defu(env.public, config.public)
16+
}
17+
}
18+
19+
if (process.server) {
20+
process.on('message', (msg: { type: string; value: Record<string, string> }) => {
21+
if (msg.type === 'update:runtime-config') {
22+
for (const [key, value] of Object.entries(msg.value)) {
23+
process.env[key] = value
24+
}
25+
26+
process!.send!({ type: 'confirm:runtime-config' })
27+
}
28+
})
29+
}
30+
})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default defineEventHandler(async (event) => {
2+
const config = useRuntimeConfig(event)
3+
return config
4+
})

src/core/runtime-config.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { snakeCase } from 'scule'
2-
import { startServer } from './server'
2+
import { useTestContext } from './context'
33

44
export function flattenObject(obj: Record<string, unknown> = {}) {
55
const flattened: Record<string, unknown> = {}
@@ -26,20 +26,36 @@ export function flattenObject(obj: Record<string, unknown> = {}) {
2626

2727
export function convertObjectToConfig(obj: Record<string, unknown>, envPrefix: string) {
2828
const makeEnvKey = (str: string) => `${envPrefix}${snakeCase(str).toUpperCase()}`
29-
29+
3030
const env: Record<string, unknown> = {}
3131
const flattened = flattenObject(obj)
3232
for (const key in flattened) {
3333
env[makeEnvKey(key)] = flattened[key]
3434
}
35-
35+
3636
return env
3737
}
3838

3939
export async function setRuntimeConfig(config: Record<string, unknown>, envPrefix = 'NUXT_') {
4040
const env = convertObjectToConfig(config, envPrefix)
41-
await startServer({ env })
41+
const ctx = useTestContext()
42+
43+
let updatedConfig = false
44+
ctx.serverProcess?.once('message', (msg: { type: string }) => {
45+
if (msg.type === 'confirm:runtime-config') {
46+
updatedConfig = true
47+
}
48+
})
4249

43-
// restore
44-
return async () => startServer()
50+
ctx.serverProcess?.send({ type: 'update:runtime-config', value: env })
51+
52+
// Wait for confirmation to ensure
53+
for (let i = 0; i < 10; i++) {
54+
if (updatedConfig) break
55+
await new Promise((resolve) => setTimeout(resolve, 1000))
56+
}
57+
58+
if (!updatedConfig) {
59+
throw new Error('Missing confirmation of runtime config update!')
60+
}
4561
}

src/core/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { execa } from 'execa'
1+
import { execa, execaNode } from 'execa'
22
import { getRandomPort, waitForPort } from 'get-port-please'
33
import type { FetchOptions } from 'ofetch'
44
import { $fetch as _$fetch, fetch as _fetch } from 'ofetch'

0 commit comments

Comments
 (0)