Skip to content

Commit

Permalink
feat: add prepr cms image provider integration (nuxt#823)
Browse files Browse the repository at this point in the history
  • Loading branch information
FranciscoKloganB authored Jun 12, 2023
1 parent 8782712 commit 6b8ab9c
Show file tree
Hide file tree
Showing 14 changed files with 270 additions and 0 deletions.
59 changes: 59 additions & 0 deletions docs/content/5.providers/prepr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Prepr

Nuxt Image integration with Prepr CMS

Integration between [Prepr](https://prepr.io/) and Nuxt Image.

To use this provider you just need to specify the `projectName` of your project in Prepr.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
image: {
prepr: {
// E.g.: https://YourProjectName.prepr.io/
projectName: 'YourProjectName',
}
}
})
```

## Modifiers

The Prepr provider supports a number of additional modifiers. For a full list,
check out [the Prepr documentation](https://docs.prepr.io/reference/rest/v1/assets-resizing/).
All current transformations currently mentioned in Prepr docs are supported.

For the time being you might find the following links useful:

- [Assets Resizing via REST API](https://docs.prepr.io/reference/rest/v1/assets-resizing/)
- [Understanding your marketing and design team workflows](https://docs.prepr.io/managing-content/images)

::alert{type="info"}
prepr.io does not provide a way to restrict what domains can
request assets to your project's CDN, nor limit the maximum size in `pixels` or
`bytes` of images that are served from the CDN.
::

### Convenience key modifiers

The following more readable modifiers are supported, in addition to Prepr's
native modifiers:

- `crop` is equivalent to `c`
- `format` is equivalent to `format`
- `height` is equivalent to `h`
- `quality` is equivalent to `q`
- `width` is equivalent to `w`

### `fit`

In addition to the values specified in the Prepr docs, which are respected, the
following options from the [default fit behavior](/components/nuxt-img#fit)
are supported:

- `cover` - this will behave like the Prepr modifier `crop`, when passed without
a value (defaults to `centre`)

::alert{type="warning"}
For the time being, other `fit` options are not supported by this provider.
::
3 changes: 3 additions & 0 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export default defineNuxtConfig({
baseURL: 'https://opt.moovweb.net'
},
prismic: {},
prepr: {
projectName: 'nuxt-prepr-demo'
},
sanity: {
projectId: 'zp7mbokg'
},
Expand Down
41 changes: 41 additions & 0 deletions playground/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,47 @@ export const providers: Provider[] = [
{ src: 'https://i.imgur.com/LFtQeX2.jpeg', quality: 10 }
]
},
// Prepr
{
name: 'prepr',
samples: [
{ src: '4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg', quality: 5 },
{ src: '4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg', height: 200 },
{ src: '4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg', width: 200 },
{ src: '4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg', width: 200, height: 200 },
{
src: '4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg',
modifiers: {
w: 250,
h: 250
}
},
{
src: '4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg',
width: 250,
height: 200,
modifiers: {
crop: 'southeast'
}
},
{
src: '4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg',
width: 250,
height: 200,
modifiers: {
crop: 'centre'
}
},
{
src: '4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg',
width: 250,
height: 200,
fit: {
cover: true
}
}
]
},
// Prismic
{
name: 'prismic',
Expand Down
1 change: 1 addition & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export default defineNuxtModule<ModuleOptions>({
name: 'NuxtImg',
filePath: resolver.resolve('./runtime/components/nuxt-img')
})

addComponent({
name: 'NuxtPicture',
filePath: resolver.resolve('./runtime/components/nuxt-picture')
Expand Down
1 change: 1 addition & 0 deletions src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const BuiltInProviders = [
'ipx',
'layer0',
'netlify',
'prepr',
'none',
'prismic',
'sanity',
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/providers/prepr/formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function formatter (key: string, value: string) {
return String(value) === 'true' ? key : `${key}_${value}`
}
42 changes: 42 additions & 0 deletions src/runtime/providers/prepr/getImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { joinURL } from 'ufo'

import type { ImageOptions, ResolvedImage, ImageCTX } from '../../../types'

import { formatter } from './formatter'
import { keyMap } from './keyMap'
import { valueMap } from './valueMap'

import { createOperationsGenerator } from '#image'

interface PreprImageOptions extends ImageOptions {
projectName: string
}

type PreprProviderGetImage = (src: string, options: PreprImageOptions, ctx: ImageCTX) => ResolvedImage

const operationsGenerator = createOperationsGenerator({
formatter,
joinWith: ',',
keyMap,
valueMap
})

const getImage: PreprProviderGetImage = (src, options, _ctx) => {
const { projectName } = options

if (typeof projectName !== 'string' || !projectName.trim()) {
throw new TypeError('[nuxt] [image] [prepr] No project name provided.')
}

const fileBucket = 'stream'
const fileOperations = operationsGenerator(options.modifiers)
const filePath = fileOperations ? joinURL(fileOperations, src) : src

const projectUrl = `https://${projectName.trim()}.${fileBucket}.prepr.io`

