Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add essential support for WebP/AVIF in @docusaurus/lqip-loader and @docusaurus/plugin-ideal-image #8686

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
"website/docusaurus.config.localized.json",
"*.xyz",
"*.docx",
"*.webp",
"*.avif",
"versioned_docs",
"*.min.*",
"jest/vendor"
Expand Down
2 changes: 1 addition & 1 deletion packages/docusaurus-plugin-ideal-image/src/deps.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ declare module '@endiliey/react-ideal-image' {
width: number;
src?: string;
size?: number;
format?: 'webp' | 'jpeg' | 'png' | 'gif';
format?: 'webp' | 'jpeg' | 'png' | 'gif' | 'avif';
};

type ThemeKey = 'placeholder' | 'img' | 'icon' | 'noscript';
Expand Down
7 changes: 6 additions & 1 deletion packages/docusaurus-plugin-ideal-image/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,19 @@ export default function pluginIdealImage(
return {};
}

const rulesRegex = new RegExp(
`\\.(?:png|jpe?g${options.enableWebpAvif ? '|webp|avif' : ''})$`,
'i',
);

return {
mergeStrategy: {
'module.rules': 'prepend',
},
module: {
rules: [
{
test: /\.(?:png|jpe?g)$/i,
test: rulesRegex,
use: [
require.resolve('@docusaurus/lqip-loader'),
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,24 @@ declare module '@docusaurus/plugin-ideal-image' {
* Tip: use network throttling in your browser to simulate slow networks.
*/
disableInDev?: boolean;
/**
* You can enable this plugin for WebP/AVIF images
* by setting this to `true`.
* Note: the default is `false` to keep backward compatibility.
*/
enableWebpAvif?: boolean;
};
}

declare module '@theme/IdealImage' {
import type {ComponentProps} from 'react';
import type {ImageWithLqip} from '@docusaurus/lqip-loader';

export type SrcType = {
width: number;
path?: string;
size?: number;
format?: 'webp' | 'jpeg' | 'png' | 'gif';
format?: 'webp' | 'jpeg' | 'png' | 'gif' | 'avif';
};

export type SrcImage = {
Expand All @@ -67,8 +74,11 @@ declare module '@theme/IdealImage' {
images: SrcType[];
};

export type IdealImageEnabledSrc = ImageWithLqip<SrcImage>;
export type IdealImageSrc = IdealImageEnabledSrc | {default: string} | string;

export interface Props extends ComponentProps<'img'> {
readonly img: {default: string} | {src: SrcImage; preSrc: string} | string;
readonly img: IdealImageSrc;
}
export default function IdealImage(props: Props): JSX.Element;
}
Binary file not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
44 changes: 38 additions & 6 deletions packages/lqip-loader/src/__tests__/lqip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import path from 'path';
import {base64} from '../lqip';

const imgPath = path.join(__dirname, '__fixtures__', 'endi.jpg');
const invalidPath = path.join(__dirname, '__fixtures__', 'docusaurus.svg');
const resolveFixturePath = (name: string) =>
path.join(__dirname, '__fixtures__', name);

const invalidPath = resolveFixturePath('docusaurus.svg');

describe('base64', () => {
it('rejects unknown or unsupported file format', async () => {
Expand All @@ -18,8 +20,38 @@ describe('base64', () => {
);
});

it('generates a valid base64', async () => {
const expectedBase64 = 'data:image/jpeg;base64,/9j/2wBDA';
await expect(base64(imgPath)).resolves.toContain(expectedBase64);
});
it.each([
['endi.jpg', 'data:image/jpeg;base64,/9j/2wBDA'],
// PNG's magic number (common to all PNGs)
['docusaurus.png', 'data:image/png;base64,iVBORw0KGgoA'],
[
'docusaurus.avif',
// cspell:disable-next-line
// AVIF's signature: \0\0\0\x1cftypavif\0\0\0\0avifmif1miaf
'data:image/avif;base64,AAAAHGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZ',
],
] as [string, string][])(
'generates a valid base64 for %s',
async (imgName, expectedBase64) => {
await expect(base64(resolveFixturePath(imgName))).resolves.toContain(
expectedBase64,
);
},
);

it.each([
[
'docusaurus.webp',
// WebP's magic number; expects size is less than 64kiB
// cspell:disable-next-line
/^data:image\/webp;base64,UklGR...AABXRUJQ/,
],
] as [string, RegExp][])(
'generates a valid base64 for %s (using regexp)',
async (imgName, expectedBase64) => {
await expect(base64(resolveFixturePath(imgName))).resolves.toMatch(
expectedBase64,
);
},
);
});
5 changes: 5 additions & 0 deletions packages/lqip-loader/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ type Options = {
palette: boolean;
};

export type ImageWithLqip<Source = string> = {
preSrc: string;
src: Source;
};

export default async function lqipLoader(
this: LoaderContext<Options>,
contentBuffer: Buffer,
Expand Down
2 changes: 2 additions & 0 deletions packages/lqip-loader/src/lqip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const SUPPORTED_MIMES: {[ext: string]: string} = {
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
png: 'image/png',
webp: 'image/webp',
avif: 'image/avif',
};

/**
Expand Down
14 changes: 14 additions & 0 deletions website/_dogfooding/_docs tests/tests/img-tests.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import docusaurusImport from '@site/static/img/docusaurus.png';

export const docusaurusRequire = require('@site/static/img/docusaurus.png');

import docusaurusWebPImport from './img/docusaurus.webp';

import docusaurusAVIFImport from './img/docusaurus.avif';

![URL encoded image](./img/oss_logo%20%282%29.png)

## Regular images
Expand All @@ -20,6 +24,16 @@ export const docusaurusRequire = require('@site/static/img/docusaurus.png');

## Ideal images

PNG

<Image img={docusaurusImport} />

<Image img={docusaurusRequire} />

WebP

<Image img={docusaurusWebPImport} />

AVIF

<Image img={docusaurusAVIFImport} />
Binary file not shown.
Binary file not shown.
2 changes: 2 additions & 0 deletions website/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ module.exports = async function createConfigAsync() {
max: 1030,
min: 640,
steps: 2,
// For ideal-image-plugin test
enableWebpAvif: true,
// Use false to debug, but it incurs huge perf costs
disableInDev: true,
}),
Expand Down