Skip to content

Commit 5ddd9c4

Browse files
authored
Fallback to RegEx based parser when using custom transformers or extractors (tailwindlabs#11335)
* handle custom `transformer` and `extractor` implementations Right now the Rust based parser can't work with custom `transfomers` or `extractors`. In a perfect world we can implement as many custom parsers/extractors in Rust such that we don't need this at all. In an almost perfect world we can pass the transformer and extractor to the Rust based parser and call the callback functions to handle all of this. This is probably what we are going to do in the future but this requires more work to make sure that: 1. It works as expected 2. Doesn't result in (major) performance issues Since it currently doesn't work with the Rust based parser, we can implement a fix for this in the meantime before we reach the "perfect" solution. One solution to this problem is to check if we do have a custom transformer or a custom extractor and if we do, then we can bail on the Rust parser completely and just use the current regex based parser. An alternative solution, the solution implemented here, is that we group the `changedContent` into 2 buckets. The bucket where we rely on the default transformer and extractor and a bucket where a custom transformer or extractor is used. Then, the bucket where we use the default transformer and extractor can still rely on the way faster Rust based parser. For the other bucket we fallback to the regex based parser. The nice part about this is that we can use both parsers at the same time, and the majority of the use cases should use the faster Rust based parser. * update changelog
1 parent 5c7a8e9 commit 5ddd9c4

File tree

2 files changed

+42
-22
lines changed

2 files changed

+42
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- Ensure `repeating-conic-gradient` is detected as an image ([#11180](https://github.com/tailwindlabs/tailwindcss/pull/11180))
1616
- Remove `autoprefixer` dependency ([#11315](https://github.com/tailwindlabs/tailwindcss/pull/11315))
1717
- Fix source maps issue resulting in a crash ([#11319](https://github.com/tailwindlabs/tailwindcss/pull/11319))
18+
- Fallback to RegEx based parser when using custom transformers or extractors ([#11335](https://github.com/tailwindlabs/tailwindcss/pull/11335))
1819

1920
### Added
2021

src/lib/expandTailwindAtRules.js

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ function getExtractor(context, fileExtension) {
2626
extractors[fileExtension] ||
2727
extractors.DEFAULT ||
2828
builtInExtractors[fileExtension] ||
29-
builtInExtractors.DEFAULT(context)
29+
// Because we call `DEFAULT(context)`, the returning function is always a new function without a
30+
// stable identity. Marking it with `DEFAULT_EXTRACTOR` allows us to check if it is the default
31+
// extractor without relying on the function identity.
32+
Object.assign(builtInExtractors.DEFAULT(context), { DEFAULT_EXTRACTOR: true })
3033
)
3134
}
3235

@@ -133,20 +136,35 @@ export default function expandTailwindAtRules(context) {
133136
env.DEBUG && console.time('Reading changed files')
134137

135138
if (flagEnabled(context.tailwindConfig, 'oxideParser')) {
136-
// TODO: Pass through or implement `extractor`
137-
for (let candidate of parseCandidateStrings(
138-
context.changedContent,
139-
IO.Parallel | Parsing.Parallel
140-
// Object.assign({}, builtInTransformers, context.tailwindConfig.content.transform)
141-
)) {
142-
candidates.add(candidate)
139+
let rustParserContent = []
140+
let regexParserContent = []
141+
142+
for (let item of context.changedContent) {
143+
let transformer = getTransformer(context.tailwindConfig, item.extension)
144+
let extractor = getExtractor(context, item.extension)
145+
146+
if (transformer === builtInTransformers.DEFAULT && extractor?.DEFAULT_EXTRACTOR === true) {
147+
rustParserContent.push(item)
148+
} else {
149+
regexParserContent.push([item, { transformer, extractor }])
150+
}
151+
}
152+
153+
if (rustParserContent.length > 0) {
154+
for (let candidate of parseCandidateStrings(
155+
rustParserContent,
156+
IO.Parallel | Parsing.Parallel
157+
)) {
158+
candidates.add(candidate)
159+
}
143160
}
144161

145-
// for (let { file, content, extension } of context.changedContent) {
146-
// let transformer = getTransformer(context.tailwindConfig, extension)
147-
// let extractor = getExtractor(context, extension)
148-
// getClassCandidatesOxide(file, transformer(content), extractor, candidates, seen)
149-
// }
162+
if (regexParserContent.length > 0) {
163+
for (let [{ file, content }, { transformer, extractor }] of regexParserContent) {
164+
content = file ? fs.readFileSync(file, 'utf8') : content
165+
getClassCandidates(transformer(content), extractor, candidates, seen)
166+
}
167+
}
150168
} else {
151169
for (let { file, content, extension } of context.changedContent) {
152170
let transformer = getTransformer(context.tailwindConfig, extension)
@@ -165,15 +183,16 @@ export default function expandTailwindAtRules(context) {
165183

166184
env.DEBUG && console.time('Generate rules')
167185
env.DEBUG && console.time('Sorting candidates')
168-
let sortedCandidates = flagEnabled(context.tailwindConfig, 'oxideParser')
169-
? candidates
170-
: new Set(
171-
[...candidates].sort((a, z) => {
172-
if (a === z) return 0
173-
if (a < z) return -1
174-
return 1
175-
})
176-
)
186+
// TODO: only sort if we are not using the oxide parser (flagEnabled(context.tailwindConfig,
187+
// 'oxideParser')) AND if we got all the candidates form the oxideParser alone. This will not
188+
// be the case currently if you have custom transformers / extractors.
189+
let sortedCandidates = new Set(
190+
[...candidates].sort((a, z) => {
191+
if (a === z) return 0
192+
if (a < z) return -1
193+
return 1
194+
})
195+
)
177196
env.DEBUG && console.timeEnd('Sorting candidates')
178197
generateRules(sortedCandidates, context)
179198
env.DEBUG && console.timeEnd('Generate rules')

0 commit comments

Comments
 (0)