From 63390c97b2f11bb05c926089e2951ea2030f2fae Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Wed, 11 Sep 2024 17:02:24 +0200 Subject: [PATCH] Throw a useful error when `tailwindcss` is used as a PostCSS plugin (#14378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While upgrading a project to Tailwind CSS v4, I forgot to remove the `tailwindcss` import from the PostCSS config. As a result of this, I was greeted with the following message: ``` node:internal/process/promises:289 triggerUncaughtException(err, true /* fromPromise */); ^ [Failed to load PostCSS config: Failed to load PostCSS config (searchPath: /Users/philipp/dev/project): [TypeError] Invalid PostCSS Plugin found at: plugins[0] (@/Users/philipp/dev/project/postcss.config.js) TypeError: Invalid PostCSS Plugin found at: plugins[0] ``` I don't think this was particularly helpful, so Iā€™m proposing we add a default function export to the `tailwindcss` package so when it's used inside PostCSS, we can control the error message. So I changed it to something along these lines: ``` It looks like you're trying to use the \`tailwindcss\` package as a PostCSS plugin. This is no longer possible since Tailwind CSS v4. If you want to continue to use Tailwind CSS with PostCSS, please install \`@tailwindcss/postcss\` and change your PostCSS config file. at w (/Users/philipp/dev/project/node_modules/tailwindcss/node_modules/tailwindcss/dist/lib.js:1:21233) at Object. (/Users/philipp/dev/project/node_modules/tailwindcss/postcss.config.cjs:3:13) at Module._compile (node:internal/modules/cjs/loader:1358:14) at Module._extensions..js (node:internal/modules/cjs/loader:1416:10) at Module.load (node:internal/modules/cjs/loader:1208:32) at Module._load (node:internal/modules/cjs/loader:1024:12) at cjsLoader (node:internal/modules/esm/translators:348:17) at ModuleWrap. (node:internal/modules/esm/translators:297:7) at ModuleJob.run (node:internal/modules/esm/module_job:222:25) at async ModuleLoader.import (node:internal/modules/esm/loader:316:24) ``` This is also a good place to link to the migration guides once we have them šŸ™‚ --------- Co-authored-by: Adam Wathan --- CHANGELOG.md | 4 ++ .../postcss/core-as-postcss-plugin.test.ts | 58 +++++++++++++++++++ integrations/utils.ts | 14 ++++- packages/tailwindcss/src/compat/colors.cts | 4 ++ .../tailwindcss/src/compat/default-theme.cts | 4 ++ packages/tailwindcss/src/index.cts | 14 +++++ packages/tailwindcss/src/index.ts | 6 ++ packages/tailwindcss/tsup.config.ts | 10 +--- 8 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 integrations/postcss/core-as-postcss-plugin.test.ts create mode 100644 packages/tailwindcss/src/index.cts diff --git a/CHANGELOG.md b/CHANGELOG.md index 571cec88da8e..ec1c7ff37f8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Make sure tuple theme values in JS configs take precedence over `@theme default` values ([#14359](https://github.com/tailwindlabs/tailwindcss/pull/14359)) - Improve IntelliSense completions for `border` utilities ([#14370](https://github.com/tailwindlabs/tailwindcss/pull/14370)) +### Changed + +- Improve the error message when the `tailwindcss` package is used as a PostCSS plugin ([#14378](https://github.com/tailwindlabs/tailwindcss/pull/14378)) + ## [4.0.0-alpha.23] - 2024-09-05 ### Added diff --git a/integrations/postcss/core-as-postcss-plugin.test.ts b/integrations/postcss/core-as-postcss-plugin.test.ts new file mode 100644 index 000000000000..85779e7b26f8 --- /dev/null +++ b/integrations/postcss/core-as-postcss-plugin.test.ts @@ -0,0 +1,58 @@ +import { expect } from 'vitest' +import { css, js, json, test } from '../utils' + +const variantConfig = { + string: { + 'postcss.config.js': js` + module.exports = { + plugins: { + tailwindcss: {}, + }, + } + `, + }, + ESM: { + 'postcss.config.mjs': js` + import tailwindcss from 'tailwindcss' + export default { + plugins: [tailwindcss()], + } + `, + }, + CJS: { + 'postcss.config.cjs': js` + let tailwindcss = require('tailwindcss') + module.exports = { + plugins: [tailwindcss()], + } + `, + }, +} + +for (let variant of Object.keys(variantConfig)) { + test( + `can not use \`tailwindcss\` as a postcss module (${variant})`, + { + fs: { + ...variantConfig[variant], + 'package.json': json` + { + "dependencies": { + "postcss": "^8", + "postcss-cli": "^10", + "tailwindcss": "workspace:^" + } + } + `, + 'src/index.css': css`@import 'tailwindcss';`, + }, + }, + async ({ exec }) => { + expect( + exec('pnpm postcss src/index.css --output dist/out.css', undefined, { ignoreStdErr: true }), + ).rejects.toThrowError( + `It looks like you're trying to use \`tailwindcss\` directly as a PostCSS plugin. The PostCSS plugin has moved to a separate package, so to continue using Tailwind CSS with PostCSS you'll need to install \`@tailwindcss/postcss\` and update your PostCSS configuration.`, + ) + }, + ) +} diff --git a/integrations/utils.ts b/integrations/utils.ts index e5f550c0531c..7530321d09fa 100644 --- a/integrations/utils.ts +++ b/integrations/utils.ts @@ -23,6 +23,10 @@ interface ChildProcessOptions { cwd?: string } +interface ExecOptions { + ignoreStdErr?: boolean +} + interface TestConfig { fs: { [filePath: string]: string @@ -30,7 +34,7 @@ interface TestConfig { } interface TestContext { root: string - exec(command: string, options?: ChildProcessOptions): Promise + exec(command: string, options?: ChildProcessOptions, execOptions?: ExecOptions): Promise spawn(command: string, options?: ChildProcessOptions): Promise getFreePort(): Promise fs: { @@ -84,7 +88,11 @@ export function test( let context = { root, - async exec(command: string, childProcessOptions: ChildProcessOptions = {}) { + async exec( + command: string, + childProcessOptions: ChildProcessOptions = {}, + execOptions: ExecOptions = {}, + ) { let cwd = childProcessOptions.cwd ?? root if (debug && cwd !== root) { let relative = path.relative(root, cwd) @@ -101,7 +109,7 @@ export function test( }, (error, stdout, stderr) => { if (error) { - console.error(stderr) + if (execOptions.ignoreStdErr !== true) console.error(stderr) reject(error) } else { resolve(stdout.toString()) diff --git a/packages/tailwindcss/src/compat/colors.cts b/packages/tailwindcss/src/compat/colors.cts index b903bf39beed..92cd2f66bb85 100644 --- a/packages/tailwindcss/src/compat/colors.cts +++ b/packages/tailwindcss/src/compat/colors.cts @@ -1,4 +1,8 @@ import colors from './colors.ts' +// This file exists so that `colors.ts` can be written one time but be +// compatible with both CJS and ESM. Without it we get a `.default` export when +// using `require` in CJS. + // @ts-ignore export = colors diff --git a/packages/tailwindcss/src/compat/default-theme.cts b/packages/tailwindcss/src/compat/default-theme.cts index 235d60252a5d..aab00f35743a 100644 --- a/packages/tailwindcss/src/compat/default-theme.cts +++ b/packages/tailwindcss/src/compat/default-theme.cts @@ -1,4 +1,8 @@ import defaultTheme from './default-theme.ts' +// This file exists so that `default-theme.ts` can be written one time but be +// compatible with both CJS and ESM. Without it we get a `.default` export when +// using `require` in CJS. + // @ts-ignore export = defaultTheme diff --git a/packages/tailwindcss/src/index.cts b/packages/tailwindcss/src/index.cts new file mode 100644 index 000000000000..fc4b691f943c --- /dev/null +++ b/packages/tailwindcss/src/index.cts @@ -0,0 +1,14 @@ +import postcssPlugin, * as tailwindcss from './index.ts' + +// This file exists so that `index.ts` can be written one time but be +// compatible with both CJS and ESM. Without it we get a `.default` export when +// using `require` in CJS. + +for (let key in tailwindcss) { + if (key === 'default') continue + // @ts-ignore + postcssPlugin[key] = tailwindcss[key] +} + +// @ts-ignore +export = postcssPlugin diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index c3cd9e70c237..277ef12ff819 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -498,3 +498,9 @@ function getVersion() { return version } } + +export default function postcssPluginWarning() { + throw new Error( + `It looks like you're trying to use \`tailwindcss\` directly as a PostCSS plugin. The PostCSS plugin has moved to a separate package, so to continue using Tailwind CSS with PostCSS you'll need to install \`@tailwindcss/postcss\` and update your PostCSS configuration.`, + ) +} diff --git a/packages/tailwindcss/tsup.config.ts b/packages/tailwindcss/tsup.config.ts index 25803da45289..1a8e238873b8 100644 --- a/packages/tailwindcss/tsup.config.ts +++ b/packages/tailwindcss/tsup.config.ts @@ -1,19 +1,12 @@ import { defineConfig } from 'tsup' export default defineConfig([ - { - format: ['esm', 'cjs'], - minify: true, - dts: true, - entry: { - lib: 'src/index.ts', - }, - }, { format: ['esm'], minify: true, dts: true, entry: { + lib: 'src/index.ts', plugin: 'src/plugin.ts', colors: 'src/compat/colors.ts', 'default-theme': 'src/compat/default-theme.ts', @@ -25,6 +18,7 @@ export default defineConfig([ dts: true, entry: { plugin: 'src/plugin.cts', + lib: 'src/index.cts', colors: 'src/compat/colors.cts', 'default-theme': 'src/compat/default-theme.cts', },