Skip to content

Commit ba55a44

Browse files
Fix native ESM config loading in v3 (#18938)
Unfortunately for backwards compatibility purposes (with `loadConfig` at least) we can't switch things to use `import(…)` because there's baked in knowledge that the config is loaded synchronously for v3. This PR does two things: - Defers to `require(…)` which allows newer versions that support `require(esm)` to work natively. This works around the need to switch to `import(…)` for those versions. - Allows newer versions of `postcss-load-config` enabling better ESM+TypeScript support for PostCSS configs in the CLI. We support v4, v5, and v6 of `postcss-load-config` simultaneously so any of those versions should work. I've verified that newer node versions seem to install v6 while earlier ones like Node v14 install v4 of `postcss-load-config`. So this should be a backwards compatible change. - [x] needs tests for `import.meta.resolve(…)` - [x] needs tests for ESM postcss configs Fixes #14152 Fixes #14423
1 parent ff52f8c commit ba55a44

File tree

9 files changed

+164
-45
lines changed

9 files changed

+164
-45
lines changed

.github/workflows/ci-stable.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727

2828
strategy:
2929
matrix:
30-
node-version: [14, 18]
30+
node-version: [14, 18, 20, 22, 24]
3131

3232
steps:
3333
- uses: actions/checkout@v3

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424

2525
strategy:
2626
matrix:
27-
node-version: [14, 18]
27+
node-version: [14, 18, 20, 22, 24]
2828

2929
steps:
3030
- uses: actions/checkout@v3

.github/workflows/integration-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
- vite
3232
- webpack-4
3333
- webpack-5
34-
node-version: [18]
34+
node-version: [18, 20]
3535
fail-fast: false
3636

3737
steps:

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Improve support for raw `supports-[…]` queries in arbitrary values ([#13605](https://github.com/tailwindlabs/tailwindcss/pull/13605))
1313
- Fix `require.cache` error when loaded through a TypeScript file in Node 22.18+ ([#18665](https://github.com/tailwindlabs/tailwindcss/pull/18665))
14+
- Support `import.meta.resolve(…)` in configs for new enough Node.js versions ([#18938](https://github.com/tailwindlabs/tailwindcss/pull/18938))
15+
- 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))
1416

1517
## [3.4.17] - 2024-12-17
1618

integrations/tailwindcss-cli/tests/cli.test.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,52 @@ describe('Build command', () => {
207207
)
208208
})
209209

210+
test('configs support import.meta', async () => {
211+
// Skip this test in Node 18 as this only works with
212+
// `require(esm)` in Node 20.19+
213+
if (process.versions.node.startsWith('18.')) {
214+
expect(true).toBe(true)
215+
return
216+
}
217+
218+
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
219+
220+
let customConfig = `
221+
console.log(import.meta.url)
222+
console.log(import.meta.resolve('./tailwind.config.mjs'))
223+
export default ${JSON.stringify(
224+
{
225+
content: ['./src/index.html'],
226+
theme: {
227+
extend: {
228+
fontWeight: {
229+
bold: 'BOLD',
230+
},
231+
},
232+
},
233+
corePlugins: {
234+
preflight: false,
235+
},
236+
plugins: [],
237+
},
238+
null,
239+
2
240+
)}
241+
`
242+
243+
await writeInputFile('../tailwind.config.mjs', customConfig)
244+
245+
await $(`${EXECUTABLE} --output ./dist/main.css --config ./tailwind.config.mjs`)
246+
247+
expect(await readOutputFile('main.css')).toIncludeCss(
248+
css`
249+
.font-bold {
250+
font-weight: BOLD;
251+
}
252+
`
253+
)
254+
})
255+
210256
test('--content', async () => {
211257
await writeInputFile('other.html', html`<div class="font-bold"></div>`)
212258

@@ -391,6 +437,79 @@ describe('Build command', () => {
391437
expect(contents).toContain(`/*# sourceMappingURL`)
392438
})
393439

440+
test('--postcss supports ESM configs', async () => {
441+
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
442+
443+
let customConfig = javascript`
444+
import * as path from 'path'
445+
import { createRequire } from 'module'
446+
const require = createRequire(import.meta.url)
447+
448+
export default {
449+
map: { inline: true },
450+
plugins: [
451+
function tailwindcss() {
452+
return require(path.resolve('..', '..'))
453+
},
454+
],
455+
}
456+
`
457+
458+
await removeFile('./postcss.config.js')
459+
await writeInputFile('../postcss.config.mjs', customConfig)
460+
461+
await $(`${EXECUTABLE} --output ./dist/main.css --postcss`)
462+
463+
let contents = await readOutputFile('main.css')
464+
465+
expect(contents).toIncludeCss(
466+
css`
467+
.font-bold {
468+
font-weight: 700;
469+
}
470+
`
471+
)
472+
473+
expect(contents).toContain(`/*# sourceMappingURL`)
474+
})
475+
476+
test('--postcss supports TS configs', async () => {
477+
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
478+
479+
let customConfig = javascript`
480+
import * as path from 'path'
481+
import { createRequire } from 'module'
482+
import type { AcceptedPlugin } from 'postcss'
483+
const require = createRequire(import.meta.url)
484+
485+
export default {
486+
map: { inline: true },
487+
plugins: [
488+
function tailwindcss() {
489+
return require(path.resolve('..', '..'))
490+
} as AcceptedPlugin,
491+
],
492+
}
493+
`
494+
495+
await removeFile('./postcss.config.js')
496+
await writeInputFile('../postcss.config.ts', customConfig)
497+
498+
await $(`${EXECUTABLE} --output ./dist/main.css --postcss`)
499+
500+
let contents = await readOutputFile('main.css')
501+
502+
expect(contents).toIncludeCss(
503+
css`
504+
.font-bold {
505+
font-weight: 700;
506+
}
507+
`
508+
)
509+
510+
expect(contents).toContain(`/*# sourceMappingURL`)
511+
})
512+
394513
test('postcss-import is supported by default', async () => {
395514
cleanupFile('src/test.css')
396515

package-lock.json

Lines changed: 32 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
"fast-glob": "^3.3.2",
7373
"glob-parent": "^6.0.2",
7474
"is-glob": "^4.0.3",
75-
"jiti": "^1.21.6",
75+
"jiti": "^1.21.7",
7676
"lilconfig": "^3.1.3",
7777
"micromatch": "^4.0.8",
7878
"normalize-path": "^3.0.0",
@@ -81,7 +81,7 @@
8181
"postcss": "^8.4.47",
8282
"postcss-import": "^15.1.0",
8383
"postcss-js": "^4.0.1",
84-
"postcss-load-config": "^4.0.2",
84+
"postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
8585
"postcss-nested": "^6.2.0",
8686
"postcss-selector-parser": "^6.1.2",
8787
"resolve": "^1.22.8",

src/cli/build/plugin.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,13 @@ async function loadPostCssPlugins(customPostCssPath) {
4343
config.plugins = []
4444
}
4545

46+
// We have to await these because in v5 and v6 of postcss-load-config
47+
// these functions return promises while they don't in v4. Awaiting a
48+
// non-promise is basically a no-op so this is safe to do.
4649
return {
4750
file,
48-
plugins: loadPlugins(config, file),
49-
options: loadOptions(config, file),
51+
plugins: await loadPlugins(config, file),
52+
options: await loadOptions(config, file),
5053
}
5154
})()
5255
: await postcssrc()

src/lib/load-config.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,8 @@ export function loadConfig(path: string): Config {
3535
let config = (function () {
3636
if (!path) return {}
3737

38-
// Always use jiti for now. There is a a bug that occurs in Node v22.12+
39-
// where imported files return invalid results
40-
return lazyJiti()(path)
41-
42-
// Always use jiti for ESM or TS files
43-
if (
44-
path &&
45-
(path.endsWith('.mjs') ||
46-
path.endsWith('.ts') ||
47-
path.endsWith('.cts') ||
48-
path.endsWith('.mts'))
49-
) {
50-
return lazyJiti()(path)
51-
}
52-
5338
try {
54-
return path ? require(path) : {}
39+
return require(path)
5540
} catch {
5641
return lazyJiti()(path)
5742
}

0 commit comments

Comments
 (0)