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

Commit e82bb0f

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

4 files changed

+102
-12
lines changed

src/lib/expandTailwindAtRules.js

+33-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,35 @@ 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 fileSpecificExtension = (tailwindConfig.extractors || []).find((extractor) =>
30+
extractor.extensions.includes(fileExtension)
31+
)
32+
33+
return fileSpecificExtension || purgeOptions.defaultExtractor || defaultJitExtractor
34+
}
35+
1036
// Scans template contents for possible classes. This is a hot path on initial build but
1137
// not too important for subsequent builds. The faster the better though — if we can speed
1238
// up these regexes by 50% that could cut initial build time by like 20%.
13-
function getClassCandidates(content, contentMatchCache, candidates, seen) {
39+
function getClassCandidates(content, extractor, contentMatchCache, candidates, seen) {
1440
for (let line of content.split('\n')) {
1541
line = line.trim()
1642

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

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)
56+
for (let match of lineMatchesSet) {
3757
candidates.add(match)
3858
}
3959

40-
contentMatchCache.set(line, allMatches)
60+
contentMatchCache.set(line, lineMatchesSet)
4161
}
4262
}
4363
}
@@ -143,7 +163,8 @@ function expandTailwindAtRules(context, registerDependency) {
143163
env.DEBUG && console.time('Reading changed files')
144164
for (let file of context.changedFiles) {
145165
let content = fs.readFileSync(file, 'utf8')
146-
getClassCandidates(content, contentMatchCache, candidates, seen)
166+
let extractor = getExtractor(file, context.tailwindConfig)
167+
getClassCandidates(content, extractor, contentMatchCache, candidates, seen)
147168
}
148169
env.DEBUG && console.timeEnd('Reading changed files')
149170

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)