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

[WIP] feat: add setRuntimeConfig module utility using IPC signal #670

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
38 changes: 36 additions & 2 deletions examples/module/test/basic.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,49 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { $fetch, 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</div>')
expect(html).toContain('<span id="runtime">original value</span></div>')
})

it('changes runtime config client-side', async () => {
const browser = await getBrowser()
const page = await browser.newPage()
await page.goto(url('/'))

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 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 () => {
expect(await $fetch('/')).toContain('<span id="runtime">original value</span></div>')

await setRuntimeConfig({ public: { myValue: 'overwritten by test!' } }, { restart: true })

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

<script setup>
const config = useRuntimeConfig()
</script>
5 changes: 5 additions & 0 deletions examples/module/test/fixtures/basic/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import MyModule from '../../../src/module'

export default defineNuxtConfig({
runtimeConfig: {
public: {
myValue: 'original value',
},
},
modules: [
MyModule
]
Expand Down
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
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"pathe": "^1.1.1",
"perfect-debounce": "^1.0.0",
"radix3": "^1.1.0",
"scule": "^1.1.1",
"std-env": "^3.6.0",
"ufo": "^1.3.2",
"unenv": "^1.8.0",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 67 additions & 0 deletions src/core/runtime-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { snakeCase } from 'scule'
import { useTestContext } from './context'
import { startServer } from './server'

export function flattenObject(obj: Record<string, unknown> = {}) {
const flattened: Record<string, unknown> = {}

for (const key in obj) {
if (!(key in obj)) continue

const entry = obj[key]
if (typeof entry !== 'object' || entry == null) {
flattened[key] = obj[key]
continue
}
const flatObject = flattenObject(entry as Record<string, unknown>)

for (const x in flatObject) {
if (!(x in flatObject)) continue

flattened[key + '_' + x] = flatObject[x]
}
}

return flattened
}

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
}

type SetRuntimeConfigOptions = { envPrefix?: string; restart?: boolean }
export async function setRuntimeConfig(config: Record<string, unknown>, options: SetRuntimeConfigOptions = {}) {
const env = convertObjectToConfig(config, options.envPrefix ?? 'NUXT_')
const ctx = useTestContext()

if (options.restart) {
return await startServer({ env })
}

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

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!')
}
}
20 changes: 12 additions & 8 deletions 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 All @@ -10,7 +10,11 @@ import { useTestContext } from './context'
// eslint-disable-next-line
const kit: typeof _kit = _kit.default || _kit

export async function startServer () {
export interface StartServerOptions {
env?: Record<string, unknown>
}

export async function startServer (options: StartServerOptions = {}) {
const ctx = useTestContext()
await stopServer()
const host = '127.0.0.1'
Expand All @@ -26,7 +30,8 @@ export async function startServer () {
_PORT: String(port), // Used by internal _dev command
PORT: String(port),
HOST: host,
NODE_ENV: 'development'
NODE_ENV: 'development',
...options.env
}
})
await waitForPort(port, { retries: 32, host }).catch(() => {})
Expand All @@ -45,16 +50,15 @@ export async function startServer () {
ctx.serverProcess.kill()
throw lastError || new Error('Timeout waiting for dev server!')
} else {
ctx.serverProcess = execa('node', [
resolve(ctx.nuxt!.options.nitro.output!.dir!, 'server/index.mjs')
], {
ctx.serverProcess = execaNode(resolve(ctx.nuxt!.options.nitro.output!.dir!, 'server/index.mjs'), {
stdio: 'inherit',
env: {
...process.env,
PORT: String(port),
HOST: host,
NODE_ENV: 'test'
}
NODE_ENV: 'test',
...options.env,
},
})
await waitForPort(port, { retries: 20, host })
}
Expand Down
1 change: 1 addition & 0 deletions src/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export * from './core/nuxt'
export * from './core/server'
export * from './core/setup/index'
export * from './core/run'
export { setRuntimeConfig } from './core/runtime-config'
export * from './core/types'
Loading