mirror of
https://github.com/strapi/strapi.git
synced 2025-08-24 08:38:52 +00:00
enhancement: use file path in place of streams to optimize sharp fragmentation & libvips caching (#20080)
Co-authored-by: DMehaffy <derrickmehaffy@gmail.com>
This commit is contained in:
parent
896ff28d88
commit
0742c5784d
@ -50,6 +50,7 @@ const getFileData = (filePath) => ({
|
|||||||
ext: '.png',
|
ext: '.png',
|
||||||
folder: null,
|
folder: null,
|
||||||
folderPath: '/',
|
folderPath: '/',
|
||||||
|
filepath: filePath,
|
||||||
getStream: () => fs.createReadStream(filePath),
|
getStream: () => fs.createReadStream(filePath),
|
||||||
hash: 'image_d9b4f84424',
|
hash: 'image_d9b4f84424',
|
||||||
height: 1000,
|
height: 1000,
|
||||||
|
@ -8,7 +8,7 @@ const { join } = require('path');
|
|||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
file: { bytesToKbytes, writableDiscardStream },
|
file: { bytesToKbytes },
|
||||||
} = require('@strapi/utils');
|
} = require('@strapi/utils');
|
||||||
const { getService } = require('../utils');
|
const { getService } = require('../utils');
|
||||||
|
|
||||||
@ -26,15 +26,21 @@ const writeStreamToFile = (stream, path) =>
|
|||||||
writeStream.on('error', reject);
|
writeStream.on('error', reject);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getMetadata = async (file) =>
|
const getMetadata = async (file) => {
|
||||||
new Promise((resolve, reject) => {
|
if (!file.filepath) {
|
||||||
const pipeline = sharp();
|
return new Promise((resolve, reject) => {
|
||||||
pipeline.metadata().then(resolve).catch(reject);
|
const pipeline = sharp();
|
||||||
file.getStream().pipe(pipeline);
|
pipeline.metadata().then(resolve).catch(reject);
|
||||||
});
|
file.getStream().pipe(pipeline);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return sharp(file.filepath).metadata();
|
||||||
|
};
|
||||||
|
|
||||||
const getDimensions = async (file) => {
|
const getDimensions = async (file) => {
|
||||||
const { width = null, height = null } = await getMetadata(file);
|
const { width = null, height = null } = await getMetadata(file);
|
||||||
|
|
||||||
return { width, height };
|
return { width, height };
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,18 +53,31 @@ const THUMBNAIL_RESIZE_OPTIONS = {
|
|||||||
const resizeFileTo = async (file, options, { name, hash }) => {
|
const resizeFileTo = async (file, options, { name, hash }) => {
|
||||||
const filePath = join(file.tmpWorkingDirectory, hash);
|
const filePath = join(file.tmpWorkingDirectory, hash);
|
||||||
|
|
||||||
await writeStreamToFile(file.getStream().pipe(sharp().resize(options)), filePath);
|
let newInfo;
|
||||||
|
if (!file.filepath) {
|
||||||
|
const transform = sharp()
|
||||||
|
.resize(options)
|
||||||
|
.on('info', (info) => {
|
||||||
|
newInfo = info;
|
||||||
|
});
|
||||||
|
|
||||||
|
await writeStreamToFile(file.getStream().pipe(transform), filePath);
|
||||||
|
} else {
|
||||||
|
newInfo = await sharp(file.filepath).resize(options).toFile(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { width, height, size } = newInfo;
|
||||||
|
|
||||||
const newFile = {
|
const newFile = {
|
||||||
name,
|
name,
|
||||||
hash,
|
hash,
|
||||||
ext: file.ext,
|
ext: file.ext,
|
||||||
mime: file.mime,
|
mime: file.mime,
|
||||||
|
filepath: filePath,
|
||||||
path: file.path || null,
|
path: file.path || null,
|
||||||
getStream: () => fs.createReadStream(filePath),
|
getStream: () => fs.createReadStream(filePath),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { width, height, size } = await getMetadata(newFile);
|
|
||||||
|
|
||||||
Object.assign(newFile, { width, height, size: bytesToKbytes(size), sizeInBytes: size });
|
Object.assign(newFile, { width, height, size: bytesToKbytes(size), sizeInBytes: size });
|
||||||
return newFile;
|
return newFile;
|
||||||
};
|
};
|
||||||
@ -89,12 +108,16 @@ const optimize = async (file) => {
|
|||||||
'upload'
|
'upload'
|
||||||
).getSettings();
|
).getSettings();
|
||||||
|
|
||||||
const newFile = { ...file };
|
const { format, size } = await getMetadata(file);
|
||||||
|
|
||||||
const { width, height, size, format } = await getMetadata(newFile);
|
|
||||||
|
|
||||||
if (sizeOptimization || autoOrientation) {
|
if (sizeOptimization || autoOrientation) {
|
||||||
const transformer = sharp();
|
let transformer;
|
||||||
|
if (!file.filepath) {
|
||||||
|
transformer = sharp();
|
||||||
|
} else {
|
||||||
|
transformer = sharp(file.filepath);
|
||||||
|
}
|
||||||
|
|
||||||
// reduce image quality
|
// reduce image quality
|
||||||
transformer[format]({ quality: sizeOptimization ? 80 : 100 });
|
transformer[format]({ quality: sizeOptimization ? 80 : 100 });
|
||||||
// rotate image based on EXIF data
|
// rotate image based on EXIF data
|
||||||
@ -103,24 +126,38 @@ const optimize = async (file) => {
|
|||||||
}
|
}
|
||||||
const filePath = join(file.tmpWorkingDirectory, `optimized-${file.hash}`);
|
const filePath = join(file.tmpWorkingDirectory, `optimized-${file.hash}`);
|
||||||
|
|
||||||
await writeStreamToFile(file.getStream().pipe(transformer), filePath);
|
let newInfo;
|
||||||
|
if (!file.filepath) {
|
||||||
|
transformer.on('info', (info) => {
|
||||||
|
newInfo = info;
|
||||||
|
});
|
||||||
|
|
||||||
|
await writeStreamToFile(file.getStream().pipe(transformer), filePath);
|
||||||
|
} else {
|
||||||
|
newInfo = await transformer.toFile(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { width: newWidth, height: newHeight, size: newSize } = newInfo;
|
||||||
|
|
||||||
|
const newFile = { ...file };
|
||||||
|
|
||||||
newFile.getStream = () => fs.createReadStream(filePath);
|
newFile.getStream = () => fs.createReadStream(filePath);
|
||||||
|
newFile.filepath = filePath;
|
||||||
|
|
||||||
|
if (newSize > size) {
|
||||||
|
// Ignore optimization if output is bigger than original
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign(newFile, {
|
||||||
|
width: newWidth,
|
||||||
|
height: newHeight,
|
||||||
|
size: bytesToKbytes(newSize),
|
||||||
|
sizeInBytes: newSize,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { width: newWidth, height: newHeight, size: newSize } = await getMetadata(newFile);
|
return file;
|
||||||
|
|
||||||
if (newSize > size) {
|
|
||||||
// Ignore optimization if output is bigger than original
|
|
||||||
return { ...file, width, height, size: bytesToKbytes(size), sizeInBytes: size };
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.assign(newFile, {
|
|
||||||
width: newWidth,
|
|
||||||
height: newHeight,
|
|
||||||
size: bytesToKbytes(newSize),
|
|
||||||
sizeInBytes: newSize,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_BREAKPOINTS = {
|
const DEFAULT_BREAKPOINTS = {
|
||||||
@ -187,16 +224,22 @@ const isSupportedImage = (...args) => {
|
|||||||
/**
|
/**
|
||||||
* Applies a simple image transformation to see if the image is faulty/corrupted.
|
* Applies a simple image transformation to see if the image is faulty/corrupted.
|
||||||
*/
|
*/
|
||||||
const isFaultyImage = (file) =>
|
const isFaultyImage = async (file) => {
|
||||||
new Promise((resolve) => {
|
if (!file.filepath) {
|
||||||
file
|
return new Promise((resolve, reject) => {
|
||||||
.getStream()
|
const pipeline = sharp();
|
||||||
.pipe(sharp().rotate())
|
pipeline.stats().then(resolve).catch(reject);
|
||||||
.on('error', () => resolve(true))
|
file.getStream().pipe(pipeline);
|
||||||
.pipe(writableDiscardStream())
|
});
|
||||||
.on('error', () => resolve(true))
|
}
|
||||||
.on('close', () => resolve(false));
|
|
||||||
});
|
try {
|
||||||
|
await sharp(file.filepath).stats();
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const isOptimizableImage = async (file) => {
|
const isOptimizableImage = async (file) => {
|
||||||
let format;
|
let format;
|
||||||
|
@ -15,10 +15,17 @@ module.exports = ({ strapi }) => ({
|
|||||||
file.stream = file.getStream();
|
file.stream = file.getStream();
|
||||||
await strapi.plugin('upload').provider.uploadStream(file);
|
await strapi.plugin('upload').provider.uploadStream(file);
|
||||||
delete file.stream;
|
delete file.stream;
|
||||||
|
|
||||||
|
if ('filepath' in file) {
|
||||||
|
delete file.filepath;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
file.buffer = await streamToBuffer(file.getStream());
|
file.buffer = await streamToBuffer(file.getStream());
|
||||||
await strapi.plugin('upload').provider.upload(file);
|
await strapi.plugin('upload').provider.upload(file);
|
||||||
delete file.buffer;
|
delete file.buffer;
|
||||||
|
if ('filepath' in file) {
|
||||||
|
delete file.filepath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -163,6 +163,8 @@ module.exports = ({ strapi }) => ({
|
|||||||
tmpWorkingDirectory: file.tmpWorkingDirectory,
|
tmpWorkingDirectory: file.tmpWorkingDirectory,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
currentFile.filepath = file.path;
|
||||||
currentFile.getStream = () => fs.createReadStream(file.path);
|
currentFile.getStream = () => fs.createReadStream(file.path);
|
||||||
|
|
||||||
const { optimize, isImage, isFaultyImage, isOptimizableImage } = strapi
|
const { optimize, isImage, isFaultyImage, isOptimizableImage } = strapi
|
||||||
|
Loading…
x
Reference in New Issue
Block a user