tailwindcss 3.0.5 → 3.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -3
- package/README.md +8 -4
- package/lib/css/preflight.css +1 -1
- package/lib/index.js +1 -3
- package/lib/lib/collapseDuplicateDeclarations.js +52 -1
- package/lib/lib/defaultExtractor.js +42 -0
- package/lib/lib/evaluateTailwindFunctions.js +1 -1
- package/lib/lib/expandApplyAtRules.js +87 -15
- package/lib/lib/expandTailwindAtRules.js +2 -24
- package/lib/lib/generateRules.js +4 -1
- package/lib/lib/sharedState.js +33 -4
- package/lib/util/color.js +23 -8
- package/lib/util/resolveConfig.js +9 -1
- package/lib/util/toPath.js +6 -1
- package/package.json +10 -11
- package/peers/index.js +75 -87
- package/src/css/preflight.css +1 -1
- package/src/index.js +1 -7
- package/src/lib/collapseDuplicateDeclarations.js +66 -1
- package/src/lib/defaultExtractor.js +48 -0
- package/src/lib/evaluateTailwindFunctions.js +1 -1
- package/src/lib/expandApplyAtRules.js +90 -19
- package/src/lib/expandTailwindAtRules.js +2 -23
- package/src/lib/generateRules.js +5 -1
- package/src/lib/sharedState.js +40 -4
- package/src/util/color.js +20 -7
- package/src/util/resolveConfig.js +11 -1
- package/src/util/toPath.js +23 -1
- package/stubs/defaultConfig.stub.js +2 -2
- package/lib/lib/setupWatchingContext.js +0 -288
- package/src/lib/setupWatchingContext.js +0 -311
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
import postcss from 'postcss'
|
|
2
2
|
import parser from 'postcss-selector-parser'
|
|
3
|
+
|
|
3
4
|
import { resolveMatches } from './generateRules'
|
|
4
5
|
import bigSign from '../util/bigSign'
|
|
5
6
|
import escapeClassName from '../util/escapeClassName'
|
|
6
7
|
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
function extractClasses(node) {
|
|
9
|
+
let classes = new Set()
|
|
10
|
+
let container = postcss.root({ nodes: [node.clone()] })
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
})
|
|
12
|
+
container.walkRules((rule) => {
|
|
13
|
+
parser((selectors) => {
|
|
14
|
+
selectors.walkClasses((classSelector) => {
|
|
15
|
+
classes.add(classSelector.value)
|
|
16
|
+
})
|
|
17
|
+
}).processSync(rule.selector)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return Array.from(classes)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function extractBaseCandidates(candidates, separator) {
|
|
24
|
+
let baseClasses = new Set()
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
for (let candidate of candidates) {
|
|
27
|
+
baseClasses.add(candidate.split(separator).pop())
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return Array.from(baseClasses)
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
function prefix(context, selector) {
|
|
@@ -212,15 +223,40 @@ function processApply(root, context) {
|
|
|
212
223
|
let siblings = []
|
|
213
224
|
|
|
214
225
|
for (let [applyCandidate, important, rules] of candidates) {
|
|
215
|
-
let base = applyCandidate.split(context.tailwindConfig.separator).pop()
|
|
216
|
-
|
|
217
226
|
for (let [meta, node] of rules) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
227
|
+
let parentClasses = extractClasses(parent)
|
|
228
|
+
let nodeClasses = extractClasses(node)
|
|
229
|
+
|
|
230
|
+
// Add base utility classes from the @apply node to the list of
|
|
231
|
+
// classes to check whether it intersects and therefore results in a
|
|
232
|
+
// circular dependency or not.
|
|
233
|
+
//
|
|
234
|
+
// E.g.:
|
|
235
|
+
// .foo {
|
|
236
|
+
// @apply hover:a; // This applies "a" but with a modifier
|
|
237
|
+
// }
|
|
238
|
+
//
|
|
239
|
+
// We only have to do that with base classes of the `node`, not of the `parent`
|
|
240
|
+
// E.g.:
|
|
241
|
+
// .hover\:foo {
|
|
242
|
+
// @apply bar;
|
|
243
|
+
// }
|
|
244
|
+
// .bar {
|
|
245
|
+
// @apply foo;
|
|
246
|
+
// }
|
|
247
|
+
//
|
|
248
|
+
// This should not result in a circular dependency because we are
|
|
249
|
+
// just applying `.foo` and the rule above is `.hover\:foo` which is
|
|
250
|
+
// unrelated. However, if we were to apply `hover:foo` then we _did_
|
|
251
|
+
// have to include this one.
|
|
252
|
+
nodeClasses = nodeClasses.concat(
|
|
253
|
+
extractBaseCandidates(nodeClasses, context.tailwindConfig.separator)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
let intersects = parentClasses.some((selector) => nodeClasses.includes(selector))
|
|
257
|
+
if (intersects) {
|
|
222
258
|
throw node.error(
|
|
223
|
-
`
|
|
259
|
+
`You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`
|
|
224
260
|
)
|
|
225
261
|
}
|
|
226
262
|
|
|
@@ -230,6 +266,42 @@ function processApply(root, context) {
|
|
|
230
266
|
|
|
231
267
|
if (canRewriteSelector) {
|
|
232
268
|
root.walkRules((rule) => {
|
|
269
|
+
// Let's imagine you have the following structure:
|
|
270
|
+
//
|
|
271
|
+
// .foo {
|
|
272
|
+
// @apply bar;
|
|
273
|
+
// }
|
|
274
|
+
//
|
|
275
|
+
// @supports (a: b) {
|
|
276
|
+
// .bar {
|
|
277
|
+
// color: blue
|
|
278
|
+
// }
|
|
279
|
+
//
|
|
280
|
+
// .something-unrelated {}
|
|
281
|
+
// }
|
|
282
|
+
//
|
|
283
|
+
// In this case we want to apply `.bar` but it happens to be in
|
|
284
|
+
// an atrule node. We clone that node instead of the nested one
|
|
285
|
+
// because we still want that @supports rule to be there once we
|
|
286
|
+
// applied everything.
|
|
287
|
+
//
|
|
288
|
+
// However it happens to be that the `.something-unrelated` is
|
|
289
|
+
// also in that same shared @supports atrule. This is not good,
|
|
290
|
+
// and this should not be there. The good part is that this is
|
|
291
|
+
// a clone already and it can be safely removed. The question is
|
|
292
|
+
// how do we know we can remove it. Basically what we can do is
|
|
293
|
+
// match it against the applyCandidate that you want to apply. If
|
|
294
|
+
// it doesn't match the we can safely delete it.
|
|
295
|
+
//
|
|
296
|
+
// If we didn't do this, then the `replaceSelector` function
|
|
297
|
+
// would have replaced this with something that didn't exist and
|
|
298
|
+
// therefore it removed the selector altogether. In this specific
|
|
299
|
+
// case it would result in `{}` instead of `.something-unrelated {}`
|
|
300
|
+
if (!extractClasses(rule).some((candidate) => candidate === applyCandidate)) {
|
|
301
|
+
rule.remove()
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
|
|
233
305
|
rule.selector = replaceSelector(parent.selector, rule.selector, applyCandidate)
|
|
234
306
|
|
|
235
307
|
rule.walkDecls((d) => {
|
|
@@ -250,7 +322,6 @@ function processApply(root, context) {
|
|
|
250
322
|
// Inject the rules, sorted, correctly
|
|
251
323
|
let nodes = siblings.sort(([a], [z]) => bigSign(a.sort - z.sort)).map((s) => s[1])
|
|
252
324
|
|
|
253
|
-
// console.log(parent)
|
|
254
325
|
// `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
|
|
255
326
|
parent.after(nodes)
|
|
256
327
|
}
|
|
@@ -3,33 +3,12 @@ import * as sharedState from './sharedState'
|
|
|
3
3
|
import { generateRules } from './generateRules'
|
|
4
4
|
import bigSign from '../util/bigSign'
|
|
5
5
|
import cloneNodes from '../util/cloneNodes'
|
|
6
|
+
import { defaultExtractor } from './defaultExtractor'
|
|
6
7
|
|
|
7
8
|
let env = sharedState.env
|
|
8
9
|
|
|
9
|
-
const PATTERNS = [
|
|
10
|
-
/([^<>"'`\s]*\[\w*'[^"`\s]*'?\])/.source, // font-['some_font',sans-serif]
|
|
11
|
-
/([^<>"'`\s]*\[\w*"[^"`\s]*"?\])/.source, // font-["some_font",sans-serif]
|
|
12
|
-
/([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')]
|
|
13
|
-
/([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")]
|
|
14
|
-
/([^<>"'`\s]*\[\w*\('[^"`\s]*'\)\])/.source, // bg-[url('...'),url('...')]
|
|
15
|
-
/([^<>"'`\s]*\[\w*\("[^'`\s]*"\)\])/.source, // bg-[url("..."),url("...")]
|
|
16
|
-
/([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
|
|
17
|
-
/([^<>"'`\s]*\["[^"'`\s]*"\])/.source, // `content-["hello"]` but not `content-["hello"]"]`
|
|
18
|
-
/([^<>"'`\s]*\[[^<>"'`\s]*:'[^"'`\s]*'\])/.source, // `[content:'hello']` but not `[content:"hello"]`
|
|
19
|
-
/([^<>"'`\s]*\[[^<>"'`\s]*:"[^"'`\s]*"\])/.source, // `[content:"hello"]` but not `[content:'hello']`
|
|
20
|
-
/([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
|
|
21
|
-
/([^<>"'`\s]*[^"'`\s:])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
|
|
22
|
-
].join('|')
|
|
23
|
-
const BROAD_MATCH_GLOBAL_REGEXP = new RegExp(PATTERNS, 'g')
|
|
24
|
-
const INNER_MATCH_GLOBAL_REGEXP = /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g
|
|
25
|
-
|
|
26
10
|
const builtInExtractors = {
|
|
27
|
-
DEFAULT:
|
|
28
|
-
let broadMatches = content.match(BROAD_MATCH_GLOBAL_REGEXP) || []
|
|
29
|
-
let innerMatches = content.match(INNER_MATCH_GLOBAL_REGEXP) || []
|
|
30
|
-
|
|
31
|
-
return [...broadMatches, ...innerMatches]
|
|
32
|
-
},
|
|
11
|
+
DEFAULT: defaultExtractor,
|
|
33
12
|
}
|
|
34
13
|
|
|
35
14
|
const builtInTransformers = {
|
package/src/lib/generateRules.js
CHANGED
|
@@ -112,6 +112,11 @@ function applyVariant(variant, matches, context) {
|
|
|
112
112
|
let result = []
|
|
113
113
|
|
|
114
114
|
for (let [meta, rule] of matches) {
|
|
115
|
+
// Don't generate variants for user css
|
|
116
|
+
if (meta.layer === 'user') {
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
let container = postcss.root({ nodes: [rule.clone()] })
|
|
116
121
|
|
|
117
122
|
for (let [variantSort, variantFunction] of variantFunctionTuples) {
|
|
@@ -325,7 +330,6 @@ function* resolveMatchedPlugins(classCandidate, context) {
|
|
|
325
330
|
for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) {
|
|
326
331
|
if (context.candidateRuleMap.has(prefix)) {
|
|
327
332
|
yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]
|
|
328
|
-
return
|
|
329
333
|
}
|
|
330
334
|
}
|
|
331
335
|
}
|
package/src/lib/sharedState.js
CHANGED
|
@@ -1,10 +1,46 @@
|
|
|
1
1
|
export const env = {
|
|
2
|
-
TAILWIND_MODE: process.env.TAILWIND_MODE,
|
|
3
2
|
NODE_ENV: process.env.NODE_ENV,
|
|
4
|
-
DEBUG: process.env.DEBUG
|
|
5
|
-
TAILWIND_DISABLE_TOUCH: process.env.TAILWIND_DISABLE_TOUCH !== undefined,
|
|
6
|
-
TAILWIND_TOUCH_DIR: process.env.TAILWIND_TOUCH_DIR,
|
|
3
|
+
DEBUG: resolveDebug(process.env.DEBUG),
|
|
7
4
|
}
|
|
8
5
|
export const contextMap = new Map()
|
|
9
6
|
export const configContextMap = new Map()
|
|
10
7
|
export const contextSourcesMap = new Map()
|
|
8
|
+
|
|
9
|
+
export function resolveDebug(debug) {
|
|
10
|
+
if (debug === undefined) {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Environment variables are strings, so convert to boolean
|
|
15
|
+
if (debug === 'true' || debug === '1') {
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (debug === 'false' || debug === '0') {
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Keep the debug convention into account:
|
|
24
|
+
// DEBUG=* -> This enables all debug modes
|
|
25
|
+
// DEBUG=projectA,projectB,projectC -> This enables debug for projectA, projectB and projectC
|
|
26
|
+
// DEBUG=projectA:* -> This enables all debug modes for projectA (if you have sub-types)
|
|
27
|
+
// DEBUG=projectA,-projectB -> This enables debug for projectA and explicitly disables it for projectB
|
|
28
|
+
|
|
29
|
+
if (debug === '*') {
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let debuggers = debug.split(',').map((d) => d.split(':')[0])
|
|
34
|
+
|
|
35
|
+
// Ignoring tailwindcss
|
|
36
|
+
if (debuggers.includes('-tailwindcss')) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Including tailwindcss
|
|
41
|
+
if (debuggers.includes('tailwindcss')) {
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return false
|
|
46
|
+
}
|
package/src/util/color.js
CHANGED
|
@@ -5,8 +5,11 @@ let SHORT_HEX = /^#([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i
|
|
|
5
5
|
let VALUE = `(?:\\d+|\\d*\\.\\d+)%?`
|
|
6
6
|
let SEP = `(?:\\s*,\\s*|\\s+)`
|
|
7
7
|
let ALPHA_SEP = `\\s*[,/]\\s*`
|
|
8
|
-
let
|
|
9
|
-
`^
|
|
8
|
+
let RGB = new RegExp(
|
|
9
|
+
`^rgba?\\(\\s*(${VALUE})${SEP}(${VALUE})${SEP}(${VALUE})(?:${ALPHA_SEP}(${VALUE}))?\\s*\\)$`
|
|
10
|
+
)
|
|
11
|
+
let HSL = new RegExp(
|
|
12
|
+
`^hsla?\\(\\s*((?:${VALUE})(?:deg|rad|grad|turn)?)${SEP}(${VALUE})${SEP}(${VALUE})(?:${ALPHA_SEP}(${VALUE}))?\\s*\\)$`
|
|
10
13
|
)
|
|
11
14
|
|
|
12
15
|
export function parseColor(value) {
|
|
@@ -37,13 +40,23 @@ export function parseColor(value) {
|
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
let
|
|
43
|
+
let rgbMatch = value.match(RGB)
|
|
44
|
+
|
|
45
|
+
if (rgbMatch !== null) {
|
|
46
|
+
return {
|
|
47
|
+
mode: 'rgb',
|
|
48
|
+
color: [rgbMatch[1], rgbMatch[2], rgbMatch[3]].map((v) => v.toString()),
|
|
49
|
+
alpha: rgbMatch[4]?.toString?.(),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let hslMatch = value.match(HSL)
|
|
41
54
|
|
|
42
|
-
if (
|
|
55
|
+
if (hslMatch !== null) {
|
|
43
56
|
return {
|
|
44
|
-
mode:
|
|
45
|
-
color: [
|
|
46
|
-
alpha:
|
|
57
|
+
mode: 'hsl',
|
|
58
|
+
color: [hslMatch[1], hslMatch[2], hslMatch[3]].map((v) => v.toString()),
|
|
59
|
+
alpha: hslMatch[4]?.toString?.(),
|
|
47
60
|
}
|
|
48
61
|
}
|
|
49
62
|
|
|
@@ -6,6 +6,8 @@ import colors from '../public/colors'
|
|
|
6
6
|
import { defaults } from './defaults'
|
|
7
7
|
import { toPath } from './toPath'
|
|
8
8
|
import { normalizeConfig } from './normalizeConfig'
|
|
9
|
+
import isPlainObject from './isPlainObject'
|
|
10
|
+
import { cloneDeep } from './cloneDeep'
|
|
9
11
|
|
|
10
12
|
function isFunction(input) {
|
|
11
13
|
return typeof input === 'function'
|
|
@@ -144,7 +146,15 @@ function resolveFunctionKeys(object) {
|
|
|
144
146
|
val = isFunction(val) ? val(resolvePath, configUtils) : val
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
|
|
149
|
+
if (val === undefined) {
|
|
150
|
+
return defaultValue
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (isPlainObject(val)) {
|
|
154
|
+
return cloneDeep(val)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return val
|
|
148
158
|
}
|
|
149
159
|
|
|
150
160
|
resolvePath.theme = resolvePath
|
package/src/util/toPath.js
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a path string into an array of path segments.
|
|
3
|
+
*
|
|
4
|
+
* Square bracket notation `a[b]` may be used to "escape" dots that would otherwise be interpreted as path separators.
|
|
5
|
+
*
|
|
6
|
+
* Example:
|
|
7
|
+
* a -> ['a]
|
|
8
|
+
* a.b.c -> ['a', 'b', 'c']
|
|
9
|
+
* a[b].c -> ['a', 'b', 'c']
|
|
10
|
+
* a[b.c].e.f -> ['a', 'b.c', 'e', 'f']
|
|
11
|
+
* a[b][c][d] -> ['a', 'b', 'c', 'd']
|
|
12
|
+
*
|
|
13
|
+
* @param {string|string[]} path
|
|
14
|
+
**/
|
|
1
15
|
export function toPath(path) {
|
|
2
16
|
if (Array.isArray(path)) return path
|
|
3
|
-
|
|
17
|
+
|
|
18
|
+
let openBrackets = path.split('[').length - 1
|
|
19
|
+
let closedBrackets = path.split(']').length - 1
|
|
20
|
+
|
|
21
|
+
if (openBrackets !== closedBrackets) {
|
|
22
|
+
throw new Error(`Path is invalid. Has unbalanced brackets: ${path}`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return path.split(/\.(?![^\[]*\])|[\[\]]/g).filter(Boolean)
|
|
4
26
|
}
|
|
@@ -854,8 +854,8 @@ module.exports = {
|
|
|
854
854
|
none: 'none',
|
|
855
855
|
all: 'all',
|
|
856
856
|
DEFAULT:
|
|
857
|
-
'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter',
|
|
858
|
-
colors: 'background-color, border-color, color, fill, stroke',
|
|
857
|
+
'color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter',
|
|
858
|
+
colors: 'color, background-color, border-color, text-decoration-color, fill, stroke',
|
|
859
859
|
opacity: 'opacity',
|
|
860
860
|
shadow: 'box-shadow',
|
|
861
861
|
transform: 'transform',
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
exports.default = setupWatchingContext;
|
|
6
|
-
var _fs = _interopRequireDefault(require("fs"));
|
|
7
|
-
var _path = _interopRequireDefault(require("path"));
|
|
8
|
-
var _tmp = _interopRequireDefault(require("tmp"));
|
|
9
|
-
var _chokidar = _interopRequireDefault(require("chokidar"));
|
|
10
|
-
var _fastGlob = _interopRequireDefault(require("fast-glob"));
|
|
11
|
-
var _quickLru = _interopRequireDefault(require("quick-lru"));
|
|
12
|
-
var _normalizePath = _interopRequireDefault(require("normalize-path"));
|
|
13
|
-
var _hashConfig = _interopRequireDefault(require("../util/hashConfig"));
|
|
14
|
-
var _log = _interopRequireDefault(require("../util/log"));
|
|
15
|
-
var _getModuleDependencies = _interopRequireDefault(require("../lib/getModuleDependencies"));
|
|
16
|
-
var _resolveConfig = _interopRequireDefault(require("../public/resolve-config"));
|
|
17
|
-
var _resolveConfigPath = _interopRequireDefault(require("../util/resolveConfigPath"));
|
|
18
|
-
var _setupContextUtils = require("./setupContextUtils");
|
|
19
|
-
function _interopRequireDefault(obj) {
|
|
20
|
-
return obj && obj.__esModule ? obj : {
|
|
21
|
-
default: obj
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
// This is used to trigger rebuilds. Just updating the timestamp
|
|
25
|
-
// is significantly faster than actually writing to the file (10x).
|
|
26
|
-
function touch(filename) {
|
|
27
|
-
let time = new Date();
|
|
28
|
-
try {
|
|
29
|
-
_fs.default.utimesSync(filename, time, time);
|
|
30
|
-
} catch (err) {
|
|
31
|
-
_fs.default.closeSync(_fs.default.openSync(filename, 'w'));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
let watchers = new WeakMap();
|
|
35
|
-
function getWatcher(context) {
|
|
36
|
-
if (watchers.has(context)) {
|
|
37
|
-
return watchers.get(context);
|
|
38
|
-
}
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
function setWatcher(context, watcher) {
|
|
42
|
-
return watchers.set(context, watcher);
|
|
43
|
-
}
|
|
44
|
-
let touchFiles = new WeakMap();
|
|
45
|
-
function getTouchFile(context) {
|
|
46
|
-
if (touchFiles.has(context)) {
|
|
47
|
-
return touchFiles.get(context);
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
function setTouchFile(context, touchFile) {
|
|
52
|
-
return touchFiles.set(context, touchFile);
|
|
53
|
-
}
|
|
54
|
-
let configPaths = new WeakMap();
|
|
55
|
-
function getConfigPath(context, configOrPath) {
|
|
56
|
-
if (!configPaths.has(context)) {
|
|
57
|
-
configPaths.set(context, (0, _resolveConfigPath).default(configOrPath));
|
|
58
|
-
}
|
|
59
|
-
return configPaths.get(context);
|
|
60
|
-
}
|
|
61
|
-
function rebootWatcher(context, configPath, configDependencies, candidateFiles) {
|
|
62
|
-
let touchFile = getTouchFile(context);
|
|
63
|
-
if (touchFile === null) {
|
|
64
|
-
touchFile = _tmp.default.fileSync().name;
|
|
65
|
-
setTouchFile(context, touchFile);
|
|
66
|
-
touch(touchFile);
|
|
67
|
-
}
|
|
68
|
-
let watcher = getWatcher(context);
|
|
69
|
-
Promise.resolve(watcher ? watcher.close() : null).then(()=>{
|
|
70
|
-
_log.default.info([
|
|
71
|
-
'Tailwind CSS is watching for changes...',
|
|
72
|
-
'https://tailwindcss.com/docs/just-in-time-mode#watch-mode-and-one-off-builds',
|
|
73
|
-
]);
|
|
74
|
-
watcher = _chokidar.default.watch([
|
|
75
|
-
...candidateFiles,
|
|
76
|
-
...configDependencies
|
|
77
|
-
], {
|
|
78
|
-
ignoreInitial: true,
|
|
79
|
-
awaitWriteFinish: process.platform === 'win32' ? {
|
|
80
|
-
stabilityThreshold: 50,
|
|
81
|
-
pollInterval: 10
|
|
82
|
-
} : false
|
|
83
|
-
});
|
|
84
|
-
setWatcher(context, watcher);
|
|
85
|
-
watcher.on('add', (file)=>{
|
|
86
|
-
let changedFile = _path.default.resolve('.', file);
|
|
87
|
-
let content = _fs.default.readFileSync(changedFile, 'utf8');
|
|
88
|
-
let extension = _path.default.extname(changedFile).slice(1);
|
|
89
|
-
context.changedContent.push({
|
|
90
|
-
content,
|
|
91
|
-
extension
|
|
92
|
-
});
|
|
93
|
-
touch(touchFile);
|
|
94
|
-
});
|
|
95
|
-
watcher.on('change', (file)=>{
|
|
96
|
-
// If it was a config dependency, touch the config file to trigger a new context.
|
|
97
|
-
// This is not really that clean of a solution but it's the fastest, because we
|
|
98
|
-
// can do a very quick check on each build to see if the config has changed instead
|
|
99
|
-
// of having to get all of the module dependencies and check every timestamp each
|
|
100
|
-
// time.
|
|
101
|
-
if (configDependencies.has(file)) {
|
|
102
|
-
for (let dependency of configDependencies){
|
|
103
|
-
delete require.cache[require.resolve(dependency)];
|
|
104
|
-
}
|
|
105
|
-
touch(configPath);
|
|
106
|
-
} else {
|
|
107
|
-
let changedFile = _path.default.resolve('.', file);
|
|
108
|
-
let content = _fs.default.readFileSync(changedFile, 'utf8');
|
|
109
|
-
let extension = _path.default.extname(changedFile).slice(1);
|
|
110
|
-
context.changedContent.push({
|
|
111
|
-
content,
|
|
112
|
-
extension
|
|
113
|
-
});
|
|
114
|
-
touch(touchFile);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
watcher.on('unlink', (file)=>{
|
|
118
|
-
// Touch the config file if any of the dependencies are deleted.
|
|
119
|
-
if (configDependencies.has(file)) {
|
|
120
|
-
for (let dependency of configDependencies){
|
|
121
|
-
delete require.cache[require.resolve(dependency)];
|
|
122
|
-
}
|
|
123
|
-
touch(configPath);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
let configPathCache = new _quickLru.default({
|
|
129
|
-
maxSize: 100
|
|
130
|
-
});
|
|
131
|
-
let configDependenciesCache = new WeakMap();
|
|
132
|
-
function getConfigDependencies(context) {
|
|
133
|
-
if (!configDependenciesCache.has(context)) {
|
|
134
|
-
configDependenciesCache.set(context, new Set());
|
|
135
|
-
}
|
|
136
|
-
return configDependenciesCache.get(context);
|
|
137
|
-
}
|
|
138
|
-
let candidateFilesCache = new WeakMap();
|
|
139
|
-
function getCandidateFiles(context, tailwindConfig) {
|
|
140
|
-
if (candidateFilesCache.has(context)) {
|
|
141
|
-
return candidateFilesCache.get(context);
|
|
142
|
-
}
|
|
143
|
-
let candidateFiles = tailwindConfig.content.files.filter((item)=>typeof item === 'string'
|
|
144
|
-
).map((contentPath)=>(0, _normalizePath).default(contentPath)
|
|
145
|
-
);
|
|
146
|
-
return candidateFilesCache.set(context, candidateFiles).get(context);
|
|
147
|
-
}
|
|
148
|
-
// Get the config object based on a path
|
|
149
|
-
function getTailwindConfig(configOrPath) {
|
|
150
|
-
let userConfigPath = (0, _resolveConfigPath).default(configOrPath);
|
|
151
|
-
if (userConfigPath !== null) {
|
|
152
|
-
let [prevConfig, prevModified = -Infinity, prevConfigHash] = configPathCache.get(userConfigPath) || [];
|
|
153
|
-
let modified = _fs.default.statSync(userConfigPath).mtimeMs;
|
|
154
|
-
// It hasn't changed (based on timestamp)
|
|
155
|
-
if (modified <= prevModified) {
|
|
156
|
-
return [
|
|
157
|
-
prevConfig,
|
|
158
|
-
userConfigPath,
|
|
159
|
-
prevConfigHash,
|
|
160
|
-
[
|
|
161
|
-
userConfigPath
|
|
162
|
-
]
|
|
163
|
-
];
|
|
164
|
-
}
|
|
165
|
-
// It has changed (based on timestamp), or first run
|
|
166
|
-
delete require.cache[userConfigPath];
|
|
167
|
-
let newConfig = (0, _resolveConfig).default(require(userConfigPath));
|
|
168
|
-
let newHash = (0, _hashConfig).default(newConfig);
|
|
169
|
-
configPathCache.set(userConfigPath, [
|
|
170
|
-
newConfig,
|
|
171
|
-
modified,
|
|
172
|
-
newHash
|
|
173
|
-
]);
|
|
174
|
-
return [
|
|
175
|
-
newConfig,
|
|
176
|
-
userConfigPath,
|
|
177
|
-
newHash,
|
|
178
|
-
[
|
|
179
|
-
userConfigPath
|
|
180
|
-
]
|
|
181
|
-
];
|
|
182
|
-
}
|
|
183
|
-
// It's a plain object, not a path
|
|
184
|
-
let newConfig = (0, _resolveConfig).default(configOrPath.config === undefined ? configOrPath : configOrPath.config);
|
|
185
|
-
return [
|
|
186
|
-
newConfig,
|
|
187
|
-
null,
|
|
188
|
-
(0, _hashConfig).default(newConfig),
|
|
189
|
-
[]
|
|
190
|
-
];
|
|
191
|
-
}
|
|
192
|
-
function resolvedChangedContent(context, candidateFiles) {
|
|
193
|
-
let changedContent = context.tailwindConfig.content.files.filter((item)=>typeof item.raw === 'string'
|
|
194
|
-
).map(({ raw , extension ='html' })=>({
|
|
195
|
-
content: raw,
|
|
196
|
-
extension
|
|
197
|
-
})
|
|
198
|
-
);
|
|
199
|
-
for (let changedFile of resolveChangedFiles(context, candidateFiles)){
|
|
200
|
-
let content = _fs.default.readFileSync(changedFile, 'utf8');
|
|
201
|
-
let extension = _path.default.extname(changedFile).slice(1);
|
|
202
|
-
changedContent.push({
|
|
203
|
-
content,
|
|
204
|
-
extension
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
return changedContent;
|
|
208
|
-
}
|
|
209
|
-
let scannedContentCache = new WeakMap();
|
|
210
|
-
function resolveChangedFiles(context, candidateFiles) {
|
|
211
|
-
let changedFiles = new Set();
|
|
212
|
-
// If we're not set up and watching files ourselves, we need to do
|
|
213
|
-
// the work of grabbing all of the template files for candidate
|
|
214
|
-
// detection.
|
|
215
|
-
if (!scannedContentCache.has(context)) {
|
|
216
|
-
let files = _fastGlob.default.sync(candidateFiles);
|
|
217
|
-
for (let file of files){
|
|
218
|
-
changedFiles.add(file);
|
|
219
|
-
}
|
|
220
|
-
scannedContentCache.set(context, true);
|
|
221
|
-
}
|
|
222
|
-
return changedFiles;
|
|
223
|
-
}
|
|
224
|
-
function setupWatchingContext(configOrPath) {
|
|
225
|
-
return ({ tailwindDirectives , registerDependency })=>{
|
|
226
|
-
return (root, result)=>{
|
|
227
|
-
let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = getTailwindConfig(configOrPath);
|
|
228
|
-
let contextDependencies = new Set(configDependencies);
|
|
229
|
-
// If there are no @tailwind rules, we don't consider this CSS file or it's dependencies
|
|
230
|
-
// to be dependencies of the context. Can reuse the context even if they change.
|
|
231
|
-
// We may want to think about `@layer` being part of this trigger too, but it's tough
|
|
232
|
-
// because it's impossible for a layer in one file to end up in the actual @tailwind rule
|
|
233
|
-
// in another file since independent sources are effectively isolated.
|
|
234
|
-
if (tailwindDirectives.size > 0) {
|
|
235
|
-
// Add current css file as a context dependencies.
|
|
236
|
-
contextDependencies.add(result.opts.from);
|
|
237
|
-
// Add all css @import dependencies as context dependencies.
|
|
238
|
-
for (let message of result.messages){
|
|
239
|
-
if (message.type === 'dependency') {
|
|
240
|
-
contextDependencies.add(message.file);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
let [context, isNewContext] = (0, _setupContextUtils).getContext(root, result, tailwindConfig, userConfigPath, tailwindConfigHash, contextDependencies);
|
|
245
|
-
let candidateFiles = getCandidateFiles(context, tailwindConfig);
|
|
246
|
-
let contextConfigDependencies = getConfigDependencies(context);
|
|
247
|
-
for (let file of configDependencies){
|
|
248
|
-
registerDependency({
|
|
249
|
-
type: 'dependency',
|
|
250
|
-
file
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
context.disposables.push((oldContext)=>{
|
|
254
|
-
let watcher = getWatcher(oldContext);
|
|
255
|
-
if (watcher !== null) {
|
|
256
|
-
watcher.close();
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
let configPath = getConfigPath(context, configOrPath);
|
|
260
|
-
if (configPath !== null) {
|
|
261
|
-
for (let dependency of (0, _getModuleDependencies).default(configPath)){
|
|
262
|
-
if (dependency.file === configPath) {
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
contextConfigDependencies.add(dependency.file);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (isNewContext) {
|
|
269
|
-
rebootWatcher(context, configPath, contextConfigDependencies, candidateFiles);
|
|
270
|
-
}
|
|
271
|
-
// Register our temp file as a dependency — we write to this file
|
|
272
|
-
// to trigger rebuilds.
|
|
273
|
-
let touchFile = getTouchFile(context);
|
|
274
|
-
if (touchFile) {
|
|
275
|
-
registerDependency({
|
|
276
|
-
type: 'dependency',
|
|
277
|
-
file: touchFile
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
if (tailwindDirectives.size > 0) {
|
|
281
|
-
for (let changedContent of resolvedChangedContent(context, candidateFiles)){
|
|
282
|
-
context.changedContent.push(changedContent);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return context;
|
|
286
|
-
};
|
|
287
|
-
};
|
|
288
|
-
}
|