Skip to content

Commit

Permalink
feat: auto detect image ratio (#37)
Browse files Browse the repository at this point in the history
Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
Co-authored-by: pooya parsa <pyapar@gmail.com>
  • Loading branch information
3 people committed Oct 21, 2020
1 parent 268d4e3 commit 0f6e17e
Show file tree
Hide file tree
Showing 31 changed files with 736 additions and 284 deletions.
2 changes: 1 addition & 1 deletion docs/content/en/examples/random-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Let's create a `./providers/random/` directory with two files inside:

```js{}[~/providers/random/runtime.js]
export default {
generateURL(src, modifiers, options) {
getImage(src, modifiers, options) {
return {
url: 'https://source.unsplash.com/random/600x400'
}
Expand Down
2 changes: 1 addition & 1 deletion docs/providers/random/runtime.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default {
generateURL (src, modifiers, options) {
getImage (src, modifiers, options) {
return {
url: 'https://source.unsplash.com/random/600x400'
}
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
"test:unit": "jest test/unit --forceExit"
},
"dependencies": {
"defu": "^3.1.0",
"hasha": "^5.2.2",
"ipx": "^0.3.2",
"ipx": "0.4.0-rc.1",
"node-fetch": "^2.6.1",
"upath": "^2.0.0"
},
Expand All @@ -46,9 +47,9 @@
"jsdom-global": "^3.0.2",
"nuxt-edge": "latest",
"playwright": "^1.5.1",
"rimraf": "latest",
"rimraf": "^3.0.2",
"standard-version": "latest",
"typescript": "latest"
"typescript": "^4.0.3"
},
"publishConfig": {
"access": "public"
Expand Down
44 changes: 44 additions & 0 deletions playground/pages/free.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<template>
<div class="container">
<nuxt-image
v-for="(image, i) in [...images, ...images, ...images, ...images, ...images]"
:key="'img-' + i"
:src="image.src"
:format="image.format"
:alt="image.alt"
class="image"
sets="320"
/>
</div>
</template>

<script>
export default {
asyncData () {
return {
images: [
{ src: 'cloudinary:/remote/nuxt-org/blog/going-full-static/main.png', alt: 'Cloudinary' },
{ src: 'fastly:/image.jpg', alt: 'fastify' },
{ src: 'imgix:/examples/bluehat.jpg', alt: 'imgix' },
// { src: 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Aconcagua2016.jpg/600px-Aconcagua2016.jpg', alt: 'Aconcagua Argentina' },
{ src: '/images/2000px-Everest_kalapatthar.jpg', alt: 'Mount Everest Nepal' },
{ src: '/images/2000px-Mont-Blanc_from_Planpraz_station.jpg', alt: 'Mount Kilimanjaro Tanzania' },
{ src: '/images/2000px-Mount_Vinson_from_NW_at_Vinson_Plateau_by_Christian_Stangl.jpg', alt: 'Vinson Massif Antarctic' },
{ src: '/images/2000px-Wonder_Lake_and_Denali.jpg', alt: 'Denali Alaska' },
{ src: '/images/1280px-K2_2006b.jpg', alt: 'K2' },
{ src: '/images/damavand.jpg', alt: 'Damavand' },
{ src: '/images/1280px-ChoOyu-fromGokyo.jpg', alt: 'Cho Oyu' },
{ src: 'twicpics:/v1/placeholder:200x100:medium-violet-red', alt: 'Placeholder', format: 'jpg' }
]
}
}
}
</script>

<style scoped>
.container {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
</style>
4 changes: 3 additions & 1 deletion playground/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
:src="image.src"
:alt="image.alt"
class="image"
:placeholder="true"
width="100"
height="100"
sets="320"
/>
</div>
Expand All @@ -20,7 +23,6 @@
.container .image {
width: 320px;
height: 320px;
margin: 10px;
border-radius: 3px;
}
Expand Down
56 changes: 35 additions & 21 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from 'path'
import defu from 'defu'
import fs from 'fs-extra'
import upath from 'upath'
import { ModuleOptions, ProviderFactory } from 'types'
Expand All @@ -10,31 +11,14 @@ function imageModule (moduleOptions: ModuleOptions) {

const options: ModuleOptions = {
presets: [],
providers: {},
...nuxt.options.image,
...moduleOptions
}

// Ensure at least one provider is set
if (!options.providers || !Object.keys(options.providers).length) {
options.providers = { local: {} }
}

// Add default `lqip` preset
if (!options.presets.some(preset => preset.name === 'lqip')) {
options.presets.unshift({
name: 'lqip',
modifiers: {
width: 30
}
})
}

// Apply local defaults
if (options.providers.local && typeof options.providers.local === 'object') {
options.providers.local = {
dir: path.resolve(nuxt.options.srcDir, nuxt.options.dir.static),
...options.providers.local
}
// Ensure local provider is set
if (!options.providers.length || options.providers.local) {
options.providers.local = prepareLocalProvider(this, options.providers.local)
}

if (!options.defaultProvider) {
Expand Down Expand Up @@ -151,5 +135,35 @@ function handleStaticGeneration (nuxt: any) {
})
}

function prepareLocalProvider ({ nuxt, options }, providerOptions) {
// Default port
const defaultPort =
process.env.PORT ||
process.env.npm_package_config_nuxt_port ||
(options.server && options.server.port) ||
3000

// Default host
let defaultHost =
process.env.HOST ||
process.env.npm_package_config_nuxt_host ||
(options.server && options.server.host) ||
'localhost'

/* istanbul ignore if */
if (defaultHost === '0.0.0.0') {
defaultHost = 'localhost'
}

// Default prefix
const prefix = '/'

return defu(providerOptions, {
baseURL: `http://${defaultHost}:${defaultPort}${prefix}`,
internalBaseURL: `http://${defaultHost}:${defaultPort}${prefix}`,
dir: path.resolve(nuxt.options.srcDir, nuxt.options.dir.static)
})
}

