docusaurus/packages/docusaurus-bundler/src/minifyHtml.ts
Sébastien Lorber ece9432b3a
Some checks failed
Argos CI / take-screenshots (push) Has been cancelled
Build Hash Router / Build Hash Router (push) Has been cancelled
Canary Release / Publish Canary (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Continuous Releases / Continuous Releases (push) Has been cancelled
E2E Tests / E2E — Yarn v1 (18.0) (push) Has been cancelled
E2E Tests / E2E — Yarn v1 (20) (push) Has been cancelled
E2E Tests / E2E — Yarn v1 (22) (push) Has been cancelled
E2E Tests / E2E — Yarn v1 (24) (push) Has been cancelled
E2E Tests / E2E — Yarn v1 Windows (push) Has been cancelled
E2E Tests / E2E — Yarn Berry (node-modules, -s) (push) Has been cancelled
E2E Tests / E2E — Yarn Berry (node-modules, -st) (push) Has been cancelled
E2E Tests / E2E — Yarn Berry (pnp, -s) (push) Has been cancelled
E2E Tests / E2E — Yarn Berry (pnp, -st) (push) Has been cancelled
E2E Tests / E2E — npm (push) Has been cancelled
E2E Tests / E2E — pnpm (push) Has been cancelled
fix(ssg): HTML minifier should preserve `<head>` for `og:image` crawlers (#11383)
2025-08-25 14:22:10 +02:00

125 lines
3.7 KiB
TypeScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {minify as terserHtmlMinifier} from 'html-minifier-terser';
import {importSwcHtmlMinifier} from './importFaster';
// Historical env variable
const SkipHtmlMinification = process.env.SKIP_HTML_MINIFICATION === 'true';
export type HtmlMinifierType = 'swc' | 'terser';
export type HtmlMinifierResult = {
code: string;
warnings: string[];
};
export type HtmlMinifier = {
minify: (html: string) => Promise<HtmlMinifierResult>;
};
const NoopMinifier: HtmlMinifier = {
minify: async (html: string) => ({code: html, warnings: []}),
};
export async function getHtmlMinifier({
type,
}: {
type: HtmlMinifierType;
}): Promise<HtmlMinifier> {
if (SkipHtmlMinification) {
return NoopMinifier;
}
if (type === 'swc') {
return getSwcMinifier();
} else {
return getTerserMinifier();
}
}
// Minify html with https://github.com/DanielRuf/html-minifier-terser
async function getTerserMinifier(): Promise<HtmlMinifier> {
return {
minify: async function minifyHtmlWithTerser(html) {
try {
const code = await terserHtmlMinifier(html, {
// When enabled => React hydration errors
removeComments: false,
removeRedundantAttributes: false,
removeEmptyAttributes: false,
sortAttributes: false,
sortClassName: false,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true,
minifyJS: true,
});
return {code, warnings: []};
} catch (err) {
throw new Error(`HTML minification failed (Terser)`, {
cause: err as Error,
});
}
},
};
}
// Minify html with @swc/html
// Not well-documented but fast!
// See https://github.com/swc-project/swc/discussions/9616
async function getSwcMinifier(): Promise<HtmlMinifier> {
const swcHtmlMinifier = await importSwcHtmlMinifier();
return {
minify: async function minifyHtmlWithSwc(html) {
try {
const result = await swcHtmlMinifier(Buffer.from(html), {
// Removing comments can lead to React hydration errors
// See https://x.com/sebastienlorber/status/1841966927440478577
removeComments: false,
// TODO maybe it's fine to only keep <!-- --> React comments?
preserveComments: [],
// Keep <head> tag: important for social image crawlers like LinkedIn
// See https://github.com/swc-project/swc/issues/10994
tagOmission: 'keep-head-and-body',
// Sorting these attributes (class) can lead to React hydration errors
sortSpaceSeparatedAttributeValues: false,
sortAttributes: false,
// When enabled => hydration error for className={"yt-lite "}
normalizeAttributes: false,
// When enabled => hydration error for className=""
removeEmptyAttributes: false,
// When enabled => hydration error for <a target="_self">
removeRedundantAttributes: 'none',
minifyJs: true,
minifyJson: true,
minifyCss: true,
});
const warnings = (result.errors ?? []).map((diagnostic) => {
return `[HTML minifier diagnostic - ${diagnostic.level}] ${
diagnostic.message
} - ${JSON.stringify(diagnostic.span)}`;
});
return {
code: result.code,
warnings,
};
} catch (err) {
throw new Error(`HTML minification failed (SWC)`, {
cause: err as Error,
});
}
},
};
}