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 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.2...HEAD
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
@@ -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: content1 , extension ="html" } of rawContent){
174
- content1.push({
175
- content: content1,
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
@@ -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
- if (modified > prevModified) {
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
- notClassCache: new Set(),
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 !== ":merge") {
81
+ } else if (a.type === "class" && b.type === "pseudo" && b.value.startsWith("::")) {
82
82
  return -1;
83
- } else if (a.type === "pseudo" && a.value !== ":merge" && b.type === "class") {
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.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.25.0",
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",
@@ -195,8 +195,8 @@ let state = {
195
195
  return file !== null && typeof file === 'object'
196
196
  })
197
197
 
198
- for (let { raw: content, extension = 'html' } of rawContent) {
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
@@ -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
- if (modified > prevModified) {
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
- notClassCache: new Set(),
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 !== ':merge') {
72
+ } else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) {
73
73
  return -1
74
- } else if (a.type === 'pseudo' && a.value !== ':merge' && b.type === 'class') {
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', [