Allow resolving an arbitrary number of stacked config files

This commit is contained in:
Adam Wathan 2019-10-11 17:48:52 -04:00
parent 5ba0cc1e1d
commit e7b831fc09
2 changed files with 166 additions and 4 deletions

View File

@ -1234,3 +1234,133 @@ test('custom properties are multiplied by -1 for negative values', () => {
variants: {},
})
})
test('more than two config objects can be resolved', () => {
const firstConfig = {
theme: {
extend: {
fontFamily: () => ({
code: ['Menlo', 'monospace'],
}),
colors: {
red: 'red',
},
backgroundColor: {
customBackgroundOne: '#bada55',
},
textDecorationColor: {
orange: 'orange'
}
},
},
}
const secondConfig = {
prefix: '-',
important: false,
separator: ':',
theme: {
extend: {
fontFamily: {
quote: ['Helvetica', 'serif'],
},
colors: {
green: 'green',
},
backgroundColor: {
customBackgroundTwo: '#facade',
},
textDecorationColor: theme => theme('colors')
},
},
}
const thirdConfig = {
prefix: '-',
important: false,
separator: ':',
theme: {
extend: {
fontFamily: {
hero: ['Futura', 'sans-serif'],
},
colors: {
pink: 'pink',
},
backgroundColor: () => ({
customBackgroundThree: '#c0ffee',
}),
textDecorationColor: {
lime: 'lime',
}
},
},
}
const defaultConfig = {
prefix: '-',
important: false,
separator: ':',
theme: {
fontFamily: {
body: ['Arial', 'sans-serif'],
display: ['Georgia', 'serif'],
},
colors: {
blue: 'blue',
},
backgroundColor: theme => theme('colors'),
},
variants: {
backgroundColor: ['responsive', 'hover', 'focus'],
},
}
const result = resolveConfig([
firstConfig,
secondConfig,
thirdConfig,
defaultConfig
])
expect(result).toEqual({
prefix: '-',
important: false,
separator: ':',
theme: {
fontFamily: {
body: ['Arial', 'sans-serif'],
display: ['Georgia', 'serif'],
code: ['Menlo', 'monospace'],
quote: ['Helvetica', 'serif'],
hero: ['Futura', 'sans-serif'],
},
colors: {
red: 'red',
green: 'green',
blue: 'blue',
pink: 'pink',
},
backgroundColor: {
red: 'red',
green: 'green',
blue: 'blue',
pink: 'pink',
customBackgroundOne: '#bada55',
customBackgroundTwo: '#facade',
customBackgroundThree: '#c0ffee',
},
textDecorationColor: {
red: 'red',
green: 'green',
blue: 'blue',
pink: 'pink',
orange: 'orange',
lime: 'lime',
},
},
variants: {
backgroundColor: ['responsive', 'hover', 'focus'],
},
})
})

View File

@ -1,7 +1,11 @@
import some from 'lodash/some'
import mergeWith from 'lodash/mergeWith'
import assignWith from 'lodash/assignWith'
import isFunction from 'lodash/isFunction'
import isUndefined from 'lodash/isUndefined'
import defaults from 'lodash/defaults'
import map from 'lodash/map'
import reduce from 'lodash/reduce'
import toPath from 'lodash/toPath'
import negateValue from './negateValue'
@ -23,18 +27,46 @@ function value(valueToResolve, ...args) {
return isFunction(valueToResolve) ? valueToResolve(...args) : valueToResolve
}
function mergeThemes(themes) {
const theme = (({ extend, ...t }) => t)(themes.reduce((merged, t) => {
return defaults(merged, t)
}, {}))
// In order to resolve n config objects, we combine all of their `extend` properties
// into arrays instead of objects so they aren't overridden.
const extend = themes.reduce((merged, { extend }) => {
return mergeWith(merged, extend, (mergedValue, extendValue) => {
if (isUndefined(mergedValue)) {
return [extendValue]
}
if (Array.isArray(mergedValue)) {
return [...mergedValue, extendValue]
}
return [mergedValue, extendValue]
})
}, {})
return {
...theme,
extend,
}
}
function mergeExtensions({ extend, ...theme }) {
return mergeWith(theme, extend, (themeValue, extensions) => {
if (!isFunction(themeValue) && !isFunction(extensions)) {
// The `extend` property is an array, so we need to check if it contains any functions
if (!isFunction(themeValue) && !some(extensions, isFunction)) {
return {
...themeValue,
...extensions,
...Object.assign({}, ...extensions),
}
}
return (resolveThemePath, utils) => ({
...value(themeValue, resolveThemePath, utils),
...value(extensions, resolveThemePath, utils),
...Object.assign({}, ...extensions.map(e => value(e, resolveThemePath, utils))),
})
})
}
@ -65,7 +97,7 @@ function resolveFunctionKeys(object) {
export default function resolveConfig(configs) {
return defaults(
{
theme: resolveFunctionKeys(mergeExtensions(defaults({}, ...map(configs, 'theme')))),
theme: resolveFunctionKeys(mergeExtensions(mergeThemes(map(configs, 'theme')))),
variants: (firstVariants => {
return Array.isArray(firstVariants)
? firstVariants