Skip to content

Commit 606e264

Browse files
feat: add images.remoteURLPatterns option to NetlifyDev (#348)
* feat: add `images.remoteImages` option to `NetlifyDev` * refactor: rename option
1 parent 0376cdc commit 606e264

File tree

3 files changed

+61
-1
lines changed

3 files changed

+61
-1
lines changed

packages/dev/src/main.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,9 @@ describe('Handling requests', () => {
492492
// for local images
493493
enabled: false,
494494
},
495+
images: {
496+
remoteURLPatterns: [`^${remoteServerAddress}/allowed-via-option/.*`],
497+
},
495498
})
496499

497500
await dev.start()
@@ -517,6 +520,16 @@ describe('Handling requests', () => {
517520
await getImageResponseSize(allowedRemoteImageResponse ?? new Response('No @netlify/dev response')),
518521
).toMatchObject({ width: 100, height: 50 })
519522

523+
const allowedRemoteImage2Request = new Request(
524+
`https://site.netlify/.netlify/images?url=${encodeURIComponent(`${remoteServerAddress}/allowed-via-option/image`)}&w=100`,
525+
)
526+
const allowedRemoteImage2Response = await dev.handle(allowedRemoteImage2Request)
527+
expect(allowedRemoteImage2Response?.ok).toBe(true)
528+
expect(allowedRemoteImage2Response?.headers.get('content-type')).toMatch(/^image\//)
529+
expect(
530+
await getImageResponseSize(allowedRemoteImage2Response ?? new Response('No @netlify/dev response')),
531+
).toMatchObject({ width: 100, height: 50 })
532+
520533
const notAllowedRemoteImageRequest = new Request(
521534
`https://site.netlify/.netlify/images?url=${encodeURIComponent(`${remoteServerAddress}/not-allowed/image`)}&w=100`,
522535
)

packages/dev/src/main.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ export interface Features {
7171
*/
7272
images?: {
7373
enabled?: boolean
74+
75+
/**
76+
* Allowed URL patterns for remote images, described as an array of regular
77+
* expression strings. This list will be merged with the `remote_images`
78+
* configuration property.
79+
*
80+
* {@link} https://docs.netlify.com/image-cdn/overview/#remote-path
81+
*/
82+
remoteURLPatterns?: string[]
7483
}
7584

7685
/**
@@ -152,6 +161,7 @@ export class NetlifyDev {
152161
static: boolean
153162
}
154163
#headersHandler?: HeadersHandler
164+
#imageRemoteURLPatterns: string[]
155165
#imageHandler?: ImageHandler
156166
#logger: Logger
157167
#projectRoot: string
@@ -184,6 +194,7 @@ export class NetlifyDev {
184194
static: options.staticFiles?.enabled !== false,
185195
}
186196
this.#functionsServePath = path.join(projectRoot, '.netlify', 'functions-serve')
197+
this.#imageRemoteURLPatterns = options.images?.remoteURLPatterns ?? []
187198
this.#logger = options.logger ?? globalThis.console
188199
this.#serverAddress = options.serverAddress
189200
this.#projectRoot = projectRoot
@@ -537,8 +548,12 @@ export class NetlifyDev {
537548
}
538549

539550
if (this.#features.images) {
551+
const remoteImages = [...(this.#config?.config?.images?.remote_images ?? []), ...this.#imageRemoteURLPatterns]
552+
540553
this.#imageHandler = new ImageHandler({
541-
imagesConfig: this.#config?.config.images,
554+
imagesConfig: {
555+
remote_images: remoteImages,
556+
},
542557
logger: this.#logger,
543558
})
544559
}

packages/images/src/main.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,38 @@ describe('`ImageHandler`', () => {
311311
expect(width / height).toBe(IMAGE_WIDTH / IMAGE_HEIGHT)
312312
}, 30_000)
313313

314+
test('allow matching any remote image when using a wildcard pattern', async () => {
315+
mockCreateIPXWebServer.mockImplementation(
316+
(await vi.importActual<typeof import('ipx')>('ipx')).createIPXWebServer,
317+
)
318+
319+
const imageHandler = new ImageHandler({
320+
logger: createMockLogger(),
321+
imagesConfig: {
322+
remote_images: [`(.*)`],
323+
},
324+
})
325+
326+
const requestedWidth = 100
327+
328+
const url = new URL('/.netlify/images', 'https://netlify.com')
329+
url.searchParams.set('url', remoteServerAddress)
330+
url.searchParams.set('w', requestedWidth.toString())
331+
332+
const match = imageHandler.match(new Request(url))
333+
334+
expect(match).toBeDefined()
335+
336+
const response = await match!.handle('https://example.netlify')
337+
338+
expect(response.ok).toBe(true)
339+
340+
const { width, height } = await getImageResponseSize(response)
341+
342+
expect(width).toBe(requestedWidth)
343+
expect(width / height).toBe(IMAGE_WIDTH / IMAGE_HEIGHT)
344+
}, 30_000)
345+
314346
test('does not allow remote images not matching configured patterns', async () => {
315347
const imageHandler = new ImageHandler({
316348
logger: createMockLogger(),

0 commit comments

Comments
 (0)