Skip to content

Commit 0be1513

Browse files
authored
Merge pull request #317 from netlify/mk/image-function-refactor
fix: refactor image function
2 parents d468c10 + 6b2e413 commit 0be1513

File tree

3 files changed

+131
-33
lines changed

3 files changed

+131
-33
lines changed

package-lock.json

+56-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
"find-cache-dir": "^3.3.1",
6666
"find-up": "^5.0.0",
6767
"fs-extra": "^9.1.0",
68+
"image-type": "^4.1.0",
69+
"is-svg": "^4.3.1",
6870
"make-dir": "^3.1.0",
6971
"mime-types": "^2.1.30",
7072
"moize": "^6.0.0",

src/lib/templates/imageFunction.js

+73-30
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,95 @@
1-
const path = require('path')
21
const { builder } = require('@netlify/functions')
32
const sharp = require('sharp')
43
const fetch = require('node-fetch')
4+
const imageType = require('image-type')
5+
const isSvg = require('is-svg')
56

6-
// Function used to mimic next/image and sharp
7+
function getImageType(buffer) {
8+
const type = imageType(buffer)
9+
if (type) {
10+
return type
11+
}
12+
if (isSvg(buffer)) {
13+
return { ext: 'svg', mime: 'image/svg' }
14+
}
15+
return null
16+
}
17+
18+
const IGNORED_FORMATS = new Set(['svg', 'gif'])
19+
const OUTPUT_FORMATS = new Set(['png', 'jpg', 'webp', 'avif'])
20+
21+
// Function used to mimic next/image
722
const handler = async (event) => {
823
const [, , url, w = 500, q = 75] = event.path.split('/')
9-
const parsedUrl = decodeURIComponent(url)
24+
// Work-around a bug in redirect handling. Remove when fixed.
25+
const parsedUrl = decodeURIComponent(url).replace('+', '%20')
1026
const width = parseInt(w)
11-
const quality = parseInt(q)
27+
28+
if (!width) {
29+
return {
30+
statusCode: 400,
31+
body: 'Invalid image parameters',
32+
}
33+
}
34+
35+
const quality = parseInt(q) || 60
1236

1337
const imageUrl = parsedUrl.startsWith('/')
1438
? `${process.env.DEPLOY_URL || `http://${event.headers.host}`}${parsedUrl}`
1539
: parsedUrl
40+
1641
const imageData = await fetch(imageUrl)
42+
43+
if (!imageData.ok) {
44+
console.error(`Failed to download image ${imageUrl}. Status ${imageData.status} ${imageData.statusText}`)
45+
return {
46+
statusCode: imageData.status,
47+
body: imageData.statusText,
48+
}
49+
}
50+
1751
const bufferData = await imageData.buffer()
18-
const ext = path.extname(imageUrl)
19-
const mimeType = ext === 'jpg' ? `image/jpeg` : `image/${ext}`
20-
21-
let image
22-
let imageBuffer
23-
24-
if (mimeType === 'image/gif') {
25-
image = await sharp(bufferData, { animated: true })
26-
// gif resizing in sharp seems unstable (https://github.com/lovell/sharp/issues/2275)
27-
imageBuffer = await image.toBuffer()
28-
} else {
29-
image = await sharp(bufferData)
30-
if (mimeType === 'image/webp') {
31-
image = image.webp({ quality })
32-
} else if (mimeType === 'image/jpeg') {
33-
image = image.jpeg({ quality })
34-
} else if (mimeType === 'image/png') {
35-
image = image.png({ quality })
36-
} else if (mimeType === 'image/avif') {
37-
image = image.avif({ quality })
38-
} else if (mimeType === 'image/tiff') {
39-
image = image.tiff({ quality })
40-
} else if (mimeType === 'image/heif') {
41-
image = image.heif({ quality })
52+
53+
const type = getImageType(bufferData)
54+
55+
if (!type) {
56+
return { statusCode: 400, body: 'Source does not appear to be an image' }
57+
}
58+
59+
let { ext } = type
60+
61+
// For unsupported formats (gif, svg) we redirect to the original
62+
if (IGNORED_FORMATS.has(ext)) {
63+
return {
64+
statusCode: 302,
65+
headers: {
66+
Location: imageUrl,
67+
},
4268
}
43-
imageBuffer = await image.resize(width).toBuffer()
4469
}
4570

71+
if (process.env.FORCE_WEBP_OUTPUT) {
72+
ext = 'webp'
73+
}
74+
75+
if (!OUTPUT_FORMATS.has(ext)) {
76+
ext = 'jpg'
77+
}
78+
79+
// The format methods are just to set options: they don't
80+
// make it return that format.
81+
const { info, data: imageBuffer } = await sharp(bufferData)
82+
.jpeg({ quality, force: ext === 'jpg' })
83+
.webp({ quality, force: ext === 'webp' })
84+
.png({ quality, force: ext === 'png' })
85+
.avif({ quality, force: ext === 'avif' })
86+
.resize(width)
87+
.toBuffer({ resolveWithObject: true })
88+
4689
return {
4790
statusCode: 200,
4891
headers: {
49-
'Content-Type': mimeType,
92+
'Content-Type': `image/${info.format}`,
5093
},
5194
body: imageBuffer.toString('base64'),
5295
isBase64Encoded: true,

0 commit comments

Comments
 (0)