Skip to content

Commit

Permalink
Merge branch 'main' into slorber/poc-rspack
Browse files Browse the repository at this point in the history
  • Loading branch information
slorber committed Oct 4, 2024
2 parents 9eebd7f + 912c495 commit 4f40e3b
Show file tree
Hide file tree
Showing 17 changed files with 361 additions and 47 deletions.
1 change: 1 addition & 0 deletions packages/docusaurus-bundler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"postcss": "^8.4.26",
"postcss-loader": "^7.3.3",
"file-loader": "^6.2.0",
"html-minifier-terser": "^7.2.0",
"mini-css-extract-plugin": "^2.9.1",
"null-loader": "^4.0.1",
"react-dev-utils": "^12.0.1",
Expand Down
13 changes: 11 additions & 2 deletions packages/docusaurus-bundler/src/importFaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import type {
} from 'terser-webpack-plugin';
import type {MinimizerOptions as CssMinimizerOptions} from 'css-minimizer-webpack-plugin';

async function importFaster() {
type FasterModule = Awaited<typeof import('@docusaurus/faster')>;

async function importFaster(): Promise<FasterModule> {
return import('@docusaurus/faster');
}

async function ensureFaster() {
async function ensureFaster(): Promise<FasterModule> {
try {
return await importFaster();
} catch (error) {
Expand Down Expand Up @@ -47,6 +49,13 @@ export async function importSwcJsMinimizerOptions(): Promise<
return faster.getSwcJsMinimizerOptions() as JsMinimizerOptions<CustomOptions>;
}

export async function importSwcHtmlMinifier(): Promise<
ReturnType<FasterModule['getSwcHtmlMinifier']>
> {
const faster = await ensureFaster();
return faster.getSwcHtmlMinifier();
}

export async function importBrowserslistQueries(): Promise<string[]> {
const faster = await ensureFaster();
return faster.getBrowserslistQueries();
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-bundler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ export {
} from './currentBundler';

export {getMinimizers} from './minification';
export {getHtmlMinifier, type HtmlMinifier} from './minifyHtml';
export {createJsLoaderFactory} from './loaders/jsLoader';
export {createStyleLoadersFactory} from './loaders/styleLoader';
148 changes: 148 additions & 0 deletions packages/docusaurus-bundler/src/minifyHtml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* 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 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 HtmlMinifier = {
minify: (html: string) => Promise<string>;
};

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

type SiteConfigSlice = {
future: {
experimental_faster: Pick<
DocusaurusConfig['future']['experimental_faster'],
'swcHtmlMinimizer'
>;
};
};

export async function getHtmlMinifier({
siteConfig,
}: {
siteConfig: SiteConfigSlice;
}): Promise<HtmlMinifier> {
if (SkipHtmlMinification) {
return NoopMinifier;
}
if (siteConfig.future.experimental_faster.swcHtmlMinimizer) {
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 {
return await terserHtmlMinifier(html, {
removeComments: false,
removeRedundantAttributes: true,
removeEmptyAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true,
minifyJS: true,
});
} 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: [],

// Sorting these attributes (class) can lead to React hydration errors
sortSpaceSeparatedAttributeValues: false,
sortAttributes: false,

// @ts-expect-error: bad type https://github.com/swc-project/swc/pull/9615
removeRedundantAttributes: 'all',
removeEmptyAttributes: true,
minifyJs: true,
minifyJson: true,
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;
} catch (err) {
throw new Error(`HTML minification failed (SWC)`, {
cause: err as Error,
});
}
},
};
}
1 change: 1 addition & 0 deletions packages/docusaurus-faster/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"dependencies": {
"@rspack/core": "^1.0.8",
"@swc/core": "^1.7.28",
"@swc/html": "^1.7.28",
"browserslist": "^4.24.0",
"lightningcss": "^1.27.0",
"swc-loader": "^0.2.6",
Expand Down
7 changes: 6 additions & 1 deletion packages/docusaurus-faster/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
import Rspack from '@rspack/core';
import * as lightningcss from 'lightningcss';
import browserslist from 'browserslist';
import {minify as swcHtmlMinifier} from '@swc/html';
import type {RuleSetRule} from 'webpack';
import type {JsMinifyOptions} from '@swc/core';

export function getRspack(): typeof Rspack {
return Rspack;
}

export function getSwcHtmlMinifier(): typeof swcHtmlMinifier {
return swcHtmlMinifier;
}

export function getSwcJsLoaderFactory({
isServer,
}: {
Expand Down Expand Up @@ -81,6 +86,6 @@ type LightningCssMinimizerOptions = Omit<
>;

export function getLightningCssMinimizerOptions(): LightningCssMinimizerOptions {
const queries = browserslist(getBrowserslistQueries());
const queries = browserslist();
return {targets: lightningcss.browserslistToTargets(queries)};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"theme.CodeBlock.copy": "Kopiraj",
"theme.CodeBlock.copyButtonAriaLabel": "Kopiraj v odložišče",
"theme.CodeBlock.wordWrapToggle": "Preklopi oblivanje besedila",
"theme.DocSidebarItem.collapseCategoryAriaLabel": "Collapse sidebar category '{label}'",
"theme.DocSidebarItem.expandCategoryAriaLabel": "Expand sidebar category '{label}'",
"theme.DocSidebarItem.collapseCategoryAriaLabel": "Strni kategorijo stranske vrstice '{label}'",
"theme.DocSidebarItem.expandCategoryAriaLabel": "Razširite kategorijo stranske vrstice '{label}'",
"theme.ErrorPageContent.title": "Ta stran se je zrušila.",
"theme.ErrorPageContent.tryAgain": "Poskusite ponovno",
"theme.NavBar.navAriaLabel": "Glavna navigacija",
Expand All @@ -19,13 +19,13 @@
"theme.admonition.info": "informacija",
"theme.admonition.note": "opomba",
"theme.admonition.tip": "namig",
"theme.admonition.warning": "warning",
"theme.admonition.warning": "opozorilo",
"theme.blog.archive.description": "Arhiv",
"theme.blog.archive.title": "Arhiv",
"theme.blog.author.noPosts": "This author has not written any posts yet.",
"theme.blog.author.noPosts": "Ta avtor še ni napisal nobene objave.",
"theme.blog.author.pageTitle": "{authorName} - {nPosts}",
"theme.blog.authorsList.pageTitle": "Authors",
"theme.blog.authorsList.viewAll": "View All Authors",
"theme.blog.authorsList.pageTitle": "Avtorji",
"theme.blog.authorsList.viewAll": "Prikaži vse avtorje",
"theme.blog.paginator.navAriaLabel": "Navigacija kazala po blogu",
"theme.blog.paginator.newerEntries": "Novejši prispevki",
"theme.blog.paginator.olderEntries": "Starejši prispevki",
Expand All @@ -44,11 +44,11 @@
"theme.common.editThisPage": "Uredi to stran",
"theme.common.headingLinkTitle": "Direktna povezava na {heading}",
"theme.common.skipToMainContent": "Preskoči na vsebino",
"theme.contentVisibility.draftBanner.message": "This page is a draft. It will only be visible in dev and be excluded from the production build.",
"theme.contentVisibility.draftBanner.title": "Draft page",
"theme.contentVisibility.draftBanner.message": "Ta stran je osnutek. Viden bo samo v razvijalcu in bo izključen iz proizvodne zgradbe.",
"theme.contentVisibility.draftBanner.title": "Osnutek strani",
"theme.contentVisibility.unlistedBanner.message": "Ta stran ni zabeležena. Iskalniki je ne bodo indeksirali, do nje bodo uporabniki lahko dostopali le z direktno povezavo.",
"theme.contentVisibility.unlistedBanner.title": "Nezabeležena stran",
"theme.docs.DocCard.categoryDescription.plurals": "1 vnos|2 vnosy|{count} vnosy|{count} vnosov",
"theme.docs.DocCard.categoryDescription.plurals": "1 vnos|2 vnosa|{count} vnosi|{count} vnosov",
"theme.docs.breadcrumbs.home": "Domača stran",
"theme.docs.breadcrumbs.navAriaLabel": "Drobtine",
"theme.docs.paginator.navAriaLabel": "Strani z dokumenti",
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-types/src/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export type StorageConfig = {
export type FasterConfig = {
swcJsLoader: boolean;
swcJsMinimizer: boolean;
swcHtmlMinimizer: boolean;
lightningCssMinimizer: boolean;
mdxCrossCompilerCache: boolean;
rspackBundler: boolean;
Expand Down
3 changes: 1 addition & 2 deletions packages/docusaurus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,8 @@
"eta": "^2.2.0",
"eval": "^0.1.8",
"fs-extra": "^11.1.1",
"html-minifier-terser": "^7.2.0",
"html-tags": "^3.3.1",
"html-webpack-plugin": "^5.5.3",
"html-webpack-plugin": "^5.6.0",
"leven": "^3.1.0",
"lodash": "^4.17.21",
"p-map": "^4.0.0",
Expand Down
18 changes: 12 additions & 6 deletions packages/docusaurus/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import fs from 'fs-extra';
import path from 'path';
import _ from 'lodash';
import {compile} from '@docusaurus/bundler';
import {compile, getHtmlMinifier} from '@docusaurus/bundler';
import logger, {PerfLogger} from '@docusaurus/logger';
import {DOCUSAURUS_VERSION, mapAsyncSequential} from '@docusaurus/utils';
import {loadSite, loadContext, type LoadContextParams} from '../server/site';
Expand Down Expand Up @@ -268,17 +268,23 @@ async function executeSSG({
return {collectedData: {}};
}

const renderer = await PerfLogger.async('Load App renderer', () =>
loadAppRenderer({
serverBundlePath,
}),
);
const [renderer, htmlMinifier] = await Promise.all([
PerfLogger.async('Load App renderer', () =>
loadAppRenderer({
serverBundlePath,
}),
),
PerfLogger.async('Load HTML minifier', () =>
getHtmlMinifier({siteConfig: props.siteConfig}),
),
]);

const ssgResult = await PerfLogger.async('Generate static files', () =>
generateStaticFiles({
pathnames: props.routesPaths,
renderer,
params,
htmlMinifier,
}),
);

Expand Down
Loading

0 comments on commit 4f40e3b

Please sign in to comment.