362 lines
No EOL
9.7 KiB
JavaScript
362 lines
No EOL
9.7 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.resolveMatches = resolveMatches;
|
|
exports.generateRules = generateRules;
|
|
|
|
var _postcss = _interopRequireDefault(require("postcss"));
|
|
|
|
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser"));
|
|
|
|
var _parseObjectStyles = _interopRequireDefault(require("../../util/parseObjectStyles"));
|
|
|
|
var _isPlainObject = _interopRequireDefault(require("../../util/isPlainObject"));
|
|
|
|
var _prefixSelector = _interopRequireDefault(require("../../util/prefixSelector"));
|
|
|
|
var _pluginUtils = require("../../util/pluginUtils");
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
let classNameParser = (0, _postcssSelectorParser.default)(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.default.root({
|
|
nodes: [match[1].clone()]
|
|
});
|
|
|
|
container.walkRules(r => {
|
|
r.selector = (0, _prefixSelector.default)(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.default.root({
|
|
nodes: [rule.clone()]
|
|
});
|
|
|
|
container.walkRules(r => {
|
|
r.selector = (0, _pluginUtils.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.default.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 (!(0, _isPlainObject.default)(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, (0, _parseObjectStyles.default)(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.default.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];
|
|
});
|
|
} |