mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-02 10:52:35 +02:00
feat(v2): allow extend PostCSS config (#4185)
* feat(v2): allow extend PostCSS config * polish the configurePostCss system Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
parent
b3b658f687
commit
2fb642d9ee
6 changed files with 235 additions and 27 deletions
5
packages/docusaurus-types/src/index.d.ts
vendored
5
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -199,6 +199,9 @@ export type AllContent = Record<
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
// TODO improve type (not exposed by postcss-loader)
|
||||||
|
export type PostCssOptions = Record<string, any> & {plugins: any[]};
|
||||||
|
|
||||||
export interface Plugin<T, U = unknown> {
|
export interface Plugin<T, U = unknown> {
|
||||||
name: string;
|
name: string;
|
||||||
loadContent?(): Promise<T>;
|
loadContent?(): Promise<T>;
|
||||||
|
@ -220,6 +223,7 @@ export interface Plugin<T, U = unknown> {
|
||||||
isServer: boolean,
|
isServer: boolean,
|
||||||
utils: ConfigureWebpackUtils,
|
utils: ConfigureWebpackUtils,
|
||||||
): Configuration & {mergeStrategy?: ConfigureWebpackFnMergeStrategy};
|
): Configuration & {mergeStrategy?: ConfigureWebpackFnMergeStrategy};
|
||||||
|
configurePostCss?(options: PostCssOptions): PostCssOptions;
|
||||||
getThemePath?(): string;
|
getThemePath?(): string;
|
||||||
getTypeScriptThemePath?(): string;
|
getTypeScriptThemePath?(): string;
|
||||||
getPathsToWatch?(): string[];
|
getPathsToWatch?(): string[];
|
||||||
|
@ -253,6 +257,7 @@ export interface Plugin<T, U = unknown> {
|
||||||
|
|
||||||
export type ConfigureWebpackFn = Plugin<unknown>['configureWebpack'];
|
export type ConfigureWebpackFn = Plugin<unknown>['configureWebpack'];
|
||||||
export type ConfigureWebpackFnMergeStrategy = Record<string, MergeStrategy>;
|
export type ConfigureWebpackFnMergeStrategy = Record<string, MergeStrategy>;
|
||||||
|
export type ConfigurePostCssFn = Plugin<unknown>['configurePostCss'];
|
||||||
|
|
||||||
export type PluginOptions = {id?: string} & Record<string, unknown>;
|
export type PluginOptions = {id?: string} & Record<string, unknown>;
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,11 @@ import {handleBrokenLinks} from '../server/brokenLinks';
|
||||||
import {BuildCLIOptions, Props} from '@docusaurus/types';
|
import {BuildCLIOptions, Props} from '@docusaurus/types';
|
||||||
import createClientConfig from '../webpack/client';
|
import createClientConfig from '../webpack/client';
|
||||||
import createServerConfig from '../webpack/server';
|
import createServerConfig from '../webpack/server';
|
||||||
import {compile, applyConfigureWebpack} from '../webpack/utils';
|
import {
|
||||||
|
compile,
|
||||||
|
applyConfigureWebpack,
|
||||||
|
applyConfigurePostCss,
|
||||||
|
} from '../webpack/utils';
|
||||||
import CleanWebpackPlugin from '../webpack/plugins/CleanWebpackPlugin';
|
import CleanWebpackPlugin from '../webpack/plugins/CleanWebpackPlugin';
|
||||||
import {loadI18n} from '../server/i18n';
|
import {loadI18n} from '../server/i18n';
|
||||||
import {mapAsyncSequencial} from '@docusaurus/utils';
|
import {mapAsyncSequencial} from '@docusaurus/utils';
|
||||||
|
@ -166,24 +170,27 @@ async function buildLocale({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugin Lifecycle - configureWebpack.
|
// Plugin Lifecycle - configureWebpack and configurePostCss.
|
||||||
plugins.forEach((plugin) => {
|
plugins.forEach((plugin) => {
|
||||||
const {configureWebpack} = plugin;
|
const {configureWebpack, configurePostCss} = plugin;
|
||||||
if (!configureWebpack) {
|
|
||||||
return;
|
if (configurePostCss) {
|
||||||
|
clientConfig = applyConfigurePostCss(configurePostCss, clientConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientConfig = applyConfigureWebpack(
|
if (configureWebpack) {
|
||||||
configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`.
|
clientConfig = applyConfigureWebpack(
|
||||||
clientConfig,
|
configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`.
|
||||||
false,
|
clientConfig,
|
||||||
);
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
serverConfig = applyConfigureWebpack(
|
serverConfig = applyConfigureWebpack(
|
||||||
configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`.
|
configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`.
|
||||||
serverConfig,
|
serverConfig,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure generated client-manifest is cleaned first so we don't reuse
|
// Make sure generated client-manifest is cleaned first so we don't reuse
|
||||||
|
|
|
@ -24,7 +24,11 @@ import {load} from '../server';
|
||||||
import {StartCLIOptions} from '@docusaurus/types';
|
import {StartCLIOptions} from '@docusaurus/types';
|
||||||
import {CONFIG_FILE_NAME, STATIC_DIR_NAME} from '../constants';
|
import {CONFIG_FILE_NAME, STATIC_DIR_NAME} from '../constants';
|
||||||
import createClientConfig from '../webpack/client';
|
import createClientConfig from '../webpack/client';
|
||||||
import {applyConfigureWebpack, getHttpsConfig} from '../webpack/utils';
|
import {
|
||||||
|
applyConfigureWebpack,
|
||||||
|
applyConfigurePostCss,
|
||||||
|
getHttpsConfig,
|
||||||
|
} from '../webpack/utils';
|
||||||
import {getCLIOptionHost, getCLIOptionPort} from './commandUtils';
|
import {getCLIOptionHost, getCLIOptionPort} from './commandUtils';
|
||||||
import {getTranslationsLocaleDirPath} from '../server/translations/translations';
|
import {getTranslationsLocaleDirPath} from '../server/translations/translations';
|
||||||
|
|
||||||
|
@ -134,18 +138,21 @@ export default async function start(
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Plugin Lifecycle - configureWebpack.
|
// Plugin Lifecycle - configureWebpack and configurePostCss.
|
||||||
plugins.forEach((plugin) => {
|
plugins.forEach((plugin) => {
|
||||||
const {configureWebpack} = plugin;
|
const {configureWebpack, configurePostCss} = plugin;
|
||||||
if (!configureWebpack) {
|
|
||||||
return;
|
if (configurePostCss) {
|
||||||
|
config = applyConfigurePostCss(configurePostCss, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
config = applyConfigureWebpack(
|
if (configureWebpack) {
|
||||||
configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`.
|
config = applyConfigureWebpack(
|
||||||
config,
|
configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`.
|
||||||
false,
|
config,
|
||||||
);
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://webpack.js.org/configuration/dev-server
|
// https://webpack.js.org/configuration/dev-server
|
||||||
|
|
|
@ -12,7 +12,11 @@ import {
|
||||||
} from 'webpack';
|
} from 'webpack';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import {applyConfigureWebpack, getFileLoaderUtils} from '../utils';
|
import {
|
||||||
|
applyConfigureWebpack,
|
||||||
|
applyConfigurePostCss,
|
||||||
|
getFileLoaderUtils,
|
||||||
|
} from '../utils';
|
||||||
import {
|
import {
|
||||||
ConfigureWebpackFn,
|
ConfigureWebpackFn,
|
||||||
ConfigureWebpackFnMergeStrategy,
|
ConfigureWebpackFnMergeStrategy,
|
||||||
|
@ -148,3 +152,123 @@ describe('getFileLoaderUtils()', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('extending PostCSS', () => {
|
||||||
|
test('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) => {
|
||||||
|
return {
|
||||||
|
...postCssOptions,
|
||||||
|
plugins: [
|
||||||
|
...postCssOptions.plugins,
|
||||||
|
createFakePlugin('postcss-plugin-1'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}, webpackConfig);
|
||||||
|
|
||||||
|
webpackConfig = applyConfigurePostCss((postCssOptions) => {
|
||||||
|
return {
|
||||||
|
...postCssOptions,
|
||||||
|
plugins: [
|
||||||
|
createFakePlugin('postcss-plugin-2'),
|
||||||
|
...postCssOptions.plugins,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}, webpackConfig);
|
||||||
|
|
||||||
|
webpackConfig = applyConfigurePostCss((postCssOptions) => {
|
||||||
|
return {
|
||||||
|
...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).toEqual('postcss-loader-1');
|
||||||
|
|
||||||
|
const pluginNames1 = postCssLoader1.options.postcssOptions.plugins.map(
|
||||||
|
// @ts-expect-error: relax type
|
||||||
|
(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).toEqual('postcss-loader-2');
|
||||||
|
|
||||||
|
const pluginNames2 = postCssLoader2.options.postcssOptions.plugins.map(
|
||||||
|
// @ts-expect-error: relax type
|
||||||
|
(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',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ import merge from 'webpack-merge';
|
||||||
import webpack, {
|
import webpack, {
|
||||||
Configuration,
|
Configuration,
|
||||||
Loader,
|
Loader,
|
||||||
|
NewLoader,
|
||||||
Plugin,
|
Plugin,
|
||||||
RuleSetRule,
|
RuleSetRule,
|
||||||
Stats,
|
Stats,
|
||||||
|
@ -23,7 +24,7 @@ import path from 'path';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import {TransformOptions} from '@babel/core';
|
import {TransformOptions} from '@babel/core';
|
||||||
import {ConfigureWebpackFn} from '@docusaurus/types';
|
import {ConfigureWebpackFn, ConfigurePostCssFn} from '@docusaurus/types';
|
||||||
import CssNanoPreset from '@docusaurus/cssnano-preset';
|
import CssNanoPreset from '@docusaurus/cssnano-preset';
|
||||||
import {version as cacheLoaderVersion} from 'cache-loader/package.json';
|
import {version as cacheLoaderVersion} from 'cache-loader/package.json';
|
||||||
import {BABEL_CONFIG_FILE_NAME, STATIC_ASSETS_DIR_NAME} from '../constants';
|
import {BABEL_CONFIG_FILE_NAME, STATIC_ASSETS_DIR_NAME} from '../constants';
|
||||||
|
@ -175,6 +176,31 @@ export function applyConfigureWebpack(
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyConfigurePostCss(
|
||||||
|
configurePostCss: NonNullable<ConfigurePostCssFn>,
|
||||||
|
config: Configuration,
|
||||||
|
): Configuration {
|
||||||
|
type LocalPostCSSLoader = Loader & {options: {postcssOptions: any}};
|
||||||
|
|
||||||
|
function isPostCssLoader(loader: Loader): loader is LocalPostCSSLoader {
|
||||||
|
// TODO not ideal heuristic but good enough for our usecase?
|
||||||
|
return !!(loader as any)?.options?.postcssOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does not handle all edge cases, but good enough for now
|
||||||
|
config.module?.rules.map((rule) => {
|
||||||
|
for (const loader of rule.use as NewLoader[]) {
|
||||||
|
if (isPostCssLoader(loader)) {
|
||||||
|
loader.options.postcssOptions = configurePostCss(
|
||||||
|
loader.options.postcssOptions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
// See https://webpack.js.org/configuration/stats/#statswarningsfilter
|
// See https://webpack.js.org/configuration/stats/#statswarningsfilter
|
||||||
// @slorber: note sure why we have to re-implement this logic
|
// @slorber: note sure why we have to re-implement this logic
|
||||||
// just know that legacy had this only partially implemented, so completed it
|
// just know that legacy had this only partially implemented, so completed it
|
||||||
|
|
|
@ -346,6 +346,45 @@ module.exports = function (context, options) {
|
||||||
|
|
||||||
Read the [webpack-merge strategy doc](https://github.com/survivejs/webpack-merge#merging-with-strategies) for more details.
|
Read the [webpack-merge strategy doc](https://github.com/survivejs/webpack-merge#merging-with-strategies) for more details.
|
||||||
|
|
||||||
|
## `configurePostCss(options)`
|
||||||
|
|
||||||
|
Modifies [`postcssOptions` of `postcss-loader`](https://webpack.js.org/loaders/postcss-loader/#postcssoptions) during the generation of the client bundle.
|
||||||
|
|
||||||
|
Should return the mutated `postcssOptions`.
|
||||||
|
|
||||||
|
By default, `postcssOptions` looks like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const postcssOptions = {
|
||||||
|
ident: 'postcss',
|
||||||
|
plugins: [
|
||||||
|
require('postcss-preset-env')({
|
||||||
|
autoprefixer: {
|
||||||
|
flexbox: 'no-2009',
|
||||||
|
},
|
||||||
|
stage: 4,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js title="docusaurus-plugin/src/index.js"
|
||||||
|
module.exports = function (context, options) {
|
||||||
|
return {
|
||||||
|
name: 'docusaurus-plugin',
|
||||||
|
// highlight-start
|
||||||
|
configurePostCss(postcssOptions) {
|
||||||
|
// Appends new PostCSS plugin.
|
||||||
|
postcssOptions.plugins.push(require('postcss-import'));
|
||||||
|
return postcssOptions;
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## `postBuild(props)`
|
## `postBuild(props)`
|
||||||
|
|
||||||
Called when a (production) build finishes.
|
Called when a (production) build finishes.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue