Refactor duplication

This commit is contained in:
Adam Wathan 2018-07-11 11:45:45 -04:00
parent cdbea36564
commit cfe492220c
5 changed files with 67 additions and 93 deletions

View File

@ -1,9 +1,12 @@
import postcss from 'postcss'
import plugin from '../src/lib/substituteClassApplyAtRules'
import defaultConfig from '../defaultCOnfig.stub.js'
import generateUtilities from '../src/util/generateUtilities'
import defaultConfig from '../defaultConfig.stub.js'
function run(input, opts = defaultConfig) {
return postcss([plugin(opts)]).process(input, { from: undefined })
const defaultUtilities = generateUtilities(defaultConfig, [])
function run(input, config = defaultConfig, utilities = defaultUtilities) {
return postcss([plugin(config, utilities)]).process(input, { from: undefined })
}
test("it copies a class's declarations into itself", () => {
@ -199,7 +202,7 @@ test('you can apply utility classes that do not actually exist as long as they w
.foo { margin-top: 1rem; }
`
return run(input, defaultConfig).then(result => {
return run(input).then(result => {
expect(result.css).toEqual(expected)
expect(result.warnings().length).toBe(0)
})

View File

@ -1,47 +1,6 @@
import _ from 'lodash'
import postcss from 'postcss'
import escapeClassName from '../util/escapeClassName'
import utilityModules from '../utilityModules'
import prefixTree from '../util/prefixTree'
import generateModules from '../util/generateModules'
function buildShadowTable(config, pluginUtilities) {
const utilities = postcss.root()
const generatedUtilities = generateModules(utilityModules, _.fromPairs(Object.keys(config.modules).map((k) => [k, []])), config)
generatedUtilities.walkAtRules('variants', atRule => {
utilities.append(atRule.clone().nodes)
})
const tailwindUtilityTree = postcss.root({
nodes: utilities.nodes,
})
const pluginUtilityTree = postcss.root({
nodes: pluginUtilities,
})
prefixTree(tailwindUtilityTree, config.options.prefix)
const shadowTable = {}
tailwindUtilityTree.walkRules(rule => {
if (!_.has(shadowTable, rule.selector)) {
shadowTable[rule.selector] = []
}
shadowTable[rule.selector].push(rule)
})
pluginUtilityTree.walkRules(rule => {
if (!_.has(shadowTable, rule.selector)) {
shadowTable[rule.selector] = []
}
shadowTable[rule.selector].push(rule)
})
return shadowTable
}
function buildClassTable(css) {
const classTable = {}
@ -56,48 +15,55 @@ function buildClassTable(css) {
return classTable
}
function buildShadowTable(generatedUtilities) {
const utilities = postcss.root()
generatedUtilities.walkAtRules('variants', atRule => {
utilities.append(atRule.clone().nodes)
})
return buildClassTable(utilities)
}
function normalizeClassName(className) {
return `.${escapeClassName(_.trimStart(className, '.'))}`
}
function findMixin(classTable, shadowLookup, mixin, onError) {
const matches = _.get(classTable, mixin, [])
function findClass(classToApply, classTable, shadowLookup, onError) {
const matches = _.get(classTable, classToApply, [])
if (_.isEmpty(matches)) {
if (_.isEmpty(shadowLookup)) {
// prettier-ignore
onError(`\`@apply\` cannot be used with \`${mixin}\` because \`${mixin}\` either cannot be found, or it's actual definition includes a pseudo-selector like :hover, :active, etc. If you're sure that \`${mixin}\` exists, make sure that any \`@import\` statements are being properly processed *before* Tailwind CSS sees your CSS, as \`@apply\` can only be used for classes in the same CSS tree.`)
return
throw onError(`\`@apply\` cannot be used with \`${classToApply}\` because \`${classToApply}\` either cannot be found, or it's actual definition includes a pseudo-selector like :hover, :active, etc. If you're sure that \`${classToApply}\` exists, make sure that any \`@import\` statements are being properly processed *before* Tailwind CSS sees your CSS, as \`@apply\` can only be used for classes in the same CSS tree.`)
}
return findMixin(shadowLookup, {}, mixin, onError)
return findClass(classToApply, shadowLookup, {}, onError)
}
if (matches.length > 1) {
// prettier-ignore
onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is included in multiple rulesets.`)
return
throw onError(`\`@apply\` cannot be used with ${classToApply} because ${classToApply} is included in multiple rulesets.`)
}
const [match] = matches
if (match.parent.type !== 'root') {
// prettier-ignore
onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is nested inside of an at-rule (@${match.parent.name}).`)
return
throw onError(`\`@apply\` cannot be used with ${classToApply} because ${classToApply} is nested inside of an at-rule (@${match.parent.name}).`)
}
return match.clone().nodes
}
export default function(config, { components: pluginComponents = {}, utilities: pluginUtilities = {} } = {}) {
export default function(config, generatedUtilities) {
return function(css) {
const classLookup = buildClassTable(css)
const shadowLookup = buildShadowTable(config, pluginUtilities)
const shadowLookup = buildShadowTable(generatedUtilities)
css.walkRules(rule => {
rule.walkAtRules('apply', atRule => {
const mixins = postcss.list.space(atRule.params)
const classesAndProperties = postcss.list.space(atRule.params)
/*
* Don't wreck CSSNext-style @apply rules:
@ -106,20 +72,20 @@ export default function(config, { components: pluginComponents = {}, utilities:
* These are deprecated in CSSNext but still playing it safe for now.
* We might consider renaming this at-rule.
*/
const [customProperties, classes] = _.partition(mixins, mixin => {
return _.startsWith(mixin, '--')
const [customProperties, classes] = _.partition(classesAndProperties, classOrProperty => {
return _.startsWith(classOrProperty, '--')
})
const decls = _(classes)
.reject(mixin => mixin === '!important')
.flatMap(mixin => {
return findMixin(classLookup, shadowLookup, normalizeClassName(mixin), message => {
throw atRule.error(message)
.reject(cssClass => cssClass === '!important')
.flatMap(cssClass => {
return findClass(normalizeClassName(cssClass), classLookup, shadowLookup, message => {
return atRule.error(message)
})
})
.value()
_.tap(_.last(mixins) === '!important', important => {
_.tap(_.last(classesAndProperties) === '!important', important => {
decls.forEach(decl => (decl.important = important))
})

View File

@ -1,10 +1,7 @@
import fs from 'fs'
import postcss from 'postcss'
import utilityModules from '../utilityModules'
import prefixTree from '../util/prefixTree'
import generateModules from '../util/generateModules'
export default function(config, { components: pluginComponents, utilities: pluginUtilities }) {
export default function(config, { components: pluginComponents }, generatedUtilities) {
return function(css) {
css.walkAtRules('tailwind', atRule => {
if (atRule.params === 'preflight') {
@ -30,27 +27,8 @@ export default function(config, { components: pluginComponents, utilities: plugi
}
if (atRule.params === 'utilities') {
const utilities = generateModules(utilityModules, config.modules, config)
if (config.options.important) {
utilities.walkDecls(decl => (decl.important = true))
}
const tailwindUtilityTree = postcss.root({
nodes: utilities.nodes,
})
const pluginUtilityTree = postcss.root({
nodes: pluginUtilities,
})
prefixTree(tailwindUtilityTree, config.options.prefix)
tailwindUtilityTree.walk(node => (node.source = atRule.source))
pluginUtilityTree.walk(node => (node.source = atRule.source))
atRule.before(tailwindUtilityTree)
atRule.before(pluginUtilityTree)
generatedUtilities.walk(node => (node.source = atRule.source))
atRule.before(generatedUtilities)
atRule.remove()
}
})

View File

@ -6,18 +6,21 @@ import substituteVariantsAtRules from './lib/substituteVariantsAtRules'
import substituteResponsiveAtRules from './lib/substituteResponsiveAtRules'
import substituteScreenAtRules from './lib/substituteScreenAtRules'
import substituteClassApplyAtRules from './lib/substituteClassApplyAtRules'
import generateUtilities from './util/generateUtilities'
import processPlugins from './util/processPlugins'
export default function(lazyConfig) {
const config = lazyConfig()
const plugins = processPlugins(config)
const processedPlugins = processPlugins(config)
const utilities = generateUtilities(config, processedPlugins.utilities)
return postcss([
substituteTailwindAtRules(config, plugins),
substituteTailwindAtRules(config, processedPlugins, utilities.clone()),
evaluateTailwindFunctions(config),
substituteVariantsAtRules(config, plugins),
substituteVariantsAtRules(config, processedPlugins),
substituteResponsiveAtRules(config),
substituteScreenAtRules(config),
substituteClassApplyAtRules(config, plugins),
substituteClassApplyAtRules(config, utilities.clone()),
])
}

View File

@ -0,0 +1,24 @@
import _ from 'lodash'
import postcss from 'postcss'
import utilityModules from '../utilityModules'
import prefixTree from '../util/prefixTree'
import generateModules from '../util/generateModules'
export default function(config, pluginUtilities) {
const utilities = generateModules(utilityModules, config.modules, config)
if (config.options.important) {
utilities.walkDecls(decl => (decl.important = true))
}
const tailwindUtilityTree = postcss.root({
nodes: utilities.nodes,
})
prefixTree(tailwindUtilityTree, config.options.prefix)
return _.tap(postcss.root(), root => {
root.append(tailwindUtilityTree.nodes)
root.append(pluginUtilities)
})
}