Skip to content

Commit 256dc2d

Browse files
authored
feat: support custom postcss options (#9)
close: #8
1 parent f0f9e95 commit 256dc2d

File tree

9 files changed

+248
-21
lines changed

9 files changed

+248
-21
lines changed

src/TailwindCSSRspackPlugin.ts

+37-19
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { tmpdir } from 'node:os';
44
import path from 'node:path';
55
import { pathToFileURL } from 'node:url';
66

7-
import type { Rspack } from '@rsbuild/core';
7+
import type { PostCSSLoaderOptions, Rspack } from '@rsbuild/core';
88

99
/**
1010
* The options for {@link TailwindRspackPlugin}.
@@ -15,10 +15,6 @@ interface TailwindRspackPluginOptions {
1515
/**
1616
* The path to the configuration of Tailwind CSS.
1717
*
18-
* @remarks
19-
*
20-
* The default value is `tailwind.config.js`.
21-
*
2218
* @example
2319
*
2420
* Use absolute path:
@@ -58,7 +54,36 @@ interface TailwindRspackPluginOptions {
5854
* }
5955
* ```
6056
*/
61-
config?: string;
57+
config: string;
58+
59+
/**
60+
* The postcss options to be applied.
61+
*
62+
* @example
63+
*
64+
* Use `cssnano`:
65+
*
66+
* ```js
67+
* // rspack.config.js
68+
* import { TailwindRspackPlugin } from '@rsbuild/plugin-tailwindcss'
69+
*
70+
* export default {
71+
* plugins: [
72+
* new TailwindRspackPlugin({
73+
* postcssOptions: {
74+
* plugins: {
75+
* cssnano: process.env['NODE_ENV'] === 'production' ? {} : false,
76+
* },
77+
* },
78+
* }),
79+
* ],
80+
* }
81+
* ```
82+
*/
83+
postcssOptions: Exclude<
84+
PostCSSLoaderOptions['postcssOptions'],
85+
(loaderContext: Rspack.LoaderContext) => void
86+
>;
6287
}
6388

6489
/**
@@ -67,9 +92,7 @@ interface TailwindRspackPluginOptions {
6792
* @public
6893
*/
6994
class TailwindRspackPlugin {
70-
constructor(
71-
private readonly options?: TailwindRspackPluginOptions | undefined,
72-
) {}
95+
constructor(private readonly options: TailwindRspackPluginOptions) {}
7396

7497
/**
7598
* `defaultOptions` is the default options that the {@link TailwindRspackPlugin} uses.
@@ -79,33 +102,27 @@ class TailwindRspackPlugin {
79102
static defaultOptions: Readonly<Required<TailwindRspackPluginOptions>> =
80103
Object.freeze<Required<TailwindRspackPluginOptions>>({
81104
config: 'tailwind.config.js',
105+
postcssOptions: {},
82106
});
83107

84108
/**
85109
* The entry point of a Rspack plugin.
86110
* @param compiler - the Rspack compiler
87111
*/
88112
apply(compiler: Rspack.Compiler): void {
89-
new TailwindRspackPluginImpl(
90-
compiler,
91-
Object.assign({}, TailwindRspackPlugin.defaultOptions, this.options),
92-
);
113+
new TailwindRspackPluginImpl(compiler, this.options);
93114
}
94115
}
95116

96117
export { TailwindRspackPlugin };
97118
export type { TailwindRspackPluginOptions };
98119

