Skip to content

Commit

Permalink
Merge pull request #2195 from tailwindlabs/reuse-lookup
Browse files Browse the repository at this point in the history
Optimize rebuilds in long-running processes
  • Loading branch information
adamwathan authored Aug 19, 2020
2 parents d3606b7 + 2d090fe commit 82c37a9
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 183 deletions.
91 changes: 25 additions & 66 deletions __tests__/applyAtRule.test.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,15 @@
import postcss from 'postcss'
import substituteClassApplyAtRules from '../src/lib/substituteClassApplyAtRules'
import processPlugins from '../src/util/processPlugins'
import resolveConfig from '../src/util/resolveConfig'
import corePlugins from '../src/corePlugins'
import defaultConfig from '../stubs/defaultConfig.stub.js'

const resolvedDefaultConfig = resolveConfig([defaultConfig])

const { utilities: defaultUtilities } = processPlugins(
corePlugins(resolvedDefaultConfig),
resolvedDefaultConfig
)

function run(input, config = resolvedDefaultConfig, utilities = defaultUtilities) {
return postcss([
substituteClassApplyAtRules(config, () => ({
utilities,
})),
]).process(input, {
from: undefined,
})
import tailwind from '../src/index'

function run(input, config = {}) {
return postcss([tailwind({ ...config })]).process(input, { from: undefined })
}

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

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

return run(input).then(result => {
expect(result.css).toEqual(expected)
expect(result.css).toMatchCss(expected)
expect(result.warnings().length).toBe(0)
})
})
Expand All @@ -60,7 +43,7 @@ test('it removes important from applied classes by default', () => {
`

return run(input).then(result => {
expect(result.css).toEqual(expected)
expect(result.css).toMatchCss(expected)
expect(result.warnings().length).toBe(0)
})
})
Expand All @@ -77,7 +60,7 @@ test('applied rules can be made !important', () => {
`

return run(input).then(result => {
expect(result.css).toEqual(expected)
expect(result.css).toMatchCss(expected)
expect(result.warnings().length).toBe(0)
})
})
Expand All @@ -103,7 +86,7 @@ test('cssnext custom property sets are preserved', () => {
`

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

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

const config = resolveConfig([
{
...defaultConfig,
prefix: 'tw-',
},
])

return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
expect(result.css).toEqual(expected)
return run(input, { prefix: 'tw-' }).then(result => {
expect(result.css).toMatchCss(expected)
expect(result.warnings().length).toBe(0)
})
})
Expand All @@ -232,17 +208,12 @@ test('you can apply utility classes without using the given prefix when using a
.foo { margin-top: 1rem; margin-bottom: 1rem; }
`

const config = resolveConfig([
{
...defaultConfig,
prefix: () => {
return 'tw-'
},
return run(input, {
prefix: () => {
return 'tw-'
},
])

return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
expect(result.css).toEqual(expected)
}).then(result => {
expect(result.css).toMatchCss(expected)
expect(result.warnings().length).toBe(0)
})
})
Expand All @@ -256,15 +227,8 @@ test('you can apply utility classes without specificity prefix even if important
.foo { margin-top: 2rem; margin-bottom: 2rem; }
`

const config = resolveConfig([
{
...defaultConfig,
important: '#app',
},
])

return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
expect(result.css).toEqual(expected)
return run(input, { important: '#app' }).then(result => {
expect(result.css).toMatchCss(expected)
expect(result.warnings().length).toBe(0)
})
})
Expand All @@ -278,16 +242,11 @@ test('you can apply utility classes without using the given prefix even if impor
.foo { margin-top: 1rem; margin-bottom: 1rem; }
`

const config = resolveConfig([
{
...defaultConfig,
prefix: 'tw-',
important: '#app',
},
])

return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
expect(result.css).toEqual(expected)
return run(input, {
prefix: 'tw-',
important: '#app',
}).then(result => {
expect(result.css).toMatchCss(expected)
expect(result.warnings().length).toBe(0)
})
})
41 changes: 6 additions & 35 deletions __tests__/applyComplexClasses.test.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,19 @@
import postcss from 'postcss'
import substituteClassApplyAtRules from '../src/lib/substituteClassApplyAtRules'
import processPlugins from '../src/util/processPlugins'
import resolveConfig from '../src/util/resolveConfig'
import corePlugins from '../src/corePlugins'
import defaultConfig from '../stubs/defaultConfig.stub.js'
import cloneNodes from '../src/util/cloneNodes'

const resolvedDefaultConfig = resolveConfig([defaultConfig])

const defaultProcessedPlugins = processPlugins(
[...corePlugins(resolvedDefaultConfig), ...resolvedDefaultConfig.plugins],
resolvedDefaultConfig
)

const defaultGetProcessedPlugins = function() {
return {
...defaultProcessedPlugins,
base: cloneNodes(defaultProcessedPlugins.base),
components: cloneNodes(defaultProcessedPlugins.components),
utilities: cloneNodes(defaultProcessedPlugins.utilities),
}
}
import tailwind from '../src/index'

