Skip to content

Commit

Permalink
feat(core): Replace Webpack with Rspack - `siteConfig.future.experime…
Browse files Browse the repository at this point in the history
…ntal_faster.rspackBundler` (#10402)
  • Loading branch information
slorber authored Oct 11, 2024
1 parent c7fd8d1 commit 74c09ae
Show file tree
Hide file tree
Showing 17 changed files with 392 additions and 176 deletions.
31 changes: 23 additions & 8 deletions packages/docusaurus-bundler/src/currentBundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import webpack from 'webpack';
import WebpackBar from 'webpackbar';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import logger from '@docusaurus/logger';
import {importRspack} from './importFaster';
import type {FasterModule} from './importFaster';
import type {CurrentBundler, DocusaurusConfig} from '@docusaurus/types';

// We inject a site config slice because the Rspack flag might change place
Expand All @@ -32,24 +33,38 @@ export async function getCurrentBundler({
siteConfig: SiteConfigSlice;
}): Promise<CurrentBundler> {
if (isRspack(siteConfig)) {
// TODO add support for Rspack
logger.error(
'Rspack bundler is not supported yet, will use Webpack instead',
);
return {
name: 'rspack',
instance: (await importRspack()) as unknown as typeof webpack,
};
}
return {
name: 'webpack',
instance: webpack,
};
}

export function getCurrentBundlerAsRspack({
currentBundler,
}: {
currentBundler: CurrentBundler;
}): FasterModule['rspack'] {
if (currentBundler.name !== 'rspack') {
throw new Error(
`Can't getCurrentBundlerAsRspack() because current bundler is ${currentBundler.name}`,
);
}
return currentBundler.instance as unknown as FasterModule['rspack'];
}

export async function getCSSExtractPlugin({
currentBundler,
}: {
currentBundler: CurrentBundler;
}): Promise<typeof MiniCssExtractPlugin> {
if (currentBundler.name === 'rspack') {
throw new Error('Rspack bundler is not supported yet');
// @ts-expect-error: this exists only in Rspack
return currentBundler.instance.CssExtractRspackPlugin;
}
return MiniCssExtractPlugin;
}
Expand All @@ -60,9 +75,9 @@ export async function getCopyPlugin({
currentBundler: CurrentBundler;
}): Promise<typeof CopyWebpackPlugin> {
if (currentBundler.name === 'rspack') {
throw new Error('Rspack bundler is not supported yet');
// @ts-expect-error: this exists only in Rspack
return currentBundler.instance.CopyRspackPlugin;
}
// https://github.com/webpack-contrib/copy-webpack-plugin
return CopyWebpackPlugin;
}

Expand Down
12 changes: 11 additions & 1 deletion packages/docusaurus-bundler/src/importFaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
} from 'terser-webpack-plugin';
import type {MinimizerOptions as CssMinimizerOptions} from 'css-minimizer-webpack-plugin';

type FasterModule = Awaited<typeof import('@docusaurus/faster')>;
export type FasterModule = Awaited<typeof import('@docusaurus/faster')>;

async function importFaster(): Promise<FasterModule> {
return import('@docusaurus/faster');
Expand All @@ -29,6 +29,11 @@ async function ensureFaster(): Promise<FasterModule> {
}
}

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

export async function importSwcJsLoaderFactory(): Promise<
ConfigureWebpackUtils['getJSLoader']
> {
Expand All @@ -50,6 +55,11 @@ export async function importSwcHtmlMinifier(): Promise<
return faster.getSwcHtmlMinifier();
}

export async function importBrowserslistQueries(): Promise<string[]> {
const faster = await ensureFaster();
return faster.getBrowserslistQueries();
}

