Skip to content

Commit

Permalink
feat: change runtime config using ipc signal
Browse files Browse the repository at this point in the history
  • Loading branch information
BobbieGoede committed Dec 21, 2023
1 parent 235755b commit 49383f1
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 18 deletions.
40 changes: 31 additions & 9 deletions examples/module/test/basic.test.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,48 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { $fetch, setRuntimeConfig, setup } from '@nuxt/test-utils/e2e'
import { $fetch, getBrowser, setRuntimeConfig, setup, url } from '@nuxt/test-utils/e2e'

describe('ssr', async () => {
await setup({
rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)),
browser: true,
})

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

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

const html = await $fetch('/')
expect(html).toContain('<div>basic <span>overwritten by test!</span></div>')
const el = page.locator('#runtime')
expect(await el.innerText()).to.equal('original value')

await page.evaluate(() => {
window.__NUXT_TEST_RUNTIME_CONFIG_SETTER__({ public: { myValue: 'overwritten by test!' } })
})

expect(await el.innerText()).to.equal('overwritten by test!')
})

it('changes runtime config in server route', async () => {
const originalConfig = await $fetch('/api/config')
expect(originalConfig.public.myValue).to.equal('original value')

await restoreConfig()
const htmlRestored = await $fetch('/')
expect(htmlRestored).toContain('<div>basic <span>original value</span></div>')
await setRuntimeConfig({ public: { myValue: 'overwritten by test!' } })

const newConfig = await $fetch('/api/config')
expect(newConfig.public.myValue).to.equal('overwritten by test!')
})

it('changes runtime config', async () => {
await setRuntimeConfig({ public: { myValue: 'overwritten by test!' } })

const html = await $fetch('/')
expect(html).toContain('<span id="runtime">overwritten by test!</span></div>')
})
})
6 changes: 4 additions & 2 deletions examples/module/test/fixtures/basic/app.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<template>
<div>basic <span>{{ config.public.myValue }}</span></div>
<div>
basic <span id="runtime">{{ config.public.myValue }}</span>
</div>
</template>

<script setup>
const config = useRuntimeConfig();
const config = useRuntimeConfig()
</script>
30 changes: 30 additions & 0 deletions examples/module/test/fixtures/basic/plugins/ipc-listener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import defu from 'defu'
import { defineNuxtPlugin } from 'nuxt/app'

declare global {
interface Window {
__NUXT_TEST_RUNTIME_CONFIG_SETTER__: (env: { public: Record<string, unknown> }) => void
}
}

export default defineNuxtPlugin(() => {
const config = useRuntimeConfig()

if (process.client) {
window.__NUXT_TEST_RUNTIME_CONFIG_SETTER__ ??= (env: { public: Record<string, unknown> }) => {
config.public = defu(env.public, config.public)
}
}

if (process.server) {
process.on('message', (msg: { type: string; value: Record<string, string> }) => {
if (msg.type === 'update:runtime-config') {
for (const [key, value] of Object.entries(msg.value)) {
process.env[key] = value
}

process!.send!({ type: 'confirm:runtime-config' })
}
})
}
})
4 changes: 4 additions & 0 deletions examples/module/test/fixtures/basic/server/api/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig(event)
return config
})
28 changes: 22 additions & 6 deletions src/core/runtime-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { snakeCase } from 'scule'
import { startServer } from './server'
import { useTestContext } from './context'

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

export function convertObjectToConfig(obj: Record<string, unknown>, envPrefix: string) {
const makeEnvKey = (str: string) => `${envPrefix}${snakeCase(str).toUpperCase()}`

const env: Record<string, unknown> = {}
const flattened = flattenObject(obj)
for (const key in flattened) {
env[makeEnvKey(key)] = flattened[key]
}

return env
}

export async function setRuntimeConfig(config: Record<string, unknown>, envPrefix = 'NUXT_') {
const env = convertObjectToConfig(config, envPrefix)
await startServer({ env })
const ctx = useTestContext()

let updatedConfig = false
ctx.serverProcess?.once('message', (msg: { type: string }) => {
if (msg.type === 'confirm:runtime-config') {
updatedConfig = true
}
})

// restore
return async () => startServer()
ctx.serverProcess?.send({ type: 'update:runtime-config', value: env })

// Wait for confirmation to ensure
for (let i = 0; i < 10; i++) {
if (updatedConfig) break
await new Promise((resolve) => setTimeout(resolve, 1000))
}

if (!updatedConfig) {
throw new Error('Missing confirmation of runtime config update!')
}
}
2 changes: 1 addition & 1 deletion src/core/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { execa } from 'execa'
import { execa, execaNode } from 'execa'
import { getRandomPort, waitForPort } from 'get-port-please'
import type { FetchOptions } from 'ofetch'
import { $fetch as _$fetch, fetch as _fetch } from 'ofetch'
Expand Down

0 comments on commit 49383f1

Please sign in to comment.