Skip to content

Commit

Permalink
feat: support tailwindcss v3.1.x and v3.2.y (#21)
Browse files Browse the repository at this point in the history
### Summary

Support Tailwind CSS v3.1 and Tailwind CSS v3.2.

---

### Details

Since Tailwind CSS support using ESM configuration in V3.3, we need to
use CJS configuration before that.

As mentioned in
#7 (comment),
we use `readFile` + `require.resolve` to get the version of
`tailwindcss/package.json`.

- If `satisfies(version, ^3.3.0)`, we will generate ESM configuration to
support both ESM and CJS.
- Else, we generate CJS configuration.

---

### Test plan

We setup two new entries in the testing matrix

- tailwindcss v3.1.0 with Ubuntu
- tailwindcss v3.1.0 with Windows

As you can see, the test is failing at
[0e05c6a](0e05c6a).

And after the fix is landed in
[e783415](e783415),
the test for old tailwindcss is passing.

---

### Related links

close: #18 

- Tailwind CSS v3.3:
  - https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.3.0
  - tailwindlabs/tailwindcss#10785
- Tailwind CSS v3.2
  - https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.2.0
- Tailwind CSS v3.1
  - https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.1.0
  • Loading branch information
colinaaa authored Dec 28, 2024
1 parent b7e19a8 commit 34eff9d
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 21 deletions.
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 chromium

- 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 @@ -35,8 +35,10 @@
"@rslib/core": "^0.1.1",
"@rsbuild/webpack": "^1.1.3",
"@types/node": "^22.10.1",
"@types/semver": "^7.5.8",
"playwright": "^1.49.0",
"postcss": "^8.4.49",
"semver": "^7.6.3",
"simple-git-hooks": "^2.11.1",
"tailwindcss": "^3.4.15",
"typescript": "^5.7.2"
Expand Down
34 changes: 26 additions & 8 deletions pnpm-lock.yaml

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

75 changes: 65 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,80 @@ 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', {
paths: [this.compiler.context],
});

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);
11 changes: 11 additions & 0 deletions test/config/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import { fileURLToPath } from 'node:url';
import { expect, test } from '@playwright/test';
import { createRsbuild } from '@rsbuild/core';
import { pluginTailwindCSS } from '../../src';
import { supportESM } from '../helper';

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

test('should build with relative config', async ({ page }) => {
test.skip(
!supportESM(),
'Skip since the tailwindcss version does not support ESM configuration',
);

const rsbuild = await createRsbuild({
cwd: __dirname,
rsbuildConfig: {
Expand Down Expand Up @@ -39,6 +45,11 @@ test('should build with relative config', async ({ page }) => {
});

test('should build with absolute config', async ({ page }) => {
test.skip(
!supportESM(),
'Skip since the tailwindcss version does not support ESM configuration',
);

const rsbuild = await createRsbuild({
cwd: __dirname,
rsbuildConfig: {
Expand Down
Loading

0 comments on commit 34eff9d

Please sign in to comment.