export async function importLightningCssMinimizerOptions(): Promise<
CssMinimizerOptions<CustomOptions>
> {
Expand Down
13 changes: 10 additions & 3 deletions packages/docusaurus-bundler/src/loaders/__tests__/jsLoader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
* LICENSE file in the root directory of this source tree.
*/

import {fromPartial} from '@total-typescript/shoehorn';
import {fromPartial, type PartialDeep} from '@total-typescript/shoehorn';
import {createJsLoaderFactory} from '../jsLoader';

import type {RuleSetRule} from 'webpack';

describe('createJsLoaderFactory', () => {
function testJsLoaderFactory(
siteConfig?: Parameters<typeof createJsLoaderFactory>[0]['siteConfig'],
siteConfig?: PartialDeep<
Parameters<typeof createJsLoaderFactory>[0]['siteConfig']
>,
) {
return createJsLoaderFactory({
siteConfig: {
Expand All @@ -21,7 +23,12 @@ describe('createJsLoaderFactory', () => {
jsLoader: 'babel',
...siteConfig?.webpack,
},
future: fromPartial(siteConfig?.future),
future: fromPartial({
...siteConfig?.future,
experimental_faster: fromPartial({
...siteConfig?.future?.experimental_faster,
}),
}),
},
});
}
Expand Down
33 changes: 32 additions & 1 deletion packages/docusaurus-bundler/src/loaders/jsLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import {getBabelOptions} from '@docusaurus/babel';
import {importSwcJsLoaderFactory} from '../importFaster';
import {getCurrentBundler} from '../currentBundler';
import type {ConfigureWebpackUtils, DocusaurusConfig} from '@docusaurus/types';

const BabelJsLoaderFactory: ConfigureWebpackUtils['getJSLoader'] = ({
Expand All @@ -19,6 +20,25 @@ const BabelJsLoaderFactory: ConfigureWebpackUtils['getJSLoader'] = ({
};
};

const RspackJsLoaderFactory: ConfigureWebpackUtils['getJSLoader'] = () => {
return {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
transform: {
react: {
runtime: 'automatic',
},
},
},
},
};
};

// Confusing: function that creates a function that creates actual js loaders
// This is done on purpose because the js loader factory is a public API
// It is injected in configureWebpack plugin lifecycle for plugin authors
Expand All @@ -27,11 +47,22 @@ export async function createJsLoaderFactory({
}: {
siteConfig: {
webpack?: DocusaurusConfig['webpack'];
future?: {
future: {
experimental_faster: DocusaurusConfig['future']['experimental_faster'];
};
};
}): Promise<ConfigureWebpackUtils['getJSLoader']> {
const currentBundler = await getCurrentBundler({siteConfig});
const isSWCLoader = siteConfig.future.experimental_faster.swcJsLoader;

if (currentBundler.name === 'rspack') {
if (!isSWCLoader) {
throw new Error(
'When using Rspack bundler, it is required to enable swcJsLoader too',
);
}
return RspackJsLoaderFactory;
}
const jsLoader = siteConfig.webpack?.jsLoader ?? 'babel';
if (
jsLoader instanceof Function &&
Expand Down
29 changes: 27 additions & 2 deletions packages/docusaurus-bundler/src/minification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
import {
importSwcJsMinimizerOptions,
importLightningCssMinimizerOptions,
importBrowserslistQueries,
} from './importFaster';
import {getCurrentBundlerAsRspack} from './currentBundler';
import type {CustomOptions, CssNanoOptions} from 'css-minimizer-webpack-plugin';
import type {WebpackPluginInstance} from 'webpack';
import type {CurrentBundler, FasterConfig} from '@docusaurus/types';
Expand Down Expand Up @@ -48,6 +50,7 @@ async function getJsMinimizer({

return new TerserPlugin({
parallel: getTerserParallel(),
// See https://terser.org/docs/options/
terserOptions: {
parse: {
// We want uglify-js to parse ecma 8 code. However, we don't want it
Expand Down Expand Up @@ -137,8 +140,30 @@ async function getWebpackMinimizers(
async function getRspackMinimizers({
currentBundler,
}: MinimizersConfig): Promise<WebpackPluginInstance[]> {
console.log('currentBundler', currentBundler.name);
throw new Error('TODO Rspack minimizers not implemented yet');
const rspack = getCurrentBundlerAsRspack({currentBundler});
const browserslistQueries = await importBrowserslistQueries();
const swcJsMinimizerOptions = await importSwcJsMinimizerOptions();
return [
// See https://rspack.dev/plugins/rspack/swc-js-minimizer-rspack-plugin
// See https://swc.rs/docs/configuration/minification
new rspack.SwcJsMinimizerRspackPlugin({
minimizerOptions: {
minify: true,
...swcJsMinimizerOptions,
},
}),
new rspack.LightningCssMinimizerRspackPlugin({
minimizerOptions: {
...(await importLightningCssMinimizerOptions()),
// Not sure why but Rspack takes browserslist queries directly
// While LightningCSS targets are normally not browserslist queries
// We have to override the option to avoid errors
// See https://rspack.dev/plugins/rspack/lightning-css-minimizer-rspack-plugin#minimizeroptions
// See https://lightningcss.dev/transpilation.html
targets: browserslistQueries,
},
}),
] as unknown as WebpackPluginInstance[];
}

export async function getMinimizers(
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-faster/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"license": "MIT",
"dependencies": {
"@rspack/core": "^1.0.9",
"@swc/core": "^1.7.28",
"@swc/html": "^1.7.28",
"browserslist": "^4.24.0",
Expand Down
12 changes: 12 additions & 0 deletions packages/docusaurus-faster/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/

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 const rspack = Rspack;

export function getSwcHtmlMinifier(): typeof swcHtmlMinifier {
return swcHtmlMinifier;
}
Expand Down Expand Up @@ -63,6 +66,15 @@ export function getSwcJsMinimizerOptions(): JsMinifyOptions {
};
}

// We need this because of Rspack built-in LightningCSS integration
// See https://github.com/orgs/browserslist/discussions/846
export function getBrowserslistQueries(): string[] {
const queries = browserslist.loadConfig({path: process.cwd()}) ?? [
...browserslist.defaults,
];
return queries;
}

// LightningCSS doesn't expose any type for css-minimizer-webpack-plugin setup
// So we derive it ourselves
// see https://lightningcss.dev/docs.html#with-webpack
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-theme-classic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"dependencies": {
"@docusaurus/core": "3.5.2",
"@docusaurus/logger": "3.5.2",
"@docusaurus/mdx-loader": "3.5.2",
"@docusaurus/module-type-aliases": "3.5.2",
"@docusaurus/plugin-content-blog": "3.5.2",
Expand Down
45 changes: 22 additions & 23 deletions packages/docusaurus/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,32 +174,31 @@ async function buildLocale({

// We can build the 2 configs in parallel
const [{clientConfig, clientManifestPath}, {serverConfig, serverBundlePath}] =
await PerfLogger.async('Creating bundler configs', () =>
Promise.all([
getBuildClientConfig({
props,
cliOptions,
configureWebpackUtils,
}),
getBuildServerConfig({
props,
configureWebpackUtils,
}),
]),
await PerfLogger.async(
`Creating ${props.currentBundler.name} bundler configs`,
() =>
Promise.all([
getBuildClientConfig({
props,
cliOptions,
configureWebpackUtils,
}),
getBuildServerConfig({
props,
configureWebpackUtils,
}),
]),
);

// Run webpack to build JS bundle (client) and static html files (server).
await PerfLogger.async(
`Bundling with ${configureWebpackUtils.currentBundler.name}`,
() => {
return compile({
configs:
// For hash router we don't do SSG and can skip the server bundle
router === 'hash' ? [clientConfig] : [clientConfig, serverConfig],
currentBundler: configureWebpackUtils.currentBundler,
});
},
);
await PerfLogger.async(`Bundling with ${props.currentBundler.name}`, () => {
return compile({
configs:
// For hash router we don't do SSG and can skip the server bundle
router === 'hash' ? [clientConfig] : [clientConfig, serverConfig],
currentBundler: configureWebpackUtils.currentBundler,
});
});

const {collectedData} = await PerfLogger.async('SSG', () =>
executeSSG({
Expand Down
3 changes: 1 addition & 2 deletions packages/docusaurus/src/commands/start/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import path from 'path';
import merge from 'webpack-merge';
import webpack from 'webpack';
import {formatStatsErrorMessage, printStatsWarnings} from '@docusaurus/bundler';
import logger from '@docusaurus/logger';
import WebpackDevServer from 'webpack-dev-server';
Expand Down Expand Up @@ -168,7 +167,7 @@ export async function createWebpackDevServer({
configureWebpackUtils,
});

const compiler = webpack(config);
const compiler = props.currentBundler.instance(config);
registerWebpackE2ETestHook(compiler);

const defaultDevServerConfig = await createDevServerConfig({
Expand Down
Loading

0 comments on commit 74c09ae

Please sign in to comment.