diff --git a/integrations/webpack/loader.test.ts b/integrations/webpack/loader.test.ts index 3a3782c0d7a3..cc4441237b74 100644 --- a/integrations/webpack/loader.test.ts +++ b/integrations/webpack/loader.test.ts @@ -418,3 +418,78 @@ test( `) }, ) + +test( + '@tailwindcss/webpack loader isolates cache by resource including query', + { + fs: { + 'package.json': json` + { + "main": "./src/index.js", + "browser": "./src/index.js", + "dependencies": { + "css-loader": "^6", + "webpack": "^5", + "webpack-cli": "^5", + "mini-css-extract-plugin": "^2", + "tailwindcss": "workspace:^", + "@tailwindcss/webpack": "workspace:^" + } + } + `, + 'webpack.config.js': js` + let MiniCssExtractPlugin = require('mini-css-extract-plugin') + let path = require('node:path') + + module.exports = { + mode: 'development', + entry: { + a: './src/a.js', + b: './src/b.js', + }, + output: { + clean: true, + }, + plugins: [new MiniCssExtractPlugin()], + module: { + rules: [ + { + test: /.css$/i, + use: [ + MiniCssExtractPlugin.loader, + 'css-loader', + '@tailwindcss/webpack', + path.resolve(__dirname, 'query-loader.js'), + ], + }, + ], + }, + } + `, + 'query-loader.js': js` + module.exports = function (source) { + if (this.resourceQuery.includes('a')) { + return '@import "tailwindcss/utilities";\n\n@utility only-a {\n color: var(--color-red-500);\n}\n' + } + + if (this.resourceQuery.includes('b')) { + return '@import "tailwindcss/utilities";\n\n@utility only-b {\n color: var(--color-blue-500);\n}\n' + } + + return source + } + `, + 'src/a.js': js`import './index.css?a'`, + 'src/b.js': js`import './index.css?b'`, + 'src/index.css': css``, + }, + }, + async ({ fs, exec }) => { + await exec('pnpm webpack --mode=development') + + await fs.expectFileToContain('dist/a.css', ['only-a', '--color-red-500']) + await fs.expectFileToContain('dist/b.css', ['only-b', '--color-blue-500']) + await fs.expectFileNotToContain('dist/a.css', ['only-b', '--color-blue-500']) + await fs.expectFileNotToContain('dist/b.css', ['only-a', '--color-red-500']) + }, +) diff --git a/packages/@tailwindcss-webpack/src/index.ts b/packages/@tailwindcss-webpack/src/index.ts index 2c78a552b9e7..6d02f1f0b937 100644 --- a/packages/@tailwindcss-webpack/src/index.ts +++ b/packages/@tailwindcss-webpack/src/index.ts @@ -40,8 +40,12 @@ interface CacheEntry { const cache = new QuickLRU({ maxSize: 50 }) -function getContextFromCache(inputFile: string, opts: LoaderOptions): CacheEntry { - let key = `${inputFile}:${opts.base ?? ''}:${JSON.stringify(opts.optimize)}` +function getCacheKey(resourceId: string, opts: LoaderOptions): string { + return `${resourceId}:${opts.base ?? ''}:${JSON.stringify(opts.optimize)}` +} + +function getContextFromCache(resourceId: string, opts: LoaderOptions): CacheEntry { + let key = getCacheKey(resourceId, opts) if (cache.has(key)) return cache.get(key)! let entry: CacheEntry = { mtimes: new Map(), @@ -61,6 +65,7 @@ export default async function tailwindLoader( let callback = this.async() let options = this.getOptions() ?? {} let inputFile = this.resourcePath + let resourceId = this.resource let base = options.base ?? process.cwd() let shouldOptimize = options.optimize ?? process.env.NODE_ENV === 'production' let isCSSModuleFile = inputFile.endsWith('.module.css') @@ -83,7 +88,7 @@ export default async function tailwindLoader( } try { - let context = getContextFromCache(inputFile, options) + let context = getContextFromCache(resourceId, options) let inputBasePath = path.dirname(path.resolve(inputFile)) // Whether this is the first build or not @@ -273,7 +278,7 @@ export default async function tailwindLoader( callback(null, result) } catch (error) { // Clear the cache entry on error to force a full rebuild next time - let key = `${inputFile}:${options.base ?? ''}:${JSON.stringify(options.optimize)}` + let key = getCacheKey(resourceId, options) cache.delete(key) DEBUG && I.end(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`)