Skip to content

Commit a5e5268

Browse files
committed
normalize the configuration
1 parent 28deda0 commit a5e5268

File tree

6 files changed

+208
-82
lines changed

6 files changed

+208
-82
lines changed

src/lib/expandTailwindAtRules.js

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,6 @@ const builtInTransformers = {
3535

3636
function getExtractor(tailwindConfig, fileExtension) {
3737
let extractors = tailwindConfig.content.extract
38-
let contentOptions = tailwindConfig.content.options
39-
40-
if (typeof extractors === 'function') {
41-
extractors = {
42-
DEFAULT: extractors,
43-
}
44-
}
45-
if (contentOptions.defaultExtractor) {
46-
extractors.DEFAULT = contentOptions.defaultExtractor
47-
}
48-
for (let { extensions, extractor } of contentOptions.extractors || []) {
49-
for (let extension of extensions) {
50-
extractors[extension] = extractor
51-
}
52-
}
5338

5439
return (
5540
extractors[fileExtension] ||
@@ -62,12 +47,6 @@ function getExtractor(tailwindConfig, fileExtension) {
6247
function getTransformer(tailwindConfig, fileExtension) {
6348
let transformers = tailwindConfig.content.transform
6449

65-
if (typeof transformers === 'function') {
66-
transformers = {
67-
DEFAULT: transformers,
68-
}
69-
}
70-
7150
return (
7251
transformers[fileExtension] ||
7352
transformers.DEFAULT ||

src/lib/setupTrackingContext.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function getCandidateFiles(context, tailwindConfig) {
2626
return candidateFilesCache.get(context)
2727
}
2828

29-
let candidateFiles = tailwindConfig.content.content
29+
let candidateFiles = tailwindConfig.content.files
3030
.filter((item) => typeof item === 'string')
3131
.map((contentPath) => normalizePath(contentPath))
3232

@@ -77,7 +77,7 @@ function getTailwindConfig(configOrPath) {
7777
}
7878

7979
function resolvedChangedContent(context, candidateFiles, fileModifiedMap) {
80-
let changedContent = context.tailwindConfig.content.content
80+
let changedContent = context.tailwindConfig.content.files
8181
.filter((item) => typeof item.raw === 'string')
8282
.map(({ raw, extension }) => ({ content: raw, extension }))
8383

src/lib/setupWatchingContext.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ function getCandidateFiles(context, tailwindConfig) {
147147
return candidateFilesCache.get(context)
148148
}
149149

150-
let candidateFiles = tailwindConfig.content.content
150+
let candidateFiles = tailwindConfig.content.files
151151
.filter((item) => typeof item === 'string')
152152
.map((contentPath) => normalizePath(contentPath))
153153

@@ -185,7 +185,7 @@ function getTailwindConfig(configOrPath) {
185185
}
186186

187187
function resolvedChangedContent(context, candidateFiles) {
188-
let changedContent = context.tailwindConfig.content.content
188+
let changedContent = context.tailwindConfig.content.files
189189
.filter((item) => typeof item.raw === 'string')
190190
.map(({ raw, extension }) => ({ content: raw, extension }))
191191

src/util/normalizeConfig.js

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import log from './log'
2+
3+
let warnedAbout = new Set()
4+
5+
function validate(config, options) {
6+
if (!config) return false
7+
8+
for (let [k, v] of Object.entries(config)) {
9+
let types = options[k]
10+
11+
if (!types) return false
12+
13+
// Property SHOULD exist, this catches unused keys like `options`
14+
if (!types.includes(undefined) && !options.hasOwnProperty(k)) {
15+
return false
16+
}
17+
18+
if (
19+
!types.some((type) => {
20+
if (type === undefined) return true
21+
return v instanceof type
22+
})
23+
) {
24+
return false
25+
}
26+
}
27+
28+
for (let [k, types] of Object.entries(options)) {
29+
let value = config[k]
30+
if (
31+
!types.some((type) => {
32+
if (type === undefined) return true
33+
return value instanceof type
34+
})
35+
) {
36+
return false
37+
}
38+
}
39+
40+
return true
41+
}
42+
43+
export function normalizeConfig(config) {
44+
// Quick structure validation
45+
let valid = validate(config.content, {
46+
files: [Array],
47+
extract: [undefined, Function, Object],
48+
})
49+
50+
if (!valid) {
51+
if (!warnedAbout.has('purge-deprecation')) {
52+
// TODO: Improve this warning message
53+
log.warn([
54+
'The `purge` option in your tailwind.config.js file has been deprecated.',
55+
'Please look at the docs.',
56+
])
57+
warnedAbout.add('purge-deprecation')
58+
}
59+
}
60+
61+
// Normalize the `safelist`
62+
config.safelist = (() => {
63+
let { content, purge, safelist } = config
64+
65+
if (Array.isArray(safelist)) return safelist
66+
if (Array.isArray(content?.safelist)) return content.safelist
67+
if (Array.isArray(purge?.safelist)) return purge.safelist
68+
if (Array.isArray(purge?.options?.safelist)) return purge.options.safelist
69+
70+
return []
71+
})()
72+
73+
// Normalize the `content`
74+
config.content = {
75+
files: (() => {
76+
let { content, purge } = config
77+
78+
if (Array.isArray(purge)) return purge
79+
if (Array.isArray(purge?.content)) return purge.content
80+
if (Array.isArray(content)) return content
81+
if (Array.isArray(content?.content)) return content.content
82+
if (Array.isArray(content?.files)) return content.files
83+
84+
return []
85+
})(),
86+
87+
extract: (() => {
88+
let extract = (() => {
89+
if (config.purge?.extract) return config.purge.extract
90+
if (config.content?.extract) return config.content.extract
91+
92+
if (config.purge?.extract?.DEFAULT) return config.purge.extract.DEFAULT
93+
if (config.content?.extract?.DEFAULT) return config.content.extract.DEFAULT
94+
95+
if (config.purge?.options?.defaultExtractor) return config.purge.options.defaultExtractor
96+
if (config.content?.options?.defaultExtractor)
97+
return config.content.options.defaultExtractor
98+
99+
if (config.purge?.options?.extractors) return config.purge.options.extractors
100+
if (config.content?.options?.extractors) return config.content.options.extractors
101+
102+
return {}
103+
})()
104+
105+
let extractors = {}
106+
107+
// Functions
108+
if (typeof extract === 'function') {
109+
extractors.DEFAULT = extract
110+
}
111+
112+
// Arrays
113+
else if (Array.isArray(extract)) {
114+
for (let { extensions, extractor } of extract ?? []) {
115+
for (let extension of extensions) {
116+
extractors[extension] = extractor
117+
}
118+
}
119+
}
120+
121+
// Objects
122+
else if (typeof extract === 'object' && extract !== null) {
123+
Object.assign(extractors, extract)
124+
}
125+
126+
return extractors
127+
})(),
128+
129+
transform: (() => {
130+
let transform = (() => {
131+
if (config.purge?.transform) return config.purge.transform
132+
if (config.content?.transform) return config.content.transform
133+
134+
if (config.purge?.transform?.DEFAULT) return config.purge.transform.DEFAULT
135+
if (config.content?.transform?.DEFAULT) return config.content.transform.DEFAULT
136+
137+
return {}
138+
})()
139+
140+
let transformers = {}
141+
142+
if (typeof transform === 'function') {
143+
transformers.DEFAULT = transform
144+
}
145+
146+
if (typeof transform === 'object' && transform !== null) {
147+
Object.assign(transformers, transform)
148+
}
149+
150+
return transformers
151+
})(),
152+
}
153+
154+
return config
155+
}

src/util/resolveConfig.js

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import corePluginList from '../corePluginList'
33
import configurePlugins from './configurePlugins'
44
import defaultConfig from '../../stubs/defaultConfig.stub'
55
import colors from '../public/colors'
6-
import log from './log'
76
import { defaults } from './defaults'
87
import { toPath } from './toPath'
8+
import { normalizeConfig } from './normalizeConfig'
99

1010
function isFunction(input) {
1111
return typeof input === 'function'
@@ -221,59 +221,3 @@ export default function resolveConfig(configs) {
221221
)
222222
)
223223
}
224-
225-
let warnedAbout = new Set()
226-
function normalizeConfig(config) {
227-
if (!warnedAbout.has('purge-deprecation') && config.hasOwnProperty('purge')) {
228-
log.warn([
229-
'The `purge` option in your tailwind.config.js file has been deprecated.',
230-
'Please rename this to `content` instead.',
231-
])
232-
warnedAbout.add('purge-deprecation')
233-
}
234-
235-
config.content = {
236-
content: (() => {
237-
let { content, purge } = config
238-
239-
if (Array.isArray(purge)) return purge
240-
if (Array.isArray(purge?.content)) return purge.content
241-
if (Array.isArray(content)) return content
242-
if (Array.isArray(content?.content)) return content.content
243-
244-
return []
245-
})(),
246-
safelist: (() => {
247-
let { content, purge } = config
248-
249-
let [safelistKey, safelistPaths] = (() => {
250-
if (Array.isArray(content?.safelist)) return ['content.safelist', content.safelist]
251-
if (Array.isArray(purge?.safelist)) return ['purge.safelist', purge.safelist]
252-
if (Array.isArray(purge?.options?.safelist))
253-
return ['purge.options.safelist', purge.options.safelist]
254-
return [null, []]
255-
})()
256-
257-
return safelistPaths.map((content) => {
258-
if (typeof content === 'string') {
259-
return { raw: content, extension: 'html' }
260-
}
261-
262-
if (content instanceof RegExp) {
263-
throw new Error(
264-
`Values inside '${safelistKey}' can only be of type 'string', found 'regex'.`
265-
)
266-
}
267-
268-
throw new Error(
269-
`Values inside '${safelistKey}' can only be of type 'string', found '${typeof content}'.`
270-
)
271-
})
272-
})(),
273-
extract: config.content?.extract || config.purge?.extract || {},
274-
options: config.content?.options || config.purge?.options || {},
275-
transform: config.content?.transform || config.purge?.transform || {},
276-
}
277-
278-
return config
279-
}

tests/normalize-config.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { run, css } from './util/run'
2+
3+
it.each`
4+
config
5+
${{ purge: [{ raw: 'text-center' }] }}
6+
${{ purge: { content: [{ raw: 'text-center' }] } }}
7+
${{ content: { content: [{ raw: 'text-center' }] } }}
8+
`('should normalize content $config', ({ config }) => {
9+
return run('@tailwind utilities', config).then((result) => {
10+
return expect(result.css).toMatchFormattedCss(css`
11+
.text-center {
12+
text-align: center;
13+
}
14+
`)
15+
})
16+
})
17+
18+
it.each`
19+
config
20+
${{ purge: { safelist: ['text-center'] } }}
21+
${{ purge: { options: { safelist: ['text-center'] } } }}
22+
${{ content: { safelist: ['text-center'] } }}
23+
`('should normalize safelist $config', ({ config }) => {
24+
return run('@tailwind utilities', config).then((result) => {
25+
return expect(result.css).toMatchFormattedCss(css`
26+
.text-center {
27+
text-align: center;
28+
}
29+
`)
30+
})
31+
})
32+
33+
it.each`
34+
config
35+
${{ content: [{ raw: 'text-center' }], purge: { extract: () => ['font-bold'] } }}
36+
${{ content: [{ raw: 'text-center' }], purge: { extract: { DEFAULT: () => ['font-bold'] } } }}
37+
${{ content: [{ raw: 'text-center' }], purge: { options: { defaultExtractor: () => ['font-bold'] } } }}
38+
${{ content: [{ raw: 'text-center' }], purge: { options: { extractors: [{ extractor: () => ['font-bold'], extensions: ['html'] }] } } }}
39+
${{ content: [{ raw: 'text-center' }], purge: { extract: { html: () => ['font-bold'] } } }}
40+
`('should normalize extractors $config', ({ config }) => {
41+
return run('@tailwind utilities', config).then((result) => {
42+
return expect(result.css).toMatchFormattedCss(css`
43+
.font-bold {
44+
font-weight: 700;
45+
}
46+
`)
47+
})
48+
})

0 commit comments

Comments
 (0)