Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/config/twinConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const configGooberDefaults = { sassyPseudo: true }

const configTwinDefaults = state => ({
allowStyleProp: false, // Allows styles within style="blah" without throwing an error
autoCssProp: false, // Automates the import of styled-components so you can use their css prop
hasSuggestions: true, // Switch suggestions on/off when you use a tailwind class that's not found
sassyPseudo: false, // Sets selectors like hover to &:hover
// ...
// TODO: Add the rest of the twin config items here (ongoing migration)
...(state.isGoober && configGooberDefaults),
})

const isBoolean = value => typeof value === 'boolean'

const configTwinValidators = {
allowStyleProp: [
isBoolean,
'The config “allowStyleProp” can only be true or false',
],
autoCssProp: [
isBoolean,
'The config “autoCssProp” can only be true or false',
],
hasSuggestions: [
isBoolean,
'The config “hasSuggestions” can only be true or false',
],
sassyPseudo: [
isBoolean,
'The config “sassyPseudo” can only be true or false',
],
}

export { configTwinDefaults, configTwinValidators }
47 changes: 38 additions & 9 deletions src/configHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,54 @@ import { resolve } from 'path'
import { existsSync } from 'fs'
import resolveTailwindConfig from 'tailwindcss/lib/util/resolveConfig'
import defaultTailwindConfig from 'tailwindcss/stubs/defaultConfig.stub'
import { configTwinValidators, configTwinDefaults } from './config/twinConfig'
import { logGeneralError } from './logging'
import { throwIf } from './utils'

