diff --git a/CHANGELOG.md b/CHANGELOG.md index 684a2775e1f6..78f4c2df9e23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix issue with `@apply` not working as expected with `!important` inside an atrule ([#2824](https://github.com/tailwindlabs/tailwindcss/pull/2824)) - Fix issue with `@apply` not working as expected with defined classes ([#2832](https://github.com/tailwindlabs/tailwindcss/pull/2832)) +- Fix memory leak, and broken `@apply` when splitting up files ([#3032](https://github.com/tailwindlabs/tailwindcss/pull/3032)) ### Added diff --git a/__tests__/applyAtRule.test.js b/__tests__/applyAtRule.test.js index 13f7b8ef907f..3cf53394c26d 100644 --- a/__tests__/applyAtRule.test.js +++ b/__tests__/applyAtRule.test.js @@ -337,16 +337,17 @@ test('you can apply utility classes that do not actually exist as long as they w }) }) -test('the shadow lookup is only used if no @tailwind rules were in the source tree', () => { +test('shadow lookup will be constructed when we have missing @tailwind atrules', () => { const input = ` @tailwind base; + .foo { @apply mt-4; } ` expect.assertions(1) - return run(input).catch((e) => { - expect(e).toMatchObject({ name: 'CssSyntaxError' }) + return run(input).then((result) => { + expect(result.css).toContain(`.foo { margin-top: 1rem;\n}`) }) }) @@ -1362,3 +1363,40 @@ test('declarations within a rule that uses @apply with !important remain not !im expect(result.warnings().length).toBe(0) }) }) + +test('lookup tree is correctly cached based on used tailwind atrules', async () => { + const input1 = ` + @tailwind utilities; + + .foo { @apply mt-4; } + ` + + const input2 = ` + @tailwind components; + + .foo { @apply mt-4; } + ` + + let config = { + corePlugins: [], + plugins: [ + function ({ addUtilities, addComponents }) { + addUtilities({ '.mt-4': { marginTop: '1rem' } }, []) + addComponents({ '.container': { maxWidth: '500px' } }, []) + }, + ], + } + + let output1 = await run(input1, config) + let output2 = await run(input2, config) + + expect(output1.css).toMatchCss(` + .mt-4 { margin-top: 1rem; } + .foo { margin-top: 1rem; } + `) + + expect(output2.css).toMatchCss(` + .container { max-width: 500px; } + .foo { margin-top: 1rem; } + `) +}) diff --git a/perf/tailwind.config.js b/perf/tailwind.config.js index 7fb2125aae9e..5211607402b6 100644 --- a/perf/tailwind.config.js +++ b/perf/tailwind.config.js @@ -1,14 +1,12 @@ +let colors = require('../colors') module.exports = { - future: 'all', - experimental: 'all', purge: [], + darkMode: 'class', theme: { - extend: {}, + extend: { colors }, }, variants: [ 'responsive', - 'motion-safe', - 'motion-reduce', 'group-hover', 'group-focus', 'hover', @@ -19,10 +17,6 @@ module.exports = { 'visited', 'disabled', 'checked', - 'first', - 'last', - 'odd', - 'even', ], plugins: [], } diff --git a/src/lib/substituteClassApplyAtRules.js b/src/lib/substituteClassApplyAtRules.js index 7c7c89adc88d..90a3e4544206 100644 --- a/src/lib/substituteClassApplyAtRules.js +++ b/src/lib/substituteClassApplyAtRules.js @@ -11,15 +11,25 @@ import substituteScreenAtRules from './substituteScreenAtRules' import prefixSelector from '../util/prefixSelector' import { useMemo } from '../util/useMemo' -function hasAtRule(css, atRule) { - let foundAtRule = false - - css.walkAtRules(atRule, () => { - foundAtRule = true - return false - }) +function hasAtRule(css, atRule, condition) { + let found = false + + css.walkAtRules( + atRule, + condition === undefined + ? () => { + found = true + return false + } + : (node) => { + if (condition(node)) { + found = true + return false + } + } + ) - return foundAtRule + return found } function cloneWithoutChildren(node) { @@ -298,7 +308,7 @@ function processApplyAtRules(css, lookupTree, config) { return css } -let defaultTailwindTree = null +let defaultTailwindTree = new Map() export default function substituteClassApplyAtRules(config, getProcessedPlugins, configChanged) { return function (css) { @@ -307,15 +317,29 @@ export default function substituteClassApplyAtRules(config, getProcessedPlugins, return css } - // Tree already contains @tailwind rules, don't prepend default Tailwind tree - if (hasAtRule(css, 'tailwind')) { + let requiredTailwindAtRules = ['base', 'components', 'utilities'] + if ( + hasAtRule(css, 'tailwind', (node) => { + let idx = requiredTailwindAtRules.indexOf(node.params) + if (idx !== -1) requiredTailwindAtRules.splice(idx, 1) + if (requiredTailwindAtRules.length <= 0) return true + return false + }) + ) { + // Tree already contains all the at rules (requiredTailwindAtRules) return processApplyAtRules(css, postcss.root(), config) } - // Tree contains no @tailwind rules, so generate all of Tailwind's styles and - // prepend them to the user's CSS. Important for