Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support tailwindcss v3.1.x and v3.2.y #21

Merged
merged 12 commits into from
Dec 28, 2024
10 changes: 10 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ on:
jobs:
test:
runs-on: ${{ matrix.os }}
name: Test tailwindcss@${{ matrix.tailwindcss }} on ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
tailwindcss:
- latest
- "3.1.0" # The fist version that support `options.config`.

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
Expand All @@ -35,5 +39,11 @@ jobs:
- name: Install Dependencies
run: pnpm install && npx playwright install

- name: Install tailwindcss@${{ matrix.tailwindcss }}
if: ${{ matrix.tailwindcss }} != "latest"
# Tailwind CSS <= v3.4.0 does not have correct TypeScript definition, which will make `rslib build` fail.
continue-on-error: true
run: pnpm add -D -w tailwindcss@${{ matrix.tailwindcss }}

- name: Run Test
run: pnpm run test
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@
"@rsbuild/core": "^1.1.0",
"@rslib/core": "^0.0.16",
"@types/node": "^22.9.0",
"@types/semver": "^7.5.8",
"playwright": "^1.48.2",
"postcss": "^8.4.47",
"semver": "^7.6.3",
"simple-git-hooks": "^2.11.1",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3"
Expand Down
42 changes: 30 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 63 additions & 10 deletions src/TailwindCSSRspackPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { existsSync } from 'node:fs';
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises';
import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
Expand Down Expand Up @@ -286,26 +287,78 @@ class TailwindRspackPluginImpl {
await mkdir(outputDir, { recursive: true });
}

const configPath = path.resolve(outputDir, 'tailwind.config.mjs');
const [configName, configContent] = await this.#generateTailwindConfig(
userConfig,
entryModules,
);
const configPath = path.resolve(outputDir, configName);

const content = JSON.stringify(entryModules);
await writeFile(configPath, configContent);

await writeFile(
configPath,
existsSync(userConfig)
? `\
return configPath;
}

async #resolveTailwindCSSVersion(): Promise<string> {
const require = createRequire(import.meta.url);
const pkgPath = require.resolve('tailwindcss/package.json');

const content = await readFile(pkgPath, 'utf-8');

const { version } = JSON.parse(content) as { version: string };

return version;
}

async #generateTailwindConfig(
userConfig: string,
entryModules: string[],
): Promise<['tailwind.config.mjs' | 'tailwind.config.cjs', string]> {
const version = await this.#resolveTailwindCSSVersion();

const { default: satisfies } = await import(
'semver/functions/satisfies.js'
);

const content = JSON.stringify(entryModules);
if (satisfies(version, '^3.3.0')) {
// Tailwind CSS support using ESM configuration in v3.3.0
// See:
// - https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.3.0
// - https://github.com/tailwindlabs/tailwindcss/pull/10785
// - https://github.com/rspack-contrib/rsbuild-plugin-tailwindcss/issues/18
//
// In this case, we provide an ESM configuration to support both ESM and CJS.
return [
'tailwind.config.mjs',
existsSync(userConfig)
? `\
import config from '${pathToFileURL(userConfig)}'
export default {
...config,
content: ${content}
}`
: `\
: `\
export default {
content: ${content}
}`,
);
];
}

return configPath;
// Otherwise, we provide an CJS configuration since TailwindCSS would always use `require`.
return [
'tailwind.config.cjs',
existsSync(userConfig)
? `\
const config = require(${JSON.stringify(userConfig)})
module.exports = {
...config,
content: ${content}
}`
: `\
module.exports = {
content: ${content}
}`,
];
}
}

Expand Down
3 changes: 3 additions & 0 deletions test/cjs/config/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "commonjs"
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/** @type {import('tailwindcss').Config} */
export default {};
module.exports = {};
99 changes: 99 additions & 0 deletions test/cjs/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { expect, test } from '@playwright/test';
import { createRsbuild } from '@rsbuild/core';
import { pluginTailwindCSS } from '../../src';

const __dirname = dirname(fileURLToPath(import.meta.url));

test('should build with relative config', async ({ page }) => {
const rsbuild = await createRsbuild({
cwd: __dirname,
rsbuildConfig: {
plugins: [
pluginTailwindCSS({
config: './config/tailwind.config.js',
}),
],
},
});

await rsbuild.build();
const { server, urls } = await rsbuild.preview();

await page.goto(urls[0]);

const display = await page.evaluate(() => {
const el = document.getElementById('test');

if (!el) {
throw new Error('#test not found');
}

return window.getComputedStyle(el).getPropertyValue('display');
});

expect(display).toBe('flex');

await server.close();
});

test('should build with absolute config', async ({ page }) => {
const rsbuild = await createRsbuild({
cwd: __dirname,
rsbuildConfig: {
plugins: [
pluginTailwindCSS({
config: resolve(__dirname, './config/tailwind.config.js'),
}),
],
},
});

await rsbuild.build();
const { server, urls } = await rsbuild.preview();

await page.goto(urls[0]);

const display = await page.evaluate(() => {
const el = document.getElementById('test');

if (!el) {
throw new Error('#test not found');
}

return window.getComputedStyle(el).getPropertyValue('display');
});

expect(display).toBe('flex');

await server.close();
});

test('should build without tailwind.config.js', async ({ page }) => {
const rsbuild = await createRsbuild({
cwd: __dirname,
rsbuildConfig: {
plugins: [pluginTailwindCSS()],
},
});

await rsbuild.build();
const { server, urls } = await rsbuild.preview();

await page.goto(urls[0]);

const display = await page.evaluate(() => {
const el = document.getElementById('test');

if (!el) {
throw new Error('#test not found');
}

return window.getComputedStyle(el).getPropertyValue('display');
});

expect(display).toBe('flex');

await server.close();
});
11 changes: 11 additions & 0 deletions test/cjs/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'tailwindcss/utilities.css';

function className() {
return 'flex';
}

const root = document.getElementById('root');
const element = document.createElement('div');
element.id = 'test';
element.className = className();
root.appendChild(element);
Loading
Loading