Skip to content

Commit

Permalink
feat: migrate to composition api (nuxt#571)
Browse files Browse the repository at this point in the history
Co-authored-by: Pooya Parsa <[email protected]>
  • Loading branch information
Diizzayy and pi0 committed Sep 3, 2022
1 parent c823860 commit 6f2a430
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 301 deletions.
25 changes: 25 additions & 0 deletions playground/pages/bg.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<template>
<div class="preview" />
</template>

<script lang="ts" setup>
const $img = useImage()
const bg = computed(() => {
const imgUrl = $img('/images/colors.jpg', {
width: 300,
height: 300,
quality: 80
})
return `url('${imgUrl}')`
})
</script>

<style>
.preview {
width: 300px;
height: 300px;
background-image: v-bind(bg);
}
</style>
11 changes: 8 additions & 3 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { withLeadingSlash } from 'ufo'
import { defineNuxtModule, addTemplate, createResolver, addComponent, addPlugin } from '@nuxt/kit'
import { defineNuxtModule, addTemplate, addAutoImport, createResolver, addComponent, addPlugin } from '@nuxt/kit'
import { resolveProviders, detectProvider } from './provider'
import type { ImageProviders, ImageOptions, InputProvider, CreateImageOptions } from './types'

Expand Down Expand Up @@ -85,14 +85,19 @@ export default defineNuxtModule<ModuleOptions>({
nuxt.options.alias['#image'] = runtimeDir
nuxt.options.build.transpile.push(runtimeDir)

addAutoImport({
name: 'useImage',
from: resolver.resolve('runtime/composables')
})

// Add components
addComponent({
name: 'NuxtImg',
filePath: resolver.resolve('./runtime/components/nuxt-img.vue')
filePath: resolver.resolve('./runtime/components/nuxt-img')
})
addComponent({
name: 'NuxtPicture',
filePath: resolver.resolve('./runtime/components/nuxt-picture.vue')
filePath: resolver.resolve('./runtime/components/nuxt-picture')
})

// Add runtime options
Expand Down
107 changes: 107 additions & 0 deletions src/runtime/components/_base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { computed } from 'vue'
import type { ExtractPropTypes } from 'vue'
import { parseSize } from '../utils'

export const baseImageProps = {
// input source
src: { type: String, required: true },

// modifiers
format: { type: String, default: undefined },
quality: { type: [Number, String], default: undefined },
background: { type: String, default: undefined },
fit: { type: String, default: undefined },
modifiers: { type: Object as () => Record<string, any>, default: undefined },

// options
preset: { type: String, default: undefined },
provider: { type: String, default: undefined },

sizes: { type: [Object, String] as unknown as () => string | Record<string, any>, default: undefined },
preload: { type: Boolean, default: undefined },

// <img> attributes
width: { type: [String, Number], default: undefined },
height: { type: [String, Number], default: undefined },
alt: { type: String, default: undefined },
referrerpolicy: { type: String, default: undefined },
usemap: { type: String, default: undefined },
longdesc: { type: String, default: undefined },
ismap: { type: Boolean, default: undefined },
loading: { type: String, default: undefined },
crossorigin: {
type: [Boolean, String] as unknown as () => 'anonymous' | 'use-credentials' | boolean,
default: undefined,
validator: val => ['anonymous', 'use-credentials', '', true, false].includes(val)
},
decoding: {
type: String as () => 'async' | 'auto' | 'sync',
default: undefined,
validator: val => ['async', 'auto', 'sync'].includes(val)
}
}

export interface BaseImageAttrs {
width?: number
height?: number
alt?: string
referrerpolicy?: string
usemap?: string
longdesc?: string
ismap?: boolean
crossorigin?: '' | 'anonymous' | 'use-credentials'
loading?: string
decoding?: 'async' | 'auto' | 'sync'
}

export interface BaseImageModifiers {
width?: number
height?: number
format?: string
quality?: string | number
background?: string
fit?: string
[key: string]: any
}

export const useBaseImage = (props: ExtractPropTypes<typeof baseImageProps>) => {
const options = computed(() => {
return {
provider: props.provider,
preset: props.preset
}
})

const attrs = computed<BaseImageAttrs>(() => {
return <BaseImageAttrs>{
width: parseSize(props.width),
height: parseSize(props.height),
alt: props.alt,
referrerpolicy: props.referrerpolicy,
usemap: props.usemap,
longdesc: props.longdesc,
ismap: props.ismap,
crossorigin: props.crossorigin === true ? 'anonymous' : props.crossorigin || undefined,
loading: props.loading,
decoding: props.decoding
}
})

const modifiers = computed<BaseImageModifiers>(() => {
return <BaseImageModifiers> {
...props.modifiers,
width: parseSize(props.width),
height: parseSize(props.height),
format: props.format,
quality: props.quality,
background: props.background,
fit: props.fit
}
})

return {
options,
attrs,
modifiers
}
}
79 changes: 0 additions & 79 deletions src/runtime/components/image.mixin.ts

This file was deleted.

109 changes: 109 additions & 0 deletions src/runtime/components/nuxt-img.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { h, defineComponent } from 'vue'
import { useImage } from '../composables'
import { parseSize } from '../utils'
import { baseImageProps, useBaseImage } from './_base'
import { useHead } from '#imports'

export const imgProps = {
...baseImageProps,
placeholder: { type: [Boolean, String, Number, Array], default: undefined }
}

export default defineComponent({
name: 'NuxtImg',
props: imgProps,
setup: (props, ctx) => {
const $img = useImage()
const _base = useBaseImage(props)

const placeholderLoaded = ref(false)

type AttrsT = typeof _base.attrs.value & {
sizes?: string
srcset?: string
}

const sizes = computed(() => $img.getSizes(props.src, {
..._base.options.value,
sizes: props.sizes,
modifiers: {
..._base.modifiers.value,
width: parseSize(props.width),
height: parseSize(props.height)
}
}))

const attrs = computed(() => {
const attrs: AttrsT = _base.attrs.value
if (props.sizes) {
attrs.sizes = sizes.value.sizes
attrs.srcset = sizes.value.srcset
}
return attrs
})

const placeholder = computed(() => {
let placeholder = props.placeholder
if (placeholder === '') { placeholder = true }
if (!placeholder || placeholderLoaded.value) { return false }
if (typeof placeholder === 'string') { return placeholder }

const size = (Array.isArray(placeholder)
? placeholder
: (typeof placeholder === 'number' ? [placeholder, placeholder] : [10, 10])) as [w: number, h: number, q: number]

return $img(props.src, {
..._base.modifiers.value,
width: size[0],
height: size[1],
quality: size[2] || 50
}, _base.options.value)
})

const mainSrc = computed(() =>
props.sizes
? sizes.value.src
: $img(props.src, _base.modifiers.value, _base.options.value)
)

const src = computed(() => placeholder.value ? placeholder.value : mainSrc.value)

if (props.preload) {
const isResponsive = Object.values(sizes.value).every(v => v)
useHead({
link: [{
rel: 'preload',
as: 'image',
...(!isResponsive
? { href: src.value }
: {
href: sizes.value.src,
imagesizes: sizes.value.sizes,
imagesrcset: sizes.value.srcset
})
}]
})
}

const imgEl = ref<HTMLImageElement>(null)

onMounted(() => {
if (placeholder.value) {
const img = new Image()
img.src = mainSrc.value
img.onload = () => {
imgEl.value.src = mainSrc.value
placeholderLoaded.value = true
}
}
})

return () => h('img', {
ref: imgEl,
key: src.value,
src: src.value,
...attrs.value,
...ctx.attrs
})
}
})
Loading

0 comments on commit 6f2a430

Please sign in to comment.