(imageModule as any).meta = require('../package.json')
export default imageModule
4 changes: 2 additions & 2 deletions src/providers/cloudinary/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RuntimeProvider, ImageModifiers } from 'types'
import { cleanDoubleSlashes, createOperationsGenerator } from '../../runtime/provider-utils'
import { cleanDoubleSlashes, createOperationsGenerator } from '~image/utils'

const operationsGenerator = createOperationsGenerator({
keyMap: {
Expand All @@ -22,7 +22,7 @@ const operationsGenerator = createOperationsGenerator({
})

export default <RuntimeProvider> {
generateURL (src: string, modifiers: ImageModifiers, options: any) {
getImage (src: string, modifiers: ImageModifiers, options: any) {
const operations = operationsGenerator(modifiers)

return {
Expand Down
18 changes: 15 additions & 3 deletions src/providers/fastly/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { RuntimeProvider, ImageModifiers } from 'types'
import { cleanDoubleSlashes, createOperationsGenerator } from '../../runtime/provider-utils'
import { cleanDoubleSlashes, createOperationsGenerator } from '~image/utils'
import fetch from '~image/fetch'

const operationsGenerator = createOperationsGenerator({
valueMap: {
Expand All @@ -16,10 +17,21 @@ const operationsGenerator = createOperationsGenerator({
})

export default <RuntimeProvider> {
generateURL (src: string, modifiers: ImageModifiers, options: any) {
getImage (src: string, modifiers: ImageModifiers, options: any) {
const operations = operationsGenerator(modifiers)
const url = cleanDoubleSlashes(options.baseURL + src + '?' + operations)
return {
url: cleanDoubleSlashes(options.baseURL + src + '?' + operations)
url,
getInfo: async () => {
const infoString = await fetch(url).then(res => res.headers.get('fastly-io-info') || '')
const info = Object.fromEntries(infoString.split(' ').map(part => part.split('=')))
const [width, height] = (info.idim || '').split('x').map(p => parseInt(p, 10))
return {
width,
height,
bytes: info.ifsz
}
}
}
}
}
16 changes: 13 additions & 3 deletions src/providers/imgix/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { RuntimeProvider, ImageModifiers } from 'types'
import { cleanDoubleSlashes, createOperationsGenerator } from '../../runtime/provider-utils'
import { cleanDoubleSlashes, createOperationsGenerator } from '~image/utils'
import fetch from '~image/fetch'

const operationsGenerator = createOperationsGenerator({
keyMap: {
Expand All @@ -21,10 +22,19 @@ const operationsGenerator = createOperationsGenerator({
})

export default <RuntimeProvider> {
generateURL (src: string, modifiers: ImageModifiers, options: any) {
getImage (src: string, modifiers: ImageModifiers, options: any) {
const operations = operationsGenerator(modifiers)
const url = cleanDoubleSlashes(options.baseURL + src + '?' + operations)
return {
url: cleanDoubleSlashes(options.baseURL + src + '?' + operations)
url,
getInfo: async () => {
const info = await fetch(url + '&fm=json').then(res => res.json())
return {
width: info.PixelWidth,
height: info.PixelHeight,
bytes: info['Content-Length']
}
}
}
}
}
21 changes: 16 additions & 5 deletions src/providers/local/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { ProviderFactory } from 'types'
export default <ProviderFactory> function (providerOptions) {
return {
runtime: require.resolve('./runtime'),
runtimeOptions: providerOptions,
runtimeOptions: {
baseURL: providerOptions.baseURL,
internalBaseURL: providerOptions.internalBaseURL
},
middleware: createMiddleware(providerOptions)
}
}
Expand All @@ -12,10 +15,18 @@ function createMiddleware (options) {
const { IPX, IPXMiddleware } = require('ipx')

const ipx = new IPX({
input: {
adapter: 'fs',
dir: options.dir
},
inputs: [
{
name: 'local',
adapter: 'fs',
dir: options.dir
},
{
name: 'remote',
adapter: 'remote',
accept: options.accept
}
],
cache: {
cleanCron: options.clearCache || false
}
Expand Down
33 changes: 28 additions & 5 deletions src/providers/local/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { RuntimeProvider, ImageModifiers } from 'types'
import { cleanDoubleSlashes } from '../../runtime/provider-utils'
import { cleanDoubleSlashes } from '~image/utils'
import fetch from '~image/fetch'

function predictAdapter (src: string) {
return src.match(/^https?:\/\//) ? 'remote' : 'local'
}

export default <RuntimeProvider> {
generateURL (src: string, modifiers: ImageModifiers) {
getImage (src: string, modifiers: ImageModifiers, options: any) {
const operations = []

const fit = modifiers.fit ? `_${modifiers.fit}` : ''
Expand All @@ -14,10 +19,28 @@ export default <RuntimeProvider> {
operations.push(`h_${modifiers.height}${fit}`)
}

const operationsString = operations.length ? operations.join(',') : '_'
const adapter = predictAdapter(src)

const operationsString = operations.join(',') || '_'
const url = cleanDoubleSlashes(`/_image/local/${adapter}/${modifiers.format || '_'}/${operationsString}/${src}`)
const infoUrl = cleanDoubleSlashes(`/_image/local/${adapter}/${modifiers.format || 'jpg'}.json/${operationsString}_url/${src}`)

const baseURL = process.client ? options.baseURL : options.internalBaseURL

let _meta
const getMeta = () => _meta || fetch(baseURL + infoUrl).then(res => res.json())

return {
url: cleanDoubleSlashes(`/_image/local/${modifiers.format || '_'}/${operationsString}/${src}`),
isStatic: true
url,
isStatic: true,
async getInfo () {
const { width, height, size, data } = await getMeta()
return { width, height, bytes: size, placeholder: data }
},
async getPlaceHolder () {
const { data } = await getMeta()
return data
}
}
}
}
4 changes: 2 additions & 2 deletions src/providers/twicpics/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RuntimeProvider, ImageModifiers } from 'types'
import { createMapper, createOperationsGenerator, cleanDoubleSlashes } from '../../runtime/provider-utils'
import { createMapper, createOperationsGenerator, cleanDoubleSlashes } from '~image/utils'

const fits = createMapper({
fill: 'fill',
Expand All @@ -19,7 +19,7 @@ const operationsGenerator = createOperationsGenerator({
})

export default <RuntimeProvider> {
generateURL (src: string, modifiers: ImageModifiers, options: any) {
getImage (src: string, modifiers: ImageModifiers, options: any) {
const { width, height, fit, ...providerModifiers } = modifiers

if (width || height) {
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { cleanDoubleSlashes } from './utils'

export default function imageFetch (url: string) {
return fetch(cleanDoubleSlashes(url))
}
Loading

0 comments on commit 0f6e17e

Please sign in to comment.