Optimize rebuilds in long-running processes

This commit is contained in:
Adam Wathan 2020-08-19 09:33:03 -04:00
parent d3606b76dc
commit ef149cfafb
8 changed files with 122 additions and 139 deletions

View file

@ -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('')

View file

@ -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)
})
}
}

View file

@ -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 => {

View file

@ -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') })