tailwindcss/src/util/processPlugins.js
2020-05-08 13:23:26 -04:00

141 lines
4.6 KiB
JavaScript

import _ from 'lodash'
import postcss from 'postcss'
import browserslist from 'browserslist'
import Node from 'postcss/lib/node'
import isFunction from 'lodash/isFunction'
import escapeClassName from '../util/escapeClassName'
import generateVariantFunction from '../util/generateVariantFunction'
import parseObjectStyles from '../util/parseObjectStyles'
import prefixSelector from '../util/prefixSelector'
import wrapWithVariants from '../util/wrapWithVariants'
import increaseSpecificity from '../util/increaseSpecificity'
import selectorParser from 'postcss-selector-parser'
function parseStyles(styles) {
if (!Array.isArray(styles)) {
return parseStyles([styles])
}
return _.flatMap(styles, style => (style instanceof Node ? style : parseObjectStyles(style)))
}
function containsClass(value) {
return selectorParser(selectors => {
let classFound = false
selectors.walkClasses(() => (classFound = true))
return classFound
}).transformSync(value)
}
export default function(plugins, config) {
const pluginBaseStyles = []
const pluginComponents = []
const pluginUtilities = []
const pluginVariantGenerators = {}
const applyConfiguredPrefix = selector => {
return prefixSelector(config.prefix, selector)
}
const getConfigValue = (path, defaultValue) => _.get(config, path, defaultValue)
const browserslistTarget = browserslist().includes('ie 11') ? 'ie11' : 'relaxed'
plugins.forEach(plugin => {
if (plugin.__isOptionsFunction) {
plugin = plugin()
}
const handler = isFunction(plugin) ? plugin : _.get(plugin, 'handler', () => {})
handler({
postcss,
config: getConfigValue,
theme: (path, defaultValue) => getConfigValue(`theme.${path}`, defaultValue),
corePlugins: path => {
if (Array.isArray(config.corePlugins)) {
return config.corePlugins.includes(path)
}
return getConfigValue(`corePlugins.${path}`, true)
},
variants: (path, defaultValue) => {
if (Array.isArray(config.variants)) {
return config.variants
}
return getConfigValue(`variants.${path}`, defaultValue)
},
target: path => {
if (_.isString(config.target)) {
return config.target === 'browserslist' ? browserslistTarget : config.target
}
const [defaultTarget, targetOverrides] = getConfigValue('target')
const target = _.get(targetOverrides, path, defaultTarget)
return target === 'browserslist' ? browserslistTarget : target
},
e: escapeClassName,
prefix: applyConfiguredPrefix,
addUtilities: (utilities, options) => {
const defaultOptions = { variants: [], respectPrefix: true, respectImportant: true }
options = Array.isArray(options)
? Object.assign({}, defaultOptions, { variants: options })
: _.defaults(options, defaultOptions)
const styles = postcss.root({ nodes: parseStyles(utilities) })
styles.walkRules(rule => {
if (options.respectPrefix) {
rule.selector = applyConfiguredPrefix(rule.selector)
}
if (options.respectImportant && _.get(config, 'important')) {
if (config.important === true) {
rule.walkDecls(decl => (decl.important = true))
} else if (typeof config.important === 'string') {
if (containsClass(config.important)) {
throw rule.error(
`Classes are not allowed when using the \`important\` option with a string argument. Please use an ID instead.`
)
}
rule.selectors = rule.selectors.map(selector => {
return increaseSpecificity(config.important, selector)
})
}
}
})
pluginUtilities.push(wrapWithVariants(styles.nodes, options.variants))
},
addComponents: (components, options) => {
options = Object.assign({ respectPrefix: true }, options)
const styles = postcss.root({ nodes: parseStyles(components) })
styles.walkRules(rule => {
if (options.respectPrefix) {
rule.selector = applyConfiguredPrefix(rule.selector)
}
})
pluginComponents.push(...styles.nodes)
},
addBase: baseStyles => {
pluginBaseStyles.push(...parseStyles(baseStyles))
},
addVariant: (name, generator) => {
pluginVariantGenerators[name] = generateVariantFunction(generator)
},
})
})
return {
base: pluginBaseStyles,
components: pluginComponents,
utilities: pluginUtilities,
variantGenerators: pluginVariantGenerators,
}
}