Skip to content
This repository was archived by the owner on Apr 6, 2021. It is now read-only.

Commit 1dea69d

Browse files
committed
Allow custom extractors
1 parent 1529bf1 commit 1dea69d

4 files changed

+106
-12
lines changed

src/lib/expandTailwindAtRules.js

+37-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const fs = require('fs')
2+
const path = require('path')
23
const fastGlob = require('fast-glob')
34
const sharedState = require('./sharedState')
45
const { generateRules } = require('./generateRules')
@@ -7,10 +8,39 @@ const { bigSign } = require('./utils')
78
let env = sharedState.env
89
let contentMatchCache = sharedState.contentMatchCache
910

11+
const BROAD_MATCH_GLOBAL_REGEXP = /[^<>"'`\s]*[^<>"'`\s:]/g
12+
const INNER_MATCH_GLOBAL_REGEXP = /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g
13+
14+
function defaultJitExtractor(content) {
15+
let broadMatches = content.match(BROAD_MATCH_GLOBAL_REGEXP) || []
16+
let innerMatches = content.match(INNER_MATCH_GLOBAL_REGEXP) || []
17+
18+
return [...broadMatches, ...innerMatches]
19+
}
20+
21+
function getExtractor(fileName, tailwindConfig) {
22+
const purgeOptions = tailwindConfig && tailwindConfig.purge && tailwindConfig.purge.options
23+
24+
if (!purgeOptions) {
25+
return defaultJitExtractor
26+
}
27+
28+
const fileExtension = path.extname(fileName).slice(1)
29+
const fileSpecificExtractor = (purgeOptions.extractors || []).find((extractor) =>
30+
extractor.extensions.includes(fileExtension)
31+
)
32+
33+
if (fileSpecificExtractor) {
34+
return fileSpecificExtractor.extractor
35+
}
36+
37+
return purgeOptions.defaultExtractor || defaultJitExtractor
38+
}
39+
1040
// Scans template contents for possible classes. This is a hot path on initial build but
1141
// not too important for subsequent builds. The faster the better though — if we can speed
1242
// up these regexes by 50% that could cut initial build time by like 20%.
13-
function getClassCandidates(content, contentMatchCache, candidates, seen) {
43+
function getClassCandidates(content, extractor, contentMatchCache, candidates, seen) {
1444
for (let line of content.split('\n')) {
1545
line = line.trim()
1646

@@ -24,20 +54,14 @@ function getClassCandidates(content, contentMatchCache, candidates, seen) {
2454
candidates.add(match)
2555
}
2656
} else {
27-
let allMatches = new Set()
28-
let broadMatches = line.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || []
29-
let innerMatches = line.match(/[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g) || []
57+
let extractorMatches = extractor(line)
58+
let lineMatchesSet = new Set(extractorMatches)
3059

31-
for (let match of broadMatches) {
32-
allMatches.add(match)
33-
candidates.add(match)
34-
}
35-
for (let match of innerMatches) {
36-
allMatches.add(match)
60+
for (let match of lineMatchesSet) {
3761
candidates.add(match)
3862
}
3963

40-
contentMatchCache.set(line, allMatches)
64+
contentMatchCache.set(line, lineMatchesSet)
4165
}
4266
}
4367
}
@@ -143,7 +167,8 @@ function expandTailwindAtRules(context, registerDependency) {
143167
env.DEBUG && console.time('Reading changed files')
144168
for (let file of context.changedFiles) {
145169
let content = fs.readFileSync(file, 'utf8')
146-
getClassCandidates(content, contentMatchCache, candidates, seen)
170+
let extractor = getExtractor(file, context.tailwindConfig)
171+
getClassCandidates(content, extractor, contentMatchCache, candidates, seen)
147172
}
148173
env.DEBUG && console.timeEnd('Reading changed files')
149174

tests/11-custom-extractors.test.css

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
* {
2+
--tw-shadow: 0 0 #0000;
3+
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
4+
--tw-ring-offset-width: 0px;
5+
--tw-ring-offset-color: #fff;
6+
--tw-ring-color: rgba(59, 130, 246, 0.5);
7+
--tw-ring-offset-shadow: 0 0 #0000;
8+
--tw-ring-shadow: 0 0 #0000;
9+
}
10+
.bg-white {
11+
--tw-bg-opacity: 1;
12+
background-color: rgba(255, 255, 255, var(--tw-bg-opacity));
13+
}
14+
.text-indigo-500 {
15+
--tw-text-opacity: 1;
16+
color: rgba(99, 102, 241, var(--tw-text-opacity));
17+
}

tests/11-custom-extractors.test.html

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" href="/favicon.ico" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Title</title>
8+
<link rel="stylesheet" href="./tailwind.css" />
9+
</head>
10+
<body>
11+
<div class="text-indigo-500 bg-white">hello world</div>
12+
<span>text-red-500 shouldn't appear in the output</span>
13+
</body>
14+
</html>

tests/11-custom-extractors.test.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const postcss = require('postcss')
2+
const tailwind = require('../src/index.js')
3+
const fs = require('fs')
4+
const path = require('path')
5+
6+
function run(input, config = {}) {
7+
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
8+
}
9+
10+
test('custom extractors', () => {
11+
let config = {
12+
purge: {
13+
content: [path.resolve(__dirname, './11-custom-extractors.test.html')],
14+
options: {
15+
defaultExtractor: (content) => {
16+
const matches = content.match(/class="([^"]+)"/)
17+
return matches ? matches[1].split(/\s+/) : []
18+
},
19+
},
20+
},
21+
corePlugins: { preflight: false },
22+
theme: {},
23+
plugins: [],
24+
}
25+
26+
let css = `
27+
@tailwind base;
28+
@tailwind components;
29+
@tailwind utilities;
30+
`
31+
32+
return run(css, config).then((result) => {
33+
let expectedPath = path.resolve(__dirname, './11-custom-extractors.test.css')
34+
let expected = fs.readFileSync(expectedPath, 'utf8')
35+
36+
expect(result.css).toMatchCss(expected)
37+
})
38+
})

0 commit comments

Comments
 (0)