return {
url: joinURL(projectUrl, filePath)
}
}

export { getImage }
1 change: 1 addition & 0 deletions src/runtime/providers/prepr/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './getImage'
18 changes: 18 additions & 0 deletions src/runtime/providers/prepr/keyMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Key map is responsible for mapping readable "properties", which can be passed
* as modifiers of `NuxtImg` component, to URL path parameters that can be
* interpreted Prepr's REST API.
*/
const keyMap = {
crop: 'c',
format: 'format',
height: 'h',
quality: 'q',
width: 'w'
} as const

type KeyMapKey = keyof typeof keyMap
type KeyMapValue= typeof keyMap[KeyMapKey]

export { keyMap }
export type { KeyMapKey, KeyMapValue }
31 changes: 31 additions & 0 deletions src/runtime/providers/prepr/valueMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Value map is responsible for mapping readable "properties" defined in
* `keyMap.ts` aswell as native modifiers of `NuxtImg` component, to URL path
* parameter values that can be interpreted Prepr's REST API.
*
* ```Examples
* Prepr's `w` path param expects an arbitrary number, so, it does not need to be in `valueMap`
*
* Our custom param `width` maps to `w` in keyMap, so, it does not need to be in `valueMap`
*
* Prepr's `format` path param expects a string which can either be `jpg` or `png`,
* if we want to allow the user to pass <NuxtImage :modifiers="{ format: 'jpeg' }" />,
* because it is a valid option of <NuxtImg :format />, then we need to have
* `jpeg` to `jpg` because Prepr's API does not recognize `jpeg`. Similar things
* could be said for `fit=cover`, which should map to `fit=crop`
*```
*/
const valueMap = {
format: {
jpeg: 'jpg'
},
fit: {
cover: 'crop' // Prepr.io accepts value `crop` defaulting to value `centre`
}
} as const

type ValuesMapKey = keyof typeof valueMap
type ValuesMapValue = typeof valueMap[ValuesMapKey]

export { valueMap }
export type { ValuesMapKey, ValuesMapValue }
26 changes: 26 additions & 0 deletions test/e2e/__snapshots__/no-ssr.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,32 @@ exports[`browser (ssr: false) > none should render images 2`] = `
]
`;

exports[`browser (ssr: false) > prepr should render images 1`] = `
[
"https://nuxt-prepr-demo.stream.prepr.io/q_5/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_200,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_250,h_250/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/c_southeast,w_250,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/c_centre,w_250,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_250,h_200,fit_[object Object]/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
]
`;

exports[`browser (ssr: false) > prepr should render images 2`] = `
[
"https://nuxt-prepr-demo.stream.prepr.io/q_5/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_200,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_250,h_250/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/c_southeast,w_250,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/c_centre,w_250,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_250,h_200,fit_[object%20Object]/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
]
`;

