Re-use existing entries in the rule cache (#9208)
* Add test * Reuse rule cache entries when possible * Update changelog
This commit is contained in:
parent
da850424dc
commit
58cc7ed3e8
5 changed files with 125 additions and 41 deletions
|
|
@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Remove invalid `outline-hidden` utility ([#9147](https://github.com/tailwindlabs/tailwindcss/pull/9147))
|
||||
- Honor the `hidden` attribute on elements in preflight ([#9174](https://github.com/tailwindlabs/tailwindcss/pull/9174))
|
||||
- Don't stop watching atomically renamed files ([#9173](https://github.com/tailwindlabs/tailwindcss/pull/9173))
|
||||
- Re-use existing entries in the rule cache ([#9208](https://github.com/tailwindlabs/tailwindcss/pull/9208))
|
||||
- Don't output duplicate utilities ([#9208](https://github.com/tailwindlabs/tailwindcss/pull/9208))
|
||||
|
||||
## [3.1.8] - 2022-08-05
|
||||
|
||||
|
|
|
|||
|
|
@ -177,16 +177,12 @@ export default function expandTailwindAtRules(context) {
|
|||
let classCacheCount = context.classCache.size
|
||||
|
||||
env.DEBUG && console.time('Generate rules')
|
||||
let rules = generateRules(candidates, context)
|
||||
generateRules(candidates, context)
|
||||
env.DEBUG && console.timeEnd('Generate rules')
|
||||
|
||||
// We only ever add to the classCache, so if it didn't grow, there is nothing new.
|
||||
env.DEBUG && console.time('Build stylesheet')
|
||||
if (context.stylesheetCache === null || context.classCache.size !== classCacheCount) {
|
||||
for (let rule of rules) {
|
||||
context.ruleCache.add(rule)
|
||||
}
|
||||
|
||||
context.stylesheetCache = buildStylesheet([...context.ruleCache], context)
|
||||
}
|
||||
env.DEBUG && console.timeEnd('Build stylesheet')
|
||||
|
|
|
|||
|
|
@ -649,8 +649,37 @@ function inKeyframes(rule) {
|
|||
return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
|
||||
}
|
||||
|
||||
function getImportantStrategy(important) {
|
||||
if (important === true) {
|
||||
return (rule) => {
|
||||
if (inKeyframes(rule)) {
|
||||
return
|
||||
}
|
||||
|
||||
rule.walkDecls((d) => {
|
||||
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
|
||||
d.important = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof important === 'string') {
|
||||
return (rule) => {
|
||||
if (inKeyframes(rule)) {
|
||||
return
|
||||
}
|
||||
|
||||
rule.selectors = rule.selectors.map((selector) => {
|
||||
return `${important} ${selector}`
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateRules(candidates, context) {
|
||||
let allRules = []
|
||||
let strategy = getImportantStrategy(context.tailwindConfig.important)
|
||||
|
||||
for (let candidate of candidates) {
|
||||
if (context.notClassCache.has(candidate)) {
|
||||
|
|
@ -658,7 +687,11 @@ function generateRules(candidates, context) {
|
|||
}
|
||||
|
||||
if (context.classCache.has(candidate)) {
|
||||
allRules.push(context.classCache.get(candidate))
|
||||
continue
|
||||
}
|
||||
|
||||
if (context.candidateRuleCache.has(candidate)) {
|
||||
allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate)))
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -670,47 +703,27 @@ function generateRules(candidates, context) {
|
|||
}
|
||||
|
||||
context.classCache.set(candidate, matches)
|
||||
allRules.push(matches)
|
||||
}
|
||||
|
||||
// Strategy based on `tailwindConfig.important`
|
||||
let strategy = ((important) => {
|
||||
if (important === true) {
|
||||
return (rule) => {
|
||||
rule.walkDecls((d) => {
|
||||
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
|
||||
d.important = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
let rules = context.candidateRuleCache.get(candidate) ?? new Set()
|
||||
context.candidateRuleCache.set(candidate, rules)
|
||||
|
||||
if (typeof important === 'string') {
|
||||
return (rule) => {
|
||||
rule.selectors = rule.selectors.map((selector) => {
|
||||
return `${important} ${selector}`
|
||||
})
|
||||
}
|
||||
}
|
||||
})(context.tailwindConfig.important)
|
||||
for (const match of matches) {
|
||||
let [{ sort, layer, options }, rule] = match
|
||||
|
||||
return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
|
||||
if (options.respectImportant) {
|
||||
if (strategy) {
|
||||
if (options.respectImportant && strategy) {
|
||||
let container = postcss.root({ nodes: [rule.clone()] })
|
||||
container.walkRules((r) => {
|
||||
if (inKeyframes(r)) {
|
||||
return
|
||||
}
|
||||
|
||||
strategy(r)
|
||||
})
|
||||
container.walkRules(strategy)
|
||||
rule = container.nodes[0]
|
||||
}
|
||||
}
|
||||
|
||||
return [sort | context.layerOrder[layer], rule]
|
||||
})
|
||||
let newEntry = [sort | context.layerOrder[layer], rule]
|
||||
rules.add(newEntry)
|
||||
context.ruleCache.add(newEntry)
|
||||
allRules.push(newEntry)
|
||||
}
|
||||
}
|
||||
|
||||
return allRules
|
||||
}
|
||||
|
||||
function isArbitraryValue(input) {
|
||||
|
|
|
|||
|
|
@ -873,6 +873,7 @@ export function createContext(tailwindConfig, changedContent = [], root = postcs
|
|||
let context = {
|
||||
disposables: [],
|
||||
ruleCache: new Set(),
|
||||
candidateRuleCache: new Map(),
|
||||
classCache: new Map(),
|
||||
applyClassCache: new Map(),
|
||||
notClassCache: new Set(),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import * as sharedState from '../src/lib/sharedState'
|
||||
|
||||
import { run, css } from './util/run'
|
||||
import { run, css, html } from './util/run'
|
||||
|
||||
test('important boolean', () => {
|
||||
let config = {
|
||||
|
|
@ -63,3 +64,74 @@ test('important boolean', () => {
|
|||
expect(result.css).toMatchFormattedCss(expected)
|
||||
})
|
||||
})
|
||||
|
||||
// This is in a describe block so we can use `afterEach` :)
|
||||
describe('duplicate elision', () => {
|
||||
let filePath = path.resolve(__dirname, './important-boolean-duplicates.test.html')
|
||||
|
||||
afterEach(async () => await fs.promises.unlink(filePath))
|
||||
|
||||
test('important rules are not duplicated when rebuilding', async () => {
|
||||
let config = {
|
||||
important: true,
|
||||
content: [filePath],
|
||||
}
|
||||
|
||||
await fs.promises.writeFile(
|
||||
config.content[0],
|
||||
html`
|
||||
<div class="ml-2"></div>
|
||||
<div class="ml-4"></div>
|
||||
`
|
||||
)
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
let result = await run(input, config)
|
||||
let allContexts = Array.from(sharedState.contextMap.values())
|
||||
|
||||
let context = allContexts[allContexts.length - 1]
|
||||
|
||||
let ruleCacheSize1 = context.ruleCache.size
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.ml-2 {
|
||||
margin-left: 0.5rem !important;
|
||||
}
|
||||
.ml-4 {
|
||||
margin-left: 1rem !important;
|
||||
}
|
||||
`)
|
||||
|
||||
await fs.promises.writeFile(
|
||||
config.content[0],
|
||||
html`
|
||||
<div class="ml-2"></div>
|
||||
<div class="ml-6"></div>
|
||||
`
|
||||
)
|
||||
|
||||
result = await run(input, config)
|
||||
|
||||
let ruleCacheSize2 = context.ruleCache.size
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.ml-2 {
|
||||
margin-left: 0.5rem !important;
|
||||
}
|
||||
.ml-4 {
|
||||
margin-left: 1rem !important;
|
||||
}
|
||||
.ml-6 {
|
||||
margin-left: 1.5rem !important;
|
||||
}
|
||||
`)
|
||||
|
||||
// The rule cache was effectively doubling in size previously
|
||||
// because the rule cache was never de-duped
|
||||
// This ensures this behavior doesn't return
|
||||
expect(ruleCacheSize2 - ruleCacheSize1).toBeLessThan(10)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue