diff --git a/.github/workflows/ci-stable.yml b/.github/workflows/ci-stable.yml index 61ae38e6f415..d20f88fd30a0 100644 --- a/.github/workflows/ci-stable.yml +++ b/.github/workflows/ci-stable.yml @@ -27,7 +27,7 @@ jobs: strategy: matrix: - node-version: [14, 18] + node-version: [14, 18, 20, 22, 24] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71649f4bb099..f208e7086d89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: strategy: matrix: - node-version: [14, 18] + node-version: [14, 18, 20, 22, 24] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index ff5a68df1740..cb2fabe23216 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -31,7 +31,7 @@ jobs: - vite - webpack-4 - webpack-5 - node-version: [18] + node-version: [18, 20] fail-fast: false steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index bd4b0197ddd6..92f03b87c085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improve support for raw `supports-[…]` queries in arbitrary values ([#13605](https://github.com/tailwindlabs/tailwindcss/pull/13605)) - Fix `require.cache` error when loaded through a TypeScript file in Node 22.18+ ([#18665](https://github.com/tailwindlabs/tailwindcss/pull/18665)) +- Support `import.meta.resolve(…)` in configs for new enough Node.js versions ([#18938](https://github.com/tailwindlabs/tailwindcss/pull/18938)) +- Allow using newer versions of `postcss-load-config` for better ESM and TypeScript PostCSS config support with the CLI ([#18938](https://github.com/tailwindlabs/tailwindcss/pull/18938)) ## [3.4.17] - 2024-12-17 diff --git a/integrations/tailwindcss-cli/tests/cli.test.js b/integrations/tailwindcss-cli/tests/cli.test.js index 9795bd0d7018..8bf6cb93c25f 100644 --- a/integrations/tailwindcss-cli/tests/cli.test.js +++ b/integrations/tailwindcss-cli/tests/cli.test.js @@ -207,6 +207,52 @@ describe('Build command', () => { ) }) + test('configs support import.meta', async () => { + // Skip this test in Node 18 as this only works with + // `require(esm)` in Node 20.19+ + if (process.versions.node.startsWith('18.')) { + expect(true).toBe(true) + return + } + + await writeInputFile('index.html', html`
`) + + let customConfig = ` + console.log(import.meta.url) + console.log(import.meta.resolve('./tailwind.config.mjs')) + export default ${JSON.stringify( + { + content: ['./src/index.html'], + theme: { + extend: { + fontWeight: { + bold: 'BOLD', + }, + }, + }, + corePlugins: { + preflight: false, + }, + plugins: [], + }, + null, + 2 + )} + ` + + await writeInputFile('../tailwind.config.mjs', customConfig) + + await $(`${EXECUTABLE} --output ./dist/main.css --config ./tailwind.config.mjs`) + + expect(await readOutputFile('main.css')).toIncludeCss( + css` + .font-bold { + font-weight: BOLD; + } + ` + ) + }) + test('--content', async () => { await writeInputFile('other.html', html``) @@ -391,6 +437,79 @@ describe('Build command', () => { expect(contents).toContain(`/*# sourceMappingURL`) }) + test('--postcss supports ESM configs', async () => { + await writeInputFile('index.html', html``) + + let customConfig = javascript` + import * as path from 'path' + import { createRequire } from 'module' + const require = createRequire(import.meta.url) + + export default { + map: { inline: true }, + plugins: [ + function tailwindcss() { + return require(path.resolve('..', '..')) + }, + ], + } + ` + + await removeFile('./postcss.config.js') + await writeInputFile('../postcss.config.mjs', customConfig) + + await $(`${EXECUTABLE} --output ./dist/main.css --postcss`) + + let contents = await readOutputFile('main.css') + + expect(contents).toIncludeCss( + css` + .font-bold { + font-weight: 700; + } + ` + ) + + expect(contents).toContain(`/*# sourceMappingURL`) + }) + + test('--postcss supports TS configs', async () => { + await writeInputFile('index.html', html``) + + let customConfig = javascript` + import * as path from 'path' + import { createRequire } from 'module' + import type { AcceptedPlugin } from 'postcss' + const require = createRequire(import.meta.url) + + export default { + map: { inline: true }, + plugins: [ + function tailwindcss() { + return require(path.resolve('..', '..')) + } as AcceptedPlugin, + ], + } + ` + + await removeFile('./postcss.config.js') + await writeInputFile('../postcss.config.ts', customConfig) + + await $(`${EXECUTABLE} --output ./dist/main.css --postcss`) + + let contents = await readOutputFile('main.css') + + expect(contents).toIncludeCss( + css` + .font-bold { + font-weight: 700; + } + ` + ) + + expect(contents).toContain(`/*# sourceMappingURL`) + }) + test('postcss-import is supported by default', async () => { cleanupFile('src/test.css') diff --git a/package-lock.json b/package-lock.json index 2c4d2cdbaef4..5533dfb32b5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.21.6", + "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", @@ -26,7 +26,7 @@ "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", @@ -5446,9 +5446,9 @@ } }, "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -6518,9 +6518,9 @@ } }, "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "funding": [ { "type": "opencollective", @@ -6533,21 +6533,28 @@ ], "license": "MIT", "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" + "lilconfig": "^3.1.1" }, "engines": { - "node": ">= 14" + "node": ">= 18" }, "peerDependencies": { + "jiti": ">=1.21.0", "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { + "jiti": { + "optional": true + }, "postcss": { "optional": true }, - "ts-node": { + "tsx": { + "optional": true + }, + "yaml": { "optional": true } } @@ -8186,6 +8193,8 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", "license": "ISC", + "optional": true, + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -11832,9 +11841,9 @@ } }, "jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==" + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==" }, "js-tokens": { "version": "4.0.0", @@ -12464,12 +12473,11 @@ } }, "postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "requires": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" + "lilconfig": "^3.1.1" } }, "postcss-merge-longhand": { @@ -13506,7 +13514,9 @@ "yaml": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", - "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==" + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "optional": true, + "peer": true }, "yargs": { "version": "17.7.2", diff --git a/package.json b/package.json index 03484eb103c8..83e26beb587d 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.21.6", + "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", @@ -81,7 +81,7 @@ "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", diff --git a/src/cli/build/plugin.js b/src/cli/build/plugin.js index 1cb4ef5a9784..9a17520ffb97 100644 --- a/src/cli/build/plugin.js +++ b/src/cli/build/plugin.js @@ -43,10 +43,13 @@ async function loadPostCssPlugins(customPostCssPath) { config.plugins = [] } + // We have to await these because in v5 and v6 of postcss-load-config + // these functions return promises while they don't in v4. Awaiting a + // non-promise is basically a no-op so this is safe to do. return { file, - plugins: loadPlugins(config, file), - options: loadOptions(config, file), + plugins: await loadPlugins(config, file), + options: await loadOptions(config, file), } })() : await postcssrc() diff --git a/src/lib/load-config.ts b/src/lib/load-config.ts index af518d9cc6b1..c9662ac48a0e 100644 --- a/src/lib/load-config.ts +++ b/src/lib/load-config.ts @@ -35,23 +35,8 @@ export function loadConfig(path: string): Config { let config = (function () { if (!path) return {} - // Always use jiti for now. There is a a bug that occurs in Node v22.12+ - // where imported files return invalid results - return lazyJiti()(path) - - // Always use jiti for ESM or TS files - if ( - path && - (path.endsWith('.mjs') || - path.endsWith('.ts') || - path.endsWith('.cts') || - path.endsWith('.mts')) - ) { - return lazyJiti()(path) - } - try { - return path ? require(path) : {} + return require(path) } catch { return lazyJiti()(path) }