tailwindcss 3.2.1 → 3.2.2

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,19 @@ 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.2] - 2022-11-04
13
+
14
+ ### Fixed
15
+
16
+ - Escape special characters in resolved content base paths ([#9650](https://github.com/tailwindlabs/tailwindcss/pull/9650))
17
+ - Don't reuse container for array returning variant functions ([#9644](https://github.com/tailwindlabs/tailwindcss/pull/9644))
18
+ - Exclude non-relevant selectors when generating rules with the important modifier ([#9677](https://github.com/tailwindlabs/tailwindcss/issues/9677))
19
+ - Fix merging of arrays during config resolution ([#9706](https://github.com/tailwindlabs/tailwindcss/issues/9706))
20
+ - Ensure configured `font-feature-settings` are included in Preflight ([#9707](https://github.com/tailwindlabs/tailwindcss/pull/9707))
21
+ - Fix fractional values not being parsed properly inside arbitrary properties ([#9705](https://github.com/tailwindlabs/tailwindcss/pull/9705))
22
+ - Fix incorrect selectors when using `@apply` in selectors with combinators and pseudos ([#9722](https://github.com/tailwindlabs/tailwindcss/pull/9722))
23
+ - Fix cannot read properties of undefined (reading 'modifier') ([#9656](https://github.com/tailwindlabs/tailwindcss/pull/9656), [aa979d6](https://github.com/tailwindlabs/tailwindcss/commit/aa979d645f8bf4108c5fc938d7c0ba085b654c31))
24
+
12
25
  ## [3.2.1] - 2022-10-21
13
26
 
14
27
  ### Fixed
@@ -2088,7 +2101,8 @@ No release notes
2088
2101
 
2089
2102
  - Everything!
2090
2103
 
2091
- [unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.1...HEAD
2104
+ [unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.2...HEAD
2105
+ [3.2.2]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.1...v3.2.2
2092
2106
  [3.2.1]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.0...v3.2.1
2093
2107
  [3.2.0]: https://github.com/tailwindlabs/tailwindcss/compare/v3.1.8...v3.2.0
2094
2108
  [3.1.8]: https://github.com/tailwindlabs/tailwindcss/compare/v3.1.7...v3.1.8
@@ -37,22 +37,69 @@ function createWatcher(args, { state , rebuild }) {
37
37
  pollInterval: pollInterval
38
38
  } : false
39
39
  });
40
+ // A queue of rebuilds, file reads, etc… to run
40
41
  let chain = Promise.resolve();
41
- let pendingRebuilds = new Set();
42
- let changedContent = [];
42
+ /**
43
+ * A list of files that have been changed since the last rebuild
44
+ *
45
+ * @type {{file: string, content: () => Promise<string>, extension: string}[]}
46
+ */ let changedContent = [];
47
+ /**
48
+ * A list of files for which a rebuild has already been queued.
49
+ * This is used to prevent duplicate rebuilds when multiple events are fired for the same file.
50
+ * The rebuilt file is cleared from this list when it's associated rebuild has _started_
51
+ * This is because if the file is changed during a rebuild it won't trigger a new rebuild which it should
52
+ **/ let pendingRebuilds = new Set();
53
+ let _timer;
54
+ let _reject;
55
+ /**
56
+ * Rebuilds the changed files and resolves when the rebuild is
57
+ * complete regardless of whether it was successful or not
58
+ */ async function rebuildAndContinue() {
59
+ let changes = changedContent.splice(0);
60
+ // There are no changes to rebuild so we can just do nothing
61
+ if (changes.length === 0) {
62
+ return Promise.resolve();
63
+ }
64
+ // Clear all pending rebuilds for the about-to-be-built files
65
+ changes.forEach((change)=>pendingRebuilds.delete(change.file));
66
+ // Resolve the promise even when the rebuild fails
67
+ return rebuild(changes).then(()=>{}, ()=>{});
68
+ }
43
69
  /**
44
70
  *
45
71
  * @param {*} file
46
72
  * @param {(() => Promise<string>) | null} content
73
+ * @returns {Promise<void>}
47
74
  */ function recordChangedFile(file, content = null) {
48
75
  file = _path.default.resolve(file);
49
- content = content !== null && content !== void 0 ? content : async ()=>await _fs.default.promises.readFile(file, "utf8");
76
+ // Applications like Vim/Neovim fire both rename and change events in succession for atomic writes
77
+ // In that case rebuild has already been queued by rename, so can be skipped in change
78
+ if (pendingRebuilds.has(file)) {
79
+ return Promise.resolve();
80
+ }
81
+ // Mark that a rebuild of this file is going to happen
82
+ // It MUST happen synchronously before the rebuild is queued for this to be effective
83
+ pendingRebuilds.add(file);
50
84
  changedContent.push({
51
85
  file,
52
- content,
86
+ content: content !== null && content !== void 0 ? content : ()=>_fs.default.promises.readFile(file, "utf8"),
53
87
  extension: _path.default.extname(file).slice(1)
54
88
  });
55
- chain = chain.then(()=>rebuild(changedContent.splice(0)));
89
+ if (_timer) {
90
+ clearTimeout(_timer);
91
+ _reject();
92
+ }
93
+ // If a rebuild is already in progress we don't want to start another one until the 10ms timer has expired
94
+ chain = chain.then(()=>new Promise((resolve, reject)=>{
95
+ _timer = setTimeout(resolve, 10);
96
+ _reject = reject;
97
+ }));
98
+ // Resolves once this file has been rebuilt (or the rebuild for this file has failed)
99
+ // This queues as many rebuilds as there are changed files
100
+ // But those rebuilds happen after some delay
101
+ // And will immediately resolve if there are no changes
102
+ chain = chain.then(rebuildAndContinue, rebuildAndContinue);
56
103
  return chain;
57
104
  }
58
105
  watcher.on("change", (file)=>recordChangedFile(file));
@@ -91,15 +138,30 @@ function createWatcher(args, { state , rebuild }) {
91
138
  if (pendingRebuilds.has(filePath)) {
92
139
  return;
93
140
  }
141
+ // We'll go ahead and add the file to the pending rebuilds list here
142
+ // It'll be removed when the rebuild starts unless the read fails
143
+ // which will be taken care of as well
94
144
  pendingRebuilds.add(filePath);
95
- chain = chain.then(async ()=>{
96
- let content;
145
+ async function enqueue() {
97
146
  try {
98
- content = await (0, _utilsJs.readFileWithRetries)(_path.default.resolve(filePath));
99
- } finally{
100
- pendingRebuilds.delete(filePath);
147
+ // We need to read the file as early as possible outside of the chain
148
+ // because it may be gone by the time we get to it. doing the read
149
+ // immediately increases the chance that the file is still there
150
+ let content = await (0, _utilsJs.readFileWithRetries)(_path.default.resolve(filePath));
151
+ if (content === undefined) {
152
+ return;
153
+ }
154
+ // This will push the rebuild onto the chain
155
+ // @ts-ignore: TypeScript isn't picking up that content is a string here
156
+ await recordChangedFile(filePath, ()=>content);
157
+ } catch {
158
+ // If reading the file fails, it's was probably a deleted temporary file
159
+ // So we can ignore it and no rebuild is needed
101
160
  }
102
- return recordChangedFile(filePath, ()=>content);
161
+ }
162
+ enqueue().then(()=>{
163
+ // If the file read fails we still need to make sure the file isn't stuck in the pending rebuilds list
164
+ pendingRebuilds.delete(filePath);
103
165
  });
104
166
  });
105
167
  return {
@@ -22,6 +22,7 @@
22
22
  2. Prevent adjustments of font size after orientation changes in iOS.
23
23
  3. Use a more readable tab size.
24
24
  4. Use the user's configured `sans` font-family by default.
25
+ 5. Use the user's configured `sans` font-feature-settings by default.
25
26
  */
26
27
 
27
28
  html {
@@ -30,6 +31,7 @@ html {
30
31
  -moz-tab-size: 4; /* 3 */
31
32
  tab-size: 4; /* 3 */
32
33
  font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */
34
+ font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings', normal); /* 5 */
33
35
  }
34
36
 
35
37
  /*
@@ -73,13 +73,16 @@ function parseCandidateFiles(context, tailwindConfig) {
73
73
  * @param {ContentPath} contentPath
74
74
  * @returns {ContentPath}
75
75
  */ function resolveGlobPattern(contentPath) {
76
- contentPath.pattern = contentPath.glob ? `${contentPath.base}/${contentPath.glob}` : contentPath.base;
77
- contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern;
78
76
  // This is required for Windows support to properly pick up Glob paths.
79
77
  // Afaik, this technically shouldn't be needed but there's probably
80
78
  // some internal, direct path matching with a normalized path in
81
79
  // a package which can't handle mixed directory separators
82
- contentPath.pattern = (0, _normalizePath.default)(contentPath.pattern);
80
+ let base = (0, _normalizePath.default)(contentPath.base);
81
+ // If the user's file path contains any special characters (like parens) for instance fast-glob
82
+ // is like "OOOH SHINY" and treats them as such. So we have to escape the base path to fix this
83
+ base = _fastGlob.default.escapePath(base);
84
+ contentPath.pattern = contentPath.glob ? `${base}/${contentPath.glob}` : base;
85
+ contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern;
83
86
  return contentPath;
84
87
  }
85
88
  /**
@@ -72,7 +72,7 @@ function* buildRegExps(context) {
72
72
  ])) : "";
73
73
  let utility = _regex.any([
74
74
  // Arbitrary properties
75
- /\[[^\s:'"`]+:[^\s\]]+\]/,
75
+ /\[[^\s:'"`]+:[^\s]+\]/,
76
76
  // Utilities
77
77
  _regex.pattern([
78
78
  // Utility Name / Group Name
@@ -306,21 +306,40 @@ function processApply(root, context, localCache) {
306
306
  hasReplaced = true;
307
307
  });
308
308
  });
309
- // Sort tag names before class names
309
+ // Sort tag names before class names (but only sort each group (separated by a combinator)
310
+ // separately and not in total)
310
311
  // This happens when replacing `.bar` in `.foo.bar` with a tag like `section`
311
- for (const sel1 of replaced){
312
- sel1.sort((a, b)=>{
313
- if (a.type === "tag" && b.type === "class") {
314
- return -1;
315
- } else if (a.type === "class" && b.type === "tag") {
316
- return 1;
317
- } else if (a.type === "class" && b.type === "pseudo") {
318
- return -1;
319
- } else if (a.type === "pseudo" && b.type === "class") {
320
- return 1;
312
+ for (let sel1 of replaced){
313
+ let groups = [
314
+ []
315
+ ];
316
+ for (let node of sel1.nodes){
317
+ if (node.type === "combinator") {
318
+ groups.push(node);
319
+ groups.push([]);
320
+ } else {
321
+ let last = groups[groups.length - 1];
322
+ last.push(node);
321
323
  }
322
- return sel1.index(a) - sel1.index(b);
323
- });
324
+ }
325
+ sel1.nodes = [];
326
+ for (let group of groups){
327
+ if (Array.isArray(group)) {
328
+ group.sort((a, b)=>{
329
+ if (a.type === "tag" && b.type === "class") {
330
+ return -1;
331
+ } else if (a.type === "class" && b.type === "tag") {
332
+ return 1;
333
+ } else if (a.type === "class" && b.type === "pseudo") {
334
+ return -1;
335
+ } else if (a.type === "pseudo" && b.type === "class") {
336
+ return 1;
337
+ }
338
+ return 0;
339
+ });
340
+ }
341
+ sel1.nodes = sel1.nodes.concat(group);
342
+ }
324
343
  }
325
344
  sel.replaceWith(...replaced);
326
345
  });
@@ -340,7 +359,7 @@ function processApply(root, context, localCache) {
340
359
  let [applyCandidates1, important] = extractApplyCandidates(apply.params);
341
360
  if (apply.parent.type === "atrule") {
342
361
  if (apply.parent.name === "screen") {
343
- const screenType = apply.parent.params;
362
+ let screenType = apply.parent.params;
344
363
  throw apply.error(`@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates1.map((c)=>`${screenType}:${c}`).join(" ")} instead.`);
345
364
  }
346
365
  throw apply.error(`@apply is not supported within nested at-rules like @${apply.parent.name}. You can fix this by un-nesting @${apply.parent.name}.`);
@@ -364,7 +383,7 @@ function processApply(root, context, localCache) {
364
383
  ]);
365
384
  }
366
385
  }
367
- for (const [parent, [candidates1, atApplySource]] of perParentApplies){
386
+ for (let [parent, [candidates1, atApplySource]] of perParentApplies){
368
387
  let siblings = [];
369
388
  for (let [applyCandidate1, important1, rules1] of candidates1){
370
389
  let potentialApplyCandidates = [
@@ -169,7 +169,7 @@ function expandTailwindAtRules(context) {
169
169
  ], context);
170
170
  }
171
171
  env.DEBUG && console.timeEnd("Build stylesheet");
172
- let { defaults: defaultNodes , base: baseNodes , components: componentNodes , utilities: utilityNodes , variants: screenNodes , } = context.stylesheetCache;
172
+ let { defaults: defaultNodes , base: baseNodes , components: componentNodes , utilities: utilityNodes , variants: screenNodes } = context.stylesheetCache;
173
173
  // ---
174
174
  // Replace any Tailwind directives with generated CSS
175
175
  if (layerNodes.base) {
@@ -162,7 +162,7 @@ function applyImportant(matches, classCandidate) {
162
162
  ]
163
163
  });
164
164
  container.walkRules((r)=>{
165
- r.selector = (0, _pluginUtils.updateAllClasses)(r.selector, (className)=>{
165
+ r.selector = (0, _pluginUtils.updateAllClasses)((0, _pluginUtils.filterSelectorsForClass)(r.selector, classCandidate), (className)=>{
166
166
  if (className === classCandidate) {
167
167
  return `!${className}`;
168
168
  }
@@ -256,7 +256,7 @@ function applyVariant(variant, matches, context) {
256
256
  ]
257
257
  });
258
258
  for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples){
259
- let clone = containerFromArray !== null && containerFromArray !== void 0 ? containerFromArray : container.clone();
259
+ let clone = (containerFromArray !== null && containerFromArray !== void 0 ? containerFromArray : container).clone();
260
260
  let collectedFormats = [];
261
261
  function prepareBackup() {
262
262
  // Already prepared, chicken out
@@ -533,7 +533,7 @@ function parseVariant(variant) {
533
533
  variantFunctions = [].concat(variantFunctions).map((variantFunction)=>{
534
534
  if (typeof variantFunction !== "string") {
535
535
  // Safelist public API functions
536
- return (api)=>{
536
+ return (api = {})=>{
537
537
  let { args , modifySelectors , container , separator , wrap , format } = api;
538
538
  let result = variantFunction(Object.assign({
539
539
  modifySelectors,
@@ -574,12 +574,14 @@ function parseVariant(variant) {
574
574
  var ref1;
575
575
  for (let [key, value] of Object.entries((ref1 = options === null || options === void 0 ? void 0 : options.values) !== null && ref1 !== void 0 ? ref1 : {})){
576
576
  if (key === "DEFAULT") continue;
577
- api.addVariant(isSpecial ? `${variant}${key}` : `${variant}-${key}`, ({ args , container })=>variantFn(value, modifiersEnabled ? {
578
- modifier: args.modifier,
577
+ api.addVariant(isSpecial ? `${variant}${key}` : `${variant}-${key}`, ({ args , container })=>{
578
+ return variantFn(value, modifiersEnabled ? {
579
+ modifier: args === null || args === void 0 ? void 0 : args.modifier,
579
580
  container
580
581
  } : {
581
582
  container
582
- }), {
583
+ });
584
+ }, {
583
585
  ...options,
584
586
  value,
585
587
  id,
@@ -590,11 +592,13 @@ function parseVariant(variant) {
590
592
  var ref2;
591
593
  let hasDefault = "DEFAULT" in ((ref2 = options === null || options === void 0 ? void 0 : options.values) !== null && ref2 !== void 0 ? ref2 : {});
592
594
  api.addVariant(variant, ({ args , container })=>{
593
- if (args.value === _sharedState.NONE && !hasDefault) {
595
+ if ((args === null || args === void 0 ? void 0 : args.value) === _sharedState.NONE && !hasDefault) {
594
596
  return null;
595
597
  }
596
- return variantFn(args.value === _sharedState.NONE ? options.values.DEFAULT : args.value, modifiersEnabled ? {
597
- modifier: args.modifier,
598
+ var // (JetBrains) plugins.
599
+ ref;
600
+ return variantFn((args === null || args === void 0 ? void 0 : args.value) === _sharedState.NONE ? options.values.DEFAULT : (ref = args === null || args === void 0 ? void 0 : args.value) !== null && ref !== void 0 ? ref : typeof args === "string" ? args : "", modifiersEnabled ? {
601
+ modifier: args === null || args === void 0 ? void 0 : args.modifier,
598
602
  container
599
603
  } : {
600
604
  container
@@ -114,7 +114,7 @@ function finalizeSelector(format, { selector , candidate , context , isArbitrary
114
114
  // │ │ │ ╰── We will not split here
115
115
  // ╰──┴─────┴─────────────── We will split here
116
116
  //
117
- base =candidate.split(new RegExp(`\\${(ref1 = context === null || context === void 0 ? void 0 : (ref = context.tailwindConfig) === null || ref === void 0 ? void 0 : ref.separator) !== null && ref1 !== void 0 ? ref1 : ":"}(?![^[]*\\])`)).pop() , }) {
117
+ base =candidate.split(new RegExp(`\\${(ref1 = context === null || context === void 0 ? void 0 : (ref = context.tailwindConfig) === null || ref === void 0 ? void 0 : ref.separator) !== null && ref1 !== void 0 ? ref1 : ":"}(?![^[]*\\])`)).pop() }) {
118
118
  var ref2;
119
119
  let ast = (0, _postcssSelectorParser.default)().astSync(selector);
120
120
  // We explicitly DO NOT prefix classes in arbitrary variants
@@ -10,6 +10,7 @@ function _export(target, all) {
10
10
  }
11
11
  _export(exports, {
12
12
  updateAllClasses: ()=>updateAllClasses,
13
+ filterSelectorsForClass: ()=>filterSelectorsForClass,
13
14
  asValue: ()=>asValue,
14
15
  parseColorFormat: ()=>parseColorFormat,
15
16
  asColor: ()=>asColor,
@@ -43,6 +44,18 @@ function updateAllClasses(selectors, updateClass) {
43
44
  let result = parser.processSync(selectors);
44
45
  return result;
45
46
  }
47
+ function filterSelectorsForClass(selectors, classCandidate) {
48
+ let parser = (0, _postcssSelectorParser.default)((selectors)=>{
49
+ selectors.each((sel)=>{
50
+ const containsClass = sel.nodes.some((node)=>node.type === "class" && node.value === classCandidate);
51
+ if (!containsClass) {
52
+ sel.remove();
53
+ }
54
+ });
55
+ });
56
+ let result = parser.processSync(selectors);
57
+ return result;
58
+ }
46
59
  function resolveArbitraryValue(modifier, validate) {
47
60
  if (!isArbitraryValue(modifier)) {
48
61
  return undefined;
@@ -27,16 +27,13 @@ function _interopRequireDefault(obj) {
27
27
  function isFunction(input) {
28
28
  return typeof input === "function";
29
29
  }
30
- function isObject(input) {
31
- return typeof input === "object" && input !== null;
32
- }
33
30
  function mergeWith(target, ...sources) {
34
31
  let customizer = sources.pop();
35
32
  for (let source of sources){
36
33
  for(let k in source){
37
34
  let merged = customizer(target[k], source[k]);
38
35
  if (merged === undefined) {
39
- if (isObject(target[k]) && isObject(source[k])) {
36
+ if ((0, _isPlainObject.default)(target[k]) && (0, _isPlainObject.default)(source[k])) {
40
37
  target[k] = mergeWith({}, target[k], source[k], customizer);
41
38
  } else {
42
39
  target[k] = source[k];
@@ -101,11 +98,11 @@ function mergeThemes(themes) {
101
98
  }
102
99
  function mergeExtensionCustomizer(merged, value) {
103
100
  // When we have an array of objects, we do want to merge it
104
- if (Array.isArray(merged) && isObject(merged[0])) {
101
+ if (Array.isArray(merged) && (0, _isPlainObject.default)(merged[0])) {
105
102
  return merged.concat(value);
106
103
  }
107
104
  // When the incoming value is an array, and the existing config is an object, prepend the existing object
108
- if (Array.isArray(value) && isObject(value[0]) && isObject(merged)) {
105
+ if (Array.isArray(value) && (0, _isPlainObject.default)(value[0]) && (0, _isPlainObject.default)(merged)) {
109
106
  return [
110
107
  merged,
111
108
  ...value
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailwindcss",
3
- "version": "3.2.1",
3
+ "version": "3.2.2",
4
4
  "description": "A utility-first CSS framework for rapidly building custom user interfaces.",
5
5
  "license": "MIT",
6
6
  "main": "lib/index.js",
@@ -44,12 +44,12 @@
44
44
  ],
45
45
  "devDependencies": {
46
46
  "@swc/cli": "^0.1.57",
47
- "@swc/core": "^1.3.4",
47
+ "@swc/core": "^1.3.11",
48
48
  "@swc/jest": "^0.2.23",
49
49
  "@swc/register": "^0.1.10",
50
- "autoprefixer": "^10.4.12",
51
- "cssnano": "^5.1.13",
52
- "esbuild": "^0.15.10",
50
+ "autoprefixer": "^10.4.13",
51
+ "cssnano": "^5.1.14",
52
+ "esbuild": "^0.15.12",
53
53
  "eslint": "^8.25.0",
54
54
  "eslint-config-prettier": "^8.5.0",
55
55
  "eslint-plugin-prettier": "^4.2.1",
@@ -77,7 +77,7 @@
77
77
  "normalize-path": "^3.0.0",
78
78
  "object-hash": "^3.0.0",
79
79
  "picocolors": "^1.0.0",
80
- "postcss": "^8.4.17",
80
+ "postcss": "^8.4.18",
81
81
  "postcss-import": "^14.1.0",
82
82
  "postcss-js": "^4.0.0",
83
83
  "postcss-load-config": "^3.1.4",