Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"playground": {
"entry": [
"{components,layouts,middleware,pages,plugins,server,store}/**",
"{components,composables,layouts,middleware,pages,plugins,server,store}/**",
"*.ts"
]
}
Expand Down
16 changes: 16 additions & 0 deletions packages/bridge/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,22 @@ export async function setupAppBridge (_options: any) {

addTemplate(globalMiddlewareTemplate)

// Expose `$fetch` as a module export so that it can be imported regardless of plugin order
addTemplate({
filename: 'fetch.mjs',
getContents: () => [
'import { $fetch as _$fetch } from \'ofetch\'',
'if (!globalThis.$fetch) {',
' globalThis.$fetch = _$fetch',
'}',
'export const $fetch = globalThis.$fetch'
].join('\n')
})
addTemplate({
filename: 'fetch.d.ts',
getContents: () => 'export declare const $fetch: typeof globalThis.$fetch\n'
})

addWebpackPlugin(new ProtocolImportPlugin())

// Alias vue3 utilities to vue2
Expand Down
4 changes: 4 additions & 0 deletions packages/bridge/src/imports/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ const granularAppPresets: InlinePreset[] = [
imports: ['useLazyFetch'],
from: '#app/composables/fetch'
},
{
imports: ['$fetch'],
from: '#build/fetch.mjs'
},
{
imports: ['useCookie'],
from: '#app/composables/cookie'
Expand Down
10 changes: 7 additions & 3 deletions packages/bridge/src/runtime/composables/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import type { FetchOptions, FetchError } from 'ofetch'
import type { NitroFetchRequest, TypedInternalResponse, AvailableRouterMethod as _AvailableRouterMethod } from 'nitropack'
import type { $Fetch, NitroFetchRequest, TypedInternalResponse, AvailableRouterMethod as _AvailableRouterMethod } from 'nitropack'
import { hash } from 'ohash'
import { computed, unref, reactive } from 'vue'
import type { Ref } from 'vue'
import type { AsyncData, AsyncDataOptions, KeysOf, MultiWatchSources, PickFrom } from './asyncData'
import { useAsyncData } from './asyncData'
import { useRequestFetch } from './ssr'
// @ts-expect-error virtual file
import { $fetch as _$fetch } from '#build/fetch.mjs'

const $fetch: $Fetch = _$fetch

// support uppercase methods, detail: https://github.com/nuxt/nuxt/issues/22313
type AvailableRouterMethod<R extends NitroFetchRequest> = _AvailableRouterMethod<R> | Uppercase<_AvailableRouterMethod<R>>
Expand All @@ -31,7 +35,7 @@ export interface UseFetchOptions<
M extends AvailableRouterMethod<R> = AvailableRouterMethod<R>
> extends Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'watch'>, ComputedFetchOptions<R, M> {
key?: string
$fetch?: typeof globalThis.$fetch
$fetch?: $Fetch
watch?: MultiWatchSources | false
}

Expand Down Expand Up @@ -150,7 +154,7 @@ export function useFetch<
controller.signal.onabort = () => clearTimeout(timeoutId)
}

let _$fetch = opts.$fetch || globalThis.$fetch
let _$fetch = opts.$fetch || $fetch

// Use fetch with request context and headers for server direct API calls
if (process.server && !opts.$fetch) {
Expand Down
12 changes: 8 additions & 4 deletions packages/bridge/src/runtime/composables/ssr.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import type { H3Event } from 'h3'
import type { NuxtAppCompat } from '@nuxt/bridge-schema'
import { getRequestHeaders, createEvent } from 'h3'
import type { H3Event$Fetch } from 'nitropack'
import type { $Fetch, H3Event$Fetch } from 'nitropack'
import { useNuxtApp } from '../nuxt'
// @ts-expect-error virtual file
import { $fetch as _$fetch } from '#build/fetch.mjs'

const $fetch: $Fetch = _$fetch

export function useRequestHeaders<K extends string = string> (include: K[]): { [key in Lowercase<K>]?: string }
export function useRequestHeaders (): Readonly<Record<string, string>>
Expand All @@ -27,9 +31,9 @@ export function useRequestEvent (nuxtApp: NuxtAppCompat = useNuxtApp()): H3Event
return nuxtApp.ssrContext._event
}

export function useRequestFetch (): H3Event$Fetch | typeof global.$fetch {
export function useRequestFetch (): H3Event$Fetch | $Fetch {
if (process.client) {
return globalThis.$fetch
return $fetch
}
return useRequestEvent()?.$fetch || globalThis.$fetch
return useRequestEvent()?.$fetch || $fetch
}
7 changes: 2 additions & 5 deletions packages/bridge/src/runtime/nitro-bridge.client.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { $fetch } from 'ofetch'

if (!globalThis.$fetch) {
globalThis.$fetch = $fetch
}
// Ensure `globalThis.$fetch` is set for code that does not import `$fetch` explicitly
import '#build/fetch.mjs'

export default () => {}
2 changes: 2 additions & 0 deletions packages/bridge/test/mocks/fetch.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Mock for `#build/fetch.mjs`, which is generated by `setupAppBridge`
export { $fetch } from 'ofetch'
2 changes: 2 additions & 0 deletions playground/composables/global-fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// `$fetch` is used at the top level of a module imported by a layout, which is evaluated before plugins
export const customFetch = $fetch.create({})
4 changes: 4 additions & 0 deletions playground/layouts/custom.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
const count = ref(0)
const customFetchType = typeof customFetch

</script>

Expand All @@ -14,5 +15,8 @@ const count = ref(0)
<button class="add-count" @click="count++">
add count
</button>
<div class="custom-fetch">
customFetch: {{ customFetchType }}
</div>
</div>
</template>
11 changes: 11 additions & 0 deletions test/bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,17 @@ describe('layouts', () => {
expect(html).toContain('with-layout.vue')
expect(html).toContain('Custom Layout:')
})

it('should hydrate when a layout uses `$fetch` at the top level of a module', async () => {
const { page, pageErrors } = await renderPage('/with-layout')

expect(pageErrors).toEqual([])
expect(await page.locator('.custom-fetch').textContent()).toContain('customFetch: function')
await page.locator('.add-count').click()
await page.waitForFunction(() => document.querySelector('.count')?.textContent?.trim() === '1')

await page.close()
})
})

describe('nitro plugins', () => {
Expand Down
9 changes: 8 additions & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import { fileURLToPath } from 'url'
import { defineConfig } from 'vitest/config'

export default defineConfig({})
export default defineConfig({
resolve: {
alias: {
'#build/fetch.mjs': fileURLToPath(new URL('./packages/bridge/test/mocks/fetch.mjs', import.meta.url))
}
}
})
Loading