exports[`browser (ssr: false) > prismic should render images 1`] = `
[
"https://images.prismic.io/200629-sms-hoy/f596a543-d593-4296-9abd-3d3ac15f1e39_ray-hennessy-mpw37yXc_WQ-unsplash.jpg?auto=compress,format&w=600&h=900",
Expand Down
26 changes: 26 additions & 0 deletions test/e2e/__snapshots__/ssr.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,32 @@ exports[`browser (ssr: true) > none should render images 2`] = `
]
`;

exports[`browser (ssr: true) > prepr should render images 1`] = `
[
"https://nuxt-prepr-demo.stream.prepr.io/q_5/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_200,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_250,h_250/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/c_southeast,w_250,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/c_centre,w_250,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_250,h_200,fit_[object Object]/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
]
`;

exports[`browser (ssr: true) > prepr should render images 2`] = `
[
"https://nuxt-prepr-demo.stream.prepr.io/q_5/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_200,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_250,h_250/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/c_southeast,w_250,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/c_centre,w_250,h_200/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
"https://nuxt-prepr-demo.stream.prepr.io/w_250,h_200,fit_[object%20Object]/4rhkcc7xzk7d-claymemoirscom-spider-pic.jpg",
]
`;

exports[`browser (ssr: true) > prismic should render images 1`] = `
[
"https://images.prismic.io/200629-sms-hoy/f596a543-d593-4296-9abd-3d3ac15f1e39_ray-hennessy-mpw37yXc_WQ-unsplash.jpg?auto=compress,format&w=600&h=900",
Expand Down
6 changes: 6 additions & 0 deletions test/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const images = [
unsplash: { url: '/test.png' },
imagekit: { url: '/test.png' },
netlify: { url: '/test.png' },
prepr: { url: 'https://projectName.stream.prepr.io/image-test-300x450-png' },
prismic: { url: '/test.png?auto=compress,format&rect=0,0,200,200&w=100&h=100' },
sanity: { url: 'https://cdn.sanity.io/images/projectid/production/test-300x450.png?auto=format' },
contentful: { url: '/test.png' },
Expand All @@ -39,6 +40,7 @@ export const images = [
unsplash: { url: '/test.png?w=200' },
imagekit: { url: '/test.png?tr=w-200' },
netlify: { url: '/test.png?w=200&nf_resize=fit' },
prepr: { url: 'https://projectName.stream.prepr.io/w_200/image-test-300x450-png' },
prismic: { url: '/test.png?auto=compress,format&rect=0,0,200,200&w=200&h=100' },
sanity: { url: 'https://cdn.sanity.io/images/projectid/production/test-300x450.png?w=200&auto=format' },
contentful: { url: '/test.png?w=200' },
Expand All @@ -64,6 +66,7 @@ export const images = [
unsplash: { url: '/test.png?h=200' },
imagekit: { url: '/test.png?tr=h-200' },
netlify: { url: '/test.png?h=200&nf_resize=fit' },
prepr: { url: 'https://projectName.stream.prepr.io/h_200/image-test-300x450-png' },
prismic: { url: '/test.png?auto=compress,format&rect=0,0,200,200&w=100&h=200' },
sanity: { url: 'https://cdn.sanity.io/images/projectid/production/test-300x450.png?h=200&auto=format' },
contentful: { url: '/test.png?h=200' },
Expand All @@ -90,6 +93,7 @@ export const images = [
imagekit: { url: '/test.png?tr=w-200,h-200' },
netlify: { url: '/test.png?w=200&h=200&nf_resize=fit' },
prismic: { url: '/test.png?auto=compress,format&rect=0,0,200,200&w=200&h=200' },
prepr: { url: 'https://projectName.stream.prepr.io/w_200,h_200/image-test-300x450-png' },
sanity: { url: 'https://cdn.sanity.io/images/projectid/production/test-300x450.png?w=200&h=200&auto=format' },
contentful: { url: '/test.png?w=200&h=200' },
cloudimage: { url: 'https://demo.cloudimg.io/v7/test.png?width=200&height=200' },
Expand All @@ -115,6 +119,7 @@ export const images = [
imagekit: { url: '/test.png?tr=w-200,h-200,cm-pad_resize' },
netlify: { url: '/test.png?w=200&h=200&nf_resize=fit' },
prismic: { url: '/test.png?auto=compress,format&rect=0,0,200,200&w=200&h=200&fit=fill' },
prepr: { url: 'https://projectName.stream.prepr.io/w_200,h_200,fit_contain/image-test-300x450-png' },
sanity: { url: 'https://cdn.sanity.io/images/projectid/production/test-300x450.png?w=200&h=200&fit=fill&auto=format&bg=ffffff' },
contentful: { url: '/test.png?w=200&h=200&fit=fill' },
cloudimage: { url: 'https://demo.cloudimg.io/v7/test.png?width=200&height=200&func=fit' },
Expand All @@ -141,6 +146,7 @@ export const images = [
netlify: { url: '/test.png?w=200&h=200&nf_resize=fit' },
prismic: { url: '/test.png?auto=compress,format&rect=0,0,200,200&w=200&h=200&fit=fill&fm=jpeg' },
sanity: { url: 'https://cdn.sanity.io/images/projectid/production/test-300x450.png?w=200&h=200&fit=fill&fm=jpg&bg=ffffff' },
prepr: { url: 'https://projectName.stream.prepr.io/w_200,h_200,fit_contain,format_jpg/image-test-300x450-png' },
contentful: { url: '/test.png?w=200&h=200&fit=fill&fm=jpg' },
cloudimage: { url: 'https://demo.cloudimg.io/v7/test.png?width=200&height=200&func=fit&force_format=jpeg' },
edgio: { url: 'https://opt.moovweb.net/?img=/test.png&width=200&height=200&fit=contain&format=jpeg' },
Expand Down
12 changes: 12 additions & 0 deletions test/unit/providers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as cloudflare from '#image/providers/cloudflare'
import * as cloudinary from '#image/providers/cloudinary'
import * as twicpics from '#image/providers/twicpics'
import * as fastly from '#image/providers/fastly'
import * as prepr from '#image/providers/prepr'
import * as glide from '#image/providers/glide'
import * as imgix from '#image/providers/imgix'
import * as gumlet from '#image/providers/gumlet'
Expand Down Expand Up @@ -283,6 +284,17 @@ describe('Providers', () => {
}
})

it('prepr', () => {
const providerOptions = {
projectName: 'projectName'
}

for (const image of images) {
const [, modifiers] = image.args
const generated = prepr.getImage('image-test-300x450-png', { modifiers: { ...modifiers }, ...providerOptions }, emptyContext)
expect(generated).toMatchObject(image.prepr)
}
})
it('contentful', () => {
const providerOptions = {
baseURL: ''
Expand Down

0 comments on commit 6b8ab9c

Please sign in to comment.