mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Optimize rebuilds in long-running processes
This commit is contained in:
parent
d3606b76dc
commit
ef149cfafb
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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('')
|
||||
|
||||
@ -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 <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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,14 +56,19 @@ function findClass(classToApply, classTable, onError) {
|
||||
return match.clone().nodes
|
||||
}
|
||||
|
||||
export default function(config, getProcessedPlugins) {
|
||||
let shadowLookup = null
|
||||
|
||||
export default function(config, getProcessedPlugins, configChanged) {
|
||||
if (flagEnabled(config, 'applyComplexClasses')) {
|
||||
return applyComplexClasses(config, getProcessedPlugins)
|
||||
return applyComplexClasses(config, getProcessedPlugins, configChanged)
|
||||
}
|
||||
|
||||
return function(css) {
|
||||
const classLookup = buildClassTable(css)
|
||||
const shadowLookup = buildShadowTable(getProcessedPlugins().utilities)
|
||||
shadowLookup =
|
||||
configChanged || !shadowLookup
|
||||
? buildShadowTable(getProcessedPlugins().utilities)
|
||||
: shadowLookup
|
||||
|
||||
css.walkRules(rule => {
|
||||
rule.walkAtRules('apply', atRule => {
|
||||
|
||||
@ -16,25 +16,33 @@ import processPlugins from './util/processPlugins'
|
||||
import cloneNodes from './util/cloneNodes'
|
||||
import { issueFlagNotices } from './featureFlags.js'
|
||||
|
||||
import hash from 'object-hash'
|
||||
|
||||
let flagsIssued = null
|
||||
let previousConfig = null
|
||||
let processedPlugins = null
|
||||
let getProcessedPlugins = null
|
||||
|
||||
export default function(getConfig) {
|
||||
return function(css) {
|
||||
const config = getConfig()
|
||||
const configChanged = hash(previousConfig) !== hash(config)
|
||||
previousConfig = config
|
||||
|
||||
if (!flagsIssued || !_.isEqual(flagsIssued, _.pick(config, ['future', 'experimental']))) {
|
||||
flagsIssued = _.pick(config, ['future', 'experimental'])
|
||||
issueFlagNotices(config)
|
||||
}
|
||||
|
||||
const processedPlugins = processPlugins([...corePlugins(config), ...config.plugins], config)
|
||||
|
||||
const getProcessedPlugins = function() {
|
||||
return {
|
||||
...processedPlugins,
|
||||
base: cloneNodes(processedPlugins.base),
|
||||
components: cloneNodes(processedPlugins.components),
|
||||
utilities: cloneNodes(processedPlugins.utilities),
|
||||
if (configChanged) {
|
||||
processedPlugins = processPlugins([...corePlugins(config), ...config.plugins], config)
|
||||
getProcessedPlugins = function() {
|
||||
return {
|
||||
...processedPlugins,
|
||||
base: cloneNodes(processedPlugins.base),
|
||||
components: cloneNodes(processedPlugins.components),
|
||||
utilities: cloneNodes(processedPlugins.utilities),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +53,7 @@ export default function(getConfig) {
|
||||
substituteResponsiveAtRules(config),
|
||||
convertLayerAtRulesToControlComments(config),
|
||||
substituteScreenAtRules(config),
|
||||
substituteClassApplyAtRules(config, getProcessedPlugins),
|
||||
substituteClassApplyAtRules(config, getProcessedPlugins, configChanged),
|
||||
applyImportantConfiguration(config),
|
||||
purgeUnusedStyles(config),
|
||||
]).process(css, { from: _.get(css, 'source.input.file') })
|
||||
|
||||
@ -4122,6 +4122,11 @@ object-copy@^0.1.0:
|
||||
define-property "^0.2.5"
|
||||
kind-of "^3.0.3"
|
||||
|
||||
object-hash@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea"
|
||||
integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==
|
||||
|
||||
object-keys@^1.0.11, object-keys@^1.0.12:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user