Skip to content

Commit

Permalink
Add gif support (cyrilwanner#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyrilwanner committed Aug 8, 2020
1 parent 649dc09 commit c37714a
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 13 deletions.
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"test:watch": "npm test -- --watch"
},
"dependencies": {
"@wasm-codecs/gifsicle": "^1.0.0",
"@wasm-codecs/mozjpeg": "^1.0.1",
"@wasm-codecs/oxipng": "^1.0.1",
"file-loader": "^6.0.0",
Expand Down
33 changes: 31 additions & 2 deletions src/optimize/gif.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
import sharp from 'sharp';
import encode from '@wasm-codecs/gifsicle';
import { LoaderOptions } from '../options';
import { ImageOptions } from '../parseQuery';

/**
* Optimize a gif image using gifsicle
*
* @async
* @param {Buffer} image Input image
* @param {ImageOptions} imageOptions Image options
* @param {LoaderOptions['gifsicle']} [options] Gifsicle options
* @returns {Buffer} Optimized image
*/
const optimizeGif = async (image: Buffer, options?: LoaderOptions['gifsicle']): Promise<Buffer> => { // eslint-disable-line
const optimizeGif = async (
image: Buffer,
imageOptions: ImageOptions,
options?: LoaderOptions['gifsicle'],
): Promise<Buffer> => {
const encodeOptions = options || {};

if (imageOptions.resize) {
if (imageOptions.width) {
encodeOptions.width = imageOptions.width;
}

if (imageOptions.height) {
encodeOptions.height = imageOptions.height;
}
}

// optimize the image using gifsicle
return image;
const encodedImage = await encode(image, encodeOptions);

// fill missing resize values in case the image was resized
if (imageOptions.resize) {
const imageData = await sharp(encodedImage).metadata();
imageOptions.width = imageData.width; // eslint-disable-line no-param-reassign
imageOptions.height = imageData.height; // eslint-disable-line no-param-reassign
}

return encodedImage;
};

export default optimizeGif;
14 changes: 12 additions & 2 deletions src/optimize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import optimizePng from './png';
import optimizeWebp from './webp';
import optimizeSvg from './svg';
import optimizeGif from './gif';
import { ImageOptions } from '../parseQuery';

const sharpBasedOptimizers = {
jpeg: {
Expand All @@ -19,7 +20,10 @@ const sharpBasedOptimizers = {
handler: optimizeWebp,
optionsKey: 'webp',
},
} as Record<string, { handler: (image: Sharp, options?: unknown) => Promise<Buffer>; optionsKey: string }>;
} as Record<
string,
{ handler: (image: Sharp, imageOptions: ImageOptions, options?: unknown) => Promise<Buffer>; optionsKey: string }
>;

const rawBufferBasedOptimizers = {
svg: {
Expand All @@ -30,7 +34,10 @@ const rawBufferBasedOptimizers = {
handler: optimizeGif,
optionsKey: 'gifsicle',
},
} as Record<string, { handler: (image: Buffer, options?: unknown) => Promise<Buffer>; optionsKey: string }>;
} as Record<
string,
{ handler: (image: Buffer, imageOptions: ImageOptions, options?: unknown) => Promise<Buffer>; optionsKey: string }
>;

/**
* Optimize the given input image if an optimizer exists for the image format
Expand All @@ -46,18 +53,21 @@ const optimizeImage = async (
image: Sharp,
rawImage: Buffer,
format: string,
imageOptions: ImageOptions,
loaderOptions: LoaderOptions,
): Promise<Buffer> => {
if (sharpBasedOptimizers[format]) {
return sharpBasedOptimizers[format].handler(
image,
imageOptions,
(loaderOptions as Record<string, unknown>)[sharpBasedOptimizers[format].optionsKey],
);
}

if (rawBufferBasedOptimizers[format]) {
return rawBufferBasedOptimizers[format].handler(
rawImage,
imageOptions,
(loaderOptions as Record<string, unknown>)[rawBufferBasedOptimizers[format].optionsKey],
);
}
Expand Down
8 changes: 7 additions & 1 deletion src/optimize/jpeg.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { Sharp } from 'sharp';
import encode from '@wasm-codecs/mozjpeg';
import { LoaderOptions } from '../options';
import { ImageOptions } from '../parseQuery';

/**
* Optimize a jpeg image using @wasm-codecs/mozjpeg
*
* @async
* @param {Sharp} image Sharp wrapped input image
* @param {ImageOptions} imageOptions Image options
* @param {LoaderOptions['mozjpeg']} [options] Mozjpeg options
* @returns {Buffer} Optimized image
*/
const optimizeJpeg = async (image: Sharp, options?: LoaderOptions['mozjpeg']): Promise<Buffer> => {
const optimizeJpeg = async (
image: Sharp,
imageOptions: ImageOptions,
options?: LoaderOptions['mozjpeg'],
): Promise<Buffer> => {
// convert to raw image data
const {
data,
Expand Down
8 changes: 7 additions & 1 deletion src/optimize/png.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { Sharp } from 'sharp';
import encode from '@wasm-codecs/oxipng';
import { LoaderOptions } from '../options';
import { ImageOptions } from '../parseQuery';

/**
* Optimize a png image using @wasm-codecs/oxipng
*
* @async
* @param {Sharp} image Sharp wrapped input image
* @param {ImageOptions} imageOptions Image options
* @param {LoaderOptions['oxipng']} [options] Oxipng options
* @returns {Buffer} Optimized image
*/
const optimizePng = async (image: Sharp, options?: LoaderOptions['oxipng']): Promise<Buffer> => {
const optimizePng = async (
image: Sharp,
imageOptions: ImageOptions,
options?: LoaderOptions['oxipng'],
): Promise<Buffer> => {
// encode the image using @wasm-codecs/oxipng
return encode(await image.toBuffer(), options);
};
Expand Down
8 changes: 7 additions & 1 deletion src/optimize/svg.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import SVGO from 'svgo';
import { LoaderOptions } from '../options';
import { ImageOptions } from '../parseQuery';

/**
* Optimize a svg image using svgo
*
* @async
* @param {Buffer} image Input image
* @param {ImageOptions} imageOptions Image options
* @param {LoaderOptions['svgo']} [options] Svgo options
* @returns {Buffer} Optimized image
*/
const optimizeSvg = async (image: Buffer, options?: LoaderOptions['svgo']): Promise<Buffer> => {
const optimizeSvg = async (
image: Buffer,
imageOptions: ImageOptions,
options?: LoaderOptions['svgo'],
): Promise<Buffer> => {
// optimize the image using svgo
const svgo = new SVGO(options);
const { data } = await svgo.optimize(image.toString());
Expand Down
8 changes: 7 additions & 1 deletion src/optimize/webp.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { Sharp } from 'sharp';
import { LoaderOptions } from '../options';
import { ImageOptions } from '../parseQuery';

/**
* Optimize a webp image using sharp
*
* @async
* @param {Sharp} image Sharp wrapped input image
* @param {ImageOptions} imageOptions Image options
* @param {LoaderOptions['webp']} [options] Webp options
* @returns {Buffer} Optimized image
*/
const optimizeWebp = async (image: Sharp, options?: LoaderOptions['webp']): Promise<Buffer> => {
const optimizeWebp = async (
image: Sharp,
imageOptions: ImageOptions,
options?: LoaderOptions['webp'],
): Promise<Buffer> => {
// encode the image using sharp
return image.webp(options).toBuffer();
};
Expand Down
5 changes: 3 additions & 2 deletions src/options.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EncodeOptions as MozjpegOptions } from '@wasm-codecs/mozjpeg/lib/types';
import { EncodeOptions as OxipngOptions } from '@wasm-codecs/oxipng/lib/types';
import { EncodeOptions as GifsicleOptions } from '@wasm-codecs/gifsicle/lib/types';
import { WebpOptions } from 'sharp';

export interface LoaderOptions {
Expand All @@ -8,12 +9,12 @@ export interface LoaderOptions {
mozjpeg?: MozjpegOptions;
oxipng?: OxipngOptions;
webp?: WebpOptions;
gifsicle?: unknown;
gifsicle?: GifsicleOptions;
svgo?: Record<string, unknown>;
}

export interface OptionObject {
[key: string]: any; // eslint-disable-line
[key: string]: any; // eslint-disable-line
}

// default options for file- & url-loader
Expand Down
9 changes: 6 additions & 3 deletions src/processImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const processImage = async (
}

// resize image
if (imageOptions.resize) {
if (imageOptions.resize && imageMetadata.format !== 'gif') {
image = image.resize(imageOptions.width, imageOptions.height);

// fill missing resize values
Expand Down Expand Up @@ -64,8 +64,11 @@ const processImage = async (
}

// optimize image
if (imageOptions.optimize && imageMetadata.format) {
return { data: await optimizeImage(image, inputImage, imageMetadata.format, loaderOptions), info: imageMetadata };
if (imageMetadata.format && (imageOptions.optimize || (imageMetadata.format === 'gif' && imageOptions.resize))) {
return {
data: await optimizeImage(image, inputImage, imageMetadata.format, imageOptions, loaderOptions),
info: imageMetadata,
};
}

// for svg, return input image if it was not optimized
Expand Down

0 comments on commit c37714a

Please sign in to comment.