Repository: nuxt/image
Pull request #1614 (2024-11-27 17:09:41)

fix: prismic unsplash image proxy
### 🔗 Linked issue

Resolves: #1600
<!-- Please ensure there is an open issue and mention its number. For example, "resolves #123" -->

### ❓ Type of change

<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply. -->

- [ ] 📖 Documentation (updates to the documentation or readme)
- [x] 🐞 Bug fix (a non-breaking change that fixes an issue)
- [ ] 👌 Enhancement (improving an existing functionality)
- [ ] ✨ New feature (a non-breaking change that adds functionality)
- [ ] 🧹 Chore (updates to the build process or auxiliary tools and libraries)
- [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

### 📚 Description

Some images distributed through Prismic are images directly served by Unsplash's CDN. This PR takes that into account within the Prismic provider to redirect Unsplash images to the Unsplash provider.


codecov-commenter: ## [Codecov](https://app.codecov.io/gh/nuxt/image/pull/1614?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 62.93%. Comparing base [(`d2e4eb0`)](https://app.codecov.io/gh/nuxt/image/commit/d2e4eb0fb4f42654d9a202fd1ae659b7fe24a35e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt) to head [(`a609f24`)](https://app.codecov.io/gh/nuxt/image/commit/a609f24ba4cb543584512732e14bd40b8fac8979?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1614      +/-   ##
==========================================
- Coverage   62.98%   62.93%   -0.06%     
==========================================
  Files          79       79              
  Lines        3534     3529       -5     
  Branches      406      407       +1     
==========================================
- Hits         2226     2221       -5     
  Misses       1279     1279              
  Partials       29       29              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/nuxt/image/pull/1614?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nuxt).


Patch:
diff --git a/src/runtime/providers/prismic.ts b/src/runtime/providers/prismic.ts
index 3d48a91..9889b95 100644
--- a/src/runtime/providers/prismic.ts
+++ b/src/runtime/providers/prismic.ts
@@ -1,24 +1,19 @@
-import { joinURL, parseQuery, parseURL, stringifyQuery } from 'ufo'
+import { getQuery, withBase, withQuery } from 'ufo'
 import type { ProviderGetImage } from '../../module'
 import { operationsGenerator } from './imgix'
+import { getImage as getUnsplashImage, unsplashCDN } from './unsplash'
 
-const PRISMIC_IMGIX_BUCKET = 'https://images.prismic.io'
+export const prismicCDN = 'https://images.prismic.io/'
 
-// Prismic image bucket is left configurable in order to test on other environments
-export const getImage: ProviderGetImage = (
-  src,
-  { modifiers = {}, baseURL = PRISMIC_IMGIX_BUCKET } = {},
-) => {
-  const operations = operationsGenerator(modifiers)
-
-  const parsedURL = parseURL(src)
+export const getImage: ProviderGetImage = (src, { modifiers = {}, baseURL = prismicCDN } = {}, ctx) => {
+  // Some images served by Prismic are from unsplash, so we use the unsplash provider for those
+  if (src.startsWith(unsplashCDN)) {
+    return getUnsplashImage(src, { modifiers }, ctx)
+  }
 
+  const operations = operationsGenerator(modifiers)
+  // withQuery requires query parameters as an object, so I parse the modifiers into an object with getQuery
   return {
-    url: joinURL(
-      baseURL,
-      parsedURL.pathname + '?'
-      // Remove duplicated keys, prioritizing override from developers
-      + stringifyQuery(Object.assign(parseQuery(parsedURL.search), parseQuery(operations))),
-    ),
+    url: withQuery(withBase(src, baseURL), getQuery('?' + operations)),
   }
 }
diff --git a/src/runtime/providers/unsplash.ts b/src/runtime/providers/unsplash.ts
index 325918b..7b39251 100644
--- a/src/runtime/providers/unsplash.ts
+++ b/src/runtime/providers/unsplash.ts
@@ -4,7 +4,7 @@ import { getQuery, withBase, withQuery } from 'ufo'
 import type { ProviderGetImage } from '../../module'
 import { operationsGenerator } from './imgix'
 
-const unsplashCDN = 'https://images.unsplash.com/'
+export const unsplashCDN = 'https://images.unsplash.com/'
 
 export const getImage: ProviderGetImage = (src, { modifiers = {}, baseURL = unsplashCDN } = {}) => {
   const operations = operationsGenerator(modifiers)


Test patch:
diff --git a/test/providers.ts b/test/providers.ts
index 7dcabc0..25b0369 100644
--- a/test/providers.ts
+++ b/test/providers.ts
@@ -19,6 +19,7 @@ export const images = [
     netlifyLargeMedia: { 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' },
+    prismicUnsplash: { url: 'https://images.unsplash.com/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' },
     cloudimage: { url: 'https://demo.cloudimg.io/v7/test.png' },
@@ -54,6 +55,7 @@ export const images = [
     netlifyLargeMedia: { 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' },
+    prismicUnsplash: { url: 'https://images.unsplash.com/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' },
     cloudimage: { url: 'https://demo.cloudimg.io/v7/test.png?width=200' },
@@ -89,6 +91,7 @@ export const images = [
     netlifyLargeMedia: { 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' },
+    prismicUnsplash: { url: 'https://images.unsplash.com/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' },
     cloudimage: { url: 'https://demo.cloudimg.io/v7/test.png?height=200' },
@@ -123,6 +126,7 @@ export const images = [
     netlifyImageCdn: { url: '/.netlify/images?w=200&h=200&url=%2Ftest.png' },
     netlifyLargeMedia: { 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' },
+    prismicUnsplash: { url: 'https://images.unsplash.com/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' },
@@ -158,6 +162,7 @@ export const images = [
     netlifyImageCdn: { url: '/.netlify/images?w=200&h=200&fit=contain&url=%2Ftest.png' },
     netlifyLargeMedia: { 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' },
+    prismicUnsplash: { url: 'https://images.unsplash.com/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' },
@@ -193,6 +198,7 @@ export const images = [
     netlifyImageCdn: { url: '/.netlify/images?w=200&h=200&fit=contain&fm=jpg&url=%2Ftest.png' },
     netlifyLargeMedia: { 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' },
+    prismicUnsplash: { url: 'https://images.unsplash.com/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' },
diff --git a/test/unit/providers.test.ts b/test/unit/providers.test.ts
index 28fae69..7c44812 100644
--- a/test/unit/providers.test.ts
+++ b/test/unit/providers.test.ts
@@ -337,6 +337,21 @@ describe('Providers', () => {
     }
   })
 
+  it('prismic (unsplash)', () => {
+    const providerOptions = {
+      baseURL: '', // Use empty base URL for the sake of simplicity
+    }
+
+    const EXISTING_QUERY_PARAMETERS
+      = '?auto=compress,format&rect=0,0,200,200&w=100&h=100'
+
+    for (const image of images) {
+      const [, modifiers] = image.args
+      const generated = prismic.getImage(`${image.prismicUnsplash.url.split('?').shift()}${EXISTING_QUERY_PARAMETERS}`, { modifiers, ...providerOptions }, emptyContext)
+      expect(generated).toMatchObject(image.prismicUnsplash)
+    }
+  })
+
   it('sanity', () => {
     const providerOptions = {
       baseURL: '',