Skip to content

Instantly share code, notes, and snippets.

@yikZero
Created March 25, 2024 09:11
Show Gist options
  • Save yikZero/55f1189546cd34768682c3f8543e3935 to your computer and use it in GitHub Desktop.
Save yikZero/55f1189546cd34768682c3f8543e3935 to your computer and use it in GitHub Desktop.
Simplified version of plaiceholder, just support base64 and metadata
import sharp, { type Metadata, type Sharp } from 'sharp';
type SharpFormatOptions = Parameters<Sharp['toFormat']>;
type SharpModulateOptions = NonNullable<Parameters<Sharp['modulate']>[0]>;
export type GetPlaceholderSrc = Buffer;
export interface GetPlaceholderOptions extends SharpModulateOptions {
size?: number;
format?: SharpFormatOptions;
}
export interface GetPlaiceholderReturn {
metadata: Omit<Metadata, 'width' | 'height'> &
Required<Pick<Metadata, 'width' | 'height'>>;
base64: string;
}
export const getPlaceholder = async (
src: GetPlaceholderSrc,
{ size = 8, format = ['png'] }: GetPlaceholderOptions = {},
) => {
const metadata = await sharp(src)
.metadata()
.then(({ width, height, ...metadata }) => {
if (!width || !height) {
throw Error('Could not get required image metadata');
}
return { width, height, ...metadata };
});
const sizeMin = 4;
const sizeMax = 64;
const isSizeValid = sizeMin <= size && size <= sizeMax;
!isSizeValid &&
console.error(
['Please enter a `size` value between', sizeMin, 'and', sizeMax].join(
' ',
),
);
const pipeline = sharp(src)
.resize(size, size, {
fit: 'inside',
})
.toFormat(...format);
const base64 = await pipeline
.clone()
.normalise()
.toBuffer({ resolveWithObject: true })
.then(
({ data, info }) =>
`data:image/${info.format};base64,${data.toString('base64')}`,
)
.catch((err) => {
console.error('base64 generation failed', err);
throw err;
});
return {
base64,
metadata,
};
};
@yikZero
Copy link
Author

yikZero commented Mar 25, 2024

Prepare

Sharp

pnpm

pnpm add sharp

npm

npm i sharp

Usgae

With Next.js

import { getPlaceholder } from '@/lib/blur-placeholder';
import NextImage, { ImageProps } from 'next/image';

export default async function Image(props: ImageProps) {
  const { src } = props;

  if (typeof src === 'string') {
    const buffer = await fetch(src).then(async (res) => {
      return Buffer.from(await res.arrayBuffer());
    });

    const { base64, metadata } = await getPlaceholder(buffer);
    return (
      <NextImage
        width={metadata.width}
        height={metadata.height}
        placeholder="blur"
        blurDataURL={base64}
        {...props}
      />
    );
  } else return <NextImage {...props} />;
}

@yikZero
Copy link
Author

yikZero commented Mar 25, 2024

One purpose is also to work with contentlayer and to solve the plaiceholder v2.5.0 vercel build error.

@yikZero
Copy link
Author

yikZero commented Apr 7, 2024

Vercel build error:

munmap_chunk(): invalid pointer
Error: Command "pnpm run build" exited with SIGABRT

You can fix it by doing this:

const { withContentlayer } = require('next-contentlayer');

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack(config) {
    const sharpRule = {
      sharp: 'commonjs sharp',
    };

    if (Array.isArray(config.externals)) {
      config.externals.push(sharpRule);
    } else {
      config.externals = {
        ...config.externals,
        ...sharpRule,
      };
    }

    return config;
  },
  transpilePackages: ['sharp'],
};

module.exports = nextConfig;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment