From ef149cfafbc5882eb78e568db03d0fe28dd39b14 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Wed, 19 Aug 2020 09:33:03 -0400 Subject: [PATCH] Optimize rebuilds in long-running processes --- __tests__/applyAtRule.test.js | 89 +++++++------------------- __tests__/applyComplexClasses.test.js | 41 ++---------- package.json | 1 + src/featureFlags.js | 4 ++ src/flagged/applyComplexClasses.js | 84 ++++++++++++++++-------- src/lib/substituteClassApplyAtRules.js | 11 +++- src/processTailwindFeatures.js | 26 +++++--- yarn.lock | 5 ++ 8 files changed, 122 insertions(+), 139 deletions(-) diff --git a/__tests__/applyAtRule.test.js b/__tests__/applyAtRule.test.js index 18db716a8..92d4711a9 100644 --- a/__tests__/applyAtRule.test.js +++ b/__tests__/applyAtRule.test.js @@ -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' +import tailwind from '../src/index' -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, - }) +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) }) }) @@ -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) }) }) @@ -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) }) }) @@ -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) }) }) @@ -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) }) }) @@ -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) }) }) @@ -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) }) }) @@ -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) }) }) @@ -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) }) }) @@ -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) }) }) diff --git a/__tests__/applyComplexClasses.test.js b/__tests__/applyComplexClasses.test.js index c4dd5ce48..f2de66800 100644 --- a/__tests__/applyComplexClasses.test.js +++ b/__tests__/applyComplexClasses.test.js @@ -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' +import tailwind from '../src/index' -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), - } -} - -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) }) }) diff --git a/package.json b/package.json index 894734f06..31396acab 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/featureFlags.js b/src/featureFlags.js index 05a8ce335..b5d4da489 100644 --- a/src/featureFlags.js +++ b/src/featureFlags.js @@ -53,6 +53,10 @@ function futureFlagsAvailable(config) { } export function issueFlagNotices(config) { + if (process.env.JEST_WORKER_ID !== undefined) { + return + } + const log = { info(messages) { console.log('') diff --git a/src/flagged/applyComplexClasses.js b/src/flagged/applyComplexClasses.js index fe1ba1217..820f2422f 100644 --- a/src/flagged/applyComplexClasses.js +++ b/src/flagged/applyComplexClasses.js @@ -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) @@ -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 = [] @@ -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 => { @@ -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')) { @@ -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