Remove AOT (#5340)
* make `jit` mode the default when no mode is specified
* unify JIT and AOT codepaths
* ensure `Object.entries` on undefined doesn't break
It could be that sometimes you don't have values in your config (e.g.: `presets: []`), this in turn will break some plugins where we assume we have a value.
* drop AOT specific tests
These tests are all covered by JIT mode already and were AOT specific.
* simplify tests, and add a few
Some of the tests were written for AOT specifically, some were missing. We also updated the way we write those tests, essentially making Tailwind a blackbox, by testing against the final output.
Now that JIT mode is the default, this is super fast because we only generate what is used, instead of partially testing in a 3MB file or building it all, then purging.
* add some todo's to make sure we warn in a few cases
* make `darkMode: 'media'`, the default
This also includes moving dark mode tests to its own dedicated file.
* remove PostCSS 7 compat mode
* update CLI to be JIT-first
* fix integration tests
This is not a _real_ fix, but it does solve the broken test for now.
* warn when using @responsive or @variants
* remove the JIT preview warning
* remove AOT-only code paths
* remove all `mode: 'jit'` blocks
Also remove `variants: {}` since they are not useful in `JIT` mode
anymore.
* drop unused dependencies
* rename `purge` to `content`
* remove static CDN builds
* mark `--purge` as deprecated in the CLI
This will still work, but a warning will be printed and it won't show up
in the `--help` output.
* cleanup nesting plugin
We don't have to duplicate it anymore since there is no PostCSS 7
version anymore.
* make sure integration tests run in band
* cleanup folder structure
* make sure nesting folder is available
* simplify resolving of purge/content information
This commit is contained in:
parent
911e755056
commit
691ed02f63
141 changed files with 1991 additions and 10609 deletions
319
src/lib/generateRules.js
Normal file
319
src/lib/generateRules.js
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
import postcss from 'postcss'
|
||||
import selectorParser from 'postcss-selector-parser'
|
||||
import parseObjectStyles from '../util/parseObjectStyles'
|
||||
import isPlainObject from '../util/isPlainObject'
|
||||
import prefixSelector from '../util/prefixSelector'
|
||||
import { updateAllClasses } from '../util/pluginUtils'
|
||||
|
||||
let classNameParser = selectorParser((selectors) => {
|
||||
return selectors.first.filter(({ type }) => type === 'class').pop().value
|
||||
})
|
||||
|
||||
function getClassNameFromSelector(selector) {
|
||||
return classNameParser.transformSync(selector)
|
||||
}
|
||||
|
||||
// Generate match permutations for a class candidate, like:
|
||||
// ['ring-offset-blue', '100']
|
||||
// ['ring-offset', 'blue-100']
|
||||
// ['ring', 'offset-blue-100']
|
||||
// Example with dynamic classes:
|
||||
// ['grid-cols', '[[linename],1fr,auto]']
|
||||
// ['grid', 'cols-[[linename],1fr,auto]']
|
||||
function* candidatePermutations(candidate, lastIndex = Infinity) {
|
||||
if (lastIndex < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let dashIdx
|
||||
|
||||
if (lastIndex === Infinity && candidate.endsWith(']')) {
|
||||
let bracketIdx = candidate.indexOf('[')
|
||||
|
||||
// If character before `[` isn't a dash or a slash, this isn't a dynamic class
|
||||
// eg. string[]
|
||||
dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1
|
||||
} else {
|
||||
dashIdx = candidate.lastIndexOf('-', lastIndex)
|
||||
}
|
||||
|
||||
if (dashIdx < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let prefix = candidate.slice(0, dashIdx)
|
||||
let modifier = candidate.slice(dashIdx + 1)
|
||||
|
||||
yield [prefix, modifier]
|
||||
|
||||
yield* candidatePermutations(candidate, dashIdx - 1)
|
||||
}
|
||||
|
||||
function applyPrefix(matches, context) {
|
||||
if (matches.length === 0 || context.tailwindConfig.prefix === '') {
|
||||
return matches
|
||||
}
|
||||
|
||||
for (let match of matches) {
|
||||
let [meta] = match
|
||||
if (meta.options.respectPrefix) {
|
||||
let container = postcss.root({ nodes: [match[1].clone()] })
|
||||
container.walkRules((r) => {
|
||||
r.selector = prefixSelector(context.tailwindConfig.prefix, r.selector)
|
||||
})
|
||||
match[1] = container.nodes[0]
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
function applyImportant(matches) {
|
||||
if (matches.length === 0) {
|
||||
return matches
|
||||
}
|
||||
let result = []
|
||||
|
||||
for (let [meta, rule] of matches) {
|
||||
let container = postcss.root({ nodes: [rule.clone()] })
|
||||
container.walkRules((r) => {
|
||||
r.selector = updateAllClasses(r.selector, (className) => {
|
||||
return `!${className}`
|
||||
})
|
||||
r.walkDecls((d) => (d.important = true))
|
||||
})
|
||||
result.push([{ ...meta, important: true }, container.nodes[0]])
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Takes a list of rule tuples and applies a variant like `hover`, sm`,
|
||||
// whatever to it. We used to do some extra caching here to avoid generating
|
||||
// a variant of the same rule more than once, but this was never hit because
|
||||
// we cache at the entire selector level further up the tree.
|
||||
//
|
||||
// Technically you can get a cache hit if you have `hover:focus:text-center`
|
||||
// and `focus:hover:text-center` in the same project, but it doesn't feel
|
||||
// worth the complexity for that case.
|
||||
|
||||
function applyVariant(variant, matches, context) {
|
||||
if (matches.length === 0) {
|
||||
return matches
|
||||
}
|
||||
|
||||
if (context.variantMap.has(variant)) {
|
||||
let variantFunctionTuples = context.variantMap.get(variant)
|
||||
let result = []
|
||||
|
||||
for (let [meta, rule] of matches) {
|
||||
if (meta.options.respectVariants === false) {
|
||||
result.push([meta, rule])
|
||||
continue
|
||||
}
|
||||
|
||||
let container = postcss.root({ nodes: [rule.clone()] })
|
||||
|
||||
for (let [variantSort, variantFunction] of variantFunctionTuples) {
|
||||
let clone = container.clone()
|
||||
function modifySelectors(modifierFunction) {
|
||||
clone.each((rule) => {
|
||||
if (rule.type !== 'rule') {
|
||||
return
|
||||
}
|
||||
|
||||
rule.selectors = rule.selectors.map((selector) => {
|
||||
return modifierFunction({
|
||||
get className() {
|
||||
return getClassNameFromSelector(selector)
|
||||
},
|
||||
selector,
|
||||
})
|
||||
})
|
||||
})
|
||||
return clone
|
||||
}
|
||||
|
||||
let ruleWithVariant = variantFunction({
|
||||
container: clone,
|
||||
separator: context.tailwindConfig.separator,
|
||||
modifySelectors,
|
||||
})
|
||||
|
||||
if (ruleWithVariant === null) {
|
||||
continue
|
||||
}
|
||||
|
||||
let withOffset = [{ ...meta, sort: variantSort | meta.sort }, clone.nodes[0]]
|
||||
result.push(withOffset)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function parseRules(rule, cache, options = {}) {
|
||||
// PostCSS node
|
||||
if (!isPlainObject(rule) && !Array.isArray(rule)) {
|
||||
return [[rule], options]
|
||||
}
|
||||
|
||||
// Tuple
|
||||
if (Array.isArray(rule)) {
|
||||
return parseRules(rule[0], cache, rule[1])
|
||||
}
|
||||
|
||||
// Simple object
|
||||
if (!cache.has(rule)) {
|
||||
cache.set(rule, parseObjectStyles(rule))
|
||||
}
|
||||
|
||||
return [cache.get(rule), options]
|
||||
}
|
||||
|
||||
function* resolveMatchedPlugins(classCandidate, context) {
|
||||
if (context.candidateRuleMap.has(classCandidate)) {
|
||||
yield [context.candidateRuleMap.get(classCandidate), 'DEFAULT']
|
||||
}
|
||||
|
||||
let candidatePrefix = classCandidate
|
||||
let negative = false
|
||||
|
||||
const twConfigPrefix = context.tailwindConfig.prefix || ''
|
||||
const twConfigPrefixLen = twConfigPrefix.length
|
||||
if (candidatePrefix[twConfigPrefixLen] === '-') {
|
||||
negative = true
|
||||
candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
|
||||
}
|
||||
|
||||
for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) {
|
||||
if (context.candidateRuleMap.has(prefix)) {
|
||||
yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function splitWithSeparator(input, separator) {
|
||||
return input.split(new RegExp(`\\${separator}(?![^[]*\\])`, 'g'))
|
||||
}
|
||||
|
||||
function* resolveMatches(candidate, context) {
|
||||
let separator = context.tailwindConfig.separator
|
||||
let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse()
|
||||
let important = false
|
||||
|
||||
if (classCandidate.startsWith('!')) {
|
||||
important = true
|
||||
classCandidate = classCandidate.slice(1)
|
||||
}
|
||||
|
||||
// TODO: Reintroduce this in ways that doesn't break on false positives
|
||||
// function sortAgainst(toSort, against) {
|
||||
// return toSort.slice().sort((a, z) => {
|
||||
// return bigSign(against.get(a)[0] - against.get(z)[0])
|
||||
// })
|
||||
// }
|
||||
// let sorted = sortAgainst(variants, context.variantMap)
|
||||
// if (sorted.toString() !== variants.toString()) {
|
||||
// let corrected = sorted.reverse().concat(classCandidate).join(':')
|
||||
// throw new Error(`Class ${candidate} should be written as ${corrected}`)
|
||||
// }
|
||||
|
||||
for (let matchedPlugins of resolveMatchedPlugins(classCandidate, context)) {
|
||||
let matches = []
|
||||
let [plugins, modifier] = matchedPlugins
|
||||
|
||||
for (let [sort, plugin] of plugins) {
|
||||
if (typeof plugin === 'function') {
|
||||
for (let ruleSet of [].concat(plugin(modifier))) {
|
||||
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
|
||||
for (let rule of rules) {
|
||||
matches.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only process static plugins on exact matches
|
||||
else if (modifier === 'DEFAULT') {
|
||||
let ruleSet = plugin
|
||||
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
|
||||
for (let rule of rules) {
|
||||
matches.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matches = applyPrefix(matches, context)
|
||||
|
||||
if (important) {
|
||||
matches = applyImportant(matches, context)
|
||||
}
|
||||
|
||||
for (let variant of variants) {
|
||||
matches = applyVariant(variant, matches, context)
|
||||
}
|
||||
|
||||
for (let match of matches) {
|
||||
yield match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function inKeyframes(rule) {
|
||||
return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
|
||||
}
|
||||
|
||||
function generateRules(candidates, context) {
|
||||
let allRules = []
|
||||
|
||||
for (let candidate of candidates) {
|
||||
if (context.notClassCache.has(candidate)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (context.classCache.has(candidate)) {
|
||||
allRules.push(context.classCache.get(candidate))
|
||||
continue
|
||||
}
|
||||
|
||||
let matches = Array.from(resolveMatches(candidate, context))
|
||||
|
||||
if (matches.length === 0) {
|
||||
context.notClassCache.add(candidate)
|
||||
continue
|
||||
}
|
||||
|
||||
context.classCache.set(candidate, matches)
|
||||
allRules.push(matches)
|
||||
}
|
||||
|
||||
return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
|
||||
if (options.respectImportant) {
|
||||
if (context.tailwindConfig.important === true) {
|
||||
rule.walkDecls((d) => {
|
||||
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
|
||||
d.important = true
|
||||
}
|
||||
})
|
||||
} else if (typeof context.tailwindConfig.important === 'string') {
|
||||
let container = postcss.root({ nodes: [rule.clone()] })
|
||||
container.walkRules((r) => {
|
||||
if (inKeyframes(r)) {
|
||||
return
|
||||
}
|
||||
|
||||
r.selectors = r.selectors.map((selector) => {
|
||||
return `${context.tailwindConfig.important} ${selector}`
|
||||
})
|
||||
})
|
||||
rule = container.nodes[0]
|
||||
}
|
||||
}
|
||||
return [sort | context.layerOrder[layer], rule]
|
||||
})
|
||||
}
|
||||
|
||||
export { resolveMatches, generateRules }
|
||||
Loading…
Add table
Add a link
Reference in a new issue