Skip to content

Commit

Permalink
refactor: generate image with sharp
Browse files Browse the repository at this point in the history
  • Loading branch information
fpasquet committed Apr 10, 2024
1 parent 6691ab1 commit 33714ca
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 45 deletions.
19 changes: 11 additions & 8 deletions src/helpers/assetHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,48 @@ export const getCoverPath = ({
path = '/imgs/default-cover.jpg',
format,
device,
pixelRatio = 1,
pixelRatio,
}: {
path?: string;
format: ImageFormatEnum;
device: DeviceEnum;
pixelRatio?: number;
pixelRatio: number;
}): string => {
const isProd: boolean = process.env.NODE_ENV === 'production';
const directoryPath = dirname(path);
const filename = basename(path, extname(path));
const imageFormat = IMAGE_FORMATS[device][format];

const pathFile = isProd
? `${directoryPath}/${filename}-w${imageFormat.width * pixelRatio}-h${imageFormat.height * pixelRatio}.avif`
: `${path}?width=${imageFormat.width * pixelRatio}&height=${imageFormat.height * pixelRatio}&format=avif`;
? `${directoryPath}/${filename}-w${imageFormat.width}-h${imageFormat.height}-x${pixelRatio}.avif`
: `${path}?width=${imageFormat.width}&height=${imageFormat.height}&pixelRatio=${pixelRatio}&format=avif`;

return getPathFile(pathFile);
};

export const getSrcSet = (options: Parameters<typeof getCoverPath>[0]): string =>
`${getCoverPath(options)} x${options.pixelRatio}`;

export const getCover = (post: TransformedPostDataWithTransformedAuthors, format: ImageFormatEnum): PictureProps => ({
sources: [
{
media: '(max-width: 571px)',
srcSet: `${getCoverPath({
srcSet: getSrcSet({
path: post.cover?.path,
format,
pixelRatio: 2,
device: DeviceEnum.MOBILE,
})} 2x`,
}),
type: 'image/jpeg',
},
{
media: '(min-width: 572px)',
srcSet: `${getCoverPath({
srcSet: getSrcSet({
path: post.cover?.path,
format,
pixelRatio: 2,
device: DeviceEnum.DESKTOP,
})} 2x`,
}),
type: 'image/jpeg',
},
],
Expand Down
68 changes: 35 additions & 33 deletions src/helpers/generateImageFormats.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,66 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { basename, dirname, extname, resolve } from 'node:path';
import Sharp, { FormatEnum } from 'sharp';
import Sharp, { FormatEnum, OutputInfo } from 'sharp';

import { DeviceEnum, IMAGE_FORMATS } from '@/constants';
import { getPosts } from '@/helpers/markdownContentManagerHelper';

const resizeImage = async (options: {
imagePathOrBuffer: string | Buffer;
imagePath: string;
directoryPath: string;
filename: string;
width: number;
height: number;
pixelRatio: number;
extension: keyof FormatEnum;
}): Promise<void> => {
const transformedImage = Sharp(options.imagePathOrBuffer, { failOn: 'none', animated: true })
.resize({ width: options.width, height: options.height })
.toFormat(options.extension);

const transformedBuffer = await transformedImage.toBuffer();

}): Promise<OutputInfo> => {
const width = options.width * options.pixelRatio;
const height = options.height * options.pixelRatio;
const imageDestPath = resolve(
options.directoryPath,
`${options.filename}-w${options.width}-h${options.height}.${options.extension}`
`${options.filename}-w${options.width}-h${options.height}-x${options.pixelRatio}.${options.extension}`
);
writeFileSync(imageDestPath, transformedBuffer);

const sharpImage = Sharp(options.imagePath, { failOn: 'none', animated: true });
const metadata = await sharpImage.metadata();

if (metadata?.width && metadata?.height && metadata.width < width && metadata.height < height) {
console.info(
`The image "${options.imagePath}" is too small, you need to enlarge it to be able to resize it (${options.width}x${options.height})`
);
}

const transformedImage = sharpImage.resize({ width, height, withoutEnlargement: true }).toFormat(options.extension);
return transformedImage.toFile(imageDestPath);
};

