tailwindcss 3.2.2 → 3.2.4
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 +23 -1
- package/lib/cli/build/plugin.js +14 -3
- package/lib/cli/build/watching.js +6 -3
- package/lib/lib/content.js +8 -1
- package/lib/lib/expandApplyAtRules.js +2 -2
- package/lib/lib/setupContextUtils.js +10 -1
- package/lib/util/formatVariantSelector.js +2 -2
- package/lib/util/normalizeConfig.js +14 -0
- package/package.json +2 -2
- package/src/cli/build/plugin.js +19 -2
- package/src/cli/build/watching.js +6 -3
- package/src/lib/content.js +8 -1
- package/src/lib/expandApplyAtRules.js +2 -2
- package/src/lib/setupContextUtils.js +7 -1
- package/src/util/formatVariantSelector.js +2 -2
- package/src/util/normalizeConfig.js +18 -0
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
- Nothing yet!
|
|
11
11
|
|
|
12
|
+
## [3.2.4] - 2022-11-11
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Add `blocklist` option to prevent generating unwanted CSS ([#9812](https://github.com/tailwindlabs/tailwindcss/pull/9812))
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Fix watching of files on Linux when renames are involved ([#9796](https://github.com/tailwindlabs/tailwindcss/pull/9796))
|
|
21
|
+
- Make sure errors are always displayed when watching for changes ([#9810](https://github.com/tailwindlabs/tailwindcss/pull/9810))
|
|
22
|
+
|
|
23
|
+
## [3.2.3] - 2022-11-09
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Fixed use of `raw` content in the CLI ([#9773](https://github.com/tailwindlabs/tailwindcss/pull/9773))
|
|
28
|
+
- Pick up changes from files that are both context and content deps ([#9787](https://github.com/tailwindlabs/tailwindcss/pull/9787))
|
|
29
|
+
- Sort pseudo-elements ONLY after classes when using variants and `@apply` ([#9765](https://github.com/tailwindlabs/tailwindcss/pull/9765))
|
|
30
|
+
- Support important utilities in the safelist (pattern must include a `!`) ([#9791](https://github.com/tailwindlabs/tailwindcss/pull/9791))
|
|
31
|
+
|
|
12
32
|
## [3.2.2] - 2022-11-04
|
|
13
33
|
|
|
14
34
|
### Fixed
|
|
@@ -2101,7 +2121,9 @@ No release notes
|
|
|
2101
2121
|
|
|
2102
2122
|
- Everything!
|
|
2103
2123
|
|
|
2104
|
-
[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.
|
|
2124
|
+
[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.4...HEAD
|
|
2125
|
+
[3.2.4]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.3...v3.2.4
|
|
2126
|
+
[3.2.3]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.2...v3.2.3
|
|
2105
2127
|
[3.2.2]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.1...v3.2.2
|
|
2106
2128
|
[3.2.1]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.0...v3.2.1
|
|
2107
2129
|
[3.2.0]: https://github.com/tailwindlabs/tailwindcss/compare/v3.1.8...v3.2.0
|
package/lib/cli/build/plugin.js
CHANGED
|
@@ -170,9 +170,9 @@ let state = {
|
|
|
170
170
|
let rawContent = this.config.content.files.filter((file)=>{
|
|
171
171
|
return file !== null && typeof file === "object";
|
|
172
172
|
});
|
|
173
|
-
for (let { raw:
|
|
174
|
-
|
|
175
|
-
content:
|
|
173
|
+
for (let { raw: htmlContent , extension ="html" } of rawContent){
|
|
174
|
+
content.push({
|
|
175
|
+
content: htmlContent,
|
|
176
176
|
extension
|
|
177
177
|
});
|
|
178
178
|
}
|
|
@@ -311,6 +311,17 @@ async function createProcessor(args, cliConfigPath) {
|
|
|
311
311
|
let end = process.hrtime.bigint();
|
|
312
312
|
console.error();
|
|
313
313
|
console.error("Done in", (end - start) / BigInt(1e6) + "ms.");
|
|
314
|
+
}).then(()=>{}, (err)=>{
|
|
315
|
+
// TODO: If an initial build fails we can't easily pick up any PostCSS dependencies
|
|
316
|
+
// that were collected before the error occurred
|
|
317
|
+
// The result is not stored on the error so we have to store it externally
|
|
318
|
+
// and pull the messages off of it here somehow
|
|
319
|
+
// This results in a less than ideal DX because the watcher will not pick up
|
|
320
|
+
// changes to imported CSS if one of them caused an error during the initial build
|
|
321
|
+
// If you fix it and then save the main CSS file so there's no error
|
|
322
|
+
// The watcher will start watching the imported CSS files and will be
|
|
323
|
+
// resilient to future errors.
|
|
324
|
+
console.error(err);
|
|
314
325
|
});
|
|
315
326
|
}
|
|
316
327
|
/**
|
|
@@ -70,12 +70,13 @@ function createWatcher(args, { state , rebuild }) {
|
|
|
70
70
|
*
|
|
71
71
|
* @param {*} file
|
|
72
72
|
* @param {(() => Promise<string>) | null} content
|
|
73
|
+
* @param {boolean} skipPendingCheck
|
|
73
74
|
* @returns {Promise<void>}
|
|
74
|
-
*/ function recordChangedFile(file, content = null) {
|
|
75
|
+
*/ function recordChangedFile(file, content = null, skipPendingCheck = false) {
|
|
75
76
|
file = _path.default.resolve(file);
|
|
76
77
|
// Applications like Vim/Neovim fire both rename and change events in succession for atomic writes
|
|
77
78
|
// In that case rebuild has already been queued by rename, so can be skipped in change
|
|
78
|
-
if (pendingRebuilds.has(file)) {
|
|
79
|
+
if (pendingRebuilds.has(file) && !skipPendingCheck) {
|
|
79
80
|
return Promise.resolve();
|
|
80
81
|
}
|
|
81
82
|
// Mark that a rebuild of this file is going to happen
|
|
@@ -152,8 +153,10 @@ function createWatcher(args, { state , rebuild }) {
|
|
|
152
153
|
return;
|
|
153
154
|
}
|
|
154
155
|
// This will push the rebuild onto the chain
|
|
156
|
+
// We MUST skip the rebuild check here otherwise the rebuild will never happen on Linux
|
|
157
|
+
// This is because the order of events and timing is different on Linux
|
|
155
158
|
// @ts-ignore: TypeScript isn't picking up that content is a string here
|
|
156
|
-
await recordChangedFile(filePath, ()=>content);
|
|
159
|
+
await recordChangedFile(filePath, ()=>content, true);
|
|
157
160
|
} catch {
|
|
158
161
|
// If reading the file fails, it's was probably a deleted temporary file
|
|
159
162
|
// So we can ignore it and no rebuild is needed
|
package/lib/lib/content.js
CHANGED
|
@@ -160,7 +160,14 @@ function resolvedChangedContent(context, candidateFiles, fileModifiedMap) {
|
|
|
160
160
|
for (let file of files){
|
|
161
161
|
let prevModified = fileModifiedMap.has(file) ? fileModifiedMap.get(file) : -Infinity;
|
|
162
162
|
let modified = _fs.default.statSync(file).mtimeMs;
|
|
163
|
-
|
|
163
|
+
// This check is intentionally >= because we track the last modified time of context dependencies
|
|
164
|
+
// earier in the process and we want to make sure we don't miss any changes that happen
|
|
165
|
+
// when a context dependency is also a content dependency
|
|
166
|
+
// Ideally, we'd do all this tracking at one time but that is a larger refactor
|
|
167
|
+
// than we want to commit to right now, so this is a decent compromise.
|
|
168
|
+
// This should be sufficient because file modification times will be off by at least
|
|
169
|
+
// 1ms (the precision of fstat in Node) in most cases if they exist and were changed.
|
|
170
|
+
if (modified >= prevModified) {
|
|
164
171
|
changedFiles.add(file);
|
|
165
172
|
fileModifiedMap.set(file, modified);
|
|
166
173
|
}
|
|
@@ -330,9 +330,9 @@ function processApply(root, context, localCache) {
|
|
|
330
330
|
return -1;
|
|
331
331
|
} else if (a.type === "class" && b.type === "tag") {
|
|
332
332
|
return 1;
|
|
333
|
-
} else if (a.type === "class" && b.type === "pseudo") {
|
|
333
|
+
} else if (a.type === "class" && b.type === "pseudo" && b.value.startsWith("::")) {
|
|
334
334
|
return -1;
|
|
335
|
-
} else if (a.type === "pseudo" && b.type === "class") {
|
|
335
|
+
} else if (a.type === "pseudo" && a.value.startsWith("::") && b.type === "class") {
|
|
336
336
|
return 1;
|
|
337
337
|
}
|
|
338
338
|
return 0;
|
|
@@ -799,6 +799,7 @@ function registerPlugins(plugins, context) {
|
|
|
799
799
|
if (checks.length > 0) {
|
|
800
800
|
let patternMatchingCount = new Map();
|
|
801
801
|
let prefixLength = context.tailwindConfig.prefix.length;
|
|
802
|
+
let checkImportantUtils = checks.some((check)=>check.pattern.source.includes("!"));
|
|
802
803
|
for (let util of classList){
|
|
803
804
|
let utils = Array.isArray(util) ? (()=>{
|
|
804
805
|
let [utilName, options] = util;
|
|
@@ -827,6 +828,12 @@ function registerPlugins(plugins, context) {
|
|
|
827
828
|
...classes.flatMap((cls)=>Object.keys(context.tailwindConfig.theme.opacity).map((opacity)=>`${cls}/${opacity}`))
|
|
828
829
|
];
|
|
829
830
|
}
|
|
831
|
+
if (checkImportantUtils && (options === null || options === void 0 ? void 0 : options.respectImportant)) {
|
|
832
|
+
classes = [
|
|
833
|
+
...classes,
|
|
834
|
+
...classes.map((cls)=>"!" + cls)
|
|
835
|
+
];
|
|
836
|
+
}
|
|
830
837
|
return classes;
|
|
831
838
|
})() : [
|
|
832
839
|
util
|
|
@@ -1091,13 +1098,15 @@ function registerPlugins(plugins, context) {
|
|
|
1091
1098
|
markInvalidUtilityCandidate(context, candidate);
|
|
1092
1099
|
}
|
|
1093
1100
|
function createContext(tailwindConfig, changedContent = [], root = _postcss.default.root()) {
|
|
1101
|
+
var _blocklist;
|
|
1094
1102
|
let context = {
|
|
1095
1103
|
disposables: [],
|
|
1096
1104
|
ruleCache: new Set(),
|
|
1097
1105
|
candidateRuleCache: new Map(),
|
|
1098
1106
|
classCache: new Map(),
|
|
1099
1107
|
applyClassCache: new Map(),
|
|
1100
|
-
|
|
1108
|
+
// Seed the not class cache with the blocklist (which is only strings)
|
|
1109
|
+
notClassCache: new Set((_blocklist = tailwindConfig.blocklist) !== null && _blocklist !== void 0 ? _blocklist : []),
|
|
1101
1110
|
postCssNodeCache: new Map(),
|
|
1102
1111
|
candidateRuleMap: new Map(),
|
|
1103
1112
|
tailwindConfig,
|
|
@@ -78,9 +78,9 @@ function formatVariantSelector(current, ...others) {
|
|
|
78
78
|
return -1;
|
|
79
79
|
} else if (a.type === "class" && b.type === "tag") {
|
|
80
80
|
return 1;
|
|
81
|
-
} else if (a.type === "class" && b.type === "pseudo" && b.value
|
|
81
|
+
} else if (a.type === "class" && b.type === "pseudo" && b.value.startsWith("::")) {
|
|
82
82
|
return -1;
|
|
83
|
-
} else if (a.type === "pseudo" && a.value
|
|
83
|
+
} else if (a.type === "pseudo" && a.value.startsWith("::") && b.type === "class") {
|
|
84
84
|
return 1;
|
|
85
85
|
}
|
|
86
86
|
return sel.index(a) - sel.index(b);
|
|
@@ -162,6 +162,20 @@ function normalizeConfig(config) {
|
|
|
162
162
|
if (Array.isArray(purge === null || purge === void 0 ? void 0 : (ref = purge.options) === null || ref === void 0 ? void 0 : ref.safelist)) return purge.options.safelist;
|
|
163
163
|
return [];
|
|
164
164
|
})();
|
|
165
|
+
// Normalize the `blocklist`
|
|
166
|
+
config.blocklist = (()=>{
|
|
167
|
+
let { blocklist } = config;
|
|
168
|
+
if (Array.isArray(blocklist)) {
|
|
169
|
+
if (blocklist.every((item)=>typeof item === "string")) {
|
|
170
|
+
return blocklist;
|
|
171
|
+
}
|
|
172
|
+
_log.default.warn("blocklist-invalid", [
|
|
173
|
+
"The `blocklist` option must be an array of strings.",
|
|
174
|
+
"https://tailwindcss.com/docs/content-configuration#discarding-classes"
|
|
175
|
+
]);
|
|
176
|
+
}
|
|
177
|
+
return [];
|
|
178
|
+
})();
|
|
165
179
|
// Normalize prefix option
|
|
166
180
|
if (typeof config.prefix === "function") {
|
|
167
181
|
_log.default.warn("prefix-function", [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tailwindcss",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.4",
|
|
4
4
|
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"autoprefixer": "^10.4.13",
|
|
51
51
|
"cssnano": "^5.1.14",
|
|
52
52
|
"esbuild": "^0.15.12",
|
|
53
|
-
"eslint": "^8.
|
|
53
|
+
"eslint": "^8.26.0",
|
|
54
54
|
"eslint-config-prettier": "^8.5.0",
|
|
55
55
|
"eslint-plugin-prettier": "^4.2.1",
|
|
56
56
|
"jest": "^28.1.3",
|
package/src/cli/build/plugin.js
CHANGED
|
@@ -195,8 +195,8 @@ let state = {
|
|
|
195
195
|
return file !== null && typeof file === 'object'
|
|
196
196
|
})
|
|
197
197
|
|
|
198
|
-
for (let { raw:
|
|
199
|
-
content.push({ content, extension })
|
|
198
|
+
for (let { raw: htmlContent, extension = 'html' } of rawContent) {
|
|
199
|
+
content.push({ content: htmlContent, extension })
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
return content
|
|
@@ -364,6 +364,23 @@ export async function createProcessor(args, cliConfigPath) {
|
|
|
364
364
|
console.error()
|
|
365
365
|
console.error('Done in', (end - start) / BigInt(1e6) + 'ms.')
|
|
366
366
|
})
|
|
367
|
+
.then(
|
|
368
|
+
() => {},
|
|
369
|
+
(err) => {
|
|
370
|
+
// TODO: If an initial build fails we can't easily pick up any PostCSS dependencies
|
|
371
|
+
// that were collected before the error occurred
|
|
372
|
+
// The result is not stored on the error so we have to store it externally
|
|
373
|
+
// and pull the messages off of it here somehow
|
|
374
|
+
|
|
375
|
+
// This results in a less than ideal DX because the watcher will not pick up
|
|
376
|
+
// changes to imported CSS if one of them caused an error during the initial build
|
|
377
|
+
// If you fix it and then save the main CSS file so there's no error
|
|
378
|
+
// The watcher will start watching the imported CSS files and will be
|
|
379
|
+
// resilient to future errors.
|
|
380
|
+
|
|
381
|
+
console.error(err)
|
|
382
|
+
}
|
|
383
|
+
)
|
|
367
384
|
}
|
|
368
385
|
|
|
369
386
|
/**
|
|
@@ -97,14 +97,15 @@ export function createWatcher(args, { state, rebuild }) {
|
|
|
97
97
|
*
|
|
98
98
|
* @param {*} file
|
|
99
99
|
* @param {(() => Promise<string>) | null} content
|
|
100
|
+
* @param {boolean} skipPendingCheck
|
|
100
101
|
* @returns {Promise<void>}
|
|
101
102
|
*/
|
|
102
|
-
function recordChangedFile(file, content = null) {
|
|
103
|
+
function recordChangedFile(file, content = null, skipPendingCheck = false) {
|
|
103
104
|
file = path.resolve(file)
|
|
104
105
|
|
|
105
106
|
// Applications like Vim/Neovim fire both rename and change events in succession for atomic writes
|
|
106
107
|
// In that case rebuild has already been queued by rename, so can be skipped in change
|
|
107
|
-
if (pendingRebuilds.has(file)) {
|
|
108
|
+
if (pendingRebuilds.has(file) && !skipPendingCheck) {
|
|
108
109
|
return Promise.resolve()
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -198,8 +199,10 @@ export function createWatcher(args, { state, rebuild }) {
|
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
// This will push the rebuild onto the chain
|
|
202
|
+
// We MUST skip the rebuild check here otherwise the rebuild will never happen on Linux
|
|
203
|
+
// This is because the order of events and timing is different on Linux
|
|
201
204
|
// @ts-ignore: TypeScript isn't picking up that content is a string here
|
|
202
|
-
await recordChangedFile(filePath, () => content)
|
|
205
|
+
await recordChangedFile(filePath, () => content, true)
|
|
203
206
|
} catch {
|
|
204
207
|
// If reading the file fails, it's was probably a deleted temporary file
|
|
205
208
|
// So we can ignore it and no rebuild is needed
|
package/src/lib/content.js
CHANGED
|
@@ -196,7 +196,14 @@ function resolveChangedFiles(candidateFiles, fileModifiedMap) {
|
|
|
196
196
|
let prevModified = fileModifiedMap.has(file) ? fileModifiedMap.get(file) : -Infinity
|
|
197
197
|
let modified = fs.statSync(file).mtimeMs
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
// This check is intentionally >= because we track the last modified time of context dependencies
|
|
200
|
+
// earier in the process and we want to make sure we don't miss any changes that happen
|
|
201
|
+
// when a context dependency is also a content dependency
|
|
202
|
+
// Ideally, we'd do all this tracking at one time but that is a larger refactor
|
|
203
|
+
// than we want to commit to right now, so this is a decent compromise.
|
|
204
|
+
// This should be sufficient because file modification times will be off by at least
|
|
205
|
+
// 1ms (the precision of fstat in Node) in most cases if they exist and were changed.
|
|
206
|
+
if (modified >= prevModified) {
|
|
200
207
|
changedFiles.add(file)
|
|
201
208
|
fileModifiedMap.set(file, modified)
|
|
202
209
|
}
|
|
@@ -370,9 +370,9 @@ function processApply(root, context, localCache) {
|
|
|
370
370
|
return -1
|
|
371
371
|
} else if (a.type === 'class' && b.type === 'tag') {
|
|
372
372
|
return 1
|
|
373
|
-
} else if (a.type === 'class' && b.type === 'pseudo') {
|
|
373
|
+
} else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) {
|
|
374
374
|
return -1
|
|
375
|
-
} else if (a.type === 'pseudo' && b.type === 'class') {
|
|
375
|
+
} else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') {
|
|
376
376
|
return 1
|
|
377
377
|
}
|
|
378
378
|
|
|
@@ -825,6 +825,7 @@ function registerPlugins(plugins, context) {
|
|
|
825
825
|
if (checks.length > 0) {
|
|
826
826
|
let patternMatchingCount = new Map()
|
|
827
827
|
let prefixLength = context.tailwindConfig.prefix.length
|
|
828
|
+
let checkImportantUtils = checks.some((check) => check.pattern.source.includes('!'))
|
|
828
829
|
|
|
829
830
|
for (let util of classList) {
|
|
830
831
|
let utils = Array.isArray(util)
|
|
@@ -861,6 +862,10 @@ function registerPlugins(plugins, context) {
|
|
|
861
862
|
]
|
|
862
863
|
}
|
|
863
864
|
|
|
865
|
+
if (checkImportantUtils && options?.respectImportant) {
|
|
866
|
+
classes = [...classes, ...classes.map((cls) => '!' + cls)]
|
|
867
|
+
}
|
|
868
|
+
|
|
864
869
|
return classes
|
|
865
870
|
})()
|
|
866
871
|
: [util]
|
|
@@ -1160,7 +1165,8 @@ export function createContext(tailwindConfig, changedContent = [], root = postcs
|
|
|
1160
1165
|
candidateRuleCache: new Map(),
|
|
1161
1166
|
classCache: new Map(),
|
|
1162
1167
|
applyClassCache: new Map(),
|
|
1163
|
-
|
|
1168
|
+
// Seed the not class cache with the blocklist (which is only strings)
|
|
1169
|
+
notClassCache: new Set(tailwindConfig.blocklist ?? []),
|
|
1164
1170
|
postCssNodeCache: new Map(),
|
|
1165
1171
|
candidateRuleMap: new Map(),
|
|
1166
1172
|
tailwindConfig,
|
|
@@ -69,9 +69,9 @@ function resortSelector(sel) {
|
|
|
69
69
|
return -1
|
|
70
70
|
} else if (a.type === 'class' && b.type === 'tag') {
|
|
71
71
|
return 1
|
|
72
|
-
} else if (a.type === 'class' && b.type === 'pseudo' && b.value
|
|
72
|
+
} else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) {
|
|
73
73
|
return -1
|
|
74
|
-
} else if (a.type === 'pseudo' && a.value
|
|
74
|
+
} else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') {
|
|
75
75
|
return 1
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -150,6 +150,24 @@ export function normalizeConfig(config) {
|
|
|
150
150
|
return []
|
|
151
151
|
})()
|
|
152
152
|
|
|
153
|
+
// Normalize the `blocklist`
|
|
154
|
+
config.blocklist = (() => {
|
|
155
|
+
let { blocklist } = config
|
|
156
|
+
|
|
157
|
+
if (Array.isArray(blocklist)) {
|
|
158
|
+
if (blocklist.every((item) => typeof item === 'string')) {
|
|
159
|
+
return blocklist
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
log.warn('blocklist-invalid', [
|
|
163
|
+
'The `blocklist` option must be an array of strings.',
|
|
164
|
+
'https://tailwindcss.com/docs/content-configuration#discarding-classes',
|
|
165
|
+
])
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return []
|
|
169
|
+
})()
|
|
170
|
+
|
|
153
171
|
// Normalize prefix option
|
|
154
172
|
if (typeof config.prefix === 'function') {
|
|
155
173
|
log.warn('prefix-function', [
|