Skip to content

Commit aa88c3c

Browse files
onmaxharlan-zw
andauthored
fix: support Nitro 2 and 3 server runtimes (#844)
Co-authored-by: Harlan Wilton <harlan@harlanzw.com>
1 parent 1aea8bb commit aa88c3c

31 files changed

Lines changed: 321 additions & 46 deletions

‎.github/workflows/ci.yml‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,43 @@ jobs:
137137

138138
- name: Run tests
139139
run: pnpm vitest run --project typecheck --project unit --project e2e --project nuxt-runtime
140+
141+
nitro-3:
142+
runs-on: ubuntu-24.04-arm
143+
name: Nitro 3 compatibility
144+
steps:
145+
- name: Checkout
146+
uses: actions/checkout@v7
147+
with:
148+
persist-credentials: false
149+
150+
- name: Setup pnpm
151+
uses: pnpm/action-setup@v6
152+
153+
- name: Setup Node
154+
uses: actions/setup-node@v7
155+
with:
156+
node-version: lts/*
157+
cache: pnpm
158+
159+
- name: Install dependencies
160+
run: pnpm i
161+
162+
- name: Install Nuxt 5
163+
run: |
164+
pnpm add --workspace-root --save-dev "nuxt@npm:nuxt-nightly@5.0.0-29745766.482f3357" "@nuxt/kit@npm:@nuxt/kit-nightly@5.0.0-29745766.482f3357"
165+
pnpm --filter @nuxt/scripts add --save-dev "@nuxt/kit@npm:@nuxt/kit-nightly@5.0.0-29745766.482f3357"
166+
167+
- name: Dev prepare
168+
run: pnpm --filter @nuxt/scripts dev:prepare
169+
170+
- name: Nuxt prepare
171+
run: |
172+
pnpm exec nuxt prepare
173+
pnpm exec nuxt prepare test/fixtures/proxy-alias
174+
175+
- name: Typecheck Nitro 3 compatibility
176+
run: pnpm exec vue-tsc --noEmit -p test/fixtures/proxy-alias/.nuxt/tsconfig.server.json
177+
178+
- name: Run Nitro 3 compatibility tests
179+
run: pnpm vitest run --project e2e test/e2e/proxy-alias.test.ts

‎docs/content/scripts/google-recaptcha.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,10 @@ function onSubmit() {
201201
email: email.value,
202202
message: message.value
203203
}
204-
}).catch(() => null)
204+
}).catch((error) => {
205+
console.error('Failed to submit contact form', error)
206+
return null
207+
})
205208
206209
status.value = result ? 'success' : 'error'
207210
})

‎packages/devtools-app/components/DevtoolsTooltip.vue‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const sizes = {
3535
<UIcon name="i-heroicons-question-mark-circle" class="size-3 text-(--color-text-subtle) hover:text-(--color-text-muted) transition-colors" />
3636
<template #content>
3737
<divclass="devtools-tooltip-panel">
38-
<div:class="`w-max ${sizes[size || 'md']}`">
38+
<divclass="w-max":class="sizes[size || 'md']">
3939
<templatev-if="title">
4040
<divclass="font-semibold">{{ title }}</div>
4141
<divv-if="description"class="text-(--color-text-muted) text-xs">{{ description }}</div>
@@ -56,7 +56,7 @@ export const sizes = {
5656
<UIconv-else name="i-heroicons-question-mark-circle" color="primary" :size="iconSize||'md'" />
5757
<template #content>
5858
<divclass="devtools-tooltip-panel">
59-
<div:class="`w-max ${sizes[size || 'md']}`">
59+
<divclass="w-max":class="sizes[size || 'md']">
6060
<slotv-if="$slots.text"name="text" />
6161
<templatev-else-if="title">
6262
<divclass="font-semibold">

‎packages/devtools-app/components/UiTooltip.vue‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const sizes = {
3737
<UIcon name="i-carbon-help" class="size-3 text-(--color-text-subtle) hover:text-(--color-text-muted) transition-colors" />
3838
<template #content>
3939
<divclass="ui-tooltip-panel">
40-
<div:class="`w-max ${sizes[size || 'md']}`">
40+
<divclass="w-max":class="sizes[size || 'md']">
4141
<templatev-if="title">
4242
<divclass="font-semibold">
4343
{{ title }}
@@ -66,7 +66,7 @@ export const sizes = {
6666
<UIconv-else name="i-carbon-help" color="primary" :size="iconSize||'md'" />
6767
<template #content>
6868
<divclass="ui-tooltip-panel">
69-
<div:class="`w-max ${sizes[size || 'md']}`">
69+
<divclass="w-max":class="sizes[size || 'md']">
7070
<slotv-if="$slots.text"name="text" />
7171
<templatev-else-if="title">
7272
<divclass="font-semibold">

‎packages/script/src/kit.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ interface EnsurePackageInstalledOptions {
1111
}
1212

1313
asyncfunctionpromptToInstall(name: string,installCommand: ()=>Promise<void>,options: EnsurePackageInstalledOptions){
14-
if(awaitresolvePackageJSON(name).catch(()=>null))
14+
if(awaitresolvePackageJSON(name).catch(()=>{
15+
// A missing package is expected here; the install prompt handles it below.
16+
returnnull
17+
})){
1518
returntrue
19+
}
1620

1721
logger.info(`Package ${name} is missing`)
1822
if(isCI)

‎packages/script/src/module.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { setupPublicAssetStrategy } from './assets'
3535
import{buildDevtoolsData,buildDevtoolsEntry,setupDevtools}from'./devtools'
3636
import{installNuxtModule}from'./kit'
3737
import{logger}from'./logger'
38+
import{setupNitroRuntimeCompatibility}from'./nitro-compatibility'
3839
import{extractRequiredFields,migrateDeprecatedRegistryKeys,normalizeRegistryConfig}from'./normalize'
3940
import{NuxtScriptsCheckScripts}from'./plugins/check-scripts'
4041
import{generateInterceptPluginContents}from'./plugins/intercept'
@@ -514,6 +515,7 @@ export default defineNuxtModule<ModuleOptions>({
514515
logger.debug('The module is disabled, skipping setup.')
515516
return
516517
}
518+
awaitsetupNitroRuntimeCompatibility(nuxt)
517519
if(nuxt.options.dev){
518520
setupDevtools(nuxt,{standalone: config._standaloneDevtools})
519521
if(config._standaloneDevtools){
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
importtype{Nuxt}from'@nuxt/schema'
2+
import{existsSync}from'node:fs'
3+
import{pathToFileURL}from'node:url'
4+
import{addTypeTemplate,getNuxtVersion,resolvePathasresolveNuxtPath}from'@nuxt/kit'
5+
import{dirname}from'pathe'
6+
7+
typeNitroRuntimeCompatibility
8+
=|{_tag: 'nitro-v2'}
9+
|{
10+
_tag: 'nitro-v3'
11+
app: string
12+
cache: string
13+
h3: string
14+
runtimeConfig: string
15+
}
16+
17+
typeResolveNitroImport=(id: string)=>Promise<string>
18+
19+
interfaceNitroCompatibilityOptions{
20+
alias?: Record<string,string>
21+
virtual?: Record<string,string>
22+
}
23+
24+
interfaceNitroCompatibilityDependencies{
25+
addTypeTemplate: typeofaddTypeTemplate
26+
getNuxtVersion: typeofgetNuxtVersion
27+
resolveNitroImport?: ResolveNitroImport
28+
}
29+
30+
constNITRO_RUNTIME_MODULE='#nuxt-scripts/nitro'
31+
constH3_RUNTIME_MODULE='#nuxt-scripts/h3'
32+
constTYPE_TEMPLATE_FILENAME='types/nuxt-scripts-nitro.d.ts'
33+
constdefaultDependencies: NitroCompatibilityDependencies={
34+
addTypeTemplate,
35+
getNuxtVersion,
36+
}
37+
38+
constnitroV2Runtime=`export {
39+
defineCachedFunction,
40+
useNitroApp,
41+
useRuntimeConfig,
42+
} from 'nitropack/runtime'
43+
`
44+
45+
constnitroV3RuntimeTypes=`export { useNitroApp } from 'nitro/app'
46+
export { defineCachedFunction } from 'nitro/cache'
47+
export function useRuntimeConfig(event?: import('nitro/h3').H3Event): ReturnType<typeof import('nitro/runtime-config').useRuntimeConfig>
48+
`
49+
50+
functionindent(value: string,spaces: number): string{
51+
constpadding=' '.repeat(spaces)
52+
returnvalue.split('\n').map(line=>`${padding}${line}`).join('\n')
53+
}
54+
55+
functionrenderRuntimeDeclarations(compatibility: NitroRuntimeCompatibility): string{
56+
constnitroRuntime=compatibility._tag==='nitro-v3' ? nitroV3RuntimeTypes : nitroV2Runtime
57+
consth3Runtime=compatibility._tag==='nitro-v3'
58+
? `export * from 'nitro/h3'\n`
59+
: `export * from 'h3'\n`
60+
61+
return`declare module '${NITRO_RUNTIME_MODULE}' {
62+
${indent(nitroRuntime.trim(),2)}
63+
}
64+
65+
declare module '${H3_RUNTIME_MODULE}' {
66+
${indent(h3Runtime.trim(),2)}
67+
}
68+
`
69+
}
70+
71+
functionrenderNitroV3Runtime(compatibility: Extract<NitroRuntimeCompatibility,{_tag: 'nitro-v3'}>): string{
72+
return`export { useNitroApp } from ${JSON.stringify(compatibility.app)}
73+
export { defineCachedFunction } from ${JSON.stringify(compatibility.cache)}
74+
import { useRuntimeConfig as _useRuntimeConfig } from ${JSON.stringify(compatibility.runtimeConfig)}
75+
export function useRuntimeConfig(_event) { return _useRuntimeConfig() }
76+
`
77+
}
78+
79+
functionapplyNitroRuntimeCompatibility(nuxt: Nuxt,compatibility: NitroRuntimeCompatibility): void{
80+
constnuxtOptions=nuxt.optionsasNuxt['options']&{nitro?: NitroCompatibilityOptions}
81+
constnitroOptions=nuxtOptions.nitro||={}
82+
nitroOptions.alias||={}
83+
nitroOptions.virtual||={}
84+
nitroOptions.alias[H3_RUNTIME_MODULE]=compatibility._tag==='nitro-v3' ? compatibility.h3 : 'h3'
85+
nitroOptions.virtual[NITRO_RUNTIME_MODULE]=compatibility._tag==='nitro-v3'
86+
? renderNitroV3Runtime(compatibility)
87+
: nitroV2Runtime
88+
}
89+
90+
asyncfunctioncreateNuxtNitroImportResolver(): Promise<ResolveNitroImport>{
91+
constnuxtDir=dirname(awaitresolveNuxtPath('nuxt/package.json'))
92+
constnitroDir=dirname(awaitresolveNuxtPath('@nuxt/nitro-server/package.json',{cwd: nuxtDir}))
93+
94+
returnasync(id: string)=>{
95+
constresolved=awaitresolveNuxtPath(id,{cwd: nitroDir})
96+
if(!existsSync(resolved))
97+
thrownewError(`[nuxt-scripts] Could not resolve Nitro runtime helper "${id}" from "${nitroDir}".`)
98+
returnpathToFileURL(resolved).href
99+
}
100+
}
101+
102+
asyncfunctionresolveNitroV3Compatibility(resolveNitroImport: ResolveNitroImport): Promise<NitroRuntimeCompatibility>{
103+
const[app,cache,h3,runtimeConfig]=awaitPromise.all([
104+
resolveNitroImport('nitro/app'),
105+
resolveNitroImport('nitro/cache'),
106+
resolveNitroImport('nitro/h3'),
107+
resolveNitroImport('nitro/runtime-config'),
108+
])
109+
110+
return{_tag: 'nitro-v3', app, cache, h3, runtimeConfig }
111+
}
112+
113+
exportasyncfunctionsetupNitroRuntimeCompatibility(
114+
nuxt: Nuxt,
115+
dependencies: NitroCompatibilityDependencies=defaultDependencies,
116+
): Promise<void>{
117+
constcompatibility: NitroRuntimeCompatibility=Number.parseInt(dependencies.getNuxtVersion(nuxt),10)>=5
118+
? awaitresolveNitroV3Compatibility(dependencies.resolveNitroImport||awaitcreateNuxtNitroImportResolver())
119+
: {_tag: 'nitro-v2'}
120+
121+
applyNitroRuntimeCompatibility(nuxt,compatibility)
122+
nuxt.hooks.hookOnce('modules:done',()=>applyNitroRuntimeCompatibility(nuxt,compatibility))
123+
dependencies.addTypeTemplate({
124+
filename: TYPE_TEMPLATE_FILENAME,
125+
getContents: async()=>renderRuntimeDeclarations(compatibility),
126+
},{nitro: true,node: true,nuxt: true})
127+
}

‎packages/script/src/runtime/server/bluesky-embed.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import{createError,defineEventHandler,getQuery,setHeader}from'h3'
2-
import{useRuntimeConfig}from'nitropack/runtime'
1+
import{createError,defineEventHandler,getQuery,setHeader}from'#nuxt-scripts/h3'
2+
import{useRuntimeConfig}from'#nuxt-scripts/nitro'
33
import{createCachedJsonFetch}from'./utils/cached-upstream'
44
import{rewriteBlueskyPostImages}from'./utils/embed-rewriters'
55
import{withSigning}from'./utils/withSigning'
@@ -115,7 +115,7 @@ export default withSigning(defineEventHandler(async (event) => {
115115
consthandlerPath=event.path?.split('?')[0]||''
116116
constprefix=handlerPath.replace(EMBED_BSKY_SUFFIX_RE,'')||'/_scripts'
117117
constimagePath=`${prefix}/embed/bluesky-image`
118-
constsecret=(useRuntimeConfig(event)['nuxt-scripts']as{proxySecret?: string}|undefined)?.proxySecret
118+
constsecret=(useRuntimeConfig()['nuxt-scripts']as{proxySecret?: string}|undefined)?.proxySecret
119119
rewriteBlueskyPostImages(post,imagePath,secret)
120120

121121
// Cache for 10 minutes

‎packages/script/src/runtime/server/google-maps-geocode-proxy.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import{createError,defineEventHandler,getQuery,setHeader}from'h3'
2-
import{useRuntimeConfig}from'nitropack/runtime'
31
import{withQuery}from'ufo'
2+
import{createError,defineEventHandler,getQuery,setHeader}from'#nuxt-scripts/h3'
3+
import{useRuntimeConfig}from'#nuxt-scripts/nitro'
44
import{createCachedJsonFetch}from'./utils/cached-upstream'
55
import{withSigning}from'./utils/withSigning'
66

‎packages/script/src/runtime/server/google-static-maps-proxy.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import{createError,defineEventHandler,getQuery,setHeader}from'h3'
2-
import{useRuntimeConfig}from'nitropack/runtime'
31
import{withQuery}from'ufo'
2+
import{createError,defineEventHandler,getQuery,setHeader}from'#nuxt-scripts/h3'
3+
import{useRuntimeConfig}from'#nuxt-scripts/nitro'
44
import{createCachedBinaryFetch}from'./utils/cached-upstream'
55
import{PAGE_TOKEN_PARAM,PAGE_TOKEN_TS_PARAM,SIG_PARAM}from'./utils/sign-constants'
66
import{withSigning}from'./utils/withSigning'

0 commit comments

Comments
 (0)