export const generateImageFormats = async (): Promise<void> => {
const posts = getPosts();

const transformedBufferPromises: Promise<void>[] = [];
performance.mark('generate-image-formats-start');

const transformedBufferPromises: Promise<OutputInfo>[] = [];
const covers = posts.filter((post) => post.cover?.path).map((post) => post.cover?.path);
covers.push('/imgs/default-cover.jpg');

performance.mark('generate-image-formats-start');
for (const cover of covers) {
const imagePath = resolve(process.cwd(), 'public', cover!.slice(1) as string);
for (const path of covers) {
const imagePath = resolve(process.cwd(), 'public', path!.slice(1) as string);
const directoryPath = dirname(imagePath);
const filename = basename(imagePath, extname(imagePath));
const extension = 'avif';

const originalImageBuffer = readFileSync(imagePath);

for (const device of Object.values(DeviceEnum)) {
for (const pixelRatio of [1, 2]) {
for (const format of Object.values(IMAGE_FORMATS[device])) {
const width = format.width * pixelRatio;
const height = format.height * pixelRatio;
transformedBufferPromises.push(
resizeImage({
imagePathOrBuffer: originalImageBuffer,
filename,
directoryPath,
width,
height,
extension,
})
);
}
for (const format of Object.values(IMAGE_FORMATS[device])) {
transformedBufferPromises.push(
resizeImage({
imagePath,
filename,
directoryPath,
width: format.width,
height: format.height,
pixelRatio: 2,
extension,
})
);
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/middlewares/imageMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ export const imageMiddleware = async (req: Request, res: Response): Promise<unkn
const params: {
width?: number;
height?: number;
pixelRatio?: number;
format?: FormatEnum;
quality?: number;
} = {
width: urlSearchParams.get('width') ? parseInt(urlSearchParams.get('width') as string) : undefined,
height: urlSearchParams.get('height') ? parseInt(urlSearchParams.get('height') as string) : undefined,
pixelRatio: urlSearchParams.get('pixelRatio') ? parseInt(urlSearchParams.get('pixelRatio') as string) : undefined,
format: (urlSearchParams.get('format') as FormatEnum) ?? undefined,
quality: urlSearchParams.get('quality') ? parseInt(urlSearchParams.get('quality') as string) : undefined,
};
Expand All @@ -45,7 +47,9 @@ export const imageMiddleware = async (req: Request, res: Response): Promise<unkn
let transformedImage = Sharp(originalImageBuffer, { failOn: 'none', animated: true });

if (params.width && params.height) {
transformedImage = transformedImage.resize({ width: params.width, height: params.height });
const width = params.pixelRatio ? params.pixelRatio * params.width : params.width;
const height = params.pixelRatio ? params.pixelRatio * params.height : params.height;
transformedImage = transformedImage.resize({ width, height });
}

if (params.format) {
Expand Down
4 changes: 2 additions & 2 deletions src/translations/en.translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@
"title": "Agile methodologies: articles, project sheets and tutorials on our blog"
},
"title": "Our articles and feedbacks about agile methodologies used for web development projects",
"description": "<p><strong>The Agile methodology</strong> is an IT project management approach, or rather a&nbsp;<strong>“product management” approach</strong>, which allows the team to manage a project by breaking it down into short development cycles, called sprints. Today, the word Agile can refer to these values as well as the implementation frameworks, including: Scrum, Kanban, Safe, Lean… In this category, find all the articles, feedbacks and tutorials from our Agile team about <strong>Scrum, the job of Scrum Master and Product Manager and their advices on how to manage a sprint, which agile method to choose depending on the context or even on product prioritization! Good reading!</strong></p>",
"description": "<p><strong>The Agile methodology</strong> is an IT project management approach, or rather a <strong>“product management” approach</strong>, which allows the team to manage a project by breaking it down into short development cycles, called sprints. Today, the word Agile can refer to these values as well as the implementation frameworks, including: Scrum, Kanban, Safe, Lean… In this category, find all the articles, feedbacks and tutorials from our Agile team about <strong>Scrum, the job of Scrum Master and Product Manager and their advices on how to manage a sprint, which agile method to choose depending on the context or even on product prioritization! Good reading!</strong></p>",
"expertise": {
"title": "What is the most popular agile methodology?",
"description": "<strong>Scrum is the most popular agile framework in 2023</strong>. It is used by companies both as sole agile method and as hybrid agile method. <strong>Its principle of dividing work into short periods of generally 2 weeks called \"sprints\"</strong>&nbsp;<strong>makes it possible to quickly come with a first version of a viable product</strong>. Each sprint is validated one after the other by the client, which allows work to be pursued on solid foundations, the following sprints aiming to further improve the product. The process is therefore significantly more productive. At Eleven Labs, we mainly work with the agile Scrum methodology and support our clients in <strong>managing their digital products</strong>, in training teams in the agile Scrum methodology or in <strong>agile coaching</strong>."
"description": "<strong>Scrum is the most popular agile framework in 2023</strong>. It is used by companies both as sole agile method and as hybrid agile method. <strong>Its principle of dividing work into short periods of generally 2 weeks called \"sprints\" makes it possible to quickly come with a first version of a viable product</strong>. Each sprint is validated one after the other by the client, which allows work to be pursued on solid foundations, the following sprints aiming to further improve the product. The process is therefore significantly more productive. At Eleven Labs, we mainly work with the agile Scrum methodology and support our clients in <strong>managing their digital products</strong>, in training teams in the agile Scrum methodology or in <strong>agile coaching</strong>."
},
"post_list_title": "All our articles on agile methodologies"
},
Expand Down
2 changes: 1 addition & 1 deletion src/translations/fr.translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"description": "Tous nos articles de blog, REX et tutoriels autour du développement Javascript : React.js, Node.js, Nest.js, Next.js, Vue.js, Svelte.js"
},
"title": "Nos articles et retours d’expérience en développement Javascript",
"description": "<strong>Javascript est un langage de programmation dynamique complet et doté d’une incroyable flexibilité !&nbsp;</strong>Ce n’est pas pour rien que ce langage est aujourd'hui le plus utilisé par les développeurs à travers le monde. Dans cette catégorie, retrouvez tous les articles, retours d’expérience et tutoriels de nos astronautes autour de <strong>React.js, <a href=\"https://www.eleven-labs.com/dev-web/node-js/\">Node.js</a>, Nest.js, Next.js, Vue.js, Svelte.js. Vous y retrouverez également des outils à utiliser pour faciliter votre delivery, un guide pour implémenter votre propre Design System et bien plus encore ! Bonne lecture.</strong>",
"description": "<strong>Javascript est un langage de programmation dynamique complet et doté d’une incroyable flexibilité !</strong> Ce n’est pas pour rien que ce langage est aujourd'hui le plus utilisé par les développeurs à travers le monde. Dans cette catégorie, retrouvez tous les articles, retours d’expérience et tutoriels de nos astronautes autour de <strong>React.js, <a href=\"https://www.eleven-labs.com/dev-web/node-js/\">Node.js</a>, Nest.js, Next.js, Vue.js, Svelte.js. Vous y retrouverez également des outils à utiliser pour faciliter votre delivery, un guide pour implémenter votre propre Design System et bien plus encore ! Bonne lecture.</strong>",
"expertise": {
"title": "Quels types d’applications peuvent être développées en Javascript ?",
"description": "Aujourd’hui, il est quasiment possible de tout faire avec le langage Javascript : applications web et mobile, progressive web apps, logiciels, applications métier, sites web fullstack Javascript, APIs backend, jeux vidéos, applications TV et bien d’autres. En bref, Javascript est devenu en quelques années le langage central du web et celui le plus utilisé sur GitHub. Choisir Javascript pour son projet de développement web est donc, dans la majorité des cas, une bonne idée !",
Expand Down

0 comments on commit 33714ca

Please sign in to comment.