mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-06 10:20:09 +02:00
fix(core): fix handling of Swc html minifier warnings (#10581)
This commit is contained in:
parent
97e6c42099
commit
904b53b963
2 changed files with 105 additions and 56 deletions
|
@ -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
|
diagnostic.message
|
||||||
// See https://github.com/swc-project/swc/discussions/9616#discussioncomment-10846201
|
} - ${JSON.stringify(diagnostic.span)}`;
|
||||||
const ignoreSwcMinifierErrors =
|
});
|
||||||
process.env.DOCUSAURUS_IGNORE_SWC_HTML_MINIFIER_ERRORS === 'true';
|
|
||||||
if (!ignoreSwcMinifierErrors && result.errors) {
|
return {
|
||||||
const ignoredErrors: string[] = [
|
code: result.code,
|
||||||
// TODO Docusaurus seems to emit NULL chars, and minifier detects it
|
warnings,
|
||||||
// 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;
|
|
||||||
} 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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue