fix(core): fix handling of Swc html minifier warnings (#10581)

This commit is contained in:
Sébastien Lorber 2024-10-13 09:57:59 +02:00 committed by GitHub
parent 97e6c42099
commit 904b53b963
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 105 additions and 56 deletions

View file

@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import logger from '@docusaurus/logger';
import {minify as terserHtmlMinifier} from 'html-minifier-terser'; import {minify as terserHtmlMinifier} from 'html-minifier-terser';
import {importSwcHtmlMinifier} from './importFaster'; import {importSwcHtmlMinifier} from './importFaster';
import type {DocusaurusConfig} from '@docusaurus/types'; import type {DocusaurusConfig} from '@docusaurus/types';
@ -13,12 +12,17 @@ import type {DocusaurusConfig} from '@docusaurus/types';
// Historical env variable // Historical env variable
const SkipHtmlMinification = process.env.SKIP_HTML_MINIFICATION === 'true'; const SkipHtmlMinification = process.env.SKIP_HTML_MINIFICATION === 'true';
export type HtmlMinifierResult = {
code: string;
warnings: string[];
};
export type HtmlMinifier = { export type HtmlMinifier = {
minify: (html: string) => Promise<string>; minify: (html: string) => Promise<HtmlMinifierResult>;
}; };
const NoopMinifier: HtmlMinifier = { const NoopMinifier: HtmlMinifier = {
minify: async (html: string) => html, minify: async (html: string) => ({code: html, warnings: []}),
}; };
type SiteConfigSlice = { type SiteConfigSlice = {
@ -50,7 +54,7 @@ async function getTerserMinifier(): Promise<HtmlMinifier> {
return { return {
minify: async function minifyHtmlWithTerser(html) { minify: async function minifyHtmlWithTerser(html) {
try { try {
return await terserHtmlMinifier(html, { const code = await terserHtmlMinifier(html, {
removeComments: false, removeComments: false,
removeRedundantAttributes: true, removeRedundantAttributes: true,
removeEmptyAttributes: true, removeEmptyAttributes: true,
@ -59,6 +63,7 @@ async function getTerserMinifier(): Promise<HtmlMinifier> {
useShortDoctype: true, useShortDoctype: true,
minifyJS: true, minifyJS: true,
}); });
return {code, warnings: []};
} catch (err) { } catch (err) {
throw new Error(`HTML minification failed (Terser)`, { throw new Error(`HTML minification failed (Terser)`, {
cause: err as Error, cause: err as Error,
@ -95,49 +100,16 @@ async function getSwcMinifier(): Promise<HtmlMinifier> {
minifyCss: true, minifyCss: true,
}); });
// Escape hatch because SWC is quite aggressive to report errors const warnings = (result.errors ?? []).map((diagnostic) => {
// TODO figure out what to do with these errors: throw or swallow? return `[HTML minifier diagnostic - ${diagnostic.level}] ${
// See https://github.com/facebook/docusaurus/pull/10554
// See https://github.com/swc-project/swc/discussions/9616#discussioncomment-10846201
const ignoreSwcMinifierErrors =
process.env.DOCUSAURUS_IGNORE_SWC_HTML_MINIFIER_ERRORS === 'true';
if (!ignoreSwcMinifierErrors && result.errors) {
const ignoredErrors: string[] = [
// TODO Docusaurus seems to emit NULL chars, and minifier detects it
// see https://github.com/facebook/docusaurus/issues/9985
'Unexpected null character',
];
result.errors = result.errors.filter(
(diagnostic) => !ignoredErrors.includes(diagnostic.message),
);
if (result.errors.length) {
throw new Error(
`HTML minification diagnostic errors:
- ${result.errors
.map(
(diagnostic) =>
`[${diagnostic.level}] ${
diagnostic.message diagnostic.message
} - ${JSON.stringify(diagnostic.span)}`, } - ${JSON.stringify(diagnostic.span)}`;
) });
.join('\n- ')}
Note: please report the problem to the Docusaurus team return {
In the meantime, you can skip this error with ${logger.code( code: result.code,
'DOCUSAURUS_IGNORE_SWC_HTML_MINIFIER_ERRORS=true', warnings,
)}`, };
);
}
/*
if (result.errors.length) {
throw new AggregateError(
result.errors.map(
(diagnostic) => new Error(JSON.stringify(diagnostic, null, 2)),
),
);
}
*/
}
return result.code;
} catch (err) { } catch (err) {
throw new Error(`HTML minification failed (SWC)`, { throw new Error(`HTML minification failed (SWC)`, {
cause: err as Error, cause: err as Error,

View file

@ -110,6 +110,57 @@ function pathnameToFilename({
return `${outputFileName}.html`; return `${outputFileName}.html`;
} }
export function printSSGWarnings(
results: {
pathname: string;
warnings: string[];
}[],
): void {
// Escape hatch because SWC is quite aggressive to report errors
// See https://github.com/facebook/docusaurus/pull/10554
// See https://github.com/swc-project/swc/discussions/9616#discussioncomment-10846201
if (process.env.DOCUSAURUS_IGNORE_SSG_WARNINGS === 'true') {
return;
}
const ignoredWarnings: string[] = [
// TODO React/Docusaurus emit NULL chars, and minifier detects it
// see https://github.com/facebook/docusaurus/issues/9985
'Unexpected null character',
];
const keepWarning = (warning: string) => {
return !ignoredWarnings.some((iw) => warning.includes(iw));
};
const resultsWithWarnings = results
.map((result) => {
return {
...result,
warnings: result.warnings.filter(keepWarning),
};
})
.filter((result) => result.warnings.length > 0);
if (resultsWithWarnings.length) {
const message = `Docusaurus static site generation process emitted warnings for ${
resultsWithWarnings.length
} path${resultsWithWarnings.length ? 's' : ''}
This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true
Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580
- ${resultsWithWarnings
.map(
(result) => `${logger.path(result.pathname)}:
- ${result.warnings.join('\n - ')}
`,
)
.join('\n- ')}`;
logger.warn(message);
}
}
export async function generateStaticFiles({ export async function generateStaticFiles({
pathnames, pathnames,
renderer, renderer,
@ -121,8 +172,18 @@ export async function generateStaticFiles({
params: SSGParams; params: SSGParams;
htmlMinifier: HtmlMinifier; htmlMinifier: HtmlMinifier;
}): Promise<{collectedData: SiteCollectedData}> { }): Promise<{collectedData: SiteCollectedData}> {
type SSGSuccess = {pathname: string; error: null; result: AppRenderResult}; type SSGSuccess = {
type SSGError = {pathname: string; error: Error; result: null}; pathname: string;
error: null;
result: AppRenderResult;
warnings: string[];
};
type SSGError = {
pathname: string;
error: Error;
result: null;
warnings: string[];
};
type SSGResult = SSGSuccess | SSGError; type SSGResult = SSGSuccess | SSGError;
// Note that we catch all async errors on purpose // Note that we catch all async errors on purpose
@ -136,15 +197,27 @@ export async function generateStaticFiles({
params, params,
htmlMinifier, htmlMinifier,
}).then( }).then(
(result) => ({pathname, result, error: null}), (result) => ({
(error) => ({pathname, result: null, error: error as Error}), pathname,
result,
error: null,
warnings: result.warnings,
}),
(error) => ({
pathname,
result: null,
error: error as Error,
warnings: [],
}),
), ),
{concurrency: Concurrency}, {concurrency: Concurrency},
); );
printSSGWarnings(results);
const [allSSGErrors, allSSGSuccesses] = _.partition( const [allSSGErrors, allSSGSuccesses] = _.partition(
results, results,
(r): r is SSGError => !!r.error, (result): result is SSGError => !!result.error,
); );
if (allSSGErrors.length > 0) { if (allSSGErrors.length > 0) {
@ -179,7 +252,7 @@ async function generateStaticFile({
renderer: AppRenderer; renderer: AppRenderer;
params: SSGParams; params: SSGParams;
htmlMinifier: HtmlMinifier; htmlMinifier: HtmlMinifier;
}) { }): Promise<AppRenderResult & {warnings: string[]}> {
try { try {
// This only renders the app HTML // This only renders the app HTML
const result = await renderer({ const result = await renderer({
@ -190,13 +263,17 @@ async function generateStaticFile({
params, params,
result, result,
}); });
const content = await htmlMinifier.minify(fullPageHtml); const minifierResult = await htmlMinifier.minify(fullPageHtml);
await writeStaticFile({ await writeStaticFile({
pathname, pathname,
content, content: minifierResult.code,
params, params,
}); });
return result; return {
...result,
// As of today, only the html minifier can emit SSG warnings
warnings: minifierResult.warnings,
};
} catch (errorUnknown) { } catch (errorUnknown) {
const error = errorUnknown as Error; const error = errorUnknown as Error;
const tips = getSSGErrorTips(error); const tips = getSSGErrorTips(error);