mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
✨ Added format option to img-url
helper (#14962)
fixes https://github.com/TryGhost/Ghost/issues/14323 - Fixed support for resizing images from Unsplash using the `img-url` helper (previously the size property was ignored for images from Unsplash) - Added support for `avif` file formats (supported by sharp out of the box) - Added support for setting the format of images, with a new `format` option: E.g. to convert an image to webp (only works in combination with size for now, except for Unsplash where you can use it without size): ``` {{img_url @site.cover_image size="s" format="webp"}} ``` This can help improve the performance of a theme, by serving assets in `<picture>` elements with webp and fallback image formats. Usage example: ```html <picture> <source srcset="{{img_url feature_image size="s" format="avif"}} 300w, {{img_url feature_image size="m" format="avif"}} 600w, {{img_url feature_image size="l" format="avif"}} 1000w, {{img_url feature_image size="xl" format="avif"}} 2000w" sizes="(min-width: 1400px) 1400px, 92vw" type="image/avif" > <source srcset="{{img_url feature_image size="s" format="webp"}} 300w, {{img_url feature_image size="m" format="webp"}} 600w, {{img_url feature_image size="l" format="webp"}} 1000w, {{img_url feature_image size="xl" format="webp"}} 2000w" sizes="(min-width: 1400px) 1400px, 92vw" type="image/webp" > <img srcset="{{img_url feature_image size="s"}} 300w, {{img_url feature_image size="m"}} 600w, {{img_url feature_image size="l"}} 1000w, {{img_url feature_image size="xl"}} 2000w" sizes="(min-width: 1400px) 1400px, 92vw" src="{{img_url feature_image size="xl"}}" alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}" > </picture> ```
This commit is contained in:
parent
312e2330a1
commit
b7f3892be0
6 changed files with 539 additions and 29 deletions
|
@ -12,6 +12,7 @@ const url = require('url');
|
|||
const _ = require('lodash');
|
||||
const logging = require('@tryghost/logging');
|
||||
const tpl = require('@tryghost/tpl');
|
||||
const imageTransform = require('@tryghost/image-transform');
|
||||
|
||||
const messages = {
|
||||
attrIsRequired: 'Attribute is required e.g. {{img_url feature_image}}'
|
||||
|
@ -41,15 +42,26 @@ module.exports = function imgUrl(requestedImageUrl, options) {
|
|||
|
||||
// CASE: if you pass an external image, there is nothing we want to do to it!
|
||||
const isInternalImage = detectInternalImage(requestedImageUrl);
|
||||
const sizeOptions = getImageSizeOptions(options);
|
||||
|
||||
if (!isInternalImage) {
|
||||
// Detect Unsplash width and format
|
||||
const isUnsplashImage = /images\.unsplash\.com/.test(requestedImageUrl);
|
||||
if (isUnsplashImage) {
|
||||
try {
|
||||
return getUnsplashImage(requestedImageUrl, sizeOptions);
|
||||
} catch (e) {
|
||||
// ignore errors and just return the original URL
|
||||
}
|
||||
}
|
||||
|
||||
return requestedImageUrl;
|
||||
}
|
||||
|
||||
const {requestedSize, imageSizes} = getImageSizeOptions(options);
|
||||
const absoluteUrlRequested = getAbsoluteOption(options);
|
||||
|
||||
function applyImageSizes(image) {
|
||||
return getImageWithSize(image, requestedSize, imageSizes);
|
||||
return getImageWithSize(image, sizeOptions);
|
||||
}
|
||||
|
||||
function getImageUrl(image) {
|
||||
|
@ -79,10 +91,12 @@ function getAbsoluteOption(options) {
|
|||
function getImageSizeOptions(options) {
|
||||
const requestedSize = options && options.hash && options.hash.size;
|
||||
const imageSizes = options && options.data && options.data.config && options.data.config.image_sizes;
|
||||
const requestedFormat = options && options.hash && options.hash.format;
|
||||
|
||||
return {
|
||||
requestedSize,
|
||||
imageSizes
|
||||
imageSizes,
|
||||
requestedFormat
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -99,12 +113,58 @@ function detectInternalImage(requestedImageUrl) {
|
|||
return isAbsoluteInternalImage || isRelativeInternalImage;
|
||||
}
|
||||
|
||||
function getImageWithSize(imagePath, requestedSize, imageSizes) {
|
||||
function getUnsplashImage(imagePath, sizeOptions) {
|
||||
const parsedUrl = new URL(imagePath);
|
||||
const {requestedSize, imageSizes, requestedFormat} = sizeOptions;
|
||||
|
||||
if (requestedFormat) {
|
||||
const supportedFormats = ['avif', 'gif', 'jpg', 'png', 'webp'];
|
||||
if (supportedFormats.includes(requestedFormat)) {
|
||||
parsedUrl.searchParams.set('fm', requestedFormat);
|
||||
} else if (requestedFormat === 'jpeg') {
|
||||
// Map to alias
|
||||
parsedUrl.searchParams.set('fm', 'jpg');
|
||||
}
|
||||
}
|
||||
|
||||
if (!imageSizes || !imageSizes[requestedSize]) {
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
|
||||
const {width, height} = imageSizes[requestedSize];
|
||||
|
||||
if (!width && !height) {
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
|
||||
parsedUrl.searchParams.delete('w');
|
||||
parsedUrl.searchParams.delete('h');
|
||||
|
||||
if (width) {
|
||||
parsedUrl.searchParams.set('w', width);
|
||||
}
|
||||
if (height) {
|
||||
parsedUrl.searchParams.set('h', height);
|
||||
}
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} imagePath
|
||||
* @param {Object} sizeOptions
|
||||
* @param {string} sizeOptions.requestedSize
|
||||
* @param {Object[]} sizeOptions.imageSizes
|
||||
* @param {string} [sizeOptions.requestedFormat]
|
||||
* @returns
|
||||
*/
|
||||
function getImageWithSize(imagePath, sizeOptions) {
|
||||
const hasLeadingSlash = imagePath[0] === '/';
|
||||
|
||||
if (hasLeadingSlash) {
|
||||
return '/' + getImageWithSize(imagePath.slice(1), requestedSize, imageSizes);
|
||||
return '/' + getImageWithSize(imagePath.slice(1), sizeOptions);
|
||||
}
|
||||
const {requestedSize, imageSizes, requestedFormat} = sizeOptions;
|
||||
|
||||
if (!requestedSize) {
|
||||
return imagePath;
|
||||
|
@ -123,8 +183,9 @@ function getImageWithSize(imagePath, requestedSize, imageSizes) {
|
|||
const [imgBlogUrl, imageName] = imagePath.split(STATIC_IMAGE_URL_PREFIX);
|
||||
|
||||
const sizeDirectoryName = prefixIfPresent('w', width) + prefixIfPresent('h', height);
|
||||
const formatPrefix = requestedFormat && imageTransform.canTransformToFormat(requestedFormat) ? `/format/${requestedFormat}` : '';
|
||||
|
||||
return [imgBlogUrl, STATIC_IMAGE_URL_PREFIX, `/size/${sizeDirectoryName}`, imageName].join('');
|
||||
return [imgBlogUrl, STATIC_IMAGE_URL_PREFIX, `/size/${sizeDirectoryName}`, formatPrefix, imageName].join('');
|
||||
}
|
||||
|
||||
function prefixIfPresent(prefix, string) {
|
||||
|
|
|
@ -58,11 +58,6 @@ module.exports = function (req, res, next) {
|
|||
const themeImageSizes = activeTheme.get().config('image_sizes');
|
||||
const imageSizes = _.merge({}, themeImageSizes, internalImageSizes, contentImageSizes);
|
||||
|
||||
// CASE: no image_sizes config (NOTE - unlikely to be reachable now we have content sizes)
|
||||
if (!imageSizes) {
|
||||
return redirectToOriginal();
|
||||
}
|
||||
|
||||
// build a new object with keys that match the strings used in size paths like "w640h480"
|
||||
const imageDimensions = {};
|
||||
Object.keys(imageSizes).forEach((size) => {
|
||||
|
@ -106,16 +101,16 @@ module.exports = function (req, res, next) {
|
|||
return redirectToOriginal();
|
||||
}
|
||||
|
||||
// exit early if sharp isn't installed to avoid extra file reads
|
||||
if (!imageTransform.canTransformFiles()) {
|
||||
return redirectToOriginal();
|
||||
}
|
||||
|
||||
storageInstance.exists(req.url).then((exists) => {
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
// exit early if sharp isn't installed to avoid extra file reads
|
||||
if (!imageTransform.canTransformFiles()) {
|
||||
return redirectToOriginal();
|
||||
}
|
||||
|
||||
const {dir, name, ext} = path.parse(imagePath);
|
||||
const [imageNameMatched, imageName, imageNumber] = name.match(/^(.+?)(-\d+)?$/) || [null];
|
||||
|
||||
|
@ -148,7 +143,8 @@ module.exports = function (req, res, next) {
|
|||
}).then(() => {
|
||||
if (format) {
|
||||
// File extension won't match the new format, so we need to update the Content-Type header manually here
|
||||
res.type(format);
|
||||
// Express JS still uses an out of date mime package, which doesn't support avif
|
||||
res.type(format === 'avif' ? 'image/avif' : format);
|
||||
}
|
||||
next();
|
||||
}).catch(function (err) {
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
"@tryghost/errors": "1.2.14",
|
||||
"@tryghost/express-dynamic-redirects": "0.0.0",
|
||||
"@tryghost/helpers": "1.1.71",
|
||||
"@tryghost/image-transform": "1.1.0",
|
||||
"@tryghost/image-transform": "1.2.0",
|
||||
"@tryghost/job-manager": "0.0.0",
|
||||
"@tryghost/kg-card-factory": "3.1.3",
|
||||
"@tryghost/kg-default-atoms": "3.1.2",
|
||||
|
|
|
@ -135,6 +135,7 @@ describe('{{img_url}} helper', function () {
|
|||
should.exist(rendered);
|
||||
rendered.should.equal('/content/images/size/w400/my-coole-img.jpg');
|
||||
});
|
||||
|
||||
it('should output the correct url for protocol relative urls', function () {
|
||||
const rendered = img_url('//website.com/whatever/my-coole-img.jpg', {
|
||||
hash: {
|
||||
|
@ -153,6 +154,7 @@ describe('{{img_url}} helper', function () {
|
|||
should.exist(rendered);
|
||||
rendered.should.equal('//website.com/whatever/my-coole-img.jpg');
|
||||
});
|
||||
|
||||
it('should output the correct url for relative paths', function () {
|
||||
const rendered = img_url('/content/images/my-coole-img.jpg', {
|
||||
hash: {
|
||||
|
@ -190,5 +192,286 @@ describe('{{img_url}} helper', function () {
|
|||
should.exist(rendered);
|
||||
rendered.should.equal('content/images/size/w400/my-coole-img.jpg');
|
||||
});
|
||||
|
||||
it('ignores invalid size options', function () {
|
||||
const rendered = img_url('/content/images/author-image-relative-url.png', {hash: {size: 'invalid-size'}});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('/content/images/author-image-relative-url.png');
|
||||
logWarnStub.called.should.be.false();
|
||||
});
|
||||
|
||||
it('ignores misconfigured sizes', function () {
|
||||
const rendered = img_url('/content/images/author-image-relative-url.png', {
|
||||
hash: {
|
||||
size: 'medium'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
medium: {typo: 600}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('/content/images/author-image-relative-url.png');
|
||||
logWarnStub.called.should.be.false();
|
||||
});
|
||||
|
||||
it('ignores format if size is missing', function () {
|
||||
const rendered = img_url('/content/images/author-image-relative-url.png', {
|
||||
hash: {
|
||||
format: 'webp'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
w600: {width: 600}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('/content/images/author-image-relative-url.png');
|
||||
logWarnStub.called.should.be.false();
|
||||
});
|
||||
|
||||
it('adds format and size options', function () {
|
||||
const rendered = img_url('/content/images/author-image-relative-url.png', {
|
||||
hash: {
|
||||
size: 'w600',
|
||||
format: 'webp'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
w600: {width: 600}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('/content/images/size/w600/format/webp/author-image-relative-url.png');
|
||||
logWarnStub.called.should.be.false();
|
||||
});
|
||||
|
||||
it('ignores invalid formats', function () {
|
||||
const rendered = img_url('/content/images/author-image-relative-url.png', {
|
||||
hash: {
|
||||
size: 'w600',
|
||||
format: 'invalid'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
w600: {width: 600}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('/content/images/size/w600/author-image-relative-url.png');
|
||||
logWarnStub.called.should.be.false();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Unsplash', function () {
|
||||
before(function () {
|
||||
configUtils.set({url: 'http://localhost:65535/'});
|
||||
});
|
||||
|
||||
after(function () {
|
||||
configUtils.restore();
|
||||
});
|
||||
|
||||
it('works without size option', function () {
|
||||
const rendered = img_url('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000', {
|
||||
hash: {},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
medium: {
|
||||
width: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000');
|
||||
});
|
||||
|
||||
it('can change the output width', function () {
|
||||
const rendered = img_url('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000', {
|
||||
hash: {
|
||||
size: 'medium'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
medium: {
|
||||
width: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=400');
|
||||
});
|
||||
|
||||
it('can change the output height', function () {
|
||||
const rendered = img_url('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80', {
|
||||
hash: {
|
||||
size: 'medium'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
medium: {
|
||||
height: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&h=400');
|
||||
});
|
||||
|
||||
it('ignores invalid image size configurations', function () {
|
||||
const rendered = img_url('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80', {
|
||||
hash: {
|
||||
size: 'medium'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
medium: {
|
||||
typo: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80');
|
||||
});
|
||||
|
||||
it('ignores invalid urls', function () {
|
||||
const invalid = ':https://images.unsplash.com/test';
|
||||
const rendered = img_url(invalid, {
|
||||
hash: {
|
||||
size: 'medium'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
medium: {
|
||||
width: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal(invalid);
|
||||
});
|
||||
|
||||
it('ignores invalid sizes', function () {
|
||||
const rendered = img_url('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000', {
|
||||
hash: {
|
||||
size: 'invalid'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
medium: {
|
||||
width: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000');
|
||||
});
|
||||
|
||||
it('can change the output format', function () {
|
||||
const rendered = img_url('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000', {
|
||||
hash: {
|
||||
format: 'webp'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
medium: {
|
||||
width: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=webp&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000');
|
||||
});
|
||||
|
||||
it('ignores invalid formats', function () {
|
||||
const rendered = img_url('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000', {
|
||||
hash: {
|
||||
format: 'invalid'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
medium: {
|
||||
width: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000');
|
||||
});
|
||||
|
||||
it('transforms jpeg to jpg', function () {
|
||||
const rendered = img_url('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=auto&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000', {
|
||||
hash: {
|
||||
format: 'jpeg'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
medium: {
|
||||
width: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000');
|
||||
});
|
||||
|
||||
it('can change the output format and size', function () {
|
||||
const rendered = img_url('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000', {
|
||||
hash: {
|
||||
format: 'webp',
|
||||
size: 'medium'
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
image_sizes: {
|
||||
medium: {
|
||||
width: 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=webp&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=400');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,9 +4,14 @@ const storage = require('../../../../../core/server/adapters/storage');
|
|||
const activeTheme = require('../../../../../core/frontend/services/theme-engine/active');
|
||||
const handleImageSizes = require('../../../../../core/frontend/web/middleware/handle-image-sizes.js');
|
||||
const imageTransform = require('@tryghost/image-transform');
|
||||
const config = require('../../../../../core/shared/config');
|
||||
|
||||
// @TODO make these tests lovely and non specific to implementation
|
||||
describe('handleImageSizes middleware', function () {
|
||||
this.afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('calls next immediately if the url does not match /size/something/', function (done) {
|
||||
const fakeReq = {
|
||||
url: '/size/something'
|
||||
|
@ -33,6 +38,32 @@ describe('handleImageSizes middleware', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls next immediately if the file extension is missing', function (done) {
|
||||
const fakeReq = {
|
||||
url: '/size/something/file'
|
||||
};
|
||||
// CASE: second thing middleware does is try to match to a regex
|
||||
fakeReq.url.match = function () {
|
||||
throw new Error('Should have exited immediately');
|
||||
};
|
||||
handleImageSizes(fakeReq, {}, function next() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls next immediately if the file has a trailing slash', function (done) {
|
||||
const fakeReq = {
|
||||
url: '/size/something/file.jpg/'
|
||||
};
|
||||
// CASE: second thing middleware does is try to match to a regex
|
||||
fakeReq.url.match = function () {
|
||||
throw new Error('Should have exited immediately');
|
||||
};
|
||||
handleImageSizes(fakeReq, {}, function next() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls next immediately if the url does not match /size/something/', function (done) {
|
||||
const fakeReq = {
|
||||
url: '/size//'
|
||||
|
@ -74,7 +105,18 @@ describe('handleImageSizes middleware', function () {
|
|||
return {
|
||||
l: {
|
||||
width: 1000
|
||||
}
|
||||
},
|
||||
m: {
|
||||
width: 1000,
|
||||
height: 200
|
||||
},
|
||||
n: {
|
||||
height: 1000
|
||||
},
|
||||
h100: {
|
||||
height: 100
|
||||
},
|
||||
missing: {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -85,10 +127,6 @@ describe('handleImageSizes middleware', function () {
|
|||
resizeFromBufferStub = sinon.stub(imageTransform, 'resizeFromBuffer').resolves(Buffer.from([]));
|
||||
});
|
||||
|
||||
this.afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('redirects for invalid format extension', function (done) {
|
||||
const fakeReq = {
|
||||
url: '/size/w1000/format/test/image.jpg',
|
||||
|
@ -112,6 +150,52 @@ describe('handleImageSizes middleware', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('redirects for invalid sizes', function (done) {
|
||||
const fakeReq = {
|
||||
url: '/size/w123/image.jpg',
|
||||
originalUrl: '/blog/content/images/size/w123/image.jpg'
|
||||
};
|
||||
const fakeRes = {
|
||||
redirect(url) {
|
||||
try {
|
||||
url.should.equal('/blog/content/images/image.jpg');
|
||||
} catch (e) {
|
||||
return done(e);
|
||||
}
|
||||
done();
|
||||
}
|
||||
};
|
||||
handleImageSizes(fakeReq, fakeRes, function next(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
done(new Error('Should not have called next'));
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects for invalid configured size', function (done) {
|
||||
const fakeReq = {
|
||||
url: '/size/missing/image.jpg',
|
||||
originalUrl: '/blog/content/images/size/missing/image.jpg'
|
||||
};
|
||||
const fakeRes = {
|
||||
redirect(url) {
|
||||
try {
|
||||
url.should.equal('/blog/content/images/image.jpg');
|
||||
} catch (e) {
|
||||
return done(e);
|
||||
}
|
||||
done();
|
||||
}
|
||||
};
|
||||
handleImageSizes(fakeReq, fakeRes, function next(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
done(new Error('Should not have called next'));
|
||||
});
|
||||
});
|
||||
|
||||
it('returns original URL if file is empty', function (done) {
|
||||
dummyStorage.exists = async function (path) {
|
||||
if (path === '/blank_o.png') {
|
||||
|
@ -148,6 +232,58 @@ describe('handleImageSizes middleware', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('returns original URL if unsupported storage adapter', function (done) {
|
||||
dummyStorage.saveRaw = undefined;
|
||||
|
||||
const fakeReq = {
|
||||
url: '/size/w1000/blank.png',
|
||||
originalUrl: '/blog/content/images/size/w1000/blank.png'
|
||||
};
|
||||
const fakeRes = {
|
||||
redirect(url) {
|
||||
try {
|
||||
url.should.equal('/blog/content/images/blank.png');
|
||||
} catch (e) {
|
||||
return done(e);
|
||||
}
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
handleImageSizes(fakeReq, fakeRes, function next(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
done(new Error('Should not have called next'));
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects if sharp is not installed', function (done) {
|
||||
sinon.stub(imageTransform, 'canTransformFiles').returns(false);
|
||||
|
||||
const fakeReq = {
|
||||
url: '/size/w1000/blank.png',
|
||||
originalUrl: '/blog/content/images/size/w1000/blank.png'
|
||||
};
|
||||
const fakeRes = {
|
||||
redirect(url) {
|
||||
try {
|
||||
url.should.equal('/blog/content/images/blank.png');
|
||||
} catch (e) {
|
||||
return done(e);
|
||||
}
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
handleImageSizes(fakeReq, fakeRes, function next(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
done(new Error('Should not have called next'));
|
||||
});
|
||||
});
|
||||
|
||||
it('continues if file exists', function (done) {
|
||||
dummyStorage.exists = async function (path) {
|
||||
if (path === '/size/w1000/blank.png') {
|
||||
|
@ -182,8 +318,8 @@ describe('handleImageSizes middleware', function () {
|
|||
const spy = sinon.spy(dummyStorage, 'read');
|
||||
|
||||
const fakeReq = {
|
||||
url: '/size/w1000/blank.png',
|
||||
originalUrl: '/size/w1000/blank.png'
|
||||
url: '/size/h100/blank.png',
|
||||
originalUrl: '/size/h100/blank.png'
|
||||
};
|
||||
const fakeRes = {
|
||||
redirect(url) {
|
||||
|
@ -415,6 +551,40 @@ describe('handleImageSizes middleware', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('can format PNG to AVIF', function (done) {
|
||||
dummyStorage.exists = async function (path) {
|
||||
return false;
|
||||
};
|
||||
dummyStorage.read = async function (path) {
|
||||
return buffer;
|
||||
};
|
||||
|
||||
const fakeReq = {
|
||||
url: '/size/w1000/format/avif/blank.png',
|
||||
originalUrl: '/size/w1000/format/avif/blank.png'
|
||||
};
|
||||
const fakeRes = {
|
||||
redirect(url) {
|
||||
done(new Error('Should not have called redirect'));
|
||||
},
|
||||
type: function () {}
|
||||
};
|
||||
const typeStub = sinon.spy(fakeRes, 'type');
|
||||
|
||||
handleImageSizes(fakeReq, fakeRes, function next(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
try {
|
||||
resizeFromBufferStub.calledOnceWithExactly(buffer, {withoutEnlargement: true, width: 1000, format: 'avif'}).should.be.true();
|
||||
typeStub.calledOnceWithExactly('image/avif').should.be.true();
|
||||
} catch (e) {
|
||||
return done(e);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can format GIF to WEBP', function (done) {
|
||||
dummyStorage.exists = async function (path) {
|
||||
return false;
|
||||
|
|
|
@ -1703,10 +1703,10 @@
|
|||
"@tryghost/errors" "^1.2.14"
|
||||
"@tryghost/request" "^0.1.28"
|
||||
|
||||
"@tryghost/image-transform@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/image-transform/-/image-transform-1.1.0.tgz#23f1abc7eca781cd65e6c99d1bfc463a744b40c1"
|
||||
integrity sha512-8gSTIqPOnEBSOMc1s9xR43l8U0z7gorPVbBQWMQ6ZXM3hFAT8UubLdvqpO3OBDbsi115DRxVtH4RkkZVMHBfIQ==
|
||||
"@tryghost/image-transform@1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/image-transform/-/image-transform-1.2.0.tgz#fe00ac76c412ce9bfa29030f2869f118e753c1e2"
|
||||
integrity sha512-Y2KTbhUttdXp2xvA3hKfjiRV3WcbEkmeI+ipYxrdAdp8jwj97BeF//5lTakq2zb+0L0HE9EaYsHgbdvW3YTHhw==
|
||||
dependencies:
|
||||
"@tryghost/errors" "^1.2.1"
|
||||
bluebird "^3.7.2"
|
||||
|
|
Loading…
Add table
Reference in a new issue