99-
type NoUndefinedField<T> = {
100-
[P in keyof T]-?: NoUndefinedField<NonNullable<T[P]>>;
101-
};
102-
103120
class TailwindRspackPluginImpl {
104121
name = 'TailwindRspackPlugin';
105122

106123
constructor(
107124
private compiler: Rspack.Compiler,
108-
private options: NoUndefinedField<TailwindRspackPluginOptions>,
125+
private options: TailwindRspackPluginOptions,
109126
) {
110127
const { RawSource } = compiler.webpack.sources;
111128
compiler.hooks.thisCompilation.tap(this.name, (compilation) => {
@@ -146,6 +163,7 @@ class TailwindRspackPluginImpl {
146163
]);
147164

148165
const postcssTransform = postcss([
166+
...(options.postcssOptions?.plugins ?? []),
149167
// We use a config path to avoid performance issue of TailwindCSS
150168
// See: https://github.com/tailwindlabs/tailwindcss/issues/14229
151169
tailwindcss({
@@ -161,7 +179,7 @@ class TailwindRspackPluginImpl {
161179
// FIXME: add custom postcss config
162180
const transformResult = await postcssTransform.process(
163181
content,
164-
{ from: asset.name },
182+
{ from: asset.name, ...options.postcssOptions },
165183
);
166184
// FIXME: add sourcemap support
167185
compilation.updateAsset(

src/index.ts

+38-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import type { RsbuildPlugin } from '@rsbuild/core';
1+
import type {
2+
PostCSSLoaderOptions,
3+
RsbuildPlugin,
4+
Rspack,
5+
} from '@rsbuild/core';
26

37
import { TailwindRspackPlugin } from './TailwindCSSRspackPlugin.js';
48

@@ -58,13 +62,45 @@ export const pluginTailwindCSS = (
5862
name: 'rsbuild:tailwindcss',
5963

6064
setup(api) {
65+
let postcssOptions: Exclude<
66+
PostCSSLoaderOptions['postcssOptions'],
67+
(loaderContext: Rspack.LoaderContext) => void
68+
>;
69+
70+
api.modifyRsbuildConfig({
71+
order: 'post',
72+
handler(config, { mergeRsbuildConfig }) {
73+
return mergeRsbuildConfig(config, {
74+
tools: {
75+
postcss(config) {
76+
if (typeof config.postcssOptions === 'function') {
77+
throw new Error(
78+
'pluginTailwindCSS does not support using `tools.postcss` as function',
79+
);
80+
}
81+
if (config.postcssOptions) {
82+
// Remove `tailwindcss` from `postcssOptions`
83+
// to avoid `@tailwind` being transformed by `postcss-loader`.
84+
config.postcssOptions.plugins =
85+
config.postcssOptions.plugins?.filter(
86+
(p) =>
87+
'postcssPlugin' in p && p.postcssPlugin !== 'tailwindcss',
88+
) ?? [];
89+
postcssOptions = config.postcssOptions;
90+
}
91+
},
92+
},
93+
});
94+
},
95+
});
96+
6197
api.modifyBundlerChain({
6298
order: 'post',
6399
handler(chain) {
64100
chain
65101
.plugin('tailwindcss')
66102
.use(TailwindRspackPlugin, [
67-
{ config: options.config ?? 'tailwind.config.js' },
103+
{ config: options.config ?? 'tailwind.config.js', postcssOptions },
68104
]);
69105
},
70106
});

test/postcss-config/flex-to-grid.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @returns {import('postcss').AcceptedPlugin}
3+
*/
4+
export default function () {
5+
return {
6+
postcssPlugin: 'flex-to-grid',
7+
Declaration: {
8+
display(decl) {
9+
if (decl.value === 'flex') {
10+
decl.value = 'grid';
11+
}
12+
},
13+
},
14+
};
15+
}

test/postcss-config/index.test.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { dirname } from 'node:path';
2+
import { fileURLToPath } from 'node:url';
3+
import { expect, test } from '@playwright/test';
4+
import { createRsbuild } from '@rsbuild/core';
5+
import { pluginTailwindCSS } from '../../src';
6+
7+
const __dirname = dirname(fileURLToPath(import.meta.url));
8+
9+
test('should build with postcss.config.js', async ({ page }) => {
10+
const rsbuild = await createRsbuild({
11+
cwd: __dirname,
12+
rsbuildConfig: {
13+
plugins: [pluginTailwindCSS()],
14+
},
15+
});
16+
17+
await rsbuild.build();
18+
const { server, urls } = await rsbuild.preview();
19+
20+
await page.goto(urls[0]);
21+
22+
const display = await page.evaluate(() => {
23+
const el = document.getElementById('test');
24+
25+
if (!el) {
26+
throw new Error('#test not found');
27+
}
28+
29+
return window.getComputedStyle(el).getPropertyValue('display');
30+
});
31+
32+
expect(display).toBe('grid');
33+
34+
await server.close();
35+
});

test/postcss-config/postcss.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import tailwindcss from 'tailwindcss';
2+
import flexToGrid from './flex-to-grid';
3+
4+
export default {
5+
plugins: [tailwindcss(), flexToGrid()],
6+
};

test/postcss-config/src/index.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import 'tailwindcss/utilities.css';
2+
3+
function className() {
4+
return 'flex';
5+
}
6+
7+
const root = document.getElementById('root');
8+
const element = document.createElement('div');
9+
element.id = 'test';
10+
element.className = className();
11+
root.appendChild(element);

test/tools-postcss/flex-to-grid.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @returns {import('postcss').AcceptedPlugin}
3+
*/
4+
export default function () {
5+
return {
6+
postcssPlugin: 'flex-to-grid',
7+
Declaration: {
8+
display(decl) {
9+
if (decl.value === 'flex') {
10+
decl.value = 'grid';
11+
}
12+
},
13+
},
14+
};
15+
}

test/tools-postcss/index.test.ts

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { dirname } from 'node:path';
2+
import { fileURLToPath } from 'node:url';
3+
import { expect, test } from '@playwright/test';
4+
import { createRsbuild } from '@rsbuild/core';
5+
import { pluginTailwindCSS } from '../../src';
6+
7+
const __dirname = dirname(fileURLToPath(import.meta.url));
8+
9+
test('should build with tools.postcss with tailwindcss', async ({ page }) => {
10+
const { default: tailwindcss } = await import('tailwindcss');
11+
const rsbuild = await createRsbuild({
12+
cwd: __dirname,
13+
rsbuildConfig: {
14+
plugins: [pluginTailwindCSS()],
15+
tools: {
16+
postcss: {
17+
postcssOptions: {
18+
plugins: [tailwindcss()],
19+
},
20+
},
21+
},
22+
},
23+
});
24+
25+
await rsbuild.build();
26+
const { server, urls } = await rsbuild.preview();
27+
28+
await page.goto(urls[0]);
29+
30+
const display = await page.evaluate(() => {
31+
const el = document.getElementById('test');
32+
33+
if (!el) {
34+
throw new Error('#test not found');
35+
}
36+
37+
return window.getComputedStyle(el).getPropertyValue('display');
38+
});
39+
40+
expect(display).toBe('flex');
41+
42+
await server.close();
43+
});
44+
45+
test('should build with tools.postcss with custom plugin', async ({ page }) => {
46+
const { default: tailwindcss } = await import('tailwindcss');
47+
const { default: flexToGrid } = await import('./flex-to-grid.js');
48+
const rsbuild = await createRsbuild({
49+
cwd: __dirname,
50+
rsbuildConfig: {
51+
plugins: [pluginTailwindCSS()],
52+
tools: {
53+
postcss: {
54+
postcssOptions: {
55+
plugins: [flexToGrid()],
56+
},
57+
},
58+
},
59+
},
60+
});
61+
62+
await rsbuild.build();
63+
const { server, urls } = await rsbuild.preview();
64+
65+
await page.goto(urls[0]);
66+
67+
const display = await page.evaluate(() => {
68+
const el = document.getElementById('test');
69+
70+
if (!el) {
71+
throw new Error('#test not found');
72+
}
73+
74+
return window.getComputedStyle(el).getPropertyValue('display');
75+
});
76+
77+
expect(display).toBe('grid');
78+
79+
await server.close();
80+
});

test/tools-postcss/src/index.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import 'tailwindcss/utilities.css';
2+
3+
function className() {
4+
return 'flex';
5+
}
6+
7+
const root = document.getElementById('root');
8+
const element = document.createElement('div');
9+
element.id = 'test';
10+
element.className = className();
11+
root.appendChild(element);

0 commit comments

Comments
 (0)