const getConfigProperties = (state, config) => {
const getConfigTailwindProperties = (state, config) => {
const sourceRoot = state.file.opts.sourceRoot || '.'
const configFile = config && config.config

const configPath = resolve(sourceRoot, configFile || `./tailwind.config.js`)
const configExists = existsSync(configPath)
const tailwindConfig = configExists
const configTailwind = configExists
? resolveTailwindConfig([require(configPath), defaultTailwindConfig])
: resolveTailwindConfig([defaultTailwindConfig])
if (!tailwindConfig) {
if (!configTailwind) {
throw new MacroError(`Couldn’t find the Tailwind config`)
}

return {
configPath,
configExists,
tailwindConfig,
}
return { configPath, configExists, configTailwind }
}

export { getConfigProperties, resolveTailwindConfig, defaultTailwindConfig }
const runConfigValidator = ([item, value]) => {
const validatorConfig = configTwinValidators[item]
if (!validatorConfig) return true

const [validator, errorMessage] = validatorConfig

throwIf(validator(value) !== true, () => logGeneralError(errorMessage))

return true
}

const getConfigTwin = (config, state) => ({
...configTwinDefaults(state),
...config,
})

const getConfigTwinValidated = (config, state) =>
Object.entries(getConfigTwin(config, state)).reduce(
(result, item) => ({
...result,
...(runConfigValidator(item) && { [item[0]]: item[1] }),
}),
{}
)

export {
getConfigTailwindProperties,
resolveTailwindConfig,
defaultTailwindConfig,
getConfigTwinValidated,
}
10 changes: 5 additions & 5 deletions src/getStyles.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import deepMerge from 'lodash.merge'
import { assert, isEmpty, getProperties, getTheme } from './utils'
import { throwIf, isEmpty, getProperties, getTheme } from './utils'
import getPieces from './utils/getPieces'
import { astify } from './macroHelpers'
import doPrechecks, { precheckGroup } from './prechecks'
Expand All @@ -21,7 +21,7 @@ import {
} from './handlers'

export default (classes, t, state) => {
assert([null, 'null', undefined].includes(classes), () =>
throwIf([null, 'null', undefined].includes(classes), () =>
logGeneralError(
'Only plain strings can be used with "tw".\nRead more at https://twinredirect.page.link/template-literals'
)
Expand All @@ -45,7 +45,7 @@ export default (classes, t, state) => {
const pieces = getPieces({ classNameRaw, state })
const { className, hasVariants } = pieces

assert(!className, () =>
throwIf(!className, () =>
hasVariants ? logNotFoundVariant({ classNameRaw }) : logNotFoundClass
)

Expand All @@ -59,7 +59,7 @@ export default (classes, t, state) => {
} = getProperties(className, state)

// Kick off suggestions when no class matches
assert(!hasMatches && !hasUserPlugins, () =>
throwIf(!hasMatches && !hasUserPlugins, () =>
errorSuggestions({ pieces, state })
)

Expand Down Expand Up @@ -90,7 +90,7 @@ export default (classes, t, state) => {
}

// Check again there are no userPlugin matches
assert(!hasMatches && !style, () => errorSuggestions({ pieces, state }))
throwIf(!hasMatches && !style, () => errorSuggestions({ pieces, state }))

style =
style || applyTransforms({ type, pieces, style: styleHandler[type]() })
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/dynamic.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import getConfigValue from './../utils/getConfigValue'
import { assert, stripNegative } from './../utils'
import { throwIf, stripNegative } from './../utils'
import { errorSuggestions } from './../logging'

// Convert an array of objects into a single object
Expand Down Expand Up @@ -36,7 +36,7 @@ export default ({ theme, pieces, state, dynamicKey, dynamicConfig }) => {
}))
.filter(item => item.value)[0]

assert(!results || className.endsWith('-'), () =>
throwIf(!results || className.endsWith('-'), () =>
errorSuggestions({
pieces,
state,
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/userPlugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const formatKey = (selector, className, sassyPseudo) => {

export default ({
state: {
sassyPseudo,
configTwin: { sassyPseudo },
userPluginData: { components, utilities },
},
className,
Expand Down
19 changes: 3 additions & 16 deletions src/logging.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ const logDeeplyNestedClass = properties => {

const errorSuggestions = properties => {
const {
state: { hasSuggestions },
state: {
configTwin: { hasSuggestions },
},
pieces: { className },
} = properties

Expand Down Expand Up @@ -146,20 +148,6 @@ const errorSuggestions = properties => {
return spaced(`${textNotFound}\n\n${suggestionText}`)
}

const themeErrorNotString = ({ themeValue, input }) => {
const textNotFound = warning(
`${color.errorLight(input)} didn’t bring back a string theme value`
)
const suggestionText = `Try adding one of these values after a dot:\n${formatSuggestions(
Object.entries(themeValue).map(([k, v]) => ({
target: k,
value: typeof v === 'string' ? v : '...',
}))
)}`

return spaced(`${textNotFound}\n\n${suggestionText}`)
}

const themeErrorNotFound = ({ theme, input, trimInput }) => {
if (typeof theme === 'string') {
return spaced(logBadGood(input, trimInput))
Expand Down Expand Up @@ -212,7 +200,6 @@ export {
debugPlugins,
inOutPlugins,
errorSuggestions,
themeErrorNotString,
themeErrorNotFound,
logNotFoundVariant,
logNotFoundClass,
Expand Down
49 changes: 22 additions & 27 deletions src/macro.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
generateUid,
} from './macroHelpers'
import { isEmpty } from './utils'
import { getConfigProperties } from './configHelpers'
import {
getConfigTailwindProperties,
getConfigTwinValidated,
} from './configHelpers'
import {
getCssConfig,
updateCssReferences,
Expand Down Expand Up @@ -40,18 +43,24 @@ const twinMacro = ({ babel: { types: t }, references, state, config }) => {
validateImports(references)

const program = state.file.path
const { configExists, tailwindConfig } = getConfigProperties(state, config)
const { configExists, configTailwind } = getConfigTailwindProperties(
state,
config
)

// Get import presets
const styledImport = getStyledConfig(config)
const cssImport = getCssConfig(config)

// Identify the css-in-js library being used
const packageUsed = getPackageUsed({ config, cssImport, styledImport })
for (const [key, value] of Object.entries(packageUsed)) state[key] = value

const configTwin = getConfigTwinValidated(config, state)

state.configExists = configExists
state.config = tailwindConfig
state.hasSuggestions =
typeof config.hasSuggestions === 'undefined'
? true
: Boolean(config.hasSuggestions)
state.allowStyleProp =
typeof config.allowStyleProp === 'undefined'
? false
: Boolean(config.allowStyleProp)
state.config = configTailwind
state.configTwin = configTwin

state.tailwindConfigIdentifier = generateUid('tailwindConfig', program)
state.tailwindUtilsIdentifier = generateUid('tailwindUtils', program)
Expand All @@ -73,27 +82,13 @@ const twinMacro = ({ babel: { types: t }, references, state, config }) => {
state.userPluginData &&
debugPlugins(state.userPluginData)

// Styled import
const styledImport = getStyledConfig(config)
state.styledImport = styledImport
state.cssImport = cssImport

// Init identifiers
state.styledIdentifier = null
state.cssIdentifier = null

// Css import
const cssImport = getCssConfig(config)
state.cssImport = cssImport

const packageUsed = getPackageUsed({ config, cssImport, styledImport })
for (const [key, value] of Object.entries(packageUsed)) state[key] = value

// Sassy pseudo prefix (eg: the & in &:hover)
state.sassyPseudo =
config.sassyPseudo !== undefined
? config.sassyPseudo === true
: state.isGoober

// Group traversals together for better performance
program.traverse({
ImportDeclaration(path) {
Expand Down Expand Up @@ -144,7 +139,7 @@ const twinMacro = ({ babel: { types: t }, references, state, config }) => {
// Auto add css prop for styled components
if (
(state.hasTwProp || state.hasCssProp) &&
config.autoCssProp === true &&
configTwin.autoCssProp === true &&
state.isStyledComponents
) {
maybeAddCssProperty({ program, t })
Expand Down
6 changes: 3 additions & 3 deletions src/macro/globalStyles.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { addImport, generateUid } from '../macroHelpers'
import { assert } from '../utils'
import { throwIf } from '../utils'
import { logGeneralError } from './../logging'
import globalStyles from './../config/globalStyles'
import userPresets from './../config/userPresets'
Expand Down Expand Up @@ -95,14 +95,14 @@ const handleGlobalStylesFunction = ({
if (!references.GlobalStyles) return
if (references.GlobalStyles.length === 0) return

assert(references.GlobalStyles.length > 1, () =>
throwIf(references.GlobalStyles.length > 1, () =>
logGeneralError('Only one GlobalStyles import can be used')
)

const path = references.GlobalStyles[0]
const parentPath = path.findParent(x => x.isJSXElement())

assert(state.isStyledComponents && !parentPath, () =>
throwIf(state.isStyledComponents && !parentPath, () =>
logGeneralError(
'GlobalStyles must be added as a JSX element, eg: <GlobalStyles />'
)
Expand Down
40 changes: 12 additions & 28 deletions src/macro/theme.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import dlv from 'dlv'
import { replaceWithLocation, astify } from './../macroHelpers'
import { getTheme, assert } from './../utils'
import {
logGeneralError,
themeErrorNotString,
themeErrorNotFound,
} from './../logging'
import { getTheme, throwIf } from './../utils'
import { logGeneralError, themeErrorNotFound } from './../logging'

const getFunctionValue = path => {
if (path.parent.type !== 'CallExpression') return
Expand All @@ -14,7 +10,11 @@ const getFunctionValue = path => {
if (!parent) return

const argument = parent.get('arguments')[0] || ''
return { parent, input: argument.evaluate().value }

return {
parent,
input: argument.evaluate && argument.evaluate().value,
}
}

const getTaggedTemplateValue = path => {
Expand All @@ -27,13 +27,6 @@ const getTaggedTemplateValue = path => {
return { parent, input: parent.get('quasi').evaluate().value }
}

const normalizeThemeValue = foundValue =>
Array.isArray(foundValue)
? foundValue.join(', ')
: typeof foundValue === 'string'
? foundValue.trim()
: foundValue

const trimInput = themeValue => {
const arrayValues = themeValue.split('.').filter(Boolean)
if (arrayValues.length === 1) {
Expand All @@ -50,33 +43,24 @@ const handleThemeFunction = ({ references, t, state }) => {

references.theme.forEach(path => {
const { input, parent } =
getTaggedTemplateValue(path) || getFunctionValue(path)
getTaggedTemplateValue(path) || getFunctionValue(path) || ''

if (input === '') {
return replaceWithLocation(parent, astify('', t))
}

assert(!parent || !input, () =>
throwIf(!parent, () =>
logGeneralError(
"The theme value doesn’t look right\n\nTry using it like this: theme`colors.black` or theme('colors.black')"
)
)

const themeValue = dlv(theme(), input)
assert(!themeValue, () =>
const themeValue = theme(input)
throwIf(!themeValue, () =>
themeErrorNotFound({
theme: input.includes('.') ? dlv(theme(), trimInput(input)) : theme(),
input,
trimInput: trimInput(input),
})
)

const normalizedValue = normalizeThemeValue(themeValue)
assert(typeof normalizedValue !== 'string', () =>
themeErrorNotString({ themeValue, input })
)

return replaceWithLocation(parent, astify(normalizedValue, t))
return replaceWithLocation(parent, astify(themeValue, t))
})
}

Expand Down
Loading