function run(
input,
config = resolvedDefaultConfig,
getProcessedPlugins = () =>
config === resolvedDefaultConfig
? defaultGetProcessedPlugins()
: processPlugins(corePlugins(config), config)
) {
config.experimental = {
applyComplexClasses: true,
}
return postcss([substituteClassApplyAtRules(config, getProcessedPlugins)]).process(input, {
from: undefined,
})
function run(input, config = {}) {
return postcss([
tailwind({ experimental: { applyComplexClasses: true }, ...config }),
]).process(input, { from: undefined })
}

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

return run('.a { color: red; } .b { @apply a; }').then(result => {
expect(result.css).toEqual(output)
expect(result.css).toMatchCss(output)
expect(result.warnings().length).toBe(0)
})
})
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"lodash": "^4.17.15",
"node-emoji": "^1.8.1",
"normalize.css": "^8.0.1",
"object-hash": "^2.0.3",
"postcss": "^7.0.11",
"postcss-functions": "^3.0.0",
"postcss-js": "^2.0.0",
Expand Down
22 changes: 3 additions & 19 deletions src/featureFlags.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _ from 'lodash'
import chalk from 'chalk'
import log from './util/log'

const featureFlags = {
future: ['removeDeprecatedGapUtilities'],
Expand Down Expand Up @@ -53,25 +54,8 @@ function futureFlagsAvailable(config) {
}

export function issueFlagNotices(config) {
const log = {
info(messages) {
console.log('')
messages.forEach(message => {
console.log(chalk.bold.cyan('info'), '-', message)
})
},
warn(messages) {
console.log('')
messages.forEach(message => {
console.log(chalk.bold.yellow('warn'), '-', message)
})
},
risk(messages) {
console.log('')
messages.forEach(message => {
console.log(chalk.bold.magenta('risk'), '-', message)
})
},
if (process.env.JEST_WORKER_ID !== undefined) {
return
}

if (futureFlagsEnabled(config).length > 0) {
Expand Down
84 changes: 57 additions & 27 deletions src/flagged/applyComplexClasses.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,30 @@ const cloneRuleWithParent = useMemo(
rule => rule
)

function buildUtilityMap(css) {
function buildUtilityMap(css, lookupTree) {
let index = 0
const utilityMap = {}

lookupTree.walkRules(rule => {
const utilityNames = extractUtilityNames(rule.selector)

utilityNames.forEach((utilityName, i) => {
if (utilityMap[utilityName] === undefined) {
utilityMap[utilityName] = []
}

utilityMap[utilityName].push({
index,
utilityName,
classPosition: i,
get rule() {
return cloneRuleWithParent(rule)
},
})
index++
})
})

css.walkRules(rule => {
const utilityNames = extractUtilityNames(rule.selector)

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

function makeExtractUtilityRules(css, config) {
const utilityMap = buildUtilityMap(css)
function makeExtractUtilityRules(css, lookupTree, config) {
const utilityMap = buildUtilityMap(css, lookupTree)

return function extractUtilityRules(utilityNames, rule) {
const combined = []
Expand Down Expand Up @@ -182,7 +202,7 @@ function makeExtractUtilityRules(css, config) {
}

function processApplyAtRules(css, lookupTree, config) {
const extractUtilityRules = makeExtractUtilityRules(lookupTree, config)
const extractUtilityRules = makeExtractUtilityRules(css, lookupTree, config)

do {
css.walkRules(rule => {
Expand Down Expand Up @@ -259,7 +279,9 @@ function processApplyAtRules(css, lookupTree, config) {
return css
}

export default function applyComplexClasses(config, getProcessedPlugins) {
let defaultTailwindTree = null

export default function applyComplexClasses(config, getProcessedPlugins, configChanged) {
return function(css) {
// We can stop already when we don't have any @apply rules. Vue users: you're welcome!
if (!hasAtRule(css, 'apply')) {
Expand All @@ -268,31 +290,39 @@ export default function applyComplexClasses(config, getProcessedPlugins) {

// Tree already contains @tailwind rules, don't prepend default Tailwind tree
if (hasAtRule(css, 'tailwind')) {
return processApplyAtRules(css, css, config)
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 <style> blocks in Vue components.
return postcss([
substituteTailwindAtRules(config, getProcessedPlugins()),
evaluateTailwindFunctions(config),
substituteVariantsAtRules(config, getProcessedPlugins()),
substituteResponsiveAtRules(config),
convertLayerAtRulesToControlComments(config),
substituteScreenAtRules(config),
])
.process(
`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
{ from: undefined }
)
.then(result => {
// Prepend Tailwind's generated classes to the tree so they are available for `@apply`
const lookupTree = _.tap(result.root, tree => tree.append(css.clone()))
return processApplyAtRules(css, lookupTree, config)
})
const generateLookupTree =
configChanged || defaultTailwindTree === null
? () => {
return postcss([
substituteTailwindAtRules(config, getProcessedPlugins()),
evaluateTailwindFunctions(config),
substituteVariantsAtRules(config, getProcessedPlugins()),
substituteResponsiveAtRules(config),
convertLayerAtRulesToControlComments(config),
substituteScreenAtRules(config),
])
.process(
`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
{ from: undefined }
)
.then(result => {
defaultTailwindTree = result
return defaultTailwindTree
})
}
: () => Promise.resolve(defaultTailwindTree)

return generateLookupTree().then(result => {
return processApplyAtRules(css, result.root, config)
})
}
}
Loading

0 comments on commit 82c37a9

Please sign in to comment.