mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-29 18:27:56 +02:00
fix(core): configurePostCss()
should run after configureWebpack()
(#10132)
This commit is contained in:
parent
29b7a4ddbb
commit
ff5039f413
7 changed files with 620 additions and 405 deletions
|
@ -13,7 +13,7 @@ import {isMatch} from 'picomatch';
|
|||
import commander from 'commander';
|
||||
import webpack from 'webpack';
|
||||
import {loadContext} from '@docusaurus/core/src/server/site';
|
||||
import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/utils';
|
||||
import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/configure';
|
||||
import {sortRoutes} from '@docusaurus/core/src/server/plugins/routeConfig';
|
||||
import {posixPath} from '@docusaurus/utils';
|
||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||
|
|
|
@ -15,11 +15,8 @@ import {handleBrokenLinks} from '../server/brokenLinks';
|
|||
|
||||
import {createBuildClientConfig} from '../webpack/client';
|
||||
import createServerConfig from '../webpack/server';
|
||||
import {
|
||||
executePluginsConfigurePostCss,
|
||||
executePluginsConfigureWebpack,
|
||||
compile,
|
||||
} from '../webpack/utils';
|
||||
import {executePluginsConfigureWebpack} from '../webpack/configure';
|
||||
import {compile} from '../webpack/utils';
|
||||
import {PerfLogger} from '../utils';
|
||||
|
||||
import {loadI18n} from '../server/i18n';
|
||||
|
@ -325,10 +322,6 @@ async function getBuildClientConfig({
|
|||
bundleAnalyzer: cliOptions.bundleAnalyzer ?? false,
|
||||
});
|
||||
let {config} = result;
|
||||
config = executePluginsConfigurePostCss({
|
||||
plugins,
|
||||
config,
|
||||
});
|
||||
config = executePluginsConfigureWebpack({
|
||||
plugins,
|
||||
config,
|
||||
|
|
|
@ -13,12 +13,11 @@ import WebpackDevServer from 'webpack-dev-server';
|
|||
import evalSourceMapMiddleware from 'react-dev-utils/evalSourceMapMiddleware';
|
||||
import {createPollingOptions} from './watcher';
|
||||
import {
|
||||
executePluginsConfigurePostCss,
|
||||
executePluginsConfigureWebpack,
|
||||
formatStatsErrorMessage,
|
||||
getHttpsConfig,
|
||||
printStatsWarnings,
|
||||
} from '../../webpack/utils';
|
||||
import {executePluginsConfigureWebpack} from '../../webpack/configure';
|
||||
import {createStartClientConfig} from '../../webpack/client';
|
||||
import type {StartCLIOptions} from './start';
|
||||
import type {Props} from '@docusaurus/types';
|
||||
|
@ -135,7 +134,6 @@ async function getStartClientConfig({
|
|||
minify,
|
||||
poll,
|
||||
});
|
||||
config = executePluginsConfigurePostCss({plugins, config});
|
||||
config = executePluginsConfigureWebpack({
|
||||
plugins,
|
||||
config,
|
||||
|
|
458
packages/docusaurus/src/webpack/__tests__/configure.test.ts
Normal file
458
packages/docusaurus/src/webpack/__tests__/configure.test.ts
Normal file
|
@ -0,0 +1,458 @@
|
|||
/**
|
||||
* 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 * as path from 'path';
|
||||
import * as webpack from 'webpack';
|
||||
import {fromPartial} from '@total-typescript/shoehorn';
|
||||
import {
|
||||
applyConfigureWebpack,
|
||||
applyConfigurePostCss,
|
||||
executePluginsConfigureWebpack,
|
||||
} from '../configure';
|
||||
import type {Configuration} from 'webpack';
|
||||
import type {LoadedPlugin, Plugin} from '@docusaurus/types';
|
||||
|
||||
describe('extending generated webpack config', () => {
|
||||
it('direct mutation on generated webpack config object', async () => {
|
||||
// Fake generated webpack config
|
||||
let config: Configuration = {
|
||||
output: {
|
||||
path: __dirname,
|
||||
filename: 'bundle.js',
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-expect-error: Testing an edge-case that we did not write types for
|
||||
const configureWebpack: NonNullable<Plugin['configureWebpack']> = (
|
||||
generatedConfig,
|
||||
isServer,
|
||||
) => {
|
||||
if (!isServer) {
|
||||
generatedConfig.entry = 'entry.js';
|
||||
generatedConfig.output = {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'new.bundle.js',
|
||||
};
|
||||
}
|
||||
// Implicitly returning undefined to test null-safety
|
||||
};
|
||||
|
||||
config = applyConfigureWebpack(configureWebpack, config, false, undefined, {
|
||||
content: 42,
|
||||
});
|
||||
expect(config).toEqual({
|
||||
entry: 'entry.js',
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'new.bundle.js',
|
||||
},
|
||||
});
|
||||
const errors = webpack.validate(config);
|
||||
expect(errors).toBeUndefined();
|
||||
});
|
||||
|
||||
it('webpack-merge with user webpack config object', async () => {
|
||||
let config: Configuration = {
|
||||
output: {
|
||||
path: __dirname,
|
||||
filename: 'bundle.js',
|
||||
},
|
||||
};
|
||||
|
||||
const configureWebpack: Plugin['configureWebpack'] = () => ({
|
||||
entry: 'entry.js',
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'new.bundle.js',
|
||||
},
|
||||
});
|
||||
|
||||
config = applyConfigureWebpack(configureWebpack, config, false, undefined, {
|
||||
content: 42,
|
||||
});
|
||||
expect(config).toEqual({
|
||||
entry: 'entry.js',
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'new.bundle.js',
|
||||
},
|
||||
});
|
||||
const errors = webpack.validate(config);
|
||||
expect(errors).toBeUndefined();
|
||||
});
|
||||
|
||||
it('webpack-merge with custom strategy', async () => {
|
||||
const config: Configuration = {
|
||||
module: {
|
||||
rules: [{use: 'xxx'}, {use: 'yyy'}],
|
||||
},
|
||||
};
|
||||
|
||||
const createConfigureWebpack =
|
||||
(mergeStrategy?: {
|
||||
[key: string]: 'prepend' | 'append';
|
||||
}): NonNullable<Plugin['configureWebpack']> =>
|
||||
() => ({
|
||||
module: {
|
||||
rules: [{use: 'zzz'}],
|
||||
},
|
||||
mergeStrategy,
|
||||
});
|
||||
|
||||
const defaultStrategyMergeConfig = applyConfigureWebpack(
|
||||
createConfigureWebpack(),
|
||||
config,
|
||||
false,
|
||||
undefined,
|
||||
{content: 42},
|
||||
);
|
||||
expect(defaultStrategyMergeConfig).toEqual({
|
||||
module: {
|
||||
rules: [{use: 'xxx'}, {use: 'yyy'}, {use: 'zzz'}],
|
||||
},
|
||||
});
|
||||
|
||||
const prependRulesStrategyConfig = applyConfigureWebpack(
|
||||
createConfigureWebpack({'module.rules': 'prepend'}),
|
||||
config,
|
||||
false,
|
||||
undefined,
|
||||
{content: 42},
|
||||
);
|
||||
expect(prependRulesStrategyConfig).toEqual({
|
||||
module: {
|
||||
rules: [{use: 'zzz'}, {use: 'xxx'}, {use: 'yyy'}],
|
||||
},
|
||||
});
|
||||
|
||||
const uselessMergeStrategyConfig = applyConfigureWebpack(
|
||||
createConfigureWebpack({uselessAttributeName: 'append'}),
|
||||
config,
|
||||
false,
|
||||
undefined,
|
||||
{content: 42},
|
||||
);
|
||||
expect(uselessMergeStrategyConfig).toEqual({
|
||||
module: {
|
||||
rules: [{use: 'xxx'}, {use: 'yyy'}, {use: 'zzz'}],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('extending PostCSS', () => {
|
||||
it('user plugin should be appended in PostCSS loader', () => {
|
||||
let webpackConfig: Configuration = {
|
||||
output: {
|
||||
path: __dirname,
|
||||
filename: 'bundle.js',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: 'any',
|
||||
use: [
|
||||
{
|
||||
loader: 'some-loader-1',
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
loader: 'some-loader-2',
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader-1',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: [['default-postcss-loader-1-plugin']],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'some-loader-3',
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: '2nd-test',
|
||||
use: [
|
||||
{
|
||||
loader: 'postcss-loader-2',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: [['default-postcss-loader-2-plugin']],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
function createFakePlugin(name: string) {
|
||||
return [name, {}];
|
||||
}
|
||||
|
||||
// Run multiple times: ensure last run does not override previous runs
|
||||
webpackConfig = applyConfigurePostCss(
|
||||
(postCssOptions) => ({
|
||||
...postCssOptions,
|
||||
plugins: [
|
||||
...postCssOptions.plugins,
|
||||
createFakePlugin('postcss-plugin-1'),
|
||||
],
|
||||
}),
|
||||
webpackConfig,
|
||||
);
|
||||
|
||||
webpackConfig = applyConfigurePostCss(
|
||||
(postCssOptions) => ({
|
||||
...postCssOptions,
|
||||
plugins: [
|
||||
createFakePlugin('postcss-plugin-2'),
|
||||
...postCssOptions.plugins,
|
||||
],
|
||||
}),
|
||||
webpackConfig,
|
||||
);
|
||||
|
||||
webpackConfig = applyConfigurePostCss(
|
||||
(postCssOptions) => ({
|
||||
...postCssOptions,
|
||||
plugins: [
|
||||
...postCssOptions.plugins,
|
||||
createFakePlugin('postcss-plugin-3'),
|
||||
],
|
||||
}),
|
||||
webpackConfig,
|
||||
);
|
||||
|
||||
// @ts-expect-error: relax type
|
||||
const postCssLoader1 = webpackConfig.module?.rules[0].use[2];
|
||||
expect(postCssLoader1.loader).toBe('postcss-loader-1');
|
||||
|
||||
const pluginNames1 = postCssLoader1.options.postcssOptions.plugins.map(
|
||||
(p: unknown[]) => p[0],
|
||||
);
|
||||
expect(pluginNames1).toHaveLength(4);
|
||||
expect(pluginNames1).toEqual([
|
||||
'postcss-plugin-2',
|
||||
'default-postcss-loader-1-plugin',
|
||||
'postcss-plugin-1',
|
||||
'postcss-plugin-3',
|
||||
]);
|
||||
|
||||
// @ts-expect-error: relax type
|
||||
const postCssLoader2 = webpackConfig.module?.rules[1].use[0];
|
||||
expect(postCssLoader2.loader).toBe('postcss-loader-2');
|
||||
|
||||
const pluginNames2 = postCssLoader2.options.postcssOptions.plugins.map(
|
||||
(p: unknown[]) => p[0],
|
||||
);
|
||||
expect(pluginNames2).toHaveLength(4);
|
||||
expect(pluginNames2).toEqual([
|
||||
'postcss-plugin-2',
|
||||
'default-postcss-loader-2-plugin',
|
||||
'postcss-plugin-1',
|
||||
'postcss-plugin-3',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('executePluginsConfigureWebpack', () => {
|
||||
function fakePlugin(partialPlugin: Partial<LoadedPlugin>): LoadedPlugin {
|
||||
return fromPartial({
|
||||
...partialPlugin,
|
||||
});
|
||||
}
|
||||
|
||||
it('can merge Webpack aliases of 2 plugins into base config', () => {
|
||||
const config = executePluginsConfigureWebpack({
|
||||
config: {resolve: {alias: {'initial-alias': 'initial-alias-value'}}},
|
||||
isServer: false,
|
||||
jsLoader: 'babel',
|
||||
plugins: [
|
||||
fakePlugin({
|
||||
configureWebpack: () => {
|
||||
return {resolve: {alias: {'p1-alias': 'p1-alias-value'}}};
|
||||
},
|
||||
}),
|
||||
fakePlugin({
|
||||
configureWebpack: () => {
|
||||
return {resolve: {alias: {'p2-alias': 'p2-alias-value'}}};
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(config).toMatchInlineSnapshot(
|
||||
{},
|
||||
`
|
||||
{
|
||||
"resolve": {
|
||||
"alias": {
|
||||
"initial-alias": "initial-alias-value",
|
||||
"p1-alias": "p1-alias-value",
|
||||
"p2-alias": "p2-alias-value",
|
||||
},
|
||||
},
|
||||
}
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('can configurePostCSS() for all loaders added through configureWebpack()', () => {
|
||||
const config = executePluginsConfigureWebpack({
|
||||
config: {},
|
||||
isServer: false,
|
||||
jsLoader: 'babel',
|
||||
plugins: [
|
||||
fakePlugin({
|
||||
configurePostCss: (postCssOptions) => {
|
||||
// Imperative mutation should work
|
||||
postCssOptions.plugins.push('p1-added-postcss-plugin');
|
||||
return postCssOptions;
|
||||
},
|
||||
configureWebpack: () => {
|
||||
return {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.module.scss$/,
|
||||
use: 'some-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: ['p1-initial-postcss-plugin'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
fakePlugin({
|
||||
configurePostCss: (postCssOptions) => {
|
||||
postCssOptions.plugins.push('p2-added-postcss-plugin');
|
||||
return postCssOptions;
|
||||
},
|
||||
configureWebpack: () => {
|
||||
return {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.module.scss$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: ['p2-initial-postcss-plugin'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
fakePlugin({
|
||||
configurePostCss: (postCssOptions) => {
|
||||
// Functional/immutable copy mutation should work
|
||||
return {
|
||||
...postCssOptions,
|
||||
plugins: [...postCssOptions.plugins, 'p3-added-postcss-plugin'],
|
||||
};
|
||||
},
|
||||
configureWebpack: () => {
|
||||
return {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.module.scss$/,
|
||||
oneOf: [
|
||||
{
|
||||
use: 'some-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: ['p3-initial-postcss-plugin'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(config.module.rules).toHaveLength(3);
|
||||
expect(config.module.rules[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"options": {
|
||||
"postcssOptions": {
|
||||
"plugins": [
|
||||
"p1-initial-postcss-plugin",
|
||||
"p1-added-postcss-plugin",
|
||||
"p2-added-postcss-plugin",
|
||||
"p3-added-postcss-plugin",
|
||||
],
|
||||
},
|
||||
},
|
||||
"test": /\\\\\\.module\\.scss\\$/,
|
||||
"use": "some-loader",
|
||||
}
|
||||
`);
|
||||
expect(config.module.rules[1]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"test": /\\\\\\.module\\.scss\\$/,
|
||||
"use": [
|
||||
{
|
||||
"loader": "postcss-loader",
|
||||
"options": {
|
||||
"postcssOptions": {
|
||||
"plugins": [
|
||||
"p2-initial-postcss-plugin",
|
||||
"p1-added-postcss-plugin",
|
||||
"p2-added-postcss-plugin",
|
||||
"p3-added-postcss-plugin",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(config.module.rules[2]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"options": {
|
||||
"postcssOptions": {
|
||||
"plugins": [
|
||||
"p3-initial-postcss-plugin",
|
||||
"p1-added-postcss-plugin",
|
||||
"p2-added-postcss-plugin",
|
||||
"p3-added-postcss-plugin",
|
||||
],
|
||||
},
|
||||
},
|
||||
"use": "some-loader",
|
||||
},
|
||||
],
|
||||
"test": /\\\\\\.module\\.scss\\$/,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -6,15 +6,8 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import webpack, {type Configuration, type RuleSetRule} from 'webpack';
|
||||
|
||||
import {
|
||||
getCustomizableJSLoader,
|
||||
applyConfigureWebpack,
|
||||
applyConfigurePostCss,
|
||||
getHttpsConfig,
|
||||
} from '../utils';
|
||||
import type {Plugin} from '@docusaurus/types';
|
||||
import {getCustomizableJSLoader, getHttpsConfig} from '../utils';
|
||||
import type {RuleSetRule} from 'webpack';
|
||||
|
||||
describe('customize JS loader', () => {
|
||||
it('getCustomizableJSLoader defaults to babel loader', () => {
|
||||
|
@ -50,255 +43,6 @@ describe('customize JS loader', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('extending generated webpack config', () => {
|
||||
it('direct mutation on generated webpack config object', async () => {
|
||||
// Fake generated webpack config
|
||||
let config: Configuration = {
|
||||
output: {
|
||||
path: __dirname,
|
||||
filename: 'bundle.js',
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-expect-error: Testing an edge-case that we did not write types for
|
||||
const configureWebpack: NonNullable<Plugin['configureWebpack']> = (
|
||||
generatedConfig,
|
||||
isServer,
|
||||
) => {
|
||||
if (!isServer) {
|
||||
generatedConfig.entry = 'entry.js';
|
||||
generatedConfig.output = {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'new.bundle.js',
|
||||
};
|
||||
}
|
||||
// Implicitly returning undefined to test null-safety
|
||||
};
|
||||
|
||||
config = applyConfigureWebpack(configureWebpack, config, false, undefined, {
|
||||
content: 42,
|
||||
});
|
||||
expect(config).toEqual({
|
||||
entry: 'entry.js',
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'new.bundle.js',
|
||||
},
|
||||
});
|
||||
const errors = webpack.validate(config);
|
||||
expect(errors).toBeUndefined();
|
||||
});
|
||||
|
||||
it('webpack-merge with user webpack config object', async () => {
|
||||
let config: Configuration = {
|
||||
output: {
|
||||
path: __dirname,
|
||||
filename: 'bundle.js',
|
||||
},
|
||||
};
|
||||
|
||||
const configureWebpack: Plugin['configureWebpack'] = () => ({
|
||||
entry: 'entry.js',
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'new.bundle.js',
|
||||
},
|
||||
});
|
||||
|
||||
config = applyConfigureWebpack(configureWebpack, config, false, undefined, {
|
||||
content: 42,
|
||||
});
|
||||
expect(config).toEqual({
|
||||
entry: 'entry.js',
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'new.bundle.js',
|
||||
},
|
||||
});
|
||||
const errors = webpack.validate(config);
|
||||
expect(errors).toBeUndefined();
|
||||
});
|
||||
|
||||
it('webpack-merge with custom strategy', async () => {
|
||||
const config: Configuration = {
|
||||
module: {
|
||||
rules: [{use: 'xxx'}, {use: 'yyy'}],
|
||||
},
|
||||
};
|
||||
|
||||
const createConfigureWebpack =
|
||||
(mergeStrategy?: {
|
||||
[key: string]: 'prepend' | 'append';
|
||||
}): NonNullable<Plugin['configureWebpack']> =>
|
||||
() => ({
|
||||
module: {
|
||||
rules: [{use: 'zzz'}],
|
||||
},
|
||||
mergeStrategy,
|
||||
});
|
||||
|
||||
const defaultStrategyMergeConfig = applyConfigureWebpack(
|
||||
createConfigureWebpack(),
|
||||
config,
|
||||
false,
|
||||
undefined,
|
||||
{content: 42},
|
||||
);
|
||||
expect(defaultStrategyMergeConfig).toEqual({
|
||||
module: {
|
||||
rules: [{use: 'xxx'}, {use: 'yyy'}, {use: 'zzz'}],
|
||||
},
|
||||
});
|
||||
|
||||
const prependRulesStrategyConfig = applyConfigureWebpack(
|
||||
createConfigureWebpack({'module.rules': 'prepend'}),
|
||||
config,
|
||||
false,
|
||||
undefined,
|
||||
{content: 42},
|
||||
);
|
||||
expect(prependRulesStrategyConfig).toEqual({
|
||||
module: {
|
||||
rules: [{use: 'zzz'}, {use: 'xxx'}, {use: 'yyy'}],
|
||||
},
|
||||
});
|
||||
|
||||
const uselessMergeStrategyConfig = applyConfigureWebpack(
|
||||
createConfigureWebpack({uselessAttributeName: 'append'}),
|
||||
config,
|
||||
false,
|
||||
undefined,
|
||||
{content: 42},
|
||||
);
|
||||
expect(uselessMergeStrategyConfig).toEqual({
|
||||
module: {
|
||||
rules: [{use: 'xxx'}, {use: 'yyy'}, {use: 'zzz'}],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('extending PostCSS', () => {
|
||||
it('user plugin should be appended in PostCSS loader', () => {
|
||||
let webpackConfig: Configuration = {
|
||||
output: {
|
||||
path: __dirname,
|
||||
filename: 'bundle.js',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: 'any',
|
||||
use: [
|
||||
{
|
||||
loader: 'some-loader-1',
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
loader: 'some-loader-2',
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader-1',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: [['default-postcss-loader-1-plugin']],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'some-loader-3',
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: '2nd-test',
|
||||
use: [
|
||||
{
|
||||
loader: 'postcss-loader-2',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: [['default-postcss-loader-2-plugin']],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
function createFakePlugin(name: string) {
|
||||
return [name, {}];
|
||||
}
|
||||
|
||||
// Run multiple times: ensure last run does not override previous runs
|
||||
webpackConfig = applyConfigurePostCss(
|
||||
(postCssOptions) => ({
|
||||
...postCssOptions,
|
||||
plugins: [
|
||||
...postCssOptions.plugins,
|
||||
createFakePlugin('postcss-plugin-1'),
|
||||
],
|
||||
}),
|
||||
webpackConfig,
|
||||
);
|
||||
|
||||
webpackConfig = applyConfigurePostCss(
|
||||
(postCssOptions) => ({
|
||||
...postCssOptions,
|
||||
plugins: [
|
||||
createFakePlugin('postcss-plugin-2'),
|
||||
...postCssOptions.plugins,
|
||||
],
|
||||
}),
|
||||
webpackConfig,
|
||||
);
|
||||
|
||||
webpackConfig = applyConfigurePostCss(
|
||||
(postCssOptions) => ({
|
||||
...postCssOptions,
|
||||
plugins: [
|
||||
...postCssOptions.plugins,
|
||||
createFakePlugin('postcss-plugin-3'),
|
||||
],
|
||||
}),
|
||||
webpackConfig,
|
||||
);
|
||||
|
||||
// @ts-expect-error: relax type
|
||||
const postCssLoader1 = webpackConfig.module?.rules[0].use[2];
|
||||
expect(postCssLoader1.loader).toBe('postcss-loader-1');
|
||||
|
||||
const pluginNames1 = postCssLoader1.options.postcssOptions.plugins.map(
|
||||
(p: unknown[]) => p[0],
|
||||
);
|
||||
expect(pluginNames1).toHaveLength(4);
|
||||
expect(pluginNames1).toEqual([
|
||||
'postcss-plugin-2',
|
||||
'default-postcss-loader-1-plugin',
|
||||
'postcss-plugin-1',
|
||||
'postcss-plugin-3',
|
||||
]);
|
||||
|
||||
// @ts-expect-error: relax type
|
||||
const postCssLoader2 = webpackConfig.module?.rules[1].use[0];
|
||||
expect(postCssLoader2.loader).toBe('postcss-loader-2');
|
||||
|
||||
const pluginNames2 = postCssLoader2.options.postcssOptions.plugins.map(
|
||||
(p: unknown[]) => p[0],
|
||||
);
|
||||
expect(pluginNames2).toHaveLength(4);
|
||||
expect(pluginNames2).toEqual([
|
||||
'postcss-plugin-2',
|
||||
'default-postcss-loader-2-plugin',
|
||||
'postcss-plugin-1',
|
||||
'postcss-plugin-3',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHttpsConfig', () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
|
|
156
packages/docusaurus/src/webpack/configure.ts
Normal file
156
packages/docusaurus/src/webpack/configure.ts
Normal file
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
* 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 {
|
||||
mergeWithCustomize,
|
||||
customizeArray,
|
||||
customizeObject,
|
||||
} from 'webpack-merge';
|
||||
import {getCustomizableJSLoader, getStyleLoaders} from './utils';
|
||||
|
||||
import type {Configuration, RuleSetRule} from 'webpack';
|
||||
import type {
|
||||
Plugin,
|
||||
PostCssOptions,
|
||||
ConfigureWebpackUtils,
|
||||
LoadedPlugin,
|
||||
} from '@docusaurus/types';
|
||||
|
||||
/**
|
||||
* Helper function to modify webpack config
|
||||
* @param configureWebpack a webpack config or a function to modify config
|
||||
* @param config initial webpack config
|
||||
* @param isServer indicates if this is a server webpack configuration
|
||||
* @param jsLoader custom js loader config
|
||||
* @param content content loaded by the plugin
|
||||
* @returns final/ modified webpack config
|
||||
*/
|
||||
export function applyConfigureWebpack(
|
||||
configureWebpack: NonNullable<Plugin['configureWebpack']>,
|
||||
config: Configuration,
|
||||
isServer: boolean,
|
||||
jsLoader: 'babel' | ((isServer: boolean) => RuleSetRule) | undefined,
|
||||
content: unknown,
|
||||
): Configuration {
|
||||
// Export some utility functions
|
||||
const utils: ConfigureWebpackUtils = {
|
||||
getStyleLoaders,
|
||||
getJSLoader: getCustomizableJSLoader(jsLoader),
|
||||
};
|
||||
if (typeof configureWebpack === 'function') {
|
||||
const {mergeStrategy, ...res} =
|
||||
configureWebpack(config, isServer, utils, content) ?? {};
|
||||
const customizeRules = mergeStrategy ?? {};
|
||||
return mergeWithCustomize({
|
||||
customizeArray: customizeArray(customizeRules),
|
||||
customizeObject: customizeObject(customizeRules),
|
||||
})(config, res);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
export function applyConfigurePostCss(
|
||||
configurePostCss: NonNullable<Plugin['configurePostCss']>,
|
||||
config: Configuration,
|
||||
): Configuration {
|
||||
type LocalPostCSSLoader = object & {
|
||||
options: {postcssOptions: PostCssOptions};
|
||||
};
|
||||
|
||||
// Not ideal heuristic but good enough for our use-case?
|
||||
function isPostCssLoader(loader: unknown): loader is LocalPostCSSLoader {
|
||||
return !!(loader as LocalPostCSSLoader)?.options?.postcssOptions;
|
||||
}
|
||||
|
||||
// Does not handle all edge cases, but good enough for now
|
||||
function overridePostCssOptions(entry: RuleSetRule) {
|
||||
if (isPostCssLoader(entry)) {
|
||||
entry.options.postcssOptions = configurePostCss(
|
||||
entry.options.postcssOptions,
|
||||
);
|
||||
} else if (Array.isArray(entry.oneOf)) {
|
||||
entry.oneOf.forEach((r) => {
|
||||
if (r) {
|
||||
overridePostCssOptions(r);
|
||||
}
|
||||
});
|
||||
} else if (Array.isArray(entry.use)) {
|
||||
entry.use
|
||||
.filter((u) => typeof u === 'object')
|
||||
.forEach((rule) => overridePostCssOptions(rule as RuleSetRule));
|
||||
}
|
||||
}
|
||||
|
||||
config.module?.rules?.forEach((rule) =>
|
||||
overridePostCssOptions(rule as RuleSetRule),
|
||||
);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// Plugin Lifecycle - configurePostCss()
|
||||
function executePluginsConfigurePostCss({
|
||||
plugins,
|
||||
config,
|
||||
}: {
|
||||
plugins: LoadedPlugin[];
|
||||
config: Configuration;
|
||||
}): Configuration {
|
||||
let resultConfig = config;
|
||||
plugins.forEach((plugin) => {
|
||||
const {configurePostCss} = plugin;
|
||||
if (configurePostCss) {
|
||||
resultConfig = applyConfigurePostCss(
|
||||
configurePostCss.bind(plugin),
|
||||
resultConfig,
|
||||
);
|
||||
}
|
||||
});
|
||||
return resultConfig;
|
||||
}
|
||||
|
||||
// Plugin Lifecycle - configureWebpack()
|
||||
export function executePluginsConfigureWebpack({
|
||||
plugins,
|
||||
config,
|
||||
isServer,
|
||||
jsLoader,
|
||||
}: {
|
||||
plugins: LoadedPlugin[];
|
||||
config: Configuration;
|
||||
isServer: boolean;
|
||||
jsLoader: 'babel' | ((isServer: boolean) => RuleSetRule) | undefined;
|
||||
}): Configuration {
|
||||
// Step1 - Configure Webpack
|
||||
let resultConfig = config;
|
||||
plugins.forEach((plugin) => {
|
||||
const {configureWebpack} = plugin;
|
||||
if (configureWebpack) {
|
||||
resultConfig = applyConfigureWebpack(
|
||||
configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`.
|
||||
resultConfig,
|
||||
isServer,
|
||||
jsLoader,
|
||||
plugin.content,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Step2 - For client code, configure PostCSS
|
||||
// The order matters! We want to configure PostCSS on loaders
|
||||
// that were potentially added by configureWebpack
|
||||
// See https://github.com/facebook/docusaurus/issues/10106
|
||||
// Note: it's useless to configure postCSS for the server
|
||||
if (!isServer) {
|
||||
resultConfig = executePluginsConfigurePostCss({
|
||||
plugins,
|
||||
config: resultConfig,
|
||||
});
|
||||
}
|
||||
|
||||
return resultConfig;
|
||||
}
|
|
@ -11,20 +11,9 @@ 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 {
|
||||
mergeWithCustomize,
|
||||
customizeArray,
|
||||
customizeObject,
|
||||
} from 'webpack-merge';
|
||||
import webpack, {type Configuration, type RuleSetRule} from 'webpack';
|
||||
import formatWebpackMessages from 'react-dev-utils/formatWebpackMessages';
|
||||
import type {TransformOptions} from '@babel/core';
|
||||
import type {
|
||||
Plugin,
|
||||
PostCssOptions,
|
||||
ConfigureWebpackUtils,
|
||||
LoadedPlugin,
|
||||
} from '@docusaurus/types';
|
||||
|
||||
export function formatStatsErrorMessage(
|
||||
statsJson: ReturnType<webpack.Stats['toJson']> | undefined,
|
||||
|
@ -181,129 +170,6 @@ export const getCustomizableJSLoader =
|
|||
? getDefaultBabelLoader({isServer, babelOptions})
|
||||
: jsLoader(isServer);
|
||||
|
||||
/**
|
||||
* Helper function to modify webpack config
|
||||
* @param configureWebpack a webpack config or a function to modify config
|
||||
* @param config initial webpack config
|
||||
* @param isServer indicates if this is a server webpack configuration
|
||||
* @param jsLoader custom js loader config
|
||||
* @param content content loaded by the plugin
|
||||
* @returns final/ modified webpack config
|
||||
*/
|
||||
export function applyConfigureWebpack(
|
||||
configureWebpack: NonNullable<Plugin['configureWebpack']>,
|
||||
config: Configuration,
|
||||
isServer: boolean,
|
||||
jsLoader: 'babel' | ((isServer: boolean) => RuleSetRule) | undefined,
|
||||
content: unknown,
|
||||
): Configuration {
|
||||
// Export some utility functions
|
||||
const utils: ConfigureWebpackUtils = {
|
||||
getStyleLoaders,
|
||||
getJSLoader: getCustomizableJSLoader(jsLoader),
|
||||
};
|
||||
if (typeof configureWebpack === 'function') {
|
||||
const {mergeStrategy, ...res} =
|
||||
configureWebpack(config, isServer, utils, content) ?? {};
|
||||
const customizeRules = mergeStrategy ?? {};
|
||||
return mergeWithCustomize({
|
||||
customizeArray: customizeArray(customizeRules),
|
||||
customizeObject: customizeObject(customizeRules),
|
||||
})(config, res);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
export function applyConfigurePostCss(
|
||||
configurePostCss: NonNullable<Plugin['configurePostCss']>,
|
||||
config: Configuration,
|
||||
): Configuration {
|
||||
type LocalPostCSSLoader = object & {
|
||||
options: {postcssOptions: PostCssOptions};
|
||||
};
|
||||
|
||||
// Not ideal heuristic but good enough for our use-case?
|
||||
function isPostCssLoader(loader: unknown): loader is LocalPostCSSLoader {
|
||||
return !!(loader as LocalPostCSSLoader)?.options?.postcssOptions;
|
||||
}
|
||||
|
||||
// Does not handle all edge cases, but good enough for now
|
||||
function overridePostCssOptions(entry: RuleSetRule) {
|
||||
if (isPostCssLoader(entry)) {
|
||||
entry.options.postcssOptions = configurePostCss(
|
||||
entry.options.postcssOptions,
|
||||
);
|
||||
} else if (Array.isArray(entry.oneOf)) {
|
||||
entry.oneOf.forEach((r) => {
|
||||
if (r) {
|
||||
overridePostCssOptions(r);
|
||||
}
|
||||
});
|
||||
} else if (Array.isArray(entry.use)) {
|
||||
entry.use
|
||||
.filter((u) => typeof u === 'object')
|
||||
.forEach((rule) => overridePostCssOptions(rule as RuleSetRule));
|
||||
}
|
||||
}
|
||||
|
||||
config.module?.rules?.forEach((rule) =>
|
||||
overridePostCssOptions(rule as RuleSetRule),
|
||||
);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// Plugin Lifecycle - configurePostCss()
|
||||
export function executePluginsConfigurePostCss({
|
||||
plugins,
|
||||
config,
|
||||
}: {
|
||||
plugins: LoadedPlugin[];
|
||||
config: Configuration;
|
||||
}): Configuration {
|
||||
let resultConfig = config;
|
||||
plugins.forEach((plugin) => {
|
||||
const {configurePostCss} = plugin;
|
||||
if (configurePostCss) {
|
||||
resultConfig = applyConfigurePostCss(
|
||||
configurePostCss.bind(plugin),
|
||||
resultConfig,
|
||||
);
|
||||
}
|
||||
});
|
||||
return resultConfig;
|
||||
}
|
||||
|
||||
// Plugin Lifecycle - configureWebpack()
|
||||
export function executePluginsConfigureWebpack({
|
||||
plugins,
|
||||
config,
|
||||
isServer,
|
||||
jsLoader,
|
||||
}: {
|
||||
plugins: LoadedPlugin[];
|
||||
config: Configuration;
|
||||
isServer: boolean;
|
||||
jsLoader: 'babel' | ((isServer: boolean) => RuleSetRule) | undefined;
|
||||
}): Configuration {
|
||||
let resultConfig = config;
|
||||
|
||||
plugins.forEach((plugin) => {
|
||||
const {configureWebpack} = plugin;
|
||||
if (configureWebpack) {
|
||||
resultConfig = applyConfigureWebpack(
|
||||
configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`.
|
||||
resultConfig,
|
||||
isServer,
|
||||
jsLoader,
|
||||
plugin.content,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return resultConfig;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Error {
|
||||
/** @see https://webpack.js.org/api/node/#error-handling */
|
||||
|
|
Loading…
Add table
Reference in a new issue