diff --git a/src/module.ts b/src/module.ts index bc5c075c5..65a958641 100644 --- a/src/module.ts +++ b/src/module.ts @@ -45,7 +45,7 @@ export default defineNuxtModule({ name: '@nuxt/image', configKey: 'image', compatibility: { - nuxt: '^3.0.0-rc.4' + nuxt: '^3.1.0' } }, async setup (options, nuxt) { diff --git a/src/runtime/components/nuxt-img.ts b/src/runtime/components/nuxt-img.ts index 2e9f8da6f..9ae5677fa 100644 --- a/src/runtime/components/nuxt-img.ts +++ b/src/runtime/components/nuxt-img.ts @@ -1,9 +1,9 @@ import { h, defineComponent, ref, computed, onMounted } from 'vue' -import { appendHeader } from 'h3' import { useImage } from '../composables' import { parseSize } from '../utils' +import { prerenderStaticImages } from '../utils/prerender' import { baseImageProps, useBaseImage } from './_base' -import { useHead, useRequestEvent } from '#imports' +import { useHead } from '#imports' export const imgProps = { ...baseImageProps, @@ -87,12 +87,9 @@ export default defineComponent({ }) } + // Prerender static images if (process.server && process.env.prerender) { - const sources = [ - src.value, - ...(sizes.value.srcset || '').split(',').map(s => s.split(' ')[0]) - ].filter(s => s && s.includes('/_ipx/')) - appendHeader(useRequestEvent(), 'X-Nitro-Prerender', sources.join(',')) + prerenderStaticImages(src.value, sizes.value.srcset) } const imgEl = ref() diff --git a/src/runtime/components/nuxt-picture.ts b/src/runtime/components/nuxt-picture.ts index c6582e827..5da5df673 100644 --- a/src/runtime/components/nuxt-picture.ts +++ b/src/runtime/components/nuxt-picture.ts @@ -1,4 +1,5 @@ import { h, defineComponent, ref, computed, onMounted } from 'vue' +import { prerenderStaticImages } from '../utils/prerender' import { useBaseImage, baseImageProps } from './_base' import { useImage, useHead } from '#imports' import { getFileExtension } from '#image' @@ -32,9 +33,10 @@ export default defineComponent({ return formats[format.value] || originalFormat.value }) - const nSources = computed>(() => { + type Source = { srcset: string, src?: string, type?: string, sizes?: string } + const sources = computed(() => { if (format.value === 'svg') { - return [{ srcset: props.src }] + return [{ srcset: props.src }] } const formats = legacyFormat.value !== format.value @@ -42,22 +44,22 @@ export default defineComponent({ : [format.value] return formats.map((format) => { - const { srcset, sizes, src } = $img.getSizes(props.src, { + const { srcset, sizes, src } = $img.getSizes(props.src!, { ..._base.options.value, sizes: props.sizes || $img.options.screens, modifiers: { ..._base.modifiers.value, format } }) - return { src, type: `image/${format}`, sizes, srcset } + return { src, type: `image/${format}`, sizes, srcset } }) }) if (props.preload) { - const srcKey = nSources.value?.[1] ? 1 : 0 + const srcKey = sources.value?.[1] ? 1 : 0 - const link: any = { rel: 'preload', as: 'image', imagesrcset: nSources.value[srcKey].srcset } + const link: any = { rel: 'preload', as: 'image', imagesrcset: sources.value[srcKey].srcset } - if (nSources.value?.[srcKey]?.sizes) { link.imagesizes = nSources.value[srcKey].sizes } + if (sources.value?.[srcKey]?.sizes) { link.imagesizes = sources.value[srcKey].sizes } useHead({ link: [link] }) } @@ -72,27 +74,34 @@ export default defineComponent({ const imgEl = ref() + // Prerender static images + if (process.server && process.env.prerender) { + for (const src of sources.value as Source[]) { + prerenderStaticImages(src.src, src.srcset) + } + } + onMounted(() => { imgEl.value!.onload = (event) => { ctx.emit('load', event) } }) - return () => h('picture', { key: nSources.value[0].src }, [ - ...(nSources.value?.[1] + return () => h('picture', { key: sources.value[0].src }, [ + ...(sources.value?.[1] ? [h('source', { - type: nSources.value[1].type, - sizes: nSources.value[1].sizes, - srcset: nSources.value[1].srcset + type: sources.value[1].type, + sizes: sources.value[1].sizes, + srcset: sources.value[1].srcset })] : []), h('img', { ref: imgEl, ..._base.attrs.value, ...imgAttrs, - src: nSources.value[0].src, - sizes: nSources.value[0].sizes, - srcset: nSources.value[0].srcset + src: sources.value[0].src, + sizes: sources.value[0].sizes, + srcset: sources.value[0].srcset }) ]) } diff --git a/src/runtime/providers/ipx.ts b/src/runtime/providers/ipx.ts index f75852c3e..6d85a2666 100644 --- a/src/runtime/providers/ipx.ts +++ b/src/runtime/providers/ipx.ts @@ -12,7 +12,7 @@ const operationsGenerator = createOperationsGenerator({ quality: 'q', background: 'b' }, - joinWith: ',', + joinWith: '&', formatter: (key, val) => encodeParam(key) + '_' + encodeParam(val) }) diff --git a/src/runtime/utils/prerender.ts b/src/runtime/utils/prerender.ts new file mode 100644 index 000000000..43a47a1ae --- /dev/null +++ b/src/runtime/utils/prerender.ts @@ -0,0 +1,21 @@ +import { appendHeader } from 'h3' +import { useRequestEvent } from '#imports' + +export function prerenderStaticImages (src = '', srcset = '') { + if (!process.server || !process.env.prerender) { return } + + const paths = [ + src, + ...srcset.split(', ').map(s => s.trim().split(' ')[0].trim()) + ].filter(s => s && s.includes('/_ipx/')) + + if (!paths.length) { + return + } + + appendHeader( + useRequestEvent(), + 'x-nitro-prerender', + paths.map(p => encodeURIComponent(p)).join(', ') + ) +}