tailwindcss 3.1.8 → 3.2.0
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/README.md +6 -5
- package/lib/cli/build/deps.js +54 -0
- package/lib/cli/build/index.js +44 -0
- package/lib/cli/build/plugin.js +335 -0
- package/lib/cli/build/utils.js +78 -0
- package/lib/cli/build/watching.js +113 -0
- package/lib/cli/help/index.js +71 -0
- package/lib/cli/index.js +18 -0
- package/lib/cli/init/index.js +46 -0
- package/lib/cli/shared.js +12 -0
- package/lib/cli.js +11 -590
- package/lib/corePlugins.js +332 -108
- package/lib/css/preflight.css +5 -0
- package/lib/featureFlags.js +7 -4
- package/lib/index.js +6 -1
- package/lib/lib/content.js +167 -0
- package/lib/lib/defaultExtractor.js +15 -10
- package/lib/lib/detectNesting.js +2 -2
- package/lib/lib/evaluateTailwindFunctions.js +17 -1
- package/lib/lib/expandApplyAtRules.js +66 -37
- package/lib/lib/expandTailwindAtRules.js +10 -42
- package/lib/lib/findAtConfigPath.js +44 -0
- package/lib/lib/generateRules.js +180 -93
- package/lib/lib/normalizeTailwindDirectives.js +1 -1
- package/lib/lib/offsets.js +217 -0
- package/lib/lib/regex.js +1 -1
- package/lib/lib/setupContextUtils.js +339 -100
- package/lib/lib/setupTrackingContext.js +5 -39
- package/lib/lib/sharedState.js +2 -0
- package/lib/public/colors.js +1 -1
- package/lib/util/buildMediaQuery.js +6 -3
- package/lib/util/configurePlugins.js +1 -1
- package/lib/util/dataTypes.js +15 -19
- package/lib/util/formatVariantSelector.js +92 -8
- package/lib/util/getAllConfigs.js +14 -3
- package/lib/util/isValidArbitraryValue.js +1 -1
- package/lib/util/nameClass.js +3 -0
- package/lib/util/negateValue.js +15 -2
- package/lib/util/normalizeConfig.js +17 -3
- package/lib/util/normalizeScreens.js +100 -3
- package/lib/util/parseAnimationValue.js +1 -1
- package/lib/util/parseBoxShadowValue.js +1 -1
- package/lib/util/parseDependency.js +33 -54
- package/lib/util/parseGlob.js +34 -0
- package/lib/util/parseObjectStyles.js +1 -1
- package/lib/util/pluginUtils.js +86 -17
- package/lib/util/resolveConfig.js +2 -2
- package/lib/util/splitAtTopLevelOnly.js +31 -81
- package/lib/util/transformThemeValue.js +9 -2
- package/lib/util/validateConfig.js +1 -1
- package/lib/util/validateFormalSyntax.js +24 -0
- package/package.json +13 -11
- package/peers/.DS_Store +0 -0
- package/peers/.svgo.yml +75 -0
- package/peers/index.js +3332 -2032
- package/peers/orders/concentric-css.json +299 -0
- package/peers/orders/smacss.json +299 -0
- package/peers/orders/source.json +295 -0
- package/plugin.d.ts +3 -3
- package/scripts/release-channel.js +18 -0
- package/scripts/release-notes.js +21 -0
- package/src/.DS_Store +0 -0
- package/src/cli/build/deps.js +56 -0
- package/src/cli/build/index.js +45 -0
- package/src/cli/build/plugin.js +397 -0
- package/src/cli/build/utils.js +76 -0
- package/src/cli/build/watching.js +134 -0
- package/src/cli/help/index.js +70 -0
- package/src/cli/index.js +3 -0
- package/src/cli/init/index.js +50 -0
- package/src/cli/shared.js +5 -0
- package/src/cli.js +4 -696
- package/src/corePlugins.js +262 -39
- package/src/css/preflight.css +5 -0
- package/src/featureFlags.js +12 -2
- package/src/index.js +5 -0
- package/src/lib/content.js +205 -0
- package/src/lib/defaultExtractor.js +3 -0
- package/src/lib/evaluateTailwindFunctions.js +22 -1
- package/src/lib/expandApplyAtRules.js +70 -29
- package/src/lib/expandTailwindAtRules.js +8 -46
- package/src/lib/findAtConfigPath.js +48 -0
- package/src/lib/generateRules.js +223 -101
- package/src/lib/offsets.js +270 -0
- package/src/lib/setupContextUtils.js +376 -89
- package/src/lib/setupTrackingContext.js +4 -45
- package/src/lib/sharedState.js +2 -0
- package/src/util/buildMediaQuery.js +5 -3
- package/src/util/dataTypes.js +15 -17
- package/src/util/formatVariantSelector.js +113 -9
- package/src/util/getAllConfigs.js +14 -2
- package/src/util/nameClass.js +4 -0
- package/src/util/negateValue.js +10 -2
- package/src/util/normalizeConfig.js +22 -2
- package/src/util/normalizeScreens.js +99 -4
- package/src/util/parseBoxShadowValue.js +1 -1
- package/src/util/parseDependency.js +37 -42
- package/src/util/parseGlob.js +24 -0
- package/src/util/pluginUtils.js +90 -14
- package/src/util/resolveConfig.js +1 -1
- package/src/util/splitAtTopLevelOnly.js +23 -49
- package/src/util/transformThemeValue.js +9 -1
- package/src/util/validateFormalSyntax.js +34 -0
- package/stubs/defaultConfig.stub.js +19 -3
- package/tmp.css +11 -0
- package/tmp.dependency-graph.js +2 -0
- package/tmp.in.css +3 -0
- package/tmp.js +0 -0
- package/tmp.out.css +524 -0
- package/types/config.d.ts +47 -13
- package/types/generated/default-theme.d.ts +11 -0
- package/CHANGELOG.md +0 -2231
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import isGlob from 'is-glob'
|
|
6
|
+
import fastGlob from 'fast-glob'
|
|
7
|
+
import normalizePath from 'normalize-path'
|
|
8
|
+
import { parseGlob } from '../util/parseGlob'
|
|
9
|
+
import { env } from './sharedState'
|
|
10
|
+
|
|
11
|
+
/** @typedef {import('../../types/config.js').RawFile} RawFile */
|
|
12
|
+
/** @typedef {import('../../types/config.js').FilePath} FilePath */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} ContentPath
|
|
16
|
+
* @property {string} original
|
|
17
|
+
* @property {string} base
|
|
18
|
+
* @property {string | null} glob
|
|
19
|
+
* @property {boolean} ignore
|
|
20
|
+
* @property {string} pattern
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Turn a list of content paths (absolute or not; glob or not) into a list of
|
|
25
|
+
* absolute file paths that exist on the filesystem
|
|
26
|
+
*
|
|
27
|
+
* If there are symlinks in the path then multiple paths will be returned
|
|
28
|
+
* one for the symlink and one for the actual file
|
|
29
|
+
*
|
|
30
|
+
* @param {*} context
|
|
31
|
+
* @param {import('tailwindcss').Config} tailwindConfig
|
|
32
|
+
* @returns {ContentPath[]}
|
|
33
|
+
*/
|
|
34
|
+
export function parseCandidateFiles(context, tailwindConfig) {
|
|
35
|
+
let files = tailwindConfig.content.files
|
|
36
|
+
|
|
37
|
+
// Normalize the file globs
|
|
38
|
+
files = files.filter((filePath) => typeof filePath === 'string')
|
|
39
|
+
files = files.map(normalizePath)
|
|
40
|
+
|
|
41
|
+
// Split into included and excluded globs
|
|
42
|
+
let tasks = fastGlob.generateTasks(files)
|
|
43
|
+
|
|
44
|
+
/** @type {ContentPath[]} */
|
|
45
|
+
let included = []
|
|
46
|
+
|
|
47
|
+
/** @type {ContentPath[]} */
|
|
48
|
+
let excluded = []
|
|
49
|
+
|
|
50
|
+
for (const task of tasks) {
|
|
51
|
+
included.push(...task.positive.map((filePath) => parseFilePath(filePath, false)))
|
|
52
|
+
excluded.push(...task.negative.map((filePath) => parseFilePath(filePath, true)))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let paths = [...included, ...excluded]
|
|
56
|
+
|
|
57
|
+
// Resolve paths relative to the config file or cwd
|
|
58
|
+
paths = resolveRelativePaths(context, paths)
|
|
59
|
+
|
|
60
|
+
// Resolve symlinks if possible
|
|
61
|
+
paths = paths.flatMap(resolvePathSymlinks)
|
|
62
|
+
|
|
63
|
+
// Update cached patterns
|
|
64
|
+
paths = paths.map(resolveGlobPattern)
|
|
65
|
+
|
|
66
|
+
return paths
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
*
|
|
71
|
+
* @param {string} filePath
|
|
72
|
+
* @param {boolean} ignore
|
|
73
|
+
* @returns {ContentPath}
|
|
74
|
+
*/
|
|
75
|
+
function parseFilePath(filePath, ignore) {
|
|
76
|
+
let contentPath = {
|
|
77
|
+
original: filePath,
|
|
78
|
+
base: filePath,
|
|
79
|
+
ignore,
|
|
80
|
+
pattern: filePath,
|
|
81
|
+
glob: null,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (isGlob(filePath)) {
|
|
85
|
+
Object.assign(contentPath, parseGlob(filePath))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return contentPath
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
*
|
|
93
|
+
* @param {ContentPath} contentPath
|
|
94
|
+
* @returns {ContentPath}
|
|
95
|
+
*/
|
|
96
|
+
function resolveGlobPattern(contentPath) {
|
|
97
|
+
contentPath.pattern = contentPath.glob
|
|
98
|
+
? `${contentPath.base}/${contentPath.glob}`
|
|
99
|
+
: contentPath.base
|
|
100
|
+
|
|
101
|
+
contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern
|
|
102
|
+
|
|
103
|
+
// This is required for Windows support to properly pick up Glob paths.
|
|
104
|
+
// Afaik, this technically shouldn't be needed but there's probably
|
|
105
|
+
// some internal, direct path matching with a normalized path in
|
|
106
|
+
// a package which can't handle mixed directory separators
|
|
107
|
+
contentPath.pattern = normalizePath(contentPath.pattern)
|
|
108
|
+
|
|
109
|
+
return contentPath
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Resolve each path relative to the config file (when possible) if the experimental flag is enabled
|
|
114
|
+
* Otherwise, resolve relative to the current working directory
|
|
115
|
+
*
|
|
116
|
+
* @param {any} context
|
|
117
|
+
* @param {ContentPath[]} contentPaths
|
|
118
|
+
* @returns {ContentPath[]}
|
|
119
|
+
*/
|
|
120
|
+
function resolveRelativePaths(context, contentPaths) {
|
|
121
|
+
let resolveFrom = []
|
|
122
|
+
|
|
123
|
+
// Resolve base paths relative to the config file (when possible) if the experimental flag is enabled
|
|
124
|
+
if (context.userConfigPath && context.tailwindConfig.content.relative) {
|
|
125
|
+
resolveFrom = [path.dirname(context.userConfigPath)]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return contentPaths.map((contentPath) => {
|
|
129
|
+
contentPath.base = path.resolve(...resolveFrom, contentPath.base)
|
|
130
|
+
|
|
131
|
+
return contentPath
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Resolve the symlink for the base directory / file in each path
|
|
137
|
+
* These are added as additional dependencies to watch for changes because
|
|
138
|
+
* some tools (like webpack) will only watch the actual file or directory
|
|
139
|
+
* but not the symlink itself even in projects that use monorepos.
|
|
140
|
+
*
|
|
141
|
+
* @param {ContentPath} contentPath
|
|
142
|
+
* @returns {ContentPath[]}
|
|
143
|
+
*/
|
|
144
|
+
function resolvePathSymlinks(contentPath) {
|
|
145
|
+
let paths = [contentPath]
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
let resolvedPath = fs.realpathSync(contentPath.base)
|
|
149
|
+
if (resolvedPath !== contentPath.base) {
|
|
150
|
+
paths.push({
|
|
151
|
+
...contentPath,
|
|
152
|
+
base: resolvedPath,
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
// TODO: log this?
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return paths
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {any} context
|
|
164
|
+
* @param {ContentPath[]} candidateFiles
|
|
165
|
+
* @param {Map<string, number>} fileModifiedMap
|
|
166
|
+
* @returns {{ content: string, extension: string }[]}
|
|
167
|
+
*/
|
|
168
|
+
export function resolvedChangedContent(context, candidateFiles, fileModifiedMap) {
|
|
169
|
+
let changedContent = context.tailwindConfig.content.files
|
|
170
|
+
.filter((item) => typeof item.raw === 'string')
|
|
171
|
+
.map(({ raw, extension = 'html' }) => ({ content: raw, extension }))
|
|
172
|
+
|
|
173
|
+
for (let changedFile of resolveChangedFiles(candidateFiles, fileModifiedMap)) {
|
|
174
|
+
let content = fs.readFileSync(changedFile, 'utf8')
|
|
175
|
+
let extension = path.extname(changedFile).slice(1)
|
|
176
|
+
changedContent.push({ content, extension })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return changedContent
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
*
|
|
184
|
+
* @param {ContentPath[]} candidateFiles
|
|
185
|
+
* @param {Map<string, number>} fileModifiedMap
|
|
186
|
+
* @returns {Set<string>}
|
|
187
|
+
*/
|
|
188
|
+
function resolveChangedFiles(candidateFiles, fileModifiedMap) {
|
|
189
|
+
let paths = candidateFiles.map((contentPath) => contentPath.pattern)
|
|
190
|
+
|
|
191
|
+
let changedFiles = new Set()
|
|
192
|
+
env.DEBUG && console.time('Finding changed files')
|
|
193
|
+
let files = fastGlob.sync(paths, { absolute: true })
|
|
194
|
+
for (let file of files) {
|
|
195
|
+
let prevModified = fileModifiedMap.has(file) ? fileModifiedMap.get(file) : -Infinity
|
|
196
|
+
let modified = fs.statSync(file).mtimeMs
|
|
197
|
+
|
|
198
|
+
if (modified > prevModified) {
|
|
199
|
+
changedFiles.add(file)
|
|
200
|
+
fileModifiedMap.set(file, modified)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
env.DEBUG && console.timeEnd('Finding changed files')
|
|
204
|
+
return changedFiles
|
|
205
|
+
}
|
|
@@ -71,6 +71,9 @@ function* buildRegExps(context) {
|
|
|
71
71
|
let variantPatterns = [
|
|
72
72
|
// Without quotes
|
|
73
73
|
regex.any([
|
|
74
|
+
// This is here to provide special support for the `@` variant
|
|
75
|
+
regex.pattern([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, separator]),
|
|
76
|
+
|
|
74
77
|
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]),
|
|
75
78
|
regex.pattern([/[^\s"'`\[\\]+/, separator]),
|
|
76
79
|
]),
|
|
@@ -7,6 +7,7 @@ import buildMediaQuery from '../util/buildMediaQuery'
|
|
|
7
7
|
import { toPath } from '../util/toPath'
|
|
8
8
|
import { withAlphaValue } from '../util/withAlphaVariable'
|
|
9
9
|
import { parseColorFormat } from '../util/pluginUtils'
|
|
10
|
+
import log from '../util/log'
|
|
10
11
|
|
|
11
12
|
function isObject(input) {
|
|
12
13
|
return typeof input === 'object' && input !== null
|
|
@@ -196,7 +197,9 @@ function resolvePath(config, path, defaultValue) {
|
|
|
196
197
|
return results.find((result) => result.isValid) ?? results[0]
|
|
197
198
|
}
|
|
198
199
|
|
|
199
|
-
export default function (
|
|
200
|
+
export default function (context) {
|
|
201
|
+
let config = context.tailwindConfig
|
|
202
|
+
|
|
200
203
|
let functions = {
|
|
201
204
|
theme: (node, path, ...defaultValue) => {
|
|
202
205
|
let { isValid, value, error, alpha } = resolvePath(
|
|
@@ -206,6 +209,24 @@ export default function ({ tailwindConfig: config }) {
|
|
|
206
209
|
)
|
|
207
210
|
|
|
208
211
|
if (!isValid) {
|
|
212
|
+
let parentNode = node.parent
|
|
213
|
+
let candidate = parentNode?.raws.tailwind?.candidate
|
|
214
|
+
|
|
215
|
+
if (parentNode && candidate !== undefined) {
|
|
216
|
+
// Remove this utility from any caches
|
|
217
|
+
context.markInvalidUtilityNode(parentNode)
|
|
218
|
+
|
|
219
|
+
// Remove the CSS node from the markup
|
|
220
|
+
parentNode.remove()
|
|
221
|
+
|
|
222
|
+
// Show a warning
|
|
223
|
+
log.warn('invalid-theme-key-in-class', [
|
|
224
|
+
`The utility \`${candidate}\` contains an invalid theme value and was not generated.`,
|
|
225
|
+
])
|
|
226
|
+
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
|
|
209
230
|
throw node.error(error)
|
|
210
231
|
}
|
|
211
232
|
|
|
@@ -2,7 +2,6 @@ import postcss from 'postcss'
|
|
|
2
2
|
import parser from 'postcss-selector-parser'
|
|
3
3
|
|
|
4
4
|
import { resolveMatches } from './generateRules'
|
|
5
|
-
import bigSign from '../util/bigSign'
|
|
6
5
|
import escapeClassName from '../util/escapeClassName'
|
|
7
6
|
|
|
8
7
|
/** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
|
|
@@ -34,13 +33,13 @@ function extractClasses(node) {
|
|
|
34
33
|
return Object.assign(classes, { groups: normalizedGroups })
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
let selectorExtractor = parser(
|
|
36
|
+
let selectorExtractor = parser()
|
|
38
37
|
|
|
39
38
|
/**
|
|
40
39
|
* @param {string} ruleSelectors
|
|
41
40
|
*/
|
|
42
41
|
function extractSelectors(ruleSelectors) {
|
|
43
|
-
return selectorExtractor.
|
|
42
|
+
return selectorExtractor.astSync(ruleSelectors)
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
function extractBaseCandidates(candidates, separator) {
|
|
@@ -149,9 +148,7 @@ function buildLocalApplyCache(root, context) {
|
|
|
149
148
|
/** @type {ApplyCache} */
|
|
150
149
|
let cache = new Map()
|
|
151
150
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
root.walkRules((rule, idx) => {
|
|
151
|
+
root.walkRules((rule) => {
|
|
155
152
|
// Ignore rules generated by Tailwind
|
|
156
153
|
for (let node of pathToRoot(rule)) {
|
|
157
154
|
if (node.raws.tailwind?.layer !== undefined) {
|
|
@@ -161,6 +158,7 @@ function buildLocalApplyCache(root, context) {
|
|
|
161
158
|
|
|
162
159
|
// Clone what's required to represent this singular rule in the tree
|
|
163
160
|
let container = nestedClone(rule)
|
|
161
|
+
let sort = context.offsets.create('user')
|
|
164
162
|
|
|
165
163
|
for (let className of extractClasses(rule)) {
|
|
166
164
|
let list = cache.get(className) || []
|
|
@@ -169,7 +167,7 @@ function buildLocalApplyCache(root, context) {
|
|
|
169
167
|
list.push([
|
|
170
168
|
{
|
|
171
169
|
layer: 'user',
|
|
172
|
-
sort
|
|
170
|
+
sort,
|
|
173
171
|
important: false,
|
|
174
172
|
},
|
|
175
173
|
container,
|
|
@@ -299,30 +297,77 @@ function processApply(root, context, localCache) {
|
|
|
299
297
|
* What happens in this function is that we prepend a `.` and escape the candidate.
|
|
300
298
|
* This will result in `.hover\:font-bold`
|
|
301
299
|
* Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
|
|
300
|
+
*
|
|
301
|
+
* @param {string} selector
|
|
302
|
+
* @param {string} utilitySelectors
|
|
303
|
+
* @param {string} candidate
|
|
302
304
|
*/
|
|
303
|
-
// TODO: Should we use postcss-selector-parser for this instead?
|
|
304
305
|
function replaceSelector(selector, utilitySelectors, candidate) {
|
|
305
|
-
let
|
|
306
|
-
let needles = [...new Set([needle, needle.replace(/\\2c /g, '\\,')])]
|
|
306
|
+
let selectorList = extractSelectors(selector)
|
|
307
307
|
let utilitySelectorsList = extractSelectors(utilitySelectors)
|
|
308
|
+
let candidateList = extractSelectors(`.${escapeClassName(candidate)}`)
|
|
309
|
+
let candidateClass = candidateList.nodes[0].nodes[0]
|
|
310
|
+
|
|
311
|
+
selectorList.each((sel) => {
|
|
312
|
+
/** @type {Set<import('postcss-selector-parser').Selector>} */
|
|
313
|
+
let replaced = new Set()
|
|
308
314
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
315
|
+
utilitySelectorsList.each((utilitySelector) => {
|
|
316
|
+
let hasReplaced = false
|
|
317
|
+
utilitySelector = utilitySelector.clone()
|
|
312
318
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
replacedSelector = replacedSelector.replace(needle, s)
|
|
319
|
+
utilitySelector.walkClasses((node) => {
|
|
320
|
+
if (node.value !== candidateClass.value) {
|
|
321
|
+
return
|
|
317
322
|
}
|
|
318
|
-
|
|
319
|
-
|
|
323
|
+
|
|
324
|
+
// Don't replace multiple instances of the same class
|
|
325
|
+
// This is theoretically correct but only partially
|
|
326
|
+
// We'd need to generate every possible permutation of the replacement
|
|
327
|
+
// For example with `.foo + .foo { … }` and `section { @apply foo; }`
|
|
328
|
+
// We'd need to generate all of these:
|
|
329
|
+
// - `.foo + .foo`
|
|
330
|
+
// - `.foo + section`
|
|
331
|
+
// - `section + .foo`
|
|
332
|
+
// - `section + section`
|
|
333
|
+
if (hasReplaced) {
|
|
334
|
+
return
|
|
320
335
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
336
|
+
|
|
337
|
+
// Since you can only `@apply` class names this is sufficient
|
|
338
|
+
// We want to replace the matched class name with the selector the user is using
|
|
339
|
+
// Ex: Replace `.text-blue-500` with `.foo.bar:is(.something-cool)`
|
|
340
|
+
node.replaceWith(...sel.nodes.map((node) => node.clone()))
|
|
341
|
+
|
|
342
|
+
// Record that we did something and we want to use this new selector
|
|
343
|
+
replaced.add(utilitySelector)
|
|
344
|
+
|
|
345
|
+
hasReplaced = true
|
|
346
|
+
})
|
|
324
347
|
})
|
|
325
|
-
|
|
348
|
+
|
|
349
|
+
// Sort tag names before class names
|
|
350
|
+
// This happens when replacing `.bar` in `.foo.bar` with a tag like `section`
|
|
351
|
+
for (const sel of replaced) {
|
|
352
|
+
sel.sort((a, b) => {
|
|
353
|
+
if (a.type === 'tag' && b.type === 'class') {
|
|
354
|
+
return -1
|
|
355
|
+
} else if (a.type === 'class' && b.type === 'tag') {
|
|
356
|
+
return 1
|
|
357
|
+
} else if (a.type === 'class' && b.type === 'pseudo') {
|
|
358
|
+
return -1
|
|
359
|
+
} else if (a.type === 'pseudo' && b.type === 'class') {
|
|
360
|
+
return 1
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return sel.index(a) - sel.index(b)
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
sel.replaceWith(...replaced)
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
return selectorList.toString()
|
|
326
371
|
}
|
|
327
372
|
|
|
328
373
|
let perParentApplies = new Map()
|
|
@@ -506,16 +551,12 @@ function processApply(root, context, localCache) {
|
|
|
506
551
|
}
|
|
507
552
|
|
|
508
553
|
// Insert it
|
|
509
|
-
siblings.push([
|
|
510
|
-
// Ensure that when we are sorting, that we take the layer order into account
|
|
511
|
-
{ ...meta, sort: meta.sort | context.layerOrder[meta.layer] },
|
|
512
|
-
root.nodes[0],
|
|
513
|
-
])
|
|
554
|
+
siblings.push([meta.sort, root.nodes[0]])
|
|
514
555
|
}
|
|
515
556
|
}
|
|
516
557
|
|
|
517
558
|
// Inject the rules, sorted, correctly
|
|
518
|
-
let nodes =
|
|
559
|
+
let nodes = context.offsets.sort(siblings).map((s) => s[1])
|
|
519
560
|
|
|
520
561
|
// `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
|
|
521
562
|
parent.after(nodes)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import LRU from 'quick-lru'
|
|
2
2
|
import * as sharedState from './sharedState'
|
|
3
3
|
import { generateRules } from './generateRules'
|
|
4
|
-
import bigSign from '../util/bigSign'
|
|
5
4
|
import log from '../util/log'
|
|
6
5
|
import cloneNodes from '../util/cloneNodes'
|
|
7
6
|
import { defaultExtractor } from './defaultExtractor'
|
|
@@ -74,8 +73,13 @@ function getClassCandidates(content, extractor, candidates, seen) {
|
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
|
|
76
|
+
/**
|
|
77
|
+
*
|
|
78
|
+
* @param {[import('./offsets.js').RuleOffset, import('postcss').Node][]} rules
|
|
79
|
+
* @param {*} context
|
|
80
|
+
*/
|
|
77
81
|
function buildStylesheet(rules, context) {
|
|
78
|
-
let sortedRules =
|
|
82
|
+
let sortedRules = context.offsets.sort(rules)
|
|
79
83
|
|
|
80
84
|
let returnValue = {
|
|
81
85
|
base: new Set(),
|
|
@@ -83,48 +87,10 @@ function buildStylesheet(rules, context) {
|
|
|
83
87
|
components: new Set(),
|
|
84
88
|
utilities: new Set(),
|
|
85
89
|
variants: new Set(),
|
|
86
|
-
|
|
87
|
-
// All the CSS that is not Tailwind related can be put in this bucket. This
|
|
88
|
-
// will make it easier to later use this information when we want to
|
|
89
|
-
// `@apply` for example. The main reason we do this here is because we
|
|
90
|
-
// still need to make sure the order is correct. Last but not least, we
|
|
91
|
-
// will make sure to always re-inject this section into the css, even if
|
|
92
|
-
// certain rules were not used. This means that it will look like a no-op
|
|
93
|
-
// from the user's perspective, but we gathered all the useful information
|
|
94
|
-
// we need.
|
|
95
|
-
user: new Set(),
|
|
96
90
|
}
|
|
97
91
|
|
|
98
92
|
for (let [sort, rule] of sortedRules) {
|
|
99
|
-
|
|
100
|
-
returnValue.variants.add(rule)
|
|
101
|
-
continue
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (sort & context.layerOrder.base) {
|
|
105
|
-
returnValue.base.add(rule)
|
|
106
|
-
continue
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (sort & context.layerOrder.defaults) {
|
|
110
|
-
returnValue.defaults.add(rule)
|
|
111
|
-
continue
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (sort & context.layerOrder.components) {
|
|
115
|
-
returnValue.components.add(rule)
|
|
116
|
-
continue
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (sort & context.layerOrder.utilities) {
|
|
120
|
-
returnValue.utilities.add(rule)
|
|
121
|
-
continue
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (sort & context.layerOrder.user) {
|
|
125
|
-
returnValue.user.add(rule)
|
|
126
|
-
continue
|
|
127
|
-
}
|
|
93
|
+
returnValue[sort.layer].add(rule)
|
|
128
94
|
}
|
|
129
95
|
|
|
130
96
|
return returnValue
|
|
@@ -177,16 +143,12 @@ export default function expandTailwindAtRules(context) {
|
|
|
177
143
|
let classCacheCount = context.classCache.size
|
|
178
144
|
|
|
179
145
|
env.DEBUG && console.time('Generate rules')
|
|
180
|
-
|
|
146
|
+
generateRules(candidates, context)
|
|
181
147
|
env.DEBUG && console.timeEnd('Generate rules')
|
|
182
148
|
|
|
183
149
|
// We only ever add to the classCache, so if it didn't grow, there is nothing new.
|
|
184
150
|
env.DEBUG && console.time('Build stylesheet')
|
|
185
151
|
if (context.stylesheetCache === null || context.classCache.size !== classCacheCount) {
|
|
186
|
-
for (let rule of rules) {
|
|
187
|
-
context.ruleCache.add(rule)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
152
|
context.stylesheetCache = buildStylesheet([...context.ruleCache], context)
|
|
191
153
|
}
|
|
192
154
|
env.DEBUG && console.timeEnd('Build stylesheet')
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Find the @config at-rule in the given CSS AST and return the relative path to the config file
|
|
6
|
+
*
|
|
7
|
+
* @param {import('postcss').Root} root
|
|
8
|
+
* @param {import('postcss').Result} result
|
|
9
|
+
*/
|
|
10
|
+
export function findAtConfigPath(root, result) {
|
|
11
|
+
let configPath = null
|
|
12
|
+
let relativeTo = null
|
|
13
|
+
|
|
14
|
+
root.walkAtRules('config', (rule) => {
|
|
15
|
+
relativeTo = rule.source?.input.file ?? result.opts.from ?? null
|
|
16
|
+
|
|
17
|
+
if (relativeTo === null) {
|
|
18
|
+
throw rule.error(
|
|
19
|
+
'The `@config` directive cannot be used without setting `from` in your PostCSS config.'
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (configPath) {
|
|
24
|
+
throw rule.error('Only one `@config` directive is allowed per file.')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let matches = rule.params.match(/(['"])(.*?)\1/)
|
|
28
|
+
if (!matches) {
|
|
29
|
+
throw rule.error('A path is required when using the `@config` directive.')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let inputPath = matches[2]
|
|
33
|
+
if (path.isAbsolute(inputPath)) {
|
|
34
|
+
throw rule.error('The `@config` directive cannot be used with an absolute path.')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
configPath = path.resolve(path.dirname(relativeTo), inputPath)
|
|
38
|
+
if (!fs.existsSync(configPath)) {
|
|
39
|
+
throw rule.error(
|
|
40
|
+
`The config file at "${inputPath}" does not exist. Make sure the path is correct and the file exists.`
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
rule.remove()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return configPath ? configPath : null
|
|
48
|
+
}
|