Skip to content

Commit

Permalink
Better handling of SWC minifier warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
slorber committed Oct 11, 2024
1 parent 97e6c42 commit a4011c0
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 56 deletions.
66 changes: 19 additions & 47 deletions packages/docusaurus-bundler/src/minifyHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@
* LICENSE file in the root directory of this source tree.
*/

import logger from '@docusaurus/logger';
import {minify as terserHtmlMinifier} from 'html-minifier-terser';
import {importSwcHtmlMinifier} from './importFaster';
import type {DocusaurusConfig} from '@docusaurus/types';

// Historical env variable
const SkipHtmlMinification = process.env.SKIP_HTML_MINIFICATION === 'true';

export type HtmlMinifierResult = {
code: string;
warnings: string[];
};

export type HtmlMinifier = {
minify: (html: string) => Promise<string>;
minify: (html: string) => Promise<HtmlMinifierResult>;
};

const NoopMinifier: HtmlMinifier = {
minify: async (html: string) => html,
minify: async (html: string) => ({code: html, warnings: []}),
};

type SiteConfigSlice = {
Expand Down Expand Up @@ -50,7 +54,7 @@ async function getTerserMinifier(): Promise<HtmlMinifier> {
return {
minify: async function minifyHtmlWithTerser(html) {
try {
return await terserHtmlMinifier(html, {
const code = await terserHtmlMinifier(html, {
removeComments: false,
removeRedundantAttributes: true,
removeEmptyAttributes: true,
Expand All @@ -59,6 +63,7 @@ async function getTerserMinifier(): Promise<HtmlMinifier> {
useShortDoctype: true,
minifyJS: true,
});
return {code, warnings: []};
} catch (err) {
throw new Error(`HTML minification failed (Terser)`, {
cause: err as Error,
Expand Down Expand Up @@ -95,49 +100,16 @@ async function getSwcMinifier(): Promise<HtmlMinifier> {
minifyCss: true,
});

// Escape hatch because SWC is quite aggressive to report errors
// TODO figure out what to do with these errors: throw or swallow?
// 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
} - ${JSON.stringify(diagnostic.span)}`,
)
.join('\n- ')}
Note: please report the problem to the Docusaurus team
In the meantime, you can skip this error with ${logger.code(
'DOCUSAURUS_IGNORE_SWC_HTML_MINIFIER_ERRORS=true',
)}`,
);
}
/*
if (result.errors.length) {
throw new AggregateError(
result.errors.map(
(diagnostic) => new Error(JSON.stringify(diagnostic, null, 2)),
),
);
}
*/
}
return result.code;
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,
Expand Down
95 changes: 86 additions & 9 deletions packages/docusaurus/src/ssg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,57 @@ function pathnameToFilename({
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({
pathnames,
renderer,
Expand All @@ -121,8 +172,18 @@ export async function generateStaticFiles({
params: SSGParams;
htmlMinifier: HtmlMinifier;
}): Promise<{collectedData: SiteCollectedData}> {
type SSGSuccess = {pathname: string; error: null; result: AppRenderResult};
type SSGError = {pathname: string; error: Error; result: null};
type SSGSuccess = {
pathname: string;
error: null;
result: AppRenderResult;
warnings: string[];
};
type SSGError = {
pathname: string;
error: Error;
result: null;
warnings: string[];
};
type SSGResult = SSGSuccess | SSGError;

// Note that we catch all async errors on purpose
Expand All @@ -136,15 +197,25 @@ export async function generateStaticFiles({
params,
htmlMinifier,
}).then(
(result) => ({pathname, result, error: null}),
(error) => ({pathname, result: null, error: error as Error}),
(result) => ({
pathname,
result,
error: null,
warnings: result.warnings,
}),
(error) => ({
pathname,
result: null,
error: error as Error,
warnings: [],
}),
),
{concurrency: Concurrency},
);

const [allSSGErrors, allSSGSuccesses] = _.partition(
results,
(r): r is SSGError => !!r.error,
(result): result is SSGError => !!result.error,
);

if (allSSGErrors.length > 0) {
Expand All @@ -161,6 +232,8 @@ export async function generateStaticFiles({
});
}

printSSGWarnings(results);

const collectedData: SiteCollectedData = _.chain(allSSGSuccesses)
.keyBy((success) => success.pathname)
.mapValues((ssgSuccess) => ssgSuccess.result.collectedData)
Expand All @@ -179,7 +252,7 @@ async function generateStaticFile({
renderer: AppRenderer;
params: SSGParams;
htmlMinifier: HtmlMinifier;
}) {
}): Promise<AppRenderResult & {warnings: string[]}> {
try {
// This only renders the app HTML
const result = await renderer({
Expand All @@ -190,13 +263,17 @@ async function generateStaticFile({
params,
result,
});
const content = await htmlMinifier.minify(fullPageHtml);
const minifierResult = await htmlMinifier.minify(fullPageHtml);
await writeStaticFile({
pathname,
content,
content: minifierResult.code,
params,
});
return result;
return {
...result,
// As of today, only the html minifier can emit SSG warnings
warnings: minifierResult.warnings,
};
} catch (errorUnknown) {
const error = errorUnknown as Error;
const tips = getSSGErrorTips(error);
Expand Down

0 comments on commit a4011c0

Please sign in to comment.