Skip to content

Commit d94541c

Browse files
authored
Handle old to new config when normalizing the config (tailwindlabs#5658)
* immediately take the `safelist` values into account Currently we had to manually add them in the `setupTrackingContext`, `setupWatchingContext` and the `cli`. This was a bit cumbersome, because the `safelist` function (to resolve regex patterns) was implemented on the context. This means that we had to do something like this: ```js let changedContent = [] let context = createContext(config, changedContent) for (let content of context.safelist()) { changedContent.push(content) } ``` This just feels wrong in general, so now it is handled internally for you which means that we can't mess it up anymore in those 3 spots. * drop the dot from the extension Our transformers and extractors are implemented for `html` for example. However the `path.extname()` returns `.html`. This isn't an issue by default, but it could be for with custom extractors / transformers. * normalize the configuration * make shared cache local per extractor * ensure we always have an `extension` Defaults to `html` * splitup custom-extractors test * update old config structure to new structure * ensure we validate the "old" structure, and warn if invalid * add tests with "old" config, to ensure it keeps working * add missing `content` object * inline unnecessary function abstraction
1 parent 109c1c5 commit d94541c

16 files changed

+503
-234
lines changed

integrations/tailwindcss-cli/tests/integration.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ describe('static build', () => {
3434
javascript`
3535
module.exports = {
3636
content: {
37-
content: ['./src/index.html'],
38-
safelist: ['bg-red-500','bg-red-600']
37+
files: ['./src/index.html'],
3938
},
39+
safelist: ['bg-red-500','bg-red-600'],
4040
theme: {
4141
extend: {
4242
},

integrations/webpack-5/tests/integration.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,9 @@ describe.each([{ TAILWIND_MODE: 'watch' }, { TAILWIND_MODE: undefined }])('watch
230230
javascript`
231231
module.exports = {
232232
content: {
233-
content: ['./src/index.html'],
234-
safelist: ['bg-red-500','bg-red-600']
233+
files: ['./src/index.html'],
235234
},
235+
safelist: ['bg-red-500','bg-red-600'],
236236
theme: {
237237
extend: {
238238
},

src/cli.js

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -436,12 +436,8 @@ async function build() {
436436
return resolvedConfig
437437
}
438438

439-
function extractContent(config) {
440-
return config.content.content.concat(config.content.safelist)
441-
}
442-
443439
function extractFileGlobs(config) {
444-
return extractContent(config)
440+
return config.content.files
445441
.filter((file) => {
446442
// Strings in this case are files / globs. If it is something else,
447443
// like an object it's probably a raw content object. But this object
@@ -452,7 +448,7 @@ async function build() {
452448
}
453449

454450
function extractRawContent(config) {
455-
return extractContent(config).filter((file) => {
451+
return config.content.files.filter((file) => {
456452
return typeof file === 'object' && file !== null
457453
})
458454
}
@@ -467,7 +463,7 @@ async function build() {
467463
for (let file of files) {
468464
changedContent.push({
469465
content: fs.readFileSync(path.resolve(file), 'utf8'),
470-
extension: path.extname(file),
466+
extension: path.extname(file).slice(1),
471467
})
472468
}
473469

@@ -729,7 +725,7 @@ async function build() {
729725
chain = chain.then(async () => {
730726
changedContent.push({
731727
content: fs.readFileSync(path.resolve(file), 'utf8'),
732-
extension: path.extname(file),
728+
extension: path.extname(file).slice(1),
733729
})
734730

735731
await rebuild(config)
@@ -741,7 +737,7 @@ async function build() {
741737
chain = chain.then(async () => {
742738
changedContent.push({
743739
content: fs.readFileSync(path.resolve(file), 'utf8'),
744-
extension: path.extname(file),
740+
extension: path.extname(file).slice(1),
745741
})
746742

747743
await rebuild(config)

src/lib/expandTailwindAtRules.js

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import LRU from 'quick-lru'
12
import * as sharedState from './sharedState'
23
import { generateRules } from './generateRules'
34
import bigSign from '../util/bigSign'
45
import cloneNodes from '../util/cloneNodes'
56

67
let env = sharedState.env
7-
let contentMatchCache = sharedState.contentMatchCache
88

99
const PATTERNS = [
1010
/([^<>"'`\s]*\[\w*'[^"`\s]*'?\])/.source, // font-['some_font',sans-serif]
@@ -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 ||
@@ -76,10 +55,16 @@ function getTransformer(tailwindConfig, fileExtension) {
7655
)
7756
}
7857

58+
let extractorCache = new WeakMap()
59+
7960
// Scans template contents for possible classes. This is a hot path on initial build but
8061
// not too important for subsequent builds. The faster the better though — if we can speed
8162
// up these regexes by 50% that could cut initial build time by like 20%.
82-
function getClassCandidates(content, extractor, contentMatchCache, candidates, seen) {
63+
function getClassCandidates(content, extractor, candidates, seen) {
64+
if (!extractorCache.has(extractor)) {
65+
extractorCache.set(extractor, new LRU({ maxSize: 25000 }))
66+
}
67+
8368
for (let line of content.split('\n')) {
8469
line = line.trim()
8570

@@ -88,8 +73,8 @@ function getClassCandidates(content, extractor, contentMatchCache, candidates, s
8873
}
8974
seen.add(line)
9075

91-
if (contentMatchCache.has(line)) {
92-
for (let match of contentMatchCache.get(line)) {
76+
if (extractorCache.get(extractor).has(line)) {
77+
for (let match of extractorCache.get(extractor).get(line)) {
9378
candidates.add(match)
9479
}
9580
} else {
@@ -100,7 +85,7 @@ function getClassCandidates(content, extractor, contentMatchCache, candidates, s
10085
candidates.add(match)
10186
}
10287

103-
contentMatchCache.set(line, lineMatchesSet)
88+
extractorCache.get(extractor).set(line, lineMatchesSet)
10489
}
10590
}
10691
}
@@ -189,7 +174,7 @@ export default function expandTailwindAtRules(context) {
189174
for (let { content, extension } of context.changedContent) {
190175
let transformer = getTransformer(context.tailwindConfig, extension)
191176
let extractor = getExtractor(context.tailwindConfig, extension)
192-
getClassCandidates(transformer(content), extractor, contentMatchCache, candidates, seen)
177+
getClassCandidates(transformer(content), extractor, candidates, seen)
193178
}
194179

195180
// ---

src/lib/setupContextUtils.js

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -559,16 +559,13 @@ function registerPlugins(plugins, context) {
559559
)
560560
}
561561

562-
context.safelist = function () {
563-
let safelist = (context.tailwindConfig.safelist ?? []).filter(Boolean)
564-
if (safelist.length <= 0) return []
565-
566-
let output = []
562+
let safelist = (context.tailwindConfig.safelist ?? []).filter(Boolean)
563+
if (safelist.length > 0) {
567564
let checks = []
568565

569566
for (let value of safelist) {
570567
if (typeof value === 'string') {
571-
output.push(value)
568+
context.changedContent.push({ content: value, extension: 'html' })
572569
continue
573570
}
574571

@@ -584,51 +581,52 @@ function registerPlugins(plugins, context) {
584581
checks.push(value)
585582
}
586583

587-
if (checks.length <= 0) return output.map((value) => ({ raw: value, extension: 'html' }))
588-
589-
let patternMatchingCount = new Map()
590-
591-
for (let util of classList) {
592-
let utils = Array.isArray(util)
593-
? (() => {
594-
let [utilName, options] = util
595-
return Object.keys(options?.values ?? {}).map((value) => formatClass(utilName, value))
596-
})()
597-
: [util]
598-
599-
for (let util of utils) {
600-
for (let { pattern, variants = [] } of checks) {
601-
// RegExp with the /g flag are stateful, so let's reset the last
602-
// index pointer to reset the state.
603-
pattern.lastIndex = 0
604-
605-
if (!patternMatchingCount.has(pattern)) {
606-
patternMatchingCount.set(pattern, 0)
607-
}
584+
if (checks.length > 0) {
585+
let patternMatchingCount = new Map()
586+
587+
for (let util of classList) {
588+
let utils = Array.isArray(util)
589+
? (() => {
590+
let [utilName, options] = util
591+
return Object.keys(options?.values ?? {}).map((value) => formatClass(utilName, value))
592+
})()
593+
: [util]
594+
595+
for (let util of utils) {
596+
for (let { pattern, variants = [] } of checks) {
597+
// RegExp with the /g flag are stateful, so let's reset the last
598+
// index pointer to reset the state.
599+
pattern.lastIndex = 0
600+
601+
if (!patternMatchingCount.has(pattern)) {
602+
patternMatchingCount.set(pattern, 0)
603+
}
608604

609-
if (!pattern.test(util)) continue
605+
if (!pattern.test(util)) continue
610606

611-
patternMatchingCount.set(pattern, patternMatchingCount.get(pattern) + 1)
607+
patternMatchingCount.set(pattern, patternMatchingCount.get(pattern) + 1)
612608

613-
output.push(util)
614-
for (let variant of variants) {
615-
output.push(variant + context.tailwindConfig.separator + util)
609+
context.changedContent.push({ content: util, extension: 'html' })
610+
for (let variant of variants) {
611+
context.changedContent.push({
612+
content: variant + context.tailwindConfig.separator + util,
613+
extension: 'html',
614+
})
615+
}
616616
}
617617
}
618618
}
619-
}
620619

621-
for (let [regex, count] of patternMatchingCount.entries()) {
622-
if (count !== 0) continue
620+
for (let [regex, count] of patternMatchingCount.entries()) {
621+
if (count !== 0) continue
623622

624-
log.warn([
625-
// TODO: Improve this warning message
626-
`You have a regex pattern in your "safelist" config (${regex}) that doesn't match any utilities.`,
627-
'For more info, visit https://tailwindcss.com/docs/...',
628-
])
623+
log.warn([
624+
// TODO: Improve this warning message
625+
`You have a regex pattern in your "safelist" config (${regex}) that doesn't match any utilities.`,
626+
'For more info, visit https://tailwindcss.com/docs/...',
627+
])
628+
}
629629
}
630-
631-
return output.map((value) => ({ raw: value, extension: 'html' }))
632630
}
633631

634632
// Generate a list of strings for autocompletion purposes, e.g.

src/lib/setupTrackingContext.js

Lines changed: 3 additions & 5 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,11 +77,9 @@ 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')
82-
.concat(context.tailwindConfig.content.safelist)
83-
.concat(context.safelist())
84-
.map(({ raw, extension }) => ({ content: raw, extension }))
82+
.map(({ raw, extension = 'html' }) => ({ content: raw, extension }))
8583

8684
for (let changedFile of resolveChangedFiles(candidateFiles, fileModifiedMap)) {
8785
let content = fs.readFileSync(changedFile, 'utf8')

src/lib/setupWatchingContext.js

Lines changed: 3 additions & 5 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,11 +185,9 @@ 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')
190-
.concat(context.tailwindConfig.content.safelist)
191-
.concat(context.safelist())
192-
.map(({ raw, extension }) => ({ content: raw, extension }))
190+
.map(({ raw, extension = 'html' }) => ({ content: raw, extension }))
193191

194192
for (let changedFile of resolveChangedFiles(context, candidateFiles)) {
195193
let content = fs.readFileSync(changedFile, 'utf8')

src/lib/sharedState.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import LRU from 'quick-lru'
2-
31
export const env = {
42
TAILWIND_MODE: process.env.TAILWIND_MODE,
53
NODE_ENV: process.env.NODE_ENV,
@@ -10,4 +8,3 @@ export const env = {
108
export const contextMap = new Map()
119
export const configContextMap = new Map()
1210
export const contextSourcesMap = new Map()
13-
export const contentMatchCache = new LRU({ maxSize: 25000 })

0 commit comments

Comments
 (0)