Skip to content

Commit 82c37a9

Browse files
authored
Merge pull request #2195 from tailwindlabs/reuse-lookup
Optimize rebuilds in long-running processes
2 parents d3606b7 + 2d090fe commit 82c37a9

10 files changed

+155
-183
lines changed

__tests__/applyAtRule.test.js

Lines changed: 25 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,15 @@
11
import postcss from 'postcss'
2-
import substituteClassApplyAtRules from '../src/lib/substituteClassApplyAtRules'
3-
import processPlugins from '../src/util/processPlugins'
4-
import resolveConfig from '../src/util/resolveConfig'
5-
import corePlugins from '../src/corePlugins'
6-
import defaultConfig from '../stubs/defaultConfig.stub.js'
7-
8-
const resolvedDefaultConfig = resolveConfig([defaultConfig])
9-
10-
const { utilities: defaultUtilities } = processPlugins(
11-
corePlugins(resolvedDefaultConfig),
12-
resolvedDefaultConfig
13-
)
14-
15-
function run(input, config = resolvedDefaultConfig, utilities = defaultUtilities) {
16-
return postcss([
17-
substituteClassApplyAtRules(config, () => ({
18-
utilities,
19-
})),
20-
]).process(input, {
21-
from: undefined,
22-
})
2+
import tailwind from '../src/index'
3+
4+
function run(input, config = {}) {
5+
return postcss([tailwind({ ...config })]).process(input, { from: undefined })
236
}
247

25-
test("it copies a class's declarations into itself", () => {
8+
test('it copies the declarations from a class into itself', () => {
269
const output = '.a { color: red; } .b { color: red; }'
2710

2811
return run('.a { color: red; } .b { @apply .a; }').then(result => {
29-
expect(result.css).toEqual(output)
12+
expect(result.css).toMatchCss(output)
3013
expect(result.warnings().length).toBe(0)
3114
})
3215
})
@@ -43,7 +26,7 @@ test('selectors with invalid characters do not need to be manually escaped', ()
4326
`
4427

4528
return run(input).then(result => {
46-
expect(result.css).toEqual(expected)
29+
expect(result.css).toMatchCss(expected)
4730
expect(result.warnings().length).toBe(0)
4831
})
4932
})
@@ -60,7 +43,7 @@ test('it removes important from applied classes by default', () => {
6043
`
6144

6245
return run(input).then(result => {
63-
expect(result.css).toEqual(expected)
46+
expect(result.css).toMatchCss(expected)
6447
expect(result.warnings().length).toBe(0)
6548
})
6649
})
@@ -77,7 +60,7 @@ test('applied rules can be made !important', () => {
7760
`
7861

7962
return run(input).then(result => {
80-
expect(result.css).toEqual(expected)
63+
expect(result.css).toMatchCss(expected)
8164
expect(result.warnings().length).toBe(0)
8265
})
8366
})
@@ -103,7 +86,7 @@ test('cssnext custom property sets are preserved', () => {
10386
`
10487

10588
return run(input).then(result => {
106-
expect(result.css).toEqual(expected)
89+
expect(result.css).toMatchCss(expected)
10790
expect(result.warnings().length).toBe(0)
10891
})
10992
})
@@ -196,7 +179,7 @@ test('you can apply utility classes that do not actually exist as long as they w
196179
`
197180

198181
return run(input).then(result => {
199-
expect(result.css).toEqual(expected)
182+
expect(result.css).toMatchCss(expected)
200183
expect(result.warnings().length).toBe(0)
201184
})
202185
})
@@ -210,15 +193,8 @@ test('you can apply utility classes without using the given prefix', () => {
210193
.foo { margin-top: 1rem; margin-bottom: 1rem; }
211194
`
212195

213-
const config = resolveConfig([
214-
{
215-
...defaultConfig,
216-
prefix: 'tw-',
217-
},
218-
])
219-
220-
return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
221-
expect(result.css).toEqual(expected)
196+
return run(input, { prefix: 'tw-' }).then(result => {
197+
expect(result.css).toMatchCss(expected)
222198
expect(result.warnings().length).toBe(0)
223199
})
224200
})
@@ -232,17 +208,12 @@ test('you can apply utility classes without using the given prefix when using a
232208
.foo { margin-top: 1rem; margin-bottom: 1rem; }
233209
`
234210

235-
const config = resolveConfig([
236-
{
237-
...defaultConfig,
238-
prefix: () => {
239-
return 'tw-'
240-
},
211+
return run(input, {
212+
prefix: () => {
213+
return 'tw-'
241214
},
242-
])
243-
244-
return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
245-
expect(result.css).toEqual(expected)
215+
}).then(result => {
216+
expect(result.css).toMatchCss(expected)
246217
expect(result.warnings().length).toBe(0)
247218
})
248219
})
@@ -256,15 +227,8 @@ test('you can apply utility classes without specificity prefix even if important
256227
.foo { margin-top: 2rem; margin-bottom: 2rem; }
257228
`
258229

259-
const config = resolveConfig([
260-
{
261-
...defaultConfig,
262-
important: '#app',
263-
},
264-
])
265-
266-
return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
267-
expect(result.css).toEqual(expected)
230+
return run(input, { important: '#app' }).then(result => {
231+
expect(result.css).toMatchCss(expected)
268232
expect(result.warnings().length).toBe(0)
269233
})
270234
})
@@ -278,16 +242,11 @@ test('you can apply utility classes without using the given prefix even if impor
278242
.foo { margin-top: 1rem; margin-bottom: 1rem; }
279243
`
280244

281-
const config = resolveConfig([
282-
{
283-
...defaultConfig,
284-
prefix: 'tw-',
285-
important: '#app',
286-
},
287-
])
288-
289-
return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
290-
expect(result.css).toEqual(expected)
245+
return run(input, {
246+
prefix: 'tw-',
247+
important: '#app',
248+
}).then(result => {
249+
expect(result.css).toMatchCss(expected)
291250
expect(result.warnings().length).toBe(0)
292251
})
293252
})

__tests__/applyComplexClasses.test.js

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,19 @@
11
import postcss from 'postcss'
2-
import substituteClassApplyAtRules from '../src/lib/substituteClassApplyAtRules'
3-
import processPlugins from '../src/util/processPlugins'
42
import resolveConfig from '../src/util/resolveConfig'
5-
import corePlugins from '../src/corePlugins'
63
import defaultConfig from '../stubs/defaultConfig.stub.js'
7-
import cloneNodes from '../src/util/cloneNodes'
8-
9-
const resolvedDefaultConfig = resolveConfig([defaultConfig])
10-
11-
const defaultProcessedPlugins = processPlugins(
12-
[...corePlugins(resolvedDefaultConfig), ...resolvedDefaultConfig.plugins],
13-
resolvedDefaultConfig
14-
)
15-
16-
const defaultGetProcessedPlugins = function() {
17-
return {
18-
...defaultProcessedPlugins,
19-
base: cloneNodes(defaultProcessedPlugins.base),
20-
components: cloneNodes(defaultProcessedPlugins.components),
21-
utilities: cloneNodes(defaultProcessedPlugins.utilities),
22-
}
23-
}
4+
import tailwind from '../src/index'
245

25-
function run(
26-
input,
27-
config = resolvedDefaultConfig,
28-
getProcessedPlugins = () =>
29-
config === resolvedDefaultConfig
30-
? defaultGetProcessedPlugins()
31-
: processPlugins(corePlugins(config), config)
32-
) {
33-
config.experimental = {
34-
applyComplexClasses: true,
35-
}
36-
return postcss([substituteClassApplyAtRules(config, getProcessedPlugins)]).process(input, {
37-
from: undefined,
38-
})
6+
function run(input, config = {}) {
7+
return postcss([
8+
tailwind({ experimental: { applyComplexClasses: true }, ...config }),
9+
]).process(input, { from: undefined })
3910
}
4011

4112
test('it copies class declarations into itself', () => {
4213
const output = '.a { color: red; } .b { color: red; }'
4314

4415
return run('.a { color: red; } .b { @apply a; }').then(result => {
45-
expect(result.css).toEqual(output)
16+
expect(result.css).toMatchCss(output)
4617
expect(result.warnings().length).toBe(0)
4718
})
4819
})

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"lodash": "^4.17.15",
6161
"node-emoji": "^1.8.1",
6262
"normalize.css": "^8.0.1",
63+
"object-hash": "^2.0.3",
6364
"postcss": "^7.0.11",
6465
"postcss-functions": "^3.0.0",
6566
"postcss-js": "^2.0.0",

src/featureFlags.js

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import _ from 'lodash'
22
import chalk from 'chalk'
3+
import log from './util/log'
34

45
const featureFlags = {
56
future: ['removeDeprecatedGapUtilities'],
@@ -53,25 +54,8 @@ function futureFlagsAvailable(config) {
5354
}
5455

5556
export function issueFlagNotices(config) {
56-
const log = {
57-
info(messages) {
58-
console.log('')
59-
messages.forEach(message => {
60-
console.log(chalk.bold.cyan('info'), '-', message)
61-
})
62-
},
63-
warn(messages) {
64-
console.log('')
65-
messages.forEach(message => {
66-
console.log(chalk.bold.yellow('warn'), '-', message)
67-
})
68-
},
69-
risk(messages) {
70-
console.log('')
71-
messages.forEach(message => {
72-
console.log(chalk.bold.magenta('risk'), '-', message)
73-
})
74-
},
57+
if (process.env.JEST_WORKER_ID !== undefined) {
58+
return
7559
}
7660

7761
if (futureFlagsEnabled(config).length > 0) {

src/flagged/applyComplexClasses.js

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,30 @@ const cloneRuleWithParent = useMemo(
8989
rule => rule
9090
)
9191

92-
function buildUtilityMap(css) {
92+
function buildUtilityMap(css, lookupTree) {
9393
let index = 0
9494
const utilityMap = {}
9595

96+
lookupTree.walkRules(rule => {
97+
const utilityNames = extractUtilityNames(rule.selector)
98+
99+
utilityNames.forEach((utilityName, i) => {
100+
if (utilityMap[utilityName] === undefined) {
101+
utilityMap[utilityName] = []
102+
}
103+
104+
utilityMap[utilityName].push({
105+
index,
106+
utilityName,
107+
classPosition: i,
108+
get rule() {
109+
return cloneRuleWithParent(rule)
110+
},
111+
})
112+
index++
113+
})
114+
})
115+
96116
css.walkRules(rule => {
97117
const utilityNames = extractUtilityNames(rule.selector)
98118

@@ -151,8 +171,8 @@ function mergeAdjacentRules(initialRule, rulesToInsert) {
151171
return rulesToInsert.filter(r => r.nodes.length > 0)
152172
}
153173

154-
function makeExtractUtilityRules(css, config) {
155-
const utilityMap = buildUtilityMap(css)
174+
function makeExtractUtilityRules(css, lookupTree, config) {
175+
const utilityMap = buildUtilityMap(css, lookupTree)
156176

157177
return function extractUtilityRules(utilityNames, rule) {
158178
const combined = []
@@ -182,7 +202,7 @@ function makeExtractUtilityRules(css, config) {
182202
}
183203

184204
function processApplyAtRules(css, lookupTree, config) {
185-
const extractUtilityRules = makeExtractUtilityRules(lookupTree, config)
205+
const extractUtilityRules = makeExtractUtilityRules(css, lookupTree, config)
186206

187207
do {
188208
css.walkRules(rule => {
@@ -259,7 +279,9 @@ function processApplyAtRules(css, lookupTree, config) {
259279
return css
260280
}
261281

262-
export default function applyComplexClasses(config, getProcessedPlugins) {
282+
let defaultTailwindTree = null
283+
284+
export default function applyComplexClasses(config, getProcessedPlugins, configChanged) {
263285
return function(css) {
264286
// We can stop already when we don't have any @apply rules. Vue users: you're welcome!
265287
if (!hasAtRule(css, 'apply')) {
@@ -268,31 +290,39 @@ export default function applyComplexClasses(config, getProcessedPlugins) {
268290

269291
// Tree already contains @tailwind rules, don't prepend default Tailwind tree
270292
if (hasAtRule(css, 'tailwind')) {
271-
return processApplyAtRules(css, css, config)
293+
return processApplyAtRules(css, postcss.root(), config)
272294
}
273295

274296
// Tree contains no @tailwind rules, so generate all of Tailwind's styles and
275297
// prepend them to the user's CSS. Important for <style> blocks in Vue components.
276-
return postcss([
277-
substituteTailwindAtRules(config, getProcessedPlugins()),
278-
evaluateTailwindFunctions(config),
279-
substituteVariantsAtRules(config, getProcessedPlugins()),
280-
substituteResponsiveAtRules(config),
281-
convertLayerAtRulesToControlComments(config),
282-
substituteScreenAtRules(config),
283-
])
284-
.process(
285-
`
286-
@tailwind base;
287-
@tailwind components;
288-
@tailwind utilities;
289-
`,
290-
{ from: undefined }
291-
)
292-
.then(result => {
293-
// Prepend Tailwind's generated classes to the tree so they are available for `@apply`
294-
const lookupTree = _.tap(result.root, tree => tree.append(css.clone()))
295-
return processApplyAtRules(css, lookupTree, config)
296-
})
298+
const generateLookupTree =
299+
configChanged || defaultTailwindTree === null
300+
? () => {
301+
return postcss([
302+
substituteTailwindAtRules(config, getProcessedPlugins()),
303+
evaluateTailwindFunctions(config),
304+
substituteVariantsAtRules(config, getProcessedPlugins()),
305+
substituteResponsiveAtRules(config),
306+
convertLayerAtRulesToControlComments(config),
307+
substituteScreenAtRules(config),
308+
])
309+
.process(
310+
`
311+
@tailwind base;
312+
@tailwind components;
313+
@tailwind utilities;
314+
`,
315+
{ from: undefined }
316+
)
317+
.then(result => {
318+
defaultTailwindTree = result
319+
return defaultTailwindTree
320+
})
321+
}
322+
: () => Promise.resolve(defaultTailwindTree)
323+
324+
return generateLookupTree().then(result => {
325+
return processApplyAtRules(css, result.root, config)
326+
})
297327
}
298328
}

0 commit comments

Comments
 (0)