Open hannoeru opened 1 week ago
Hey @hannoeru! 👋 I'm here to help you with bug fixes, questions, and becoming a contributor. Let's squash those bugs together!
Yes, the feature to enforce NuxtHub build to Cloudflare while test-utils refer to the node build location can be implemented as a module. You can use the defineNuxtModule
function from the @nuxt/kit
package to create a custom module that configures the build process and test-utils settings accordingly.
Here is an example of how you might implement such a module:
import { defineNuxtModule, addVitePlugin, createResolver, resolvePath } from '@nuxt/kit'
import { mergeConfig } from 'vite'
import { defu } from 'defu'
import { getVitestConfigFromNuxt } from './config'
import { setupImportMocking } from './module/mock'
import { NuxtRootStubPlugin } from './module/plugins/entry'
export interface CustomModuleOptions {
cloudflareBuild?: boolean
vitestConfig?: VitestConfig
}
export default defineNuxtModule<CustomModuleOptions>({
meta: {
name: 'custom-module',
configKey: 'customModule',
},
defaults: {
cloudflareBuild: false,
},
async setup(options, nuxt) {
if (options.cloudflareBuild) {
// Enforce NuxtHub build to Cloudflare
nuxt.options.target = 'static'
nuxt.options.generate = {
fallback: true,
dir: 'dist',
}
}
if (nuxt.options.test || nuxt.options.dev) {
setupImportMocking()
}
const resolver = createResolver(import.meta.url)
addVitePlugin(NuxtRootStubPlugin.vite({
entry: await resolvePath('#app/entry', { alias: nuxt.options.alias }),
rootStubPath: await resolvePath(resolver.resolve('./runtime/nuxt-root')),
}))
if (!nuxt.options.test && !nuxt.options.dev) {
nuxt.options.vite.define ||= {}
nuxt.options.vite.define['import.meta.vitest'] = 'undefined'
}
nuxt.hook('prepare:types', ({ references }) => {
references.push({ types: 'vitest/import-meta' })
})
if (!nuxt.options.dev) return
if (process.env.TEST || process.env.VITE_TEST) return
const rawViteConfigPromise = new Promise<ViteConfig>((resolve) => {
nuxt.hook('app:resolve', () => {
nuxt.hook('vite:configResolved', (config, { isClient }) => {
if (isClient) resolve(config)
})
})
})
async function start() {
const rawViteConfig = mergeConfig({}, await rawViteConfigPromise)
const viteConfig = await getVitestConfigFromNuxt({ nuxt, viteConfig: defu({ test: options.vitestConfig }, rawViteConfig) })
viteConfig.plugins = (viteConfig.plugins || []).filter((p) => {
return !p || !('name' in p) || !vitePluginBlocklist.includes(p.name)
})
process.env.__NUXT_VITEST_RESOLVED__ = 'true'
const { startVitest } = (await import(pathToFileURL(await resolvePath('vitest/node')).href)) as typeof import('vitest/node')
const customReporter: Reporter = {
onInit(_ctx) {
ctx = _ctx
},
onTaskUpdate() {
testFiles = ctx.state.getFiles()
updateTabs()
},
onFinished() {
testFiles = ctx.state.getFiles()
updateTabs()
},
}
const watchMode = !process.env.NUXT_VITEST_DEV_TEST && !isCI
const PORT = await getPort({ port: 15555 })
const PROTOCOL = nuxt.options.devServer.https ? 'https' : 'http'
const URL = `${PROTOCOL}://localhost:${PORT}/__vitest__/`
const overrides: VitestConfig = watchMode
? {
passWithNoTests: true,
reporters: options.logToConsole
? [
...toArray(options.vitestConfig?.reporters ?? ['default']),
customReporter,
]
: [customReporter],
watch: true,
ui: true,
open: false,
api: {
port: PORT,
},
}
: { watch: false }
const promise = startVitest('test', [], defu(overrides, viteConfig.test), viteConfig)
promise.catch(() => process.exit(1))
if (watchMode) {
logger.info(`Vitest UI starting on ${URL}`)
nuxt.hook('close', () => promise.then(v => v?.close()))
await new Promise(resolve => setTimeout(resolve, 1000))
} else {
promise.then(v => nuxt.close().then(() => v?.close()).then(() => process.exit()))
}
loaded = true
}
nuxt.hook('devtools:customTabs', (tabs) => {
const failedCount = testFiles?.filter(f => f.result?.state === 'fail').length ?? 0
const passedCount = testFiles?.filter(f => f.result?.state === 'pass').length ?? 0
const totalCount = testFiles?.length ?? 0
tabs.push({
title: 'Vitest',
name: 'vitest',
icon: 'logos-vitest',
view: loaded
? {
type: 'iframe',
src: URL,
}
: {
type: 'launch',
description: 'Start tests along with Nuxt',
actions: [
{
label: promise ? 'Starting...' : 'Start Vitest',
pending: !!promise,
handle: () => {
promise = promise || start()
return promise
},
},
],
},
extraTabVNode: totalCount
? h('div', { style: { color: failedCount ? 'orange' : 'green' } }, [
h('span', {}, passedCount),
h('span', { style: { opacity: '0.5', fontSize: '0.9em' } }, '/'),
h('span', { style: { opacity: '0.8', fontSize: '0.9em' } }, totalCount),
])
: undefined,
})
})
if (options.startOnBoot) {
promise = promise || start()
promise.then(updateTabs)
}
},
})
function toArray<T>(value: T | T[]): T[] {
return Array.isArray(value) ? value : [value]
}
This module sets up the build process for Cloudflare if the cloudflareBuild
option is enabled and configures the test-utils to refer to the node build location. The getVitestConfigFromNuxt
function from the test-utils
package is used to obtain the Vitest configuration tailored for Nuxt, ensuring that the test environment is correctly set up [1][2].
To continue talking to Dosu, mention @dosu.
@atinux Can you help me to point out more details about this issue? Thanks!
Describe the feature
NuxtHub enforce build to cloudflare but test-utils only refer to node build location, so if I use setup() with build: true it will resolve to error, for the workaround now is using host that point to the dev server that can handle NuxtHub related features.
Additional information
Final checks