tailwindcss 0.0.0-insiders.bbf2b1b → 0.0.0-insiders.bcf14b1
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 +9 -1
- package/lib/cli.js +66 -0
- package/lib/corePlugins.js +2 -4
- package/lib/css/preflight.css +5 -0
- package/lib/featureFlags.js +2 -1
- package/lib/lib/expandApplyAtRules.js +58 -24
- package/lib/lib/expandTailwindAtRules.js +1 -4
- package/lib/lib/generateRules.js +43 -37
- package/lib/lib/setupContextUtils.js +1 -0
- package/lib/util/getAllConfigs.js +10 -0
- package/package.json +4 -4
- package/src/cli.js +81 -0
- package/src/corePlugins.js +1 -1
- package/src/css/preflight.css +5 -0
- package/src/featureFlags.js +5 -1
- package/src/lib/expandApplyAtRules.js +65 -18
- package/src/lib/expandTailwindAtRules.js +1 -5
- package/src/lib/generateRules.js +44 -35
- package/src/lib/setupContextUtils.js +1 -0
- package/src/util/getAllConfigs.js +11 -0
- package/types/config.d.ts +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,13 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
- Add support
|
|
12
|
+
- Add support for configuring default `font-feature-settings` for a font family ([#9039](https://github.com/tailwindlabs/tailwindcss/pull/9039))
|
|
13
13
|
- Add a standalone CLI build for 32-bit Linux on ARM (`node16-linux-armv7`) ([#9084](https://github.com/tailwindlabs/tailwindcss/pull/9084))
|
|
14
|
+
- Add future flag to disable color opacity utility plugins ([#9088](https://github.com/tailwindlabs/tailwindcss/pull/9088))
|
|
15
|
+
- Add negative value support for `outline-offset` ([#9136](https://github.com/tailwindlabs/tailwindcss/pull/9136))
|
|
14
16
|
|
|
15
17
|
### Fixed
|
|
16
18
|
|
|
17
19
|
- Use absolute paths when resolving changed files for resilience against working directory changes ([#9032](https://github.com/tailwindlabs/tailwindcss/pull/9032))
|
|
18
20
|
- Fix ring color utility generation when using `respectDefaultRingColorOpacity` ([#9070](https://github.com/tailwindlabs/tailwindcss/pull/9070))
|
|
21
|
+
- Sort tags before classes when `@applying` a selector with joined classes ([#9107](https://github.com/tailwindlabs/tailwindcss/pull/9107))
|
|
22
|
+
- Remove invalid `outline-hidden` utility ([#9147](https://github.com/tailwindlabs/tailwindcss/pull/9147))
|
|
23
|
+
- Honor the `hidden` attribute on elements in preflight ([#9174](https://github.com/tailwindlabs/tailwindcss/pull/9174))
|
|
24
|
+
- Don't stop watching atomically renamed files ([#9173](https://github.com/tailwindlabs/tailwindcss/pull/9173))
|
|
25
|
+
- Re-use existing entries in the rule cache ([#9208](https://github.com/tailwindlabs/tailwindcss/pull/9208))
|
|
26
|
+
- Don't output duplicate utilities ([#9208](https://github.com/tailwindlabs/tailwindcss/pull/9208))
|
|
19
27
|
|
|
20
28
|
## [3.1.8] - 2022-08-05
|
|
21
29
|
|
package/lib/cli.js
CHANGED
|
@@ -748,6 +748,10 @@ async function build() {
|
|
|
748
748
|
...contextDependencies,
|
|
749
749
|
...extractFileGlobs(config)
|
|
750
750
|
], {
|
|
751
|
+
// Force checking for atomic writes in all situations
|
|
752
|
+
// This causes chokidar to wait up to 100ms for a file to re-added after it's been unlinked
|
|
753
|
+
// This only works when watching directories though
|
|
754
|
+
atomic: true,
|
|
751
755
|
usePolling: shouldPoll,
|
|
752
756
|
interval: shouldPoll ? pollInterval : undefined,
|
|
753
757
|
ignoreInitial: true,
|
|
@@ -757,6 +761,7 @@ async function build() {
|
|
|
757
761
|
} : false
|
|
758
762
|
});
|
|
759
763
|
let chain = Promise.resolve();
|
|
764
|
+
let pendingRebuilds = new Set();
|
|
760
765
|
watcher.on("change", async (file)=>{
|
|
761
766
|
if (contextDependencies.has(file)) {
|
|
762
767
|
env.DEBUG && console.time("Resolve config");
|
|
@@ -782,6 +787,67 @@ async function build() {
|
|
|
782
787
|
});
|
|
783
788
|
}
|
|
784
789
|
});
|
|
790
|
+
/**
|
|
791
|
+
* When rapidly saving files atomically a couple of situations can happen:
|
|
792
|
+
* - The file is missing since the external program has deleted it by the time we've gotten around to reading it from the earlier save.
|
|
793
|
+
* - The file is being written to by the external program by the time we're going to read it and is thus treated as busy because a lock is held.
|
|
794
|
+
*
|
|
795
|
+
* To work around this we retry reading the file a handful of times with a delay between each attempt
|
|
796
|
+
*
|
|
797
|
+
* @param {string} path
|
|
798
|
+
* @param {number} tries
|
|
799
|
+
* @returns {string}
|
|
800
|
+
* @throws {Error} If the file is still missing or busy after the specified number of tries
|
|
801
|
+
*/ async function readFileWithRetries(path, tries = 5) {
|
|
802
|
+
for(let n = 0; n <= tries; n++){
|
|
803
|
+
try {
|
|
804
|
+
return await _fs.default.promises.readFile(path, "utf8");
|
|
805
|
+
} catch (err) {
|
|
806
|
+
if (n !== tries) {
|
|
807
|
+
if (err.code === "ENOENT" || err.code === "EBUSY") {
|
|
808
|
+
await new Promise((resolve)=>setTimeout(resolve, 10));
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
throw err;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// Restore watching any files that are "removed"
|
|
817
|
+
// This can happen when a file is pseudo-atomically replaced (a copy is created, overwritten, the old one is unlinked, and the new one is renamed)
|
|
818
|
+
// TODO: An an optimization we should allow removal when the config changes
|
|
819
|
+
watcher.on("unlink", (file)=>watcher.add(file));
|
|
820
|
+
// Some applications such as Visual Studio (but not VS Code)
|
|
821
|
+
// will only fire a rename event for atomic writes and not a change event
|
|
822
|
+
// This is very likely a chokidar bug but it's one we need to work around
|
|
823
|
+
// We treat this as a change event and rebuild the CSS
|
|
824
|
+
watcher.on("raw", (evt, filePath, meta)=>{
|
|
825
|
+
if (evt !== "rename") {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
let watchedPath = _path.default.resolve(meta.watchedPath);
|
|
829
|
+
// Watched path might be the file itself
|
|
830
|
+
// Or the directory it is in
|
|
831
|
+
filePath = watchedPath.endsWith(filePath) ? watchedPath : _path.default.resolve(watchedPath, filePath);
|
|
832
|
+
// Skip since we've already queued a rebuild for this file that hasn't happened yet
|
|
833
|
+
if (pendingRebuilds.has(filePath)) {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
pendingRebuilds.add(filePath);
|
|
837
|
+
chain = chain.then(async ()=>{
|
|
838
|
+
let content;
|
|
839
|
+
try {
|
|
840
|
+
content = await readFileWithRetries(_path.default.resolve(filePath));
|
|
841
|
+
} finally{
|
|
842
|
+
pendingRebuilds.delete(filePath);
|
|
843
|
+
}
|
|
844
|
+
changedContent.push({
|
|
845
|
+
content,
|
|
846
|
+
extension: _path.default.extname(filePath).slice(1)
|
|
847
|
+
});
|
|
848
|
+
await rebuild(config);
|
|
849
|
+
});
|
|
850
|
+
});
|
|
785
851
|
watcher.on("add", async (file)=>{
|
|
786
852
|
chain = chain.then(async ()=>{
|
|
787
853
|
changedContent.push({
|
package/lib/corePlugins.js
CHANGED
|
@@ -3182,9 +3182,6 @@ let corePlugins = {
|
|
|
3182
3182
|
},
|
|
3183
3183
|
".outline-double": {
|
|
3184
3184
|
"outline-style": "double"
|
|
3185
|
-
},
|
|
3186
|
-
".outline-hidden": {
|
|
3187
|
-
"outline-style": "hidden"
|
|
3188
3185
|
}
|
|
3189
3186
|
});
|
|
3190
3187
|
},
|
|
@@ -3214,7 +3211,8 @@ let corePlugins = {
|
|
|
3214
3211
|
"length",
|
|
3215
3212
|
"number",
|
|
3216
3213
|
"percentage"
|
|
3217
|
-
]
|
|
3214
|
+
],
|
|
3215
|
+
supportsNegativeValues: true
|
|
3218
3216
|
}),
|
|
3219
3217
|
outlineColor: ({ matchUtilities , theme })=>{
|
|
3220
3218
|
matchUtilities({
|
package/lib/css/preflight.css
CHANGED
package/lib/featureFlags.js
CHANGED
|
@@ -41,11 +41,11 @@ function _interopRequireDefault(obj) {
|
|
|
41
41
|
groups: normalizedGroups
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
-
let selectorExtractor = (0, _postcssSelectorParser.default)(
|
|
44
|
+
let selectorExtractor = (0, _postcssSelectorParser.default)();
|
|
45
45
|
/**
|
|
46
46
|
* @param {string} ruleSelectors
|
|
47
47
|
*/ function extractSelectors(ruleSelectors) {
|
|
48
|
-
return selectorExtractor.
|
|
48
|
+
return selectorExtractor.astSync(ruleSelectors);
|
|
49
49
|
}
|
|
50
50
|
function extractBaseCandidates(candidates, separator) {
|
|
51
51
|
let baseClasses = new Set();
|
|
@@ -268,30 +268,64 @@ function processApply(root, context, localCache) {
|
|
|
268
268
|
* What happens in this function is that we prepend a `.` and escape the candidate.
|
|
269
269
|
* This will result in `.hover\:font-bold`
|
|
270
270
|
* Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
needle.replace(/\\2c /g, "\\,")
|
|
278
|
-
])
|
|
279
|
-
];
|
|
271
|
+
*
|
|
272
|
+
* @param {string} selector
|
|
273
|
+
* @param {string} utilitySelectors
|
|
274
|
+
* @param {string} candidate
|
|
275
|
+
*/ function replaceSelector(selector, utilitySelectors, candidate) {
|
|
276
|
+
let selectorList = extractSelectors(selector);
|
|
280
277
|
let utilitySelectorsList = extractSelectors(utilitySelectors);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
278
|
+
let candidateList = extractSelectors(`.${(0, _escapeClassName.default)(candidate)}`);
|
|
279
|
+
let candidateClass = candidateList.nodes[0].nodes[0];
|
|
280
|
+
selectorList.each((sel)=>{
|
|
281
|
+
/** @type {Set<import('postcss-selector-parser').Selector>} */ let replaced = new Set();
|
|
282
|
+
utilitySelectorsList.each((utilitySelector)=>{
|
|
283
|
+
let hasReplaced = false;
|
|
284
|
+
utilitySelector = utilitySelector.clone();
|
|
285
|
+
utilitySelector.walkClasses((node)=>{
|
|
286
|
+
if (node.value !== candidateClass.value) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
// Don't replace multiple instances of the same class
|
|
290
|
+
// This is theoretically correct but only partially
|
|
291
|
+
// We'd need to generate every possible permutation of the replacement
|
|
292
|
+
// For example with `.foo + .foo { … }` and `section { @apply foo; }`
|
|
293
|
+
// We'd need to generate all of these:
|
|
294
|
+
// - `.foo + .foo`
|
|
295
|
+
// - `.foo + section`
|
|
296
|
+
// - `section + .foo`
|
|
297
|
+
// - `section + section`
|
|
298
|
+
if (hasReplaced) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
// Since you can only `@apply` class names this is sufficient
|
|
302
|
+
// We want to replace the matched class name with the selector the user is using
|
|
303
|
+
// Ex: Replace `.text-blue-500` with `.foo.bar:is(.something-cool)`
|
|
304
|
+
node.replaceWith(...sel.nodes.map((node)=>node.clone()));
|
|
305
|
+
// Record that we did something and we want to use this new selector
|
|
306
|
+
replaced.add(utilitySelector);
|
|
307
|
+
hasReplaced = true;
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
// Sort tag names before class names
|
|
311
|
+
// This happens when replacing `.bar` in `.foo.bar` with a tag like `section`
|
|
312
|
+
for (const sel1 of replaced){
|
|
313
|
+
sel1.sort((a, b)=>{
|
|
314
|
+
if (a.type === "tag" && b.type === "class") {
|
|
315
|
+
return -1;
|
|
316
|
+
} else if (a.type === "class" && b.type === "tag") {
|
|
317
|
+
return 1;
|
|
318
|
+
} else if (a.type === "class" && b.type === "pseudo") {
|
|
319
|
+
return -1;
|
|
320
|
+
} else if (a.type === "pseudo" && b.type === "class") {
|
|
321
|
+
return 1;
|
|
322
|
+
}
|
|
323
|
+
return sel1.index(a) - sel1.index(b);
|
|
324
|
+
});
|
|
292
325
|
}
|
|
293
|
-
|
|
294
|
-
})
|
|
326
|
+
sel.replaceWith(...replaced);
|
|
327
|
+
});
|
|
328
|
+
return selectorList.toString();
|
|
295
329
|
}
|
|
296
330
|
let perParentApplies = new Map();
|
|
297
331
|
// Collect all apply candidates and their rules
|
|
@@ -188,14 +188,11 @@ function expandTailwindAtRules(context) {
|
|
|
188
188
|
// Generate the actual CSS
|
|
189
189
|
let classCacheCount = context.classCache.size;
|
|
190
190
|
env.DEBUG && console.time("Generate rules");
|
|
191
|
-
|
|
191
|
+
(0, _generateRules.generateRules)(candidates, context);
|
|
192
192
|
env.DEBUG && console.timeEnd("Generate rules");
|
|
193
193
|
// We only ever add to the classCache, so if it didn't grow, there is nothing new.
|
|
194
194
|
env.DEBUG && console.time("Build stylesheet");
|
|
195
195
|
if (context.stylesheetCache === null || context.classCache.size !== classCacheCount) {
|
|
196
|
-
for (let rule of rules){
|
|
197
|
-
context.ruleCache.add(rule);
|
|
198
|
-
}
|
|
199
196
|
context.stylesheetCache = buildStylesheet([
|
|
200
197
|
...context.ruleCache
|
|
201
198
|
], context);
|
package/lib/lib/generateRules.js
CHANGED
|
@@ -665,14 +665,39 @@ function* resolveMatches(candidate, context, original = candidate) {
|
|
|
665
665
|
function inKeyframes(rule) {
|
|
666
666
|
return rule.parent && rule.parent.type === "atrule" && rule.parent.name === "keyframes";
|
|
667
667
|
}
|
|
668
|
+
function getImportantStrategy(important) {
|
|
669
|
+
if (important === true) {
|
|
670
|
+
return (rule)=>{
|
|
671
|
+
if (inKeyframes(rule)) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
rule.walkDecls((d)=>{
|
|
675
|
+
if (d.parent.type === "rule" && !inKeyframes(d.parent)) {
|
|
676
|
+
d.important = true;
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
if (typeof important === "string") {
|
|
682
|
+
return (rule)=>{
|
|
683
|
+
if (inKeyframes(rule)) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
rule.selectors = rule.selectors.map((selector)=>{
|
|
687
|
+
return `${important} ${selector}`;
|
|
688
|
+
});
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
}
|
|
668
692
|
function generateRules(candidates, context) {
|
|
669
693
|
let allRules = [];
|
|
694
|
+
let strategy = getImportantStrategy(context.tailwindConfig.important);
|
|
670
695
|
for (let candidate of candidates){
|
|
671
696
|
if (context.notClassCache.has(candidate)) {
|
|
672
697
|
continue;
|
|
673
698
|
}
|
|
674
|
-
if (context.
|
|
675
|
-
allRules.
|
|
699
|
+
if (context.candidateRuleCache.has(candidate)) {
|
|
700
|
+
allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate)));
|
|
676
701
|
continue;
|
|
677
702
|
}
|
|
678
703
|
let matches = Array.from(resolveMatches(candidate, context));
|
|
@@ -681,49 +706,30 @@ function generateRules(candidates, context) {
|
|
|
681
706
|
continue;
|
|
682
707
|
}
|
|
683
708
|
context.classCache.set(candidate, matches);
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
rule.walkDecls((d)=>{
|
|
691
|
-
if (d.parent.type === "rule" && !inKeyframes(d.parent)) {
|
|
692
|
-
d.important = true;
|
|
693
|
-
}
|
|
694
|
-
});
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
if (typeof important === "string") {
|
|
698
|
-
return (rule)=>{
|
|
699
|
-
rule.selectors = rule.selectors.map((selector)=>{
|
|
700
|
-
return `${important} ${selector}`;
|
|
701
|
-
});
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
})(context.tailwindConfig.important);
|
|
705
|
-
return allRules.flat(1).map(([{ sort , layer , options }, rule])=>{
|
|
706
|
-
if (options.respectImportant) {
|
|
707
|
-
if (strategy) {
|
|
709
|
+
var ref;
|
|
710
|
+
let rules = (ref = context.candidateRuleCache.get(candidate)) !== null && ref !== void 0 ? ref : new Set();
|
|
711
|
+
context.candidateRuleCache.set(candidate, rules);
|
|
712
|
+
for (const match of matches){
|
|
713
|
+
let [{ sort , layer , options }, rule] = match;
|
|
714
|
+
if (options.respectImportant && strategy) {
|
|
708
715
|
let container = _postcss.default.root({
|
|
709
716
|
nodes: [
|
|
710
717
|
rule.clone()
|
|
711
718
|
]
|
|
712
719
|
});
|
|
713
|
-
container.walkRules(
|
|
714
|
-
if (inKeyframes(r)) {
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
strategy(r);
|
|
718
|
-
});
|
|
720
|
+
container.walkRules(strategy);
|
|
719
721
|
rule = container.nodes[0];
|
|
720
722
|
}
|
|
723
|
+
let newEntry = [
|
|
724
|
+
sort | context.layerOrder[layer],
|
|
725
|
+
rule
|
|
726
|
+
];
|
|
727
|
+
rules.add(newEntry);
|
|
728
|
+
context.ruleCache.add(newEntry);
|
|
729
|
+
allRules.push(newEntry);
|
|
721
730
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
rule
|
|
725
|
-
];
|
|
726
|
-
});
|
|
731
|
+
}
|
|
732
|
+
return allRules;
|
|
727
733
|
}
|
|
728
734
|
function isArbitraryValue(input) {
|
|
729
735
|
return input.startsWith("[") && input.endsWith("]");
|
|
@@ -858,6 +858,7 @@ function createContext(tailwindConfig, changedContent = [], root = _postcss.defa
|
|
|
858
858
|
let context = {
|
|
859
859
|
disposables: [],
|
|
860
860
|
ruleCache: new Set(),
|
|
861
|
+
candidateRuleCache: new Map(),
|
|
861
862
|
classCache: new Map(),
|
|
862
863
|
applyClassCache: new Map(),
|
|
863
864
|
notClassCache: new Set(),
|
|
@@ -27,6 +27,16 @@ function getAllConfigs(config) {
|
|
|
27
27
|
...theme("colors")
|
|
28
28
|
})
|
|
29
29
|
}
|
|
30
|
+
},
|
|
31
|
+
disableColorOpacityUtilitiesByDefault: {
|
|
32
|
+
corePlugins: {
|
|
33
|
+
backgroundOpacity: false,
|
|
34
|
+
borderOpacity: false,
|
|
35
|
+
divideOpacity: false,
|
|
36
|
+
placeholderOpacity: false,
|
|
37
|
+
ringOpacity: false,
|
|
38
|
+
textOpacity: false
|
|
39
|
+
}
|
|
30
40
|
}
|
|
31
41
|
};
|
|
32
42
|
const experimentals = Object.keys(features).filter((feature)=>(0, _featureFlags.flagEnabled)(config, feature)).map((feature)=>features[feature]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tailwindcss",
|
|
3
|
-
"version": "0.0.0-insiders.
|
|
3
|
+
"version": "0.0.0-insiders.bcf14b1",
|
|
4
4
|
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
],
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@swc/cli": "^0.1.57",
|
|
46
|
-
"@swc/core": "^1.2.
|
|
46
|
+
"@swc/core": "^1.2.244",
|
|
47
47
|
"@swc/jest": "^0.2.22",
|
|
48
48
|
"@swc/register": "^0.1.10",
|
|
49
49
|
"autoprefixer": "^10.4.8",
|
|
50
|
-
"cssnano": "^5.1.
|
|
50
|
+
"cssnano": "^5.1.13",
|
|
51
51
|
"esbuild": "^0.14.54",
|
|
52
|
-
"eslint": "^8.
|
|
52
|
+
"eslint": "^8.21.0",
|
|
53
53
|
"eslint-config-prettier": "^8.5.0",
|
|
54
54
|
"eslint-plugin-prettier": "^4.2.1",
|
|
55
55
|
"jest": "^28.1.3",
|
package/src/cli.js
CHANGED
|
@@ -843,6 +843,11 @@ async function build() {
|
|
|
843
843
|
}
|
|
844
844
|
|
|
845
845
|
watcher = chokidar.watch([...contextDependencies, ...extractFileGlobs(config)], {
|
|
846
|
+
// Force checking for atomic writes in all situations
|
|
847
|
+
// This causes chokidar to wait up to 100ms for a file to re-added after it's been unlinked
|
|
848
|
+
// This only works when watching directories though
|
|
849
|
+
atomic: true,
|
|
850
|
+
|
|
846
851
|
usePolling: shouldPoll,
|
|
847
852
|
interval: shouldPoll ? pollInterval : undefined,
|
|
848
853
|
ignoreInitial: true,
|
|
@@ -855,6 +860,7 @@ async function build() {
|
|
|
855
860
|
})
|
|
856
861
|
|
|
857
862
|
let chain = Promise.resolve()
|
|
863
|
+
let pendingRebuilds = new Set()
|
|
858
864
|
|
|
859
865
|
watcher.on('change', async (file) => {
|
|
860
866
|
if (contextDependencies.has(file)) {
|
|
@@ -885,6 +891,81 @@ async function build() {
|
|
|
885
891
|
}
|
|
886
892
|
})
|
|
887
893
|
|
|
894
|
+
/**
|
|
895
|
+
* When rapidly saving files atomically a couple of situations can happen:
|
|
896
|
+
* - The file is missing since the external program has deleted it by the time we've gotten around to reading it from the earlier save.
|
|
897
|
+
* - The file is being written to by the external program by the time we're going to read it and is thus treated as busy because a lock is held.
|
|
898
|
+
*
|
|
899
|
+
* To work around this we retry reading the file a handful of times with a delay between each attempt
|
|
900
|
+
*
|
|
901
|
+
* @param {string} path
|
|
902
|
+
* @param {number} tries
|
|
903
|
+
* @returns {string}
|
|
904
|
+
* @throws {Error} If the file is still missing or busy after the specified number of tries
|
|
905
|
+
*/
|
|
906
|
+
async function readFileWithRetries(path, tries = 5) {
|
|
907
|
+
for (let n = 0; n <= tries; n++) {
|
|
908
|
+
try {
|
|
909
|
+
return await fs.promises.readFile(path, 'utf8')
|
|
910
|
+
} catch (err) {
|
|
911
|
+
if (n !== tries) {
|
|
912
|
+
if (err.code === 'ENOENT' || err.code === 'EBUSY') {
|
|
913
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
914
|
+
|
|
915
|
+
continue
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
throw err
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Restore watching any files that are "removed"
|
|
925
|
+
// This can happen when a file is pseudo-atomically replaced (a copy is created, overwritten, the old one is unlinked, and the new one is renamed)
|
|
926
|
+
// TODO: An an optimization we should allow removal when the config changes
|
|
927
|
+
watcher.on('unlink', (file) => watcher.add(file))
|
|
928
|
+
|
|
929
|
+
// Some applications such as Visual Studio (but not VS Code)
|
|
930
|
+
// will only fire a rename event for atomic writes and not a change event
|
|
931
|
+
// This is very likely a chokidar bug but it's one we need to work around
|
|
932
|
+
// We treat this as a change event and rebuild the CSS
|
|
933
|
+
watcher.on('raw', (evt, filePath, meta) => {
|
|
934
|
+
if (evt !== 'rename') {
|
|
935
|
+
return
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
let watchedPath = path.resolve(meta.watchedPath)
|
|
939
|
+
|
|
940
|
+
// Watched path might be the file itself
|
|
941
|
+
// Or the directory it is in
|
|
942
|
+
filePath = watchedPath.endsWith(filePath) ? watchedPath : path.resolve(watchedPath, filePath)
|
|
943
|
+
|
|
944
|
+
// Skip since we've already queued a rebuild for this file that hasn't happened yet
|
|
945
|
+
if (pendingRebuilds.has(filePath)) {
|
|
946
|
+
return
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
pendingRebuilds.add(filePath)
|
|
950
|
+
|
|
951
|
+
chain = chain.then(async () => {
|
|
952
|
+
let content
|
|
953
|
+
|
|
954
|
+
try {
|
|
955
|
+
content = await readFileWithRetries(path.resolve(filePath))
|
|
956
|
+
} finally {
|
|
957
|
+
pendingRebuilds.delete(filePath)
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
changedContent.push({
|
|
961
|
+
content,
|
|
962
|
+
extension: path.extname(filePath).slice(1),
|
|
963
|
+
})
|
|
964
|
+
|
|
965
|
+
await rebuild(config)
|
|
966
|
+
})
|
|
967
|
+
})
|
|
968
|
+
|
|
888
969
|
watcher.on('add', async (file) => {
|
|
889
970
|
chain = chain.then(async () => {
|
|
890
971
|
changedContent.push({
|
package/src/corePlugins.js
CHANGED
|
@@ -1980,7 +1980,6 @@ export let corePlugins = {
|
|
|
1980
1980
|
'.outline-dashed': { 'outline-style': 'dashed' },
|
|
1981
1981
|
'.outline-dotted': { 'outline-style': 'dotted' },
|
|
1982
1982
|
'.outline-double': { 'outline-style': 'double' },
|
|
1983
|
-
'.outline-hidden': { 'outline-style': 'hidden' },
|
|
1984
1983
|
})
|
|
1985
1984
|
},
|
|
1986
1985
|
|
|
@@ -1990,6 +1989,7 @@ export let corePlugins = {
|
|
|
1990
1989
|
|
|
1991
1990
|
outlineOffset: createUtilityPlugin('outlineOffset', [['outline-offset', ['outline-offset']]], {
|
|
1992
1991
|
type: ['length', 'number', 'percentage'],
|
|
1992
|
+
supportsNegativeValues: true,
|
|
1993
1993
|
}),
|
|
1994
1994
|
|
|
1995
1995
|
outlineColor: ({ matchUtilities, theme }) => {
|
package/src/css/preflight.css
CHANGED
package/src/featureFlags.js
CHANGED
|
@@ -6,7 +6,11 @@ let defaults = {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
let featureFlags = {
|
|
9
|
-
future: [
|
|
9
|
+
future: [
|
|
10
|
+
'hoverOnlyWhenSupported',
|
|
11
|
+
'respectDefaultRingColorOpacity',
|
|
12
|
+
'disableColorOpacityUtilitiesByDefault',
|
|
13
|
+
],
|
|
10
14
|
experimental: ['optimizeUniversalDefaults', 'matchVariant' /* , 'variantGrouping' */],
|
|
11
15
|
}
|
|
12
16
|
|
|
@@ -34,13 +34,13 @@ function extractClasses(node) {
|
|
|
34
34
|
return Object.assign(classes, { groups: normalizedGroups })
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
let selectorExtractor = parser(
|
|
37
|
+
let selectorExtractor = parser()
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* @param {string} ruleSelectors
|
|
41
41
|
*/
|
|
42
42
|
function extractSelectors(ruleSelectors) {
|
|
43
|
-
return selectorExtractor.
|
|
43
|
+
return selectorExtractor.astSync(ruleSelectors)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function extractBaseCandidates(candidates, separator) {
|
|
@@ -299,30 +299,77 @@ function processApply(root, context, localCache) {
|
|
|
299
299
|
* What happens in this function is that we prepend a `.` and escape the candidate.
|
|
300
300
|
* This will result in `.hover\:font-bold`
|
|
301
301
|
* Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
|
|
302
|
+
*
|
|
303
|
+
* @param {string} selector
|
|
304
|
+
* @param {string} utilitySelectors
|
|
305
|
+
* @param {string} candidate
|
|
302
306
|
*/
|
|
303
|
-
// TODO: Should we use postcss-selector-parser for this instead?
|
|
304
307
|
function replaceSelector(selector, utilitySelectors, candidate) {
|
|
305
|
-
let
|
|
306
|
-
let needles = [...new Set([needle, needle.replace(/\\2c /g, '\\,')])]
|
|
308
|
+
let selectorList = extractSelectors(selector)
|
|
307
309
|
let utilitySelectorsList = extractSelectors(utilitySelectors)
|
|
310
|
+
let candidateList = extractSelectors(`.${escapeClassName(candidate)}`)
|
|
311
|
+
let candidateClass = candidateList.nodes[0].nodes[0]
|
|
312
|
+
|
|
313
|
+
selectorList.each((sel) => {
|
|
314
|
+
/** @type {Set<import('postcss-selector-parser').Selector>} */
|
|
315
|
+
let replaced = new Set()
|
|
308
316
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
317
|
+
utilitySelectorsList.each((utilitySelector) => {
|
|
318
|
+
let hasReplaced = false
|
|
319
|
+
utilitySelector = utilitySelector.clone()
|
|
312
320
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
replacedSelector = replacedSelector.replace(needle, s)
|
|
321
|
+
utilitySelector.walkClasses((node) => {
|
|
322
|
+
if (node.value !== candidateClass.value) {
|
|
323
|
+
return
|
|
317
324
|
}
|
|
318
|
-
|
|
319
|
-
|
|
325
|
+
|
|
326
|
+
// Don't replace multiple instances of the same class
|
|
327
|
+
// This is theoretically correct but only partially
|
|
328
|
+
// We'd need to generate every possible permutation of the replacement
|
|
329
|
+
// For example with `.foo + .foo { … }` and `section { @apply foo; }`
|
|
330
|
+
// We'd need to generate all of these:
|
|
331
|
+
// - `.foo + .foo`
|
|
332
|
+
// - `.foo + section`
|
|
333
|
+
// - `section + .foo`
|
|
334
|
+
// - `section + section`
|
|
335
|
+
if (hasReplaced) {
|
|
336
|
+
return
|
|
320
337
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
338
|
+
|
|
339
|
+
// Since you can only `@apply` class names this is sufficient
|
|
340
|
+
// We want to replace the matched class name with the selector the user is using
|
|
341
|
+
// Ex: Replace `.text-blue-500` with `.foo.bar:is(.something-cool)`
|
|
342
|
+
node.replaceWith(...sel.nodes.map((node) => node.clone()))
|
|
343
|
+
|
|
344
|
+
// Record that we did something and we want to use this new selector
|
|
345
|
+
replaced.add(utilitySelector)
|
|
346
|
+
|
|
347
|
+
hasReplaced = true
|
|
348
|
+
})
|
|
324
349
|
})
|
|
325
|
-
|
|
350
|
+
|
|
351
|
+
// Sort tag names before class names
|
|
352
|
+
// This happens when replacing `.bar` in `.foo.bar` with a tag like `section`
|
|
353
|
+
for (const sel of replaced) {
|
|
354
|
+
sel.sort((a, b) => {
|
|
355
|
+
if (a.type === 'tag' && b.type === 'class') {
|
|
356
|
+
return -1
|
|
357
|
+
} else if (a.type === 'class' && b.type === 'tag') {
|
|
358
|
+
return 1
|
|
359
|
+
} else if (a.type === 'class' && b.type === 'pseudo') {
|
|
360
|
+
return -1
|
|
361
|
+
} else if (a.type === 'pseudo' && b.type === 'class') {
|
|
362
|
+
return 1
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return sel.index(a) - sel.index(b)
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
sel.replaceWith(...replaced)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
return selectorList.toString()
|
|
326
373
|
}
|
|
327
374
|
|
|
328
375
|
let perParentApplies = new Map()
|
|
@@ -177,16 +177,12 @@ export default function expandTailwindAtRules(context) {
|
|
|
177
177
|
let classCacheCount = context.classCache.size
|
|
178
178
|
|
|
179
179
|
env.DEBUG && console.time('Generate rules')
|
|
180
|
-
|
|
180
|
+
generateRules(candidates, context)
|
|
181
181
|
env.DEBUG && console.timeEnd('Generate rules')
|
|
182
182
|
|
|
183
183
|
// We only ever add to the classCache, so if it didn't grow, there is nothing new.
|
|
184
184
|
env.DEBUG && console.time('Build stylesheet')
|
|
185
185
|
if (context.stylesheetCache === null || context.classCache.size !== classCacheCount) {
|
|
186
|
-
for (let rule of rules) {
|
|
187
|
-
context.ruleCache.add(rule)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
186
|
context.stylesheetCache = buildStylesheet([...context.ruleCache], context)
|
|
191
187
|
}
|
|
192
188
|
env.DEBUG && console.timeEnd('Build stylesheet')
|
package/src/lib/generateRules.js
CHANGED
|
@@ -649,16 +649,45 @@ function inKeyframes(rule) {
|
|
|
649
649
|
return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
|
|
650
650
|
}
|
|
651
651
|
|
|
652
|
+
function getImportantStrategy(important) {
|
|
653
|
+
if (important === true) {
|
|
654
|
+
return (rule) => {
|
|
655
|
+
if (inKeyframes(rule)) {
|
|
656
|
+
return
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
rule.walkDecls((d) => {
|
|
660
|
+
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
|
|
661
|
+
d.important = true
|
|
662
|
+
}
|
|
663
|
+
})
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (typeof important === 'string') {
|
|
668
|
+
return (rule) => {
|
|
669
|
+
if (inKeyframes(rule)) {
|
|
670
|
+
return
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
rule.selectors = rule.selectors.map((selector) => {
|
|
674
|
+
return `${important} ${selector}`
|
|
675
|
+
})
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
652
680
|
function generateRules(candidates, context) {
|
|
653
681
|
let allRules = []
|
|
682
|
+
let strategy = getImportantStrategy(context.tailwindConfig.important)
|
|
654
683
|
|
|
655
684
|
for (let candidate of candidates) {
|
|
656
685
|
if (context.notClassCache.has(candidate)) {
|
|
657
686
|
continue
|
|
658
687
|
}
|
|
659
688
|
|
|
660
|
-
if (context.
|
|
661
|
-
allRules.
|
|
689
|
+
if (context.candidateRuleCache.has(candidate)) {
|
|
690
|
+
allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate)))
|
|
662
691
|
continue
|
|
663
692
|
}
|
|
664
693
|
|
|
@@ -670,47 +699,27 @@ function generateRules(candidates, context) {
|
|
|
670
699
|
}
|
|
671
700
|
|
|
672
701
|
context.classCache.set(candidate, matches)
|
|
673
|
-
allRules.push(matches)
|
|
674
|
-
}
|
|
675
702
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
if (important === true) {
|
|
679
|
-
return (rule) => {
|
|
680
|
-
rule.walkDecls((d) => {
|
|
681
|
-
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
|
|
682
|
-
d.important = true
|
|
683
|
-
}
|
|
684
|
-
})
|
|
685
|
-
}
|
|
686
|
-
}
|
|
703
|
+
let rules = context.candidateRuleCache.get(candidate) ?? new Set()
|
|
704
|
+
context.candidateRuleCache.set(candidate, rules)
|
|
687
705
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
rule.selectors = rule.selectors.map((selector) => {
|
|
691
|
-
return `${important} ${selector}`
|
|
692
|
-
})
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
})(context.tailwindConfig.important)
|
|
706
|
+
for (const match of matches) {
|
|
707
|
+
let [{ sort, layer, options }, rule] = match
|
|
696
708
|
|
|
697
|
-
|
|
698
|
-
if (options.respectImportant) {
|
|
699
|
-
if (strategy) {
|
|
709
|
+
if (options.respectImportant && strategy) {
|
|
700
710
|
let container = postcss.root({ nodes: [rule.clone()] })
|
|
701
|
-
container.walkRules(
|
|
702
|
-
if (inKeyframes(r)) {
|
|
703
|
-
return
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
strategy(r)
|
|
707
|
-
})
|
|
711
|
+
container.walkRules(strategy)
|
|
708
712
|
rule = container.nodes[0]
|
|
709
713
|
}
|
|
714
|
+
|
|
715
|
+
let newEntry = [sort | context.layerOrder[layer], rule]
|
|
716
|
+
rules.add(newEntry)
|
|
717
|
+
context.ruleCache.add(newEntry)
|
|
718
|
+
allRules.push(newEntry)
|
|
710
719
|
}
|
|
720
|
+
}
|
|
711
721
|
|
|
712
|
-
|
|
713
|
-
})
|
|
722
|
+
return allRules
|
|
714
723
|
}
|
|
715
724
|
|
|
716
725
|
function isArbitraryValue(input) {
|
|
@@ -873,6 +873,7 @@ export function createContext(tailwindConfig, changedContent = [], root = postcs
|
|
|
873
873
|
let context = {
|
|
874
874
|
disposables: [],
|
|
875
875
|
ruleCache: new Set(),
|
|
876
|
+
candidateRuleCache: new Map(),
|
|
876
877
|
classCache: new Map(),
|
|
877
878
|
applyClassCache: new Map(),
|
|
878
879
|
notClassCache: new Set(),
|
|
@@ -17,6 +17,17 @@ export default function getAllConfigs(config) {
|
|
|
17
17
|
}),
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
|
+
|
|
21
|
+
disableColorOpacityUtilitiesByDefault: {
|
|
22
|
+
corePlugins: {
|
|
23
|
+
backgroundOpacity: false,
|
|
24
|
+
borderOpacity: false,
|
|
25
|
+
divideOpacity: false,
|
|
26
|
+
placeholderOpacity: false,
|
|
27
|
+
ringOpacity: false,
|
|
28
|
+
textOpacity: false,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
const experimentals = Object.keys(features)
|
package/types/config.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ type SafelistConfig =
|
|
|
49
49
|
| string[]
|
|
50
50
|
| {
|
|
51
51
|
pattern: RegExp
|
|
52
|
-
variants
|
|
52
|
+
variants?: string[]
|
|
53
53
|
}[]
|
|
54
54
|
|
|
55
55
|
// Presets related config
|
|
@@ -153,7 +153,14 @@ interface ThemeConfig {
|
|
|
153
153
|
objectPosition: ResolvableTo<KeyValuePair>
|
|
154
154
|
padding: ThemeConfig['spacing']
|
|
155
155
|
textIndent: ThemeConfig['spacing']
|
|
156
|
-
fontFamily: ResolvableTo<
|
|
156
|
+
fontFamily: ResolvableTo<
|
|
157
|
+
KeyValuePair<
|
|
158
|
+
string,
|
|
159
|
+
| string
|
|
160
|
+
| string[]
|
|
161
|
+
| [fontFamily: string | string[], configuration: Partial<{ fontFeatureSettings: string }>]
|
|
162
|
+
>
|
|
163
|
+
>
|
|
157
164
|
fontSize: ResolvableTo<
|
|
158
165
|
KeyValuePair<
|
|
159
166
|
string,
|