diff --git a/CHANGELOG.md b/CHANGELOG.md index 241f3d6c7278..f9ea7a067186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure `url(…)` containing special characters such as `;` or `{}` end up in one declaration ([#14879](https://github.com/tailwindlabs/tailwindcss/pull/14879)) - Ensure adjacent rules are merged together after handling nesting when generating optimized CSS ([#14873](https://github.com/tailwindlabs/tailwindcss/pull/14873)) - Rebase `url()` inside imported CSS files when using Vite ([#14877](https://github.com/tailwindlabs/tailwindcss/pull/14877)) +- Ensure that CSS transforms from other Vite plugins correctly work in full builds (e.g. `:deep()` in Vue) ([#14871](https://github.com/tailwindlabs/tailwindcss/pull/14871)) - _Upgrade (experimental)_: Install `@tailwindcss/postcss` next to `tailwindcss` ([#14830](https://github.com/tailwindlabs/tailwindcss/pull/14830)) - _Upgrade (experimental)_: Remove whitespace around `,` separator when print arbitrary values ([#14838](https://github.com/tailwindlabs/tailwindcss/pull/14838)) - _Upgrade (experimental)_: Fix crash during upgrade when content globs escape root of project ([#14896](https://github.com/tailwindlabs/tailwindcss/pull/14896)) diff --git a/integrations/vite/nuxt.test.ts b/integrations/vite/nuxt.test.ts index 21876d2c7adf..8c7eedc386d7 100644 --- a/integrations/vite/nuxt.test.ts +++ b/integrations/vite/nuxt.test.ts @@ -1,65 +1,74 @@ import { expect } from 'vitest' import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts } from '../utils' -test( - 'dev mode', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "nuxt": "^3.13.1", - "tailwindcss": "workspace:^", - "vue": "latest" - } +const SETUP = { + fs: { + 'package.json': json` + { + "type": "module", + "dependencies": { + "@tailwindcss/vite": "workspace:^", + "nuxt": "^3.13.1", + "tailwindcss": "workspace:^", + "vue": "latest" } - `, - 'nuxt.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' + } + `, + 'nuxt.config.ts': ts` + import tailwindcss from '@tailwindcss/vite' - // https://nuxt.com/docs/api/configuration/nuxt-config - export default defineNuxtConfig({ - vite: { - plugins: [tailwindcss()], - }, + // https://nuxt.com/docs/api/configuration/nuxt-config + export default defineNuxtConfig({ + vite: { + plugins: [tailwindcss()], + }, - css: ['~/assets/css/main.css'], - devtools: { enabled: true }, - compatibilityDate: '2024-08-30', - }) - `, - 'app.vue': html` + css: ['~/assets/css/main.css'], + devtools: { enabled: true }, + compatibilityDate: '2024-08-30', + }) + `, + 'app.vue': html` +
Hello world!
+ `, - 'assets/css/main.css': css`@import 'tailwindcss';`, - }, + 'assets/css/main.css': css`@import 'tailwindcss';`, }, - async ({ fs, spawn, getFreePort }) => { - let port = await getFreePort() - await spawn(`pnpm nuxt dev --port ${port}`) +} + +test('dev mode', SETUP, async ({ fs, spawn, getFreePort }) => { + let port = await getFreePort() + await spawn(`pnpm nuxt dev --port ${port}`) - await retryAssertion(async () => { - let css = await fetchStyles(port) - expect(css).toContain(candidate`underline`) - }) + await retryAssertion(async () => { + let css = await fetchStyles(port) + expect(css).toContain(candidate`underline`) + }) - await retryAssertion(async () => { - await fs.write( - 'app.vue', - html` + await retryAssertion(async () => { + await fs.write( + 'app.vue', + html` +
Hello world!
+ `, - ) + ) - let css = await fetchStyles(port) - expect(css).toContain(candidate`underline`) - expect(css).toContain(candidate`font-bold`) - }) - }, -) + let css = await fetchStyles(port) + expect(css).toContain(candidate`underline`) + expect(css).toContain(candidate`font-bold`) + }) +}) + +test('build', SETUP, async ({ spawn, getFreePort, exec }) => { + let port = await getFreePort() + await exec(`pnpm nuxt build`) + await spawn(`PORT=${port} pnpm nuxt preview`) + + await retryAssertion(async () => { + let css = await fetchStyles(port) + expect(css).toContain(candidate`underline`) + }) +}) diff --git a/integrations/vite/other-transforms.test.ts b/integrations/vite/other-transforms.test.ts new file mode 100644 index 000000000000..71749f21c229 --- /dev/null +++ b/integrations/vite/other-transforms.test.ts @@ -0,0 +1,172 @@ +import dedent from 'dedent' +import { describe, expect } from 'vitest' +import { css, fetchStyles, html, retryAssertion, test, ts, txt } from '../utils' + +function createSetup(transformer: 'postcss' | 'lightningcss') { + return { + fs: { + 'package.json': txt` + { + "type": "module", + "dependencies": { + "@tailwindcss/vite": "workspace:^", + "tailwindcss": "workspace:^" + }, + "devDependencies": { + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^5.3.5" + } + } + `, + 'vite.config.ts': ts` + import tailwindcss from '@tailwindcss/vite' + import { defineConfig } from 'vite' + + export default defineConfig({ + css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, + build: { cssMinify: false }, + plugins: [ + tailwindcss(), + { + name: 'recolor', + transform(code, id) { + if (id.includes('.css')) { + return code.replace(/red;/g, 'blue;') + } + }, + }, + ], + }) + `, + 'index.html': html` + + + + +
Hello, world!
+ + `, + 'src/index.css': css` + @import 'tailwindcss/theme' theme(reference); + @import 'tailwindcss/utilities'; + + .foo { + color: red; + } + `, + }, + } +} + +for (let transformer of ['postcss', 'lightningcss'] as const) { + describe(transformer, () => { + test(`production build`, createSetup(transformer), async ({ fs, exec }) => { + await exec('pnpm vite build') + + let files = await fs.glob('dist/**/*.css') + expect(files).toHaveLength(1) + let [filename] = files[0] + + await fs.expectFileToContain(filename, [ + css` + .foo { + color: blue; + } + `, + // Running the transforms on utilities generated by Tailwind might change in the future + dedent` + .\[background-color\:red\] { + background-color: blue; + } + `, + ]) + }) + + test(`dev mode`, createSetup(transformer), async ({ spawn, getFreePort, fs }) => { + let port = await getFreePort() + await spawn(`pnpm vite dev --port ${port}`) + + await retryAssertion(async () => { + let styles = await fetchStyles(port, '/index.html') + expect(styles).toContain(css` + .foo { + color: blue; + } + `) + // Running the transforms on utilities generated by Tailwind might change in the future + expect(styles).toContain(dedent` + .\[background-color\:red\] { + background-color: blue; + } + `) + }) + + await retryAssertion(async () => { + await fs.write( + 'src/index.css', + css` + @import 'tailwindcss/theme' theme(reference); + @import 'tailwindcss/utilities'; + + .foo { + background-color: red; + } + `, + ) + + let styles = await fetchStyles(port) + expect(styles).toContain(css` + .foo { + background-color: blue; + } + `) + }) + }) + + test('watch mode', createSetup(transformer), async ({ spawn, fs }) => { + await spawn(`pnpm vite build --watch`) + + await retryAssertion(async () => { + let files = await fs.glob('dist/**/*.css') + expect(files).toHaveLength(1) + let [, styles] = files[0] + + expect(styles).toContain(css` + .foo { + color: blue; + } + `) + // Running the transforms on utilities generated by Tailwind might change in the future + expect(styles).toContain(dedent` + .\[background-color\:red\] { + background-color: blue; + } + `) + }) + + await retryAssertion(async () => { + await fs.write( + 'src/index.css', + css` + @import 'tailwindcss/theme' theme(reference); + @import 'tailwindcss/utilities'; + + .foo { + background-color: red; + } + `, + ) + + let files = await fs.glob('dist/**/*.css') + expect(files).toHaveLength(1) + let [, styles] = files[0] + + expect(styles).toContain(css` + .foo { + background-color: blue; + } + `) + }) + }) + }) +} diff --git a/integrations/vite/vue.test.ts b/integrations/vite/vue.test.ts index 4f582a99fcdc..53f079499dd0 100644 --- a/integrations/vite/vue.test.ts +++ b/integrations/vite/vue.test.ts @@ -51,9 +51,15 @@ test( @apply text-red-500; } - + `, }, @@ -65,5 +71,6 @@ test( expect(files).toHaveLength(1) await fs.expectFileToContain(files[0][0], [candidate`underline`, candidate`foo`]) + await fs.expectFileToContain(files[0][0], ['.bar{']) }, ) diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index e6085562d903..1ec1eac5e736 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -15,11 +15,6 @@ export default function tailwindcss(): Plugin[] { let isSSR = false let minify = false - // A list of css plugins defined in the Vite config. We need to retain these - // so that we can rerun the right transformations in build mode where we have - // to manually rebuild the css file after the compilation is done. - let cssPlugins: readonly Plugin[] = [] - // The Vite extension has two types of sources for candidates: // // 1. The module graph: These are all modules that vite transforms and we want @@ -109,8 +104,31 @@ export default function tailwindcss(): Plugin[] { }, } - for (let plugin of cssPlugins) { + for (let plugin of config!.plugins) { if (!plugin.transform) continue + + if (plugin.name.startsWith('@tailwindcss/')) { + // We do not run any Tailwind transforms anymore + continue + } else if ( + plugin.name.startsWith('vite:') && + // Apply the vite:css plugin to generated CSS for transformations like + // URL path rewriting and image inlining. + plugin.name !== 'vite:css' && + // In build mode, since `renderStart` runs after all transformations, we + // need to also apply vite:css-post. + plugin.name !== 'vite:css-post' && + // The vite:vue plugin handles CSS specific post-processing for Vue + plugin.name !== 'vite:vue' + ) { + continue + } else if (plugin.name === 'ssr-styles') { + // The Nuxt ssr-styles plugin emits styles from server-side rendered + // components, we can't run it in the `renderStart` phase so we're + // skipping it. + continue + } + let transformHandler = 'handler' in plugin.transform! ? plugin.transform.handler : plugin.transform! @@ -147,20 +165,6 @@ export default function tailwindcss(): Plugin[] { config = _config minify = config.build.cssMinify !== false isSSR = config.build.ssr !== false && config.build.ssr !== undefined - - let allowedPlugins = [ - // Apply the vite:css plugin to generated CSS for transformations like - // URL path rewriting and image inlining. - 'vite:css', - - // In build mode, since renderChunk runs after all transformations, we - // need to also apply vite:css-post. - ...(config.command === 'build' ? ['vite:css-post'] : []), - ] - - cssPlugins = config.plugins.filter((plugin) => { - return allowedPlugins.includes(plugin.name) - }) }, // Scan all non-CSS files for candidates diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4545fe4cddc4..7b80bf01c83f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1093,7 +1093,6 @@ packages: '@parcel/watcher-darwin-arm64@2.4.2-alpha.0': resolution: {integrity: sha512-2xH4Ve7OKjIh+4YRfTN3HGJa2W8KTPLOALHZj5fxcbTPwaVxdpIRItDrcikUx2u3AzGAFme7F+AZZXHnf0F15Q==} engines: {node: '>= 10.0.0'} - cpu: [arm64] os: [darwin] '@parcel/watcher-darwin-x64@2.4.1': @@ -1105,7 +1104,6 @@ packages: '@parcel/watcher-darwin-x64@2.4.2-alpha.0': resolution: {integrity: sha512-xtjmXUH4YZVah5+7Q0nb+fpRP5qZn9cFfuPuZ4k77UfUGVwhacgZyIRQgIOwMP3GkgW4TsrKQaw1KIe7L1ZqcQ==} engines: {node: '>= 10.0.0'} - cpu: [x64] os: [darwin] '@parcel/watcher-freebsd-x64@2.4.1': @@ -1129,7 +1127,6 @@ packages: '@parcel/watcher-linux-arm64-glibc@2.4.2-alpha.0': resolution: {integrity: sha512-vIIOcZf+fgsRReIK3Fw0WINvGo9UwiXfisnqYRzfpNByRZvkEPkGTIVe8iiDp72NhPTVmwIvBqM6yKDzIaw8GQ==} engines: {node: '>= 10.0.0'} - cpu: [arm64] os: [linux] '@parcel/watcher-linux-arm64-musl@2.4.1': @@ -1141,7 +1138,6 @@ packages: '@parcel/watcher-linux-arm64-musl@2.4.2-alpha.0': resolution: {integrity: sha512-gXqEAoLG9bBCbQNUgqjSOxHcjpmCZmYT9M8UvrdTMgMYgXgiWcR8igKlPRd40mCIRZSkMpN2ScSy2WjQ0bQZnQ==} engines: {node: '>= 10.0.0'} - cpu: [arm64] os: [linux] '@parcel/watcher-linux-x64-glibc@2.4.1': @@ -1153,7 +1149,6 @@ packages: '@parcel/watcher-linux-x64-glibc@2.4.2-alpha.0': resolution: {integrity: sha512-/WJJ3Y46ubwQW+Z+mzpzK3pvqn/AT7MA63NB0+k9GTLNxJQZNREensMtpJ/FJ+LVIiraEHTY22KQrsx9+DeNbw==} engines: {node: '>= 10.0.0'} - cpu: [x64] os: [linux] '@parcel/watcher-linux-x64-musl@2.4.1': @@ -1165,7 +1160,6 @@ packages: '@parcel/watcher-linux-x64-musl@2.4.2-alpha.0': resolution: {integrity: sha512-1dz4fTM5HaANk3RSRmdhALT+bNqTHawVDL1D77HwV/FuF/kSjlM3rGrJuFaCKwQ5E8CInHCcobqMN8Jh8LYaRg==} engines: {node: '>= 10.0.0'} - cpu: [x64] os: [linux] '@parcel/watcher-win32-arm64@2.4.1': @@ -1189,7 +1183,6 @@ packages: '@parcel/watcher-win32-x64@2.4.2-alpha.0': resolution: {integrity: sha512-U2abMKF7JUiIxQkos19AvTLFcnl2Xn8yIW1kzu+7B0Lux4Gkuu/BUDBroaM1s6+hwgK63NOLq9itX2Y3GwUThg==} engines: {node: '>= 10.0.0'} - cpu: [x64] os: [win32] '@parcel/watcher@2.4.1': @@ -1618,7 +1611,6 @@ packages: bun@1.1.29: resolution: {integrity: sha512-SKhpyKNZtgxrVel9ec9xon3LDv8mgpiuFhARgcJo1YIbggY2PBrKHRNiwQ6Qlb+x3ivmRurfuwWgwGexjpgBRg==} - cpu: [arm64, x64] os: [darwin, linux, win32] hasBin: true @@ -2508,13 +2500,11 @@ packages: lightningcss-darwin-arm64@1.26.0: resolution: {integrity: sha512-n4TIvHO1NY1ondKFYpL2ZX0bcC2y6yjXMD6JfyizgR8BCFNEeArINDzEaeqlfX9bXz73Bpz/Ow0nu+1qiDrBKg==} engines: {node: '>= 12.0.0'} - cpu: [arm64] os: [darwin] lightningcss-darwin-x64@1.26.0: resolution: {integrity: sha512-Rf9HuHIDi1R6/zgBkJh25SiJHF+dm9axUZW/0UoYCW1/8HV0gMI0blARhH4z+REmWiU1yYT/KyNF3h7tHyRXUg==} engines: {node: '>= 12.0.0'} - cpu: [x64] os: [darwin] lightningcss-freebsd-x64@1.26.0: @@ -2532,25 +2522,21 @@ packages: lightningcss-linux-arm64-gnu@1.26.0: resolution: {integrity: sha512-iJmZM7fUyVjH+POtdiCtExG+67TtPUTer7K/5A8DIfmPfrmeGvzfRyBltGhQz13Wi15K1lf2cPYoRaRh6vcwNA==} engines: {node: '>= 12.0.0'} - cpu: [arm64] os: [linux] lightningcss-linux-arm64-musl@1.26.0: resolution: {integrity: sha512-XxoEL++tTkyuvu+wq/QS8bwyTXZv2y5XYCMcWL45b8XwkiS8eEEEej9BkMGSRwxa5J4K+LDeIhLrS23CpQyfig==} engines: {node: '>= 12.0.0'} - cpu: [arm64] os: [linux] lightningcss-linux-x64-gnu@1.26.0: resolution: {integrity: sha512-1dkTfZQAYLj8MUSkd6L/+TWTG8V6Kfrzfa0T1fSlXCXQHrt1HC1/UepXHtKHDt/9yFwyoeayivxXAsApVxn6zA==} engines: {node: '>= 12.0.0'} - cpu: [x64] os: [linux] lightningcss-linux-x64-musl@1.26.0: resolution: {integrity: sha512-yX3Rk9m00JGCUzuUhFEojY+jf/6zHs3XU8S8Vk+FRbnr4St7cjyMXdNjuA2LjiT8e7j8xHRCH8hyZ4H/btRE4A==} engines: {node: '>= 12.0.0'} - cpu: [x64] os: [linux] lightningcss-win32-arm64-msvc@1.26.0: @@ -2562,7 +2548,6 @@ packages: lightningcss-win32-x64-msvc@1.26.0: resolution: {integrity: sha512-pYS3EyGP3JRhfqEFYmfFDiZ9/pVNfy8jVIYtrx9TVNusVyDK3gpW1w/rbvroQ4bDJi7grdUtyrYU6V2xkY/bBw==} engines: {node: '>= 12.0.0'} - cpu: [x64] os: [win32] lightningcss@1.26.0: @@ -4950,7 +4935,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 9.11.1(jiti@2.3.3) - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.11.1(jiti@2.3.3))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.11.1(jiti@2.3.3)) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.11.1(jiti@2.3.3))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.11.1(jiti@2.3.3))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@9.11.1(jiti@2.3.3)))(eslint@9.11.1(jiti@2.3.3)))(eslint@9.11.1(jiti@2.3.3)) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.11.1(jiti@2.3.3))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.11.1(jiti@2.3.3)) fast-glob: 3.3.2 get-tsconfig: 4.7.6 @@ -4968,7 +4953,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 9.13.0(jiti@2.3.3) - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.13.0(jiti@2.3.3)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.13.0(jiti@2.3.3)))(eslint@9.13.0(jiti@2.3.3)) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -4981,7 +4966,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.13.0(jiti@2.3.3)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.13.0(jiti@2.3.3)))(eslint@9.13.0(jiti@2.3.3)): dependencies: debug: 3.2.7 optionalDependencies: @@ -4992,7 +4977,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.11.1(jiti@2.3.3))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.11.1(jiti@2.3.3)): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.11.1(jiti@2.3.3))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.11.1(jiti@2.3.3))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@9.11.1(jiti@2.3.3)))(eslint@9.11.1(jiti@2.3.3)))(eslint@9.11.1(jiti@2.3.3)): dependencies: debug: 3.2.7 optionalDependencies: @@ -5013,7 +4998,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.11.1(jiti@2.3.3) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.11.1(jiti@2.3.3))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.11.1(jiti@2.3.3)) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.11.1(jiti@2.3.3))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.11.1(jiti@2.3.3))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@9.11.1(jiti@2.3.3)))(eslint@9.11.1(jiti@2.3.3)))(eslint@9.11.1(jiti@2.3.3)) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -5041,7 +5026,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.13.0(jiti@2.3.3) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.13.0(jiti@2.3.3)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.13.0(jiti@2.3.3)))(eslint@9.13.0(jiti@2.3.3)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3