refactor(core): prepare codebase for swappable bundler (#10497)

This commit is contained in:
Sébastien Lorber 2024-09-13 09:30:30 +02:00 committed by GitHub
parent 611842af91
commit 2495d059de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 430 additions and 121 deletions

View file

@ -21,6 +21,7 @@ import {sortRoutes} from '@docusaurus/core/src/server/plugins/routeConfig';
import {posixPath} from '@docusaurus/utils';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {fromPartial} from '@total-typescript/shoehorn';
import pluginContentDocs from '../index';
import {toSidebarsProp} from '../props';
import {DefaultSidebarItemsGenerator} from '../sidebars/generator';
@ -288,8 +289,11 @@ describe('simple website', () => {
},
},
isServer: false,
utils: createConfigureWebpackUtils({
siteConfig: {webpack: {jsLoader: 'babel'}},
configureWebpackUtils: await createConfigureWebpackUtils({
siteConfig: {
webpack: {jsLoader: 'babel'},
future: {experimental_faster: fromPartial({})},
},
}),
content,
});

View file

@ -0,0 +1,17 @@
/**
* 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 type webpack from 'webpack';
// We use Webpack and Rspack interchangeably because most Rspack APIs are
// compatible with Webpack. So it's ok to use Webpack types for Rspack too.
// When compatibility doesn't work, use "CurrentBundler.name"
// See https://github.com/facebook/docusaurus/pull/10402
export type CurrentBundler = {
name: 'webpack' | 'rspack';
instance: typeof webpack;
};

View file

@ -127,6 +127,7 @@ export type FasterConfig = {
swcJsLoader: boolean;
swcJsMinimizer: boolean;
mdxCrossCompilerCache: boolean;
rspackBundler: boolean;
};
export type FutureConfig = {

View file

@ -73,6 +73,8 @@ export {
HtmlTags,
} from './plugin';
export {CurrentBundler} from './bundler';
export {
RouteConfig,
PluginRouteConfig,

View file

@ -15,6 +15,7 @@ import type {ThemeConfig} from './config';
import type {LoadContext, Props} from './context';
import type {SwizzleConfig} from './swizzle';
import type {RouteConfig} from './routing';
import type {CurrentBundler} from './bundler';
export type PluginOptions = {id?: string} & {[key: string]: unknown};
@ -54,6 +55,7 @@ export type PluginContentLoadedActions = {
};
export type ConfigureWebpackUtils = {
currentBundler: CurrentBundler;
getStyleLoaders: (
isServer: boolean,
cssOptions: {[key: string]: unknown},
@ -130,7 +132,7 @@ export type Plugin<Content = unknown> = {
configureWebpack?: (
config: WebpackConfiguration,
isServer: boolean,
utils: ConfigureWebpackUtils,
configureWebpackUtils: ConfigureWebpackUtils,
content: Content,
) => WebpackConfiguration & {
mergeStrategy?: {

View file

@ -34,7 +34,12 @@ import defaultSSRTemplate from '../templates/ssr.html.template';
import type {SSGParams} from '../ssg';
import type {Manifest} from 'react-loadable-ssr-addon-v5-slorber';
import type {LoadedPlugin, Props, RouterType} from '@docusaurus/types';
import type {
ConfigureWebpackUtils,
LoadedPlugin,
Props,
RouterType,
} from '@docusaurus/types';
import type {SiteCollectedData} from '../common';
export type BuildCLIOptions = Pick<
@ -165,6 +170,8 @@ async function buildLocale({
const router = siteConfig.future.experimental_router;
const configureWebpackUtils = await createConfigureWebpackUtils({siteConfig});
// We can build the 2 configs in parallel
const [{clientConfig, clientManifestPath}, {serverConfig, serverBundlePath}] =
await PerfLogger.async('Creating webpack configs', () =>
@ -172,9 +179,11 @@ async function buildLocale({
getBuildClientConfig({
props,
cliOptions,
configureWebpackUtils,
}),
getBuildServerConfig({
props,
configureWebpackUtils,
}),
]),
);
@ -324,15 +333,18 @@ async function executeBrokenLinksCheck({
async function getBuildClientConfig({
props,
cliOptions,
configureWebpackUtils,
}: {
props: Props;
cliOptions: BuildCLIOptions;
configureWebpackUtils: ConfigureWebpackUtils;
}) {
const {plugins} = props;
const result = await createBuildClientConfig({
props,
minify: cliOptions.minify ?? true,
faster: props.siteConfig.future.experimental_faster,
configureWebpackUtils,
bundleAnalyzer: cliOptions.bundleAnalyzer ?? false,
});
let {config} = result;
@ -340,26 +352,29 @@ async function getBuildClientConfig({
plugins,
config,
isServer: false,
utils: await createConfigureWebpackUtils({
siteConfig: props.siteConfig,
}),
configureWebpackUtils,
});
return {clientConfig: config, clientManifestPath: result.clientManifestPath};
}
async function getBuildServerConfig({props}: {props: Props}) {
async function getBuildServerConfig({
props,
configureWebpackUtils,
}: {
props: Props;
configureWebpackUtils: ConfigureWebpackUtils;
}) {
const {plugins} = props;
const result = await createServerConfig({
props,
configureWebpackUtils,
});
let {config} = result;
config = executePluginsConfigureWebpack({
plugins,
config,
isServer: true,
utils: await createConfigureWebpackUtils({
siteConfig: props.siteConfig,
}),
configureWebpackUtils,
});
return {serverConfig: config, serverBundlePath: result.serverBundlePath};
}

View file

@ -23,7 +23,7 @@ import {
} from '../../webpack/configure';
import {createStartClientConfig} from '../../webpack/client';
import type {StartCLIOptions} from './start';
import type {Props} from '@docusaurus/types';
import type {ConfigureWebpackUtils, Props} from '@docusaurus/types';
import type {Compiler} from 'webpack';
import type {OpenUrlContext} from './utils';
@ -127,23 +127,26 @@ async function getStartClientConfig({
props,
minify,
poll,
configureWebpackUtils,
}: {
props: Props;
minify: boolean;
poll: number | boolean | undefined;
configureWebpackUtils: ConfigureWebpackUtils;
}) {
const {plugins, siteConfig} = props;
const {plugins} = props;
let {clientConfig: config} = await createStartClientConfig({
props,
minify,
faster: props.siteConfig.future.experimental_faster,
poll,
configureWebpackUtils,
});
config = executePluginsConfigureWebpack({
plugins,
config,
isServer: false,
utils: await createConfigureWebpackUtils({siteConfig}),
configureWebpackUtils,
});
return config;
}
@ -157,10 +160,15 @@ export async function createWebpackDevServer({
cliOptions: StartCLIOptions;
openUrlContext: OpenUrlContext;
}): Promise<WebpackDevServer> {
const configureWebpackUtils = await createConfigureWebpackUtils({
siteConfig: props.siteConfig,
});
const config = await getStartClientConfig({
props,
minify: cliOptions.minify ?? true,
poll: cliOptions.poll,
configureWebpackUtils,
});
const compiler = webpack(config);

View file

@ -10,6 +10,7 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = `
"future": {
"experimental_faster": {
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
},
@ -76,6 +77,7 @@ exports[`loadSiteConfig website with ts + js config 1`] = `
"future": {
"experimental_faster": {
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
},
@ -142,6 +144,7 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = `
"future": {
"experimental_faster": {
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
},
@ -208,6 +211,7 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = `
"future": {
"experimental_faster": {
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
},
@ -274,6 +278,7 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = `
"future": {
"experimental_faster": {
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
},
@ -340,6 +345,7 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = `
"future": {
"experimental_faster": {
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
},
@ -406,6 +412,7 @@ exports[`loadSiteConfig website with valid async config 1`] = `
"future": {
"experimental_faster": {
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
},
@ -474,6 +481,7 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = `
"future": {
"experimental_faster": {
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
},
@ -542,6 +550,7 @@ exports[`loadSiteConfig website with valid config creator function 1`] = `
"future": {
"experimental_faster": {
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
},
@ -613,6 +622,7 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = `
"future": {
"experimental_faster": {
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
},

View file

@ -80,6 +80,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
"future": {
"experimental_faster": {
"mdxCrossCompilerCache": false,
"rspackBundler": false,
"swcJsLoader": false,
"swcJsMinimizer": false,
},

View file

@ -49,6 +49,7 @@ describe('normalizeConfig', () => {
swcJsLoader: true,
swcJsMinimizer: true,
mdxCrossCompilerCache: true,
rspackBundler: true,
},
experimental_storage: {
type: 'sessionStorage',
@ -745,6 +746,7 @@ describe('future', () => {
swcJsLoader: true,
swcJsMinimizer: true,
mdxCrossCompilerCache: true,
rspackBundler: true,
},
experimental_storage: {
type: 'sessionStorage',
@ -1095,6 +1097,7 @@ describe('future', () => {
swcJsLoader: true,
swcJsMinimizer: true,
mdxCrossCompilerCache: true,
rspackBundler: true,
};
expect(
normalizeConfig({
@ -1348,5 +1351,76 @@ describe('future', () => {
`);
});
});
describe('rspackBundler', () => {
it('accepts - undefined', () => {
const faster: Partial<FasterConfig> = {
rspackBundler: undefined,
};
expect(
normalizeConfig({
future: {
experimental_faster: faster,
},
}),
).toEqual(fasterContaining({rspackBundler: false}));
});
it('accepts - true', () => {
const faster: Partial<FasterConfig> = {
rspackBundler: true,
};
expect(
normalizeConfig({
future: {
experimental_faster: faster,
},
}),
).toEqual(fasterContaining({rspackBundler: true}));
});
it('accepts - false', () => {
const faster: Partial<FasterConfig> = {
rspackBundler: false,
};
expect(
normalizeConfig({
future: {
experimental_faster: faster,
},
}),
).toEqual(fasterContaining({rspackBundler: false}));
});
it('rejects - null', () => {
// @ts-expect-error: invalid
const faster: Partial<FasterConfig> = {rspackBundler: 42};
expect(() =>
normalizeConfig({
future: {
experimental_faster: faster,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""future.experimental_faster.rspackBundler" must be a boolean
"
`);
});
it('rejects - number', () => {
// @ts-expect-error: invalid
const faster: Partial<FasterConfig> = {rspackBundler: 42};
expect(() =>
normalizeConfig({
future: {
experimental_faster: faster,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""future.experimental_faster.rspackBundler" must be a boolean
"
`);
});
});
});
});

View file

@ -45,6 +45,7 @@ export const DEFAULT_FASTER_CONFIG: FasterConfig = {
swcJsLoader: false,
swcJsMinimizer: false,
mdxCrossCompilerCache: false,
rspackBundler: false,
};
// When using the "faster: true" shortcut
@ -52,6 +53,7 @@ export const DEFAULT_FASTER_CONFIG_TRUE: FasterConfig = {
swcJsLoader: true,
swcJsMinimizer: true,
mdxCrossCompilerCache: true,
rspackBundler: true,
};
export const DEFAULT_FUTURE_CONFIG: FutureConfig = {
@ -222,6 +224,7 @@ const FASTER_CONFIG_SCHEMA = Joi.alternatives()
mdxCrossCompilerCache: Joi.boolean().default(
DEFAULT_FASTER_CONFIG.mdxCrossCompilerCache,
),
rspackBundler: Joi.boolean().default(DEFAULT_FASTER_CONFIG.rspackBundler),
}),
Joi.boolean()
.required()

View file

@ -15,8 +15,15 @@ import {
DEFAULT_FASTER_CONFIG,
DEFAULT_FUTURE_CONFIG,
} from '../../server/configValidation';
import {createConfigureWebpackUtils} from '../configure';
import type {Props} from '@docusaurus/types';
function createTestConfigureWebpackUtils() {
return createConfigureWebpackUtils({
siteConfig: {webpack: {jsLoader: 'babel'}, future: DEFAULT_FUTURE_CONFIG},
});
}
describe('babel transpilation exclude logic', () => {
it('always transpiles client dir files', () => {
const clientFiles = [
@ -115,6 +122,7 @@ describe('base webpack config', () => {
isServer: true,
minify: true,
faster: DEFAULT_FASTER_CONFIG,
configureWebpackUtils: await createTestConfigureWebpackUtils(),
})
).resolve?.alias ?? {}) as {[alias: string]: string};
// Make aliases relative so that test work on all computers
@ -125,7 +133,8 @@ describe('base webpack config', () => {
});
it('uses svg rule', async () => {
const fileLoaderUtils = utils.getFileLoaderUtils();
const isServer = true;
const fileLoaderUtils = utils.getFileLoaderUtils(isServer);
const mockSvg = jest.spyOn(fileLoaderUtils.rules, 'svg');
jest
.spyOn(utils, 'getFileLoaderUtils')
@ -136,6 +145,7 @@ describe('base webpack config', () => {
isServer: false,
minify: false,
faster: DEFAULT_FASTER_CONFIG,
configureWebpackUtils: await createTestConfigureWebpackUtils(),
});
expect(mockSvg).toHaveBeenCalled();
});

View file

@ -9,12 +9,25 @@ import webpack from 'webpack';
import {createBuildClientConfig, createStartClientConfig} from '../client';
import {loadSetup} from '../../server/__tests__/testUtils';
import {createConfigureWebpackUtils} from '../configure';
import {
DEFAULT_FASTER_CONFIG,
DEFAULT_FUTURE_CONFIG,
} from '../../server/configValidation';
function createTestConfigureWebpackUtils() {
return createConfigureWebpackUtils({
siteConfig: {webpack: {jsLoader: 'babel'}, future: DEFAULT_FUTURE_CONFIG},
});
}
describe('webpack dev config', () => {
it('simple start', async () => {
const {props} = await loadSetup('simple-site');
const {clientConfig} = await createStartClientConfig({
props,
faster: DEFAULT_FASTER_CONFIG,
configureWebpackUtils: await createTestConfigureWebpackUtils(),
minify: false,
poll: false,
});
@ -25,6 +38,8 @@ describe('webpack dev config', () => {
const {props} = await loadSetup('simple-site');
const {config} = await createBuildClientConfig({
props,
faster: DEFAULT_FASTER_CONFIG,
configureWebpackUtils: await createTestConfigureWebpackUtils(),
minify: false,
bundleAnalyzer: false,
});
@ -35,6 +50,8 @@ describe('webpack dev config', () => {
const {props} = await loadSetup('custom-site');
const {clientConfig} = await createStartClientConfig({
props,
faster: DEFAULT_FASTER_CONFIG,
configureWebpackUtils: await createTestConfigureWebpackUtils(),
minify: false,
poll: false,
});
@ -45,6 +62,8 @@ describe('webpack dev config', () => {
const {props} = await loadSetup('custom-site');
const {config} = await createBuildClientConfig({
props,
faster: DEFAULT_FASTER_CONFIG,
configureWebpackUtils: await createTestConfigureWebpackUtils(),
minify: false,
bundleAnalyzer: false,
});

View file

@ -14,17 +14,22 @@ import {
executePluginsConfigureWebpack,
createConfigureWebpackUtils,
} from '../configure';
import {DEFAULT_FUTURE_CONFIG} from '../../server/configValidation';
import type {Configuration} from 'webpack';
import type {LoadedPlugin, Plugin} from '@docusaurus/types';
const utils = createConfigureWebpackUtils({
siteConfig: {webpack: {jsLoader: 'babel'}},
});
function createTestConfigureWebpackUtils() {
return createConfigureWebpackUtils({
siteConfig: {webpack: {jsLoader: 'babel'}, future: DEFAULT_FUTURE_CONFIG},
});
}
const isServer = false;
describe('extending generated webpack config', () => {
it('direct mutation on generated webpack config object', async () => {
const utils = await createTestConfigureWebpackUtils();
// Fake generated webpack config
let config: Configuration = {
output: {
@ -52,7 +57,7 @@ describe('extending generated webpack config', () => {
configureWebpack,
config,
isServer,
utils,
configureWebpackUtils: utils,
content: {
content: 42,
},
@ -69,6 +74,8 @@ describe('extending generated webpack config', () => {
});
it('webpack-merge with user webpack config object', async () => {
const utils = await createTestConfigureWebpackUtils();
let config: Configuration = {
output: {
path: __dirname,
@ -88,7 +95,7 @@ describe('extending generated webpack config', () => {
configureWebpack,
config,
isServer,
utils,
configureWebpackUtils: utils,
content: {
content: 42,
},
@ -105,6 +112,8 @@ describe('extending generated webpack config', () => {
});
it('webpack-merge with custom strategy', async () => {
const utils = await createTestConfigureWebpackUtils();
const config: Configuration = {
module: {
rules: [{use: 'xxx'}, {use: 'yyy'}],
@ -126,7 +135,7 @@ describe('extending generated webpack config', () => {
configureWebpack: createConfigureWebpack(),
config,
isServer,
utils,
configureWebpackUtils: utils,
content: {content: 42},
});
expect(defaultStrategyMergeConfig).toEqual({
@ -139,7 +148,7 @@ describe('extending generated webpack config', () => {
configureWebpack: createConfigureWebpack({'module.rules': 'prepend'}),
config,
isServer,
utils,
configureWebpackUtils: utils,
content: {content: 42},
});
expect(prependRulesStrategyConfig).toEqual({
@ -154,7 +163,7 @@ describe('extending generated webpack config', () => {
}),
config,
isServer,
utils,
configureWebpackUtils: utils,
content: {content: 42},
});
expect(uselessMergeStrategyConfig).toEqual({
@ -293,11 +302,13 @@ describe('executePluginsConfigureWebpack', () => {
});
}
it('can merge Webpack aliases of 2 plugins into base config', () => {
it('can merge Webpack aliases of 2 plugins into base config', async () => {
const utils = await createTestConfigureWebpackUtils();
const config = executePluginsConfigureWebpack({
config: {resolve: {alias: {'initial-alias': 'initial-alias-value'}}},
isServer,
utils,
configureWebpackUtils: utils,
plugins: [
fakePlugin({
configureWebpack: () => {
@ -328,11 +339,13 @@ describe('executePluginsConfigureWebpack', () => {
);
});
it('can configurePostCSS() for all loaders added through configureWebpack()', () => {
it('can configurePostCSS() for all loaders added through configureWebpack()', async () => {
const utils = await createTestConfigureWebpackUtils();
const config = executePluginsConfigureWebpack({
config: {},
isServer,
utils,
configureWebpackUtils: utils,
plugins: [
fakePlugin({
configurePostCss: (postCssOptions) => {

View file

@ -10,6 +10,14 @@ import webpack from 'webpack';
import createServerConfig from '../server';
import {loadSetup} from '../../server/__tests__/testUtils';
import {createConfigureWebpackUtils} from '../configure';
import {DEFAULT_FUTURE_CONFIG} from '../../server/configValidation';
function createTestConfigureWebpackUtils() {
return createConfigureWebpackUtils({
siteConfig: {webpack: {jsLoader: 'babel'}, future: DEFAULT_FUTURE_CONFIG},
});
}
describe('webpack production config', () => {
it('simple', async () => {
@ -17,6 +25,7 @@ describe('webpack production config', () => {
const {props} = await loadSetup('simple-site');
const {config} = await createServerConfig({
props,
configureWebpackUtils: await createTestConfigureWebpackUtils(),
});
webpack.validate(config);
});
@ -26,6 +35,7 @@ describe('webpack production config', () => {
const {props} = await loadSetup('custom-site');
const {config} = await createServerConfig({
props,
configureWebpackUtils: await createTestConfigureWebpackUtils(),
});
webpack.validate(config);
});

View file

@ -7,17 +7,17 @@
import fs from 'fs-extra';
import path from 'path';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import {md5Hash, getFileLoaderUtils} from '@docusaurus/utils';
import {
createJsLoaderFactory,
getStyleLoaders,
getCustomBabelConfigFilePath,
} from './utils';
import {createJsLoaderFactory, getCustomBabelConfigFilePath} from './utils';
import {getMinimizers} from './minification';
import {loadThemeAliases, loadDocusaurusAliases} from './aliases';
import {getCSSExtractPlugin} from './currentBundler';
import type {Configuration} from 'webpack';
import type {FasterConfig, Props} from '@docusaurus/types';
import type {
ConfigureWebpackUtils,
FasterConfig,
Props,
} from '@docusaurus/types';
const CSS_REGEX = /\.css$/i;
const CSS_MODULE_REGEX = /\.module\.css$/i;
@ -58,11 +58,13 @@ export async function createBaseConfig({
isServer,
minify,
faster,
configureWebpackUtils,
}: {
props: Props;
isServer: boolean;
minify: boolean;
faster: FasterConfig;
configureWebpackUtils: ConfigureWebpackUtils;
}): Promise<Configuration> {
const {
outDir,
@ -88,6 +90,10 @@ export async function createBaseConfig({
const createJsLoader = await createJsLoaderFactory({siteConfig});
const CSSExtractPlugin = await getCSSExtractPlugin({
currentBundler: configureWebpackUtils.currentBundler,
});
return {
mode,
name,
@ -224,7 +230,7 @@ export async function createBaseConfig({
{
test: CSS_REGEX,
exclude: CSS_MODULE_REGEX,
use: getStyleLoaders(isServer, {
use: configureWebpackUtils.getStyleLoaders(isServer, {
importLoaders: 1,
sourceMap: !isProd,
}),
@ -233,7 +239,7 @@ export async function createBaseConfig({
// using the extension .module.css
{
test: CSS_MODULE_REGEX,
use: getStyleLoaders(isServer, {
use: configureWebpackUtils.getStyleLoaders(isServer, {
modules: {
// Using the same CSS Module class pattern in dev/prod on purpose
// See https://github.com/facebook/docusaurus/pull/10423
@ -247,7 +253,7 @@ export async function createBaseConfig({
],
},
plugins: [
new MiniCssExtractPlugin({
new CSSExtractPlugin({
filename: isProd
? 'assets/css/[name].[contenthash:8].css'
: '[name].css',

View file

@ -17,7 +17,11 @@ import ChunkAssetPlugin from './plugins/ChunkAssetPlugin';
import CleanWebpackPlugin from './plugins/CleanWebpackPlugin';
import ForceTerminatePlugin from './plugins/ForceTerminatePlugin';
import {createStaticDirectoriesCopyPlugin} from './plugins/StaticDirectoriesCopyPlugin';
import type {FasterConfig, Props} from '@docusaurus/types';
import type {
ConfigureWebpackUtils,
FasterConfig,
Props,
} from '@docusaurus/types';
import type {Configuration} from 'webpack';
async function createBaseClientConfig({
@ -25,17 +29,20 @@ async function createBaseClientConfig({
hydrate,
minify,
faster,
configureWebpackUtils,
}: {
props: Props;
hydrate: boolean;
minify: boolean;
faster: FasterConfig;
configureWebpackUtils: ConfigureWebpackUtils;
}): Promise<Configuration> {
const baseConfig = await createBaseConfig({
props,
isServer: false,
minify,
faster,
configureWebpackUtils,
});
return merge(baseConfig, {
@ -57,7 +64,10 @@ async function createBaseClientConfig({
new WebpackBar({
name: 'Client',
}),
await createStaticDirectoriesCopyPlugin({props}),
await createStaticDirectoriesCopyPlugin({
props,
currentBundler: configureWebpackUtils.currentBundler,
}),
].filter(Boolean),
});
}
@ -68,11 +78,13 @@ export async function createStartClientConfig({
minify,
poll,
faster,
configureWebpackUtils,
}: {
props: Props;
minify: boolean;
poll: number | boolean | undefined;
faster: FasterConfig;
configureWebpackUtils: ConfigureWebpackUtils;
}): Promise<{clientConfig: Configuration}> {
const {siteConfig, headTags, preBodyTags, postBodyTags} = props;
@ -82,6 +94,7 @@ export async function createStartClientConfig({
minify,
hydrate: false,
faster,
configureWebpackUtils,
}),
{
watchOptions: {
@ -116,11 +129,13 @@ export async function createBuildClientConfig({
props,
minify,
faster,
configureWebpackUtils,
bundleAnalyzer,
}: {
props: Props;
minify: boolean;
faster: FasterConfig;
configureWebpackUtils: ConfigureWebpackUtils;
bundleAnalyzer: boolean;
}): Promise<{config: Configuration; clientManifestPath: string}> {
// Apply user webpack config.
@ -137,7 +152,13 @@ export async function createBuildClientConfig({
);
const config: Configuration = merge(
await createBaseClientConfig({props, minify, faster, hydrate}),
await createBaseClientConfig({
props,
minify,
faster,
configureWebpackUtils,
hydrate,
}),
{
plugins: [
new ForceTerminatePlugin(),

View file

@ -10,8 +10,8 @@ import {
customizeArray,
customizeObject,
} from 'webpack-merge';
import {createJsLoaderFactory, getStyleLoaders} from './utils';
import {createJsLoaderFactory, createStyleLoadersFactory} from './utils';
import {getCurrentBundler} from './currentBundler';
import type {Configuration, RuleSetRule} from 'webpack';
import type {
Plugin,
@ -27,11 +27,16 @@ import type {
export async function createConfigureWebpackUtils({
siteConfig,
}: {
siteConfig: Parameters<typeof createJsLoaderFactory>[0]['siteConfig'];
siteConfig: Parameters<typeof createJsLoaderFactory>[0]['siteConfig'] &
Parameters<typeof getCurrentBundler>[0]['siteConfig'];
}): Promise<ConfigureWebpackUtils> {
const currentBundler = await getCurrentBundler({siteConfig});
const getStyleLoaders = await createStyleLoadersFactory({currentBundler});
const getJSLoader = await createJsLoaderFactory({siteConfig});
return {
currentBundler,
getStyleLoaders,
getJSLoader: await createJsLoaderFactory({siteConfig}),
getJSLoader,
};
}
@ -48,18 +53,18 @@ export function applyConfigureWebpack({
configureWebpack,
config,
isServer,
utils,
configureWebpackUtils,
content,
}: {
configureWebpack: NonNullable<Plugin['configureWebpack']>;
config: Configuration;
isServer: boolean;
utils: ConfigureWebpackUtils;
configureWebpackUtils: ConfigureWebpackUtils;
content: unknown;
}): Configuration {
if (typeof configureWebpack === 'function') {
const {mergeStrategy, ...res} =
configureWebpack(config, isServer, utils, content) ?? {};
configureWebpack(config, isServer, configureWebpackUtils, content) ?? {};
const customizeRules = mergeStrategy ?? {};
return mergeWithCustomize({
customizeArray: customizeArray(customizeRules),
@ -134,12 +139,12 @@ export function executePluginsConfigureWebpack({
plugins,
config: configInput,
isServer,
utils,
configureWebpackUtils,
}: {
plugins: LoadedPlugin[];
config: Configuration;
isServer: boolean;
utils: ConfigureWebpackUtils;
configureWebpackUtils: ConfigureWebpackUtils;
}): Configuration {
let config = configInput;
@ -151,7 +156,7 @@ export function executePluginsConfigureWebpack({
configureWebpack: configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`.
config,
isServer,
utils,
configureWebpackUtils,
content: plugin.content,
});
}

View file

@ -0,0 +1,66 @@
/**
* 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 webpack from 'webpack';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import logger from '@docusaurus/logger';
import type {CurrentBundler, DocusaurusConfig} from '@docusaurus/types';
// We inject a site config slice because the Rspack flag might change place
type SiteConfigSlice = {
future: {
experimental_faster: Pick<
DocusaurusConfig['future']['experimental_faster'],
'rspackBundler'
>;
};
};
function isRspack(siteConfig: SiteConfigSlice): boolean {
return siteConfig.future.experimental_faster.rspackBundler;
}
export async function getCurrentBundler({
siteConfig,
}: {
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: 'webpack',
instance: webpack,
};
}
export async function getCSSExtractPlugin({
currentBundler,
}: {
currentBundler: CurrentBundler;
}): Promise<typeof MiniCssExtractPlugin> {
if (currentBundler.name === 'rspack') {
throw new Error('Rspack bundler is not supported yet');
}
return MiniCssExtractPlugin;
}
export async function getCopyPlugin({
currentBundler,
}: {
currentBundler: CurrentBundler;
}): Promise<typeof CopyWebpackPlugin> {
if (currentBundler.name === 'rspack') {
throw new Error('Rspack bundler is not supported yet');
}
// https://github.com/webpack-contrib/copy-webpack-plugin
return CopyWebpackPlugin;
}

View file

@ -7,14 +7,21 @@
import path from 'path';
import fs from 'fs-extra';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import type {Props} from '@docusaurus/types';
import {getCopyPlugin} from '../currentBundler';
import type {CurrentBundler, Props} from '@docusaurus/types';
import type {WebpackPluginInstance} from 'webpack';
export async function createStaticDirectoriesCopyPlugin({
props,
currentBundler,
}: {
props: Props;
}): Promise<CopyWebpackPlugin | undefined> {
currentBundler: CurrentBundler;
}): Promise<WebpackPluginInstance | undefined> {
const CopyPlugin = await getCopyPlugin({
currentBundler,
});
const {
outDir,
siteDir,
@ -44,7 +51,7 @@ export async function createStaticDirectoriesCopyPlugin({
return undefined;
}
return new CopyWebpackPlugin({
return new CopyPlugin({
patterns: staticDirectories.map((dir) => ({
from: dir,
to: outDir,

View file

@ -10,19 +10,22 @@ import merge from 'webpack-merge';
import {NODE_MAJOR_VERSION, NODE_MINOR_VERSION} from '@docusaurus/utils';
import WebpackBar from 'webpackbar';
import {createBaseConfig} from './base';
import type {Props} from '@docusaurus/types';
import type {ConfigureWebpackUtils, Props} from '@docusaurus/types';
import type {Configuration} from 'webpack';
export default async function createServerConfig(params: {
export default async function createServerConfig({
props,
configureWebpackUtils,
}: {
props: Props;
configureWebpackUtils: ConfigureWebpackUtils;
}): Promise<{config: Configuration; serverBundlePath: string}> {
const {props} = params;
const baseConfig = await createBaseConfig({
props,
isServer: true,
minify: false,
faster: props.siteConfig.future.experimental_faster,
configureWebpackUtils,
});
const outputFilename = 'server.bundle.js';

View file

@ -10,11 +10,15 @@ import path from 'path';
import crypto from 'crypto';
import logger from '@docusaurus/logger';
import {BABEL_CONFIG_FILE_NAME} from '@docusaurus/utils';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import webpack, {type Configuration, type RuleSetRule} from 'webpack';
import webpack, {type Configuration} from 'webpack';
import formatWebpackMessages from 'react-dev-utils/formatWebpackMessages';
import {importSwcJsLoaderFactory} from '../faster';
import type {ConfigureWebpackUtils, DocusaurusConfig} from '@docusaurus/types';
import {getCSSExtractPlugin} from './currentBundler';
import type {
ConfigureWebpackUtils,
CurrentBundler,
DocusaurusConfig,
} from '@docusaurus/types';
import type {TransformOptions} from '@babel/core';
export function formatStatsErrorMessage(
@ -42,68 +46,75 @@ export function printStatsWarnings(
}
}
// Utility method to get style loaders
export function getStyleLoaders(
isServer: boolean,
cssOptionsArg: {
[key: string]: unknown;
} = {},
): RuleSetRule[] {
const cssOptions: {[key: string]: unknown} = {
// TODO turn esModule on later, see https://github.com/facebook/docusaurus/pull/6424
esModule: false,
...cssOptionsArg,
};
export async function createStyleLoadersFactory({
currentBundler,
}: {
currentBundler: CurrentBundler;
}): Promise<ConfigureWebpackUtils['getStyleLoaders']> {
const CssExtractPlugin = await getCSSExtractPlugin({currentBundler});
// On the server we don't really need to extract/emit CSS
// We only need to transform CSS module imports to a styles object
if (isServer) {
return cssOptions.modules
? [
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
]
: // Ignore regular CSS files
[{loader: require.resolve('null-loader')}];
}
return function getStyleLoaders(
isServer: boolean,
cssOptionsArg: {
[key: string]: unknown;
} = {},
) {
const cssOptions: {[key: string]: unknown} = {
// TODO turn esModule on later, see https://github.com/facebook/docusaurus/pull/6424
esModule: false,
...cssOptionsArg,
};
return [
{
loader: MiniCssExtractPlugin.loader,
options: {
esModule: true,
},
},
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
// On the server we don't really need to extract/emit CSS
// We only need to transform CSS module imports to a styles object
if (isServer) {
return cssOptions.modules
? [
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
]
: // Ignore regular CSS files
[{loader: require.resolve('null-loader')}];
}
// TODO apart for configurePostCss(), do we really need this loader?
// Note: using postcss here looks inefficient/duplicate
// But in practice, it's not a big deal because css-loader also uses postcss
// and is able to reuse the parsed AST from postcss-loader
// See https://github.com/webpack-contrib/css-loader/blob/master/src/index.js#L159
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
postcssOptions: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: [
// eslint-disable-next-line global-require
require('autoprefixer'),
],
return [
{
loader: CssExtractPlugin.loader,
options: {
esModule: true,
},
},
},
];
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
// TODO apart for configurePostCss(), do we really need this loader?
// Note: using postcss here looks inefficient/duplicate
// But in practice, it's not a big deal because css-loader also uses postcss
// and is able to reuse the parsed AST from postcss-loader
// See https://github.com/webpack-contrib/css-loader/blob/master/src/index.js#L159
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
postcssOptions: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: [
// eslint-disable-next-line global-require
require('autoprefixer'),
],
},
},
},
];
};
}
export async function getCustomBabelConfigFilePath(

View file

@ -313,6 +313,7 @@ rmiz
rsdoctor
Rsdoctor
RSDOCTOR
rspack
Rspack
rtcts
rtlcss

View file

@ -5082,7 +5082,7 @@ cacheable-request@^10.2.8:
normalize-url "^8.0.0"
responselike "^3.0.0"
call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.7:
call-bind@^1.0.2, call-bind@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
@ -8260,7 +8260,7 @@ get-caller-file@^2.0.5:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.4:
get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
@ -12816,7 +12816,7 @@ object-hash@^3.0.0:
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
object-inspect@^1.12.3, object-inspect@^1.13.1, object-inspect@^1.9.0:
object-inspect@^1.12.3, object-inspect@^1.13.1:
version "1.13.2"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff"
integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==