tailwindcss 0.0.0-insiders.da85042 → 0.0.0-insiders.db50bbb
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 +6 -1
- package/lib/cli.js +38 -3
- package/lib/lib/expandTailwindAtRules.js +1 -4
- package/lib/lib/generateRules.js +43 -37
- package/lib/lib/setupContextUtils.js +1 -0
- package/lib/util/formatVariantSelector.js +72 -2
- package/lib/util/negateValue.js +15 -2
- package/lib/util/transformThemeValue.js +8 -1
- package/package.json +3 -3
- package/src/cli.js +34 -3
- package/src/lib/expandTailwindAtRules.js +1 -5
- package/src/lib/generateRules.js +44 -35
- package/src/lib/setupContextUtils.js +1 -0
- package/src/util/formatVariantSelector.js +89 -2
- package/src/util/negateValue.js +10 -2
- package/src/util/transformThemeValue.js +9 -1
- package/types/config.d.ts +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
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
14
|
- Add future flag to disable color opacity utility plugins ([#9088](https://github.com/tailwindlabs/tailwindcss/pull/9088))
|
|
15
15
|
- Add negative value support for `outline-offset` ([#9136](https://github.com/tailwindlabs/tailwindcss/pull/9136))
|
|
16
|
+
- Allow negating utilities using min/max/clamp ([#9237](https://github.com/tailwindlabs/tailwindcss/pull/9237))
|
|
16
17
|
|
|
17
18
|
### Fixed
|
|
18
19
|
|
|
@@ -21,7 +22,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
21
22
|
- Sort tags before classes when `@applying` a selector with joined classes ([#9107](https://github.com/tailwindlabs/tailwindcss/pull/9107))
|
|
22
23
|
- Remove invalid `outline-hidden` utility ([#9147](https://github.com/tailwindlabs/tailwindcss/pull/9147))
|
|
23
24
|
- 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
|
+
- Don't stop watching atomically renamed files ([#9173](https://github.com/tailwindlabs/tailwindcss/pull/9173), [#9215](https://github.com/tailwindlabs/tailwindcss/pull/9215))
|
|
26
|
+
- Re-use existing entries in the rule cache ([#9208](https://github.com/tailwindlabs/tailwindcss/pull/9208))
|
|
27
|
+
- Don't output duplicate utilities ([#9208](https://github.com/tailwindlabs/tailwindcss/pull/9208))
|
|
28
|
+
- Fix `fontFamily` config TypeScript types ([#9214](https://github.com/tailwindlabs/tailwindcss/pull/9214))
|
|
29
|
+
- Handle variants on complex selector utilities ([#9262](https://github.com/tailwindlabs/tailwindcss/pull/9262))
|
|
25
30
|
|
|
26
31
|
## [3.1.8] - 2022-08-05
|
|
27
32
|
|
package/lib/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ const _getModuleDependencies = /*#__PURE__*/ _interopRequireDefault(require("./l
|
|
|
21
21
|
const _log = /*#__PURE__*/ _interopRequireDefault(require("./util/log"));
|
|
22
22
|
const _packageJson = /*#__PURE__*/ _interopRequireDefault(require("../package.json"));
|
|
23
23
|
const _normalizePath = /*#__PURE__*/ _interopRequireDefault(require("normalize-path"));
|
|
24
|
+
const _micromatch = /*#__PURE__*/ _interopRequireDefault(require("micromatch"));
|
|
24
25
|
const _validateConfigJs = require("./util/validateConfig.js");
|
|
25
26
|
function _interopRequireDefault(obj) {
|
|
26
27
|
return obj && obj.__esModule ? obj : {
|
|
@@ -741,6 +742,25 @@ async function build() {
|
|
|
741
742
|
return result;
|
|
742
743
|
}
|
|
743
744
|
let config = refreshConfig(configPath);
|
|
745
|
+
let contentPatterns = refreshContentPatterns(config);
|
|
746
|
+
/**
|
|
747
|
+
* @param {import('../types/config.js').RequiredConfig} config
|
|
748
|
+
* @return {{all: string[], dynamic: string[], static: string[]}}
|
|
749
|
+
**/ function refreshContentPatterns(config) {
|
|
750
|
+
let globs = extractFileGlobs(config);
|
|
751
|
+
let tasks = _fastGlob.default.generateTasks(globs, {
|
|
752
|
+
absolute: true
|
|
753
|
+
});
|
|
754
|
+
let dynamicPatterns = tasks.filter((task)=>task.dynamic).flatMap((task)=>task.patterns);
|
|
755
|
+
let staticPatterns = tasks.filter((task)=>!task.dynamic).flatMap((task)=>task.patterns);
|
|
756
|
+
return {
|
|
757
|
+
all: [
|
|
758
|
+
...staticPatterns,
|
|
759
|
+
...dynamicPatterns
|
|
760
|
+
],
|
|
761
|
+
dynamic: dynamicPatterns
|
|
762
|
+
};
|
|
763
|
+
}
|
|
744
764
|
if (input) {
|
|
745
765
|
contextDependencies.add(_path.default.resolve(input));
|
|
746
766
|
}
|
|
@@ -767,6 +787,7 @@ async function build() {
|
|
|
767
787
|
env.DEBUG && console.time("Resolve config");
|
|
768
788
|
context = null;
|
|
769
789
|
config = refreshConfig(configPath);
|
|
790
|
+
contentPatterns = refreshContentPatterns(config);
|
|
770
791
|
env.DEBUG && console.timeEnd("Resolve config");
|
|
771
792
|
env.DEBUG && console.time("Watch new files");
|
|
772
793
|
let globs = extractFileGlobs(config);
|
|
@@ -816,7 +837,15 @@ async function build() {
|
|
|
816
837
|
// Restore watching any files that are "removed"
|
|
817
838
|
// 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
839
|
// TODO: An an optimization we should allow removal when the config changes
|
|
819
|
-
watcher.on("unlink", (file)=>
|
|
840
|
+
watcher.on("unlink", (file)=>{
|
|
841
|
+
file = (0, _normalizePath.default)(file);
|
|
842
|
+
// Only re-add the file if it's not covered by a dynamic pattern
|
|
843
|
+
if (!_micromatch.default.some([
|
|
844
|
+
file
|
|
845
|
+
], contentPatterns.dynamic)) {
|
|
846
|
+
watcher.add(file);
|
|
847
|
+
}
|
|
848
|
+
});
|
|
820
849
|
// Some applications such as Visual Studio (but not VS Code)
|
|
821
850
|
// will only fire a rename event for atomic writes and not a change event
|
|
822
851
|
// This is very likely a chokidar bug but it's one we need to work around
|
|
@@ -825,10 +854,16 @@ async function build() {
|
|
|
825
854
|
if (evt !== "rename") {
|
|
826
855
|
return;
|
|
827
856
|
}
|
|
828
|
-
let watchedPath =
|
|
857
|
+
let watchedPath = meta.watchedPath;
|
|
829
858
|
// Watched path might be the file itself
|
|
830
859
|
// Or the directory it is in
|
|
831
|
-
filePath = watchedPath.endsWith(filePath) ? watchedPath : _path.default.
|
|
860
|
+
filePath = watchedPath.endsWith(filePath) ? watchedPath : _path.default.join(watchedPath, filePath);
|
|
861
|
+
// Skip this event since the files it is for does not match any of the registered content globs
|
|
862
|
+
if (!_micromatch.default.some([
|
|
863
|
+
filePath
|
|
864
|
+
], contentPatterns.all)) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
832
867
|
// Skip since we've already queued a rebuild for this file that hasn't happened yet
|
|
833
868
|
if (pendingRebuilds.has(filePath)) {
|
|
834
869
|
return;
|
|
@@ -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(),
|
|
@@ -45,6 +45,48 @@ function formatVariantSelector(current, ...others) {
|
|
|
45
45
|
}
|
|
46
46
|
return current;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Given any node in a selector this gets the "simple" selector it's a part of
|
|
50
|
+
* A simple selector is just a list of nodes without any combinators
|
|
51
|
+
* Technically :is(), :not(), :has(), etc… can have combinators but those are nested
|
|
52
|
+
* inside the relevant node and won't be picked up so they're fine to ignore
|
|
53
|
+
*
|
|
54
|
+
* @param {import('postcss-selector-parser').Node} node
|
|
55
|
+
* @returns {import('postcss-selector-parser').Node[]}
|
|
56
|
+
**/ function simpleSelectorForNode(node) {
|
|
57
|
+
/** @type {import('postcss-selector-parser').Node[]} */ let nodes = [];
|
|
58
|
+
// Walk backwards until we hit a combinator node (or the start)
|
|
59
|
+
while(node.prev() && node.prev().type !== "combinator"){
|
|
60
|
+
node = node.prev();
|
|
61
|
+
}
|
|
62
|
+
// Now record all non-combinator nodes until we hit one (or the end)
|
|
63
|
+
while(node && node.type !== "combinator"){
|
|
64
|
+
nodes.push(node);
|
|
65
|
+
node = node.next();
|
|
66
|
+
}
|
|
67
|
+
return nodes;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resorts the nodes in a selector to ensure they're in the correct order
|
|
71
|
+
* Tags go before classes, and pseudo classes go after classes
|
|
72
|
+
*
|
|
73
|
+
* @param {import('postcss-selector-parser').Selector} sel
|
|
74
|
+
* @returns {import('postcss-selector-parser').Selector}
|
|
75
|
+
**/ function resortSelector(sel) {
|
|
76
|
+
sel.sort((a, b)=>{
|
|
77
|
+
if (a.type === "tag" && b.type === "class") {
|
|
78
|
+
return -1;
|
|
79
|
+
} else if (a.type === "class" && b.type === "tag") {
|
|
80
|
+
return 1;
|
|
81
|
+
} else if (a.type === "class" && b.type === "pseudo" && b.value !== ":merge") {
|
|
82
|
+
return -1;
|
|
83
|
+
} else if (a.type === "pseudo" && a.value !== ":merge" && b.type === "class") {
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
return sel.index(a) - sel.index(b);
|
|
87
|
+
});
|
|
88
|
+
return sel;
|
|
89
|
+
}
|
|
48
90
|
var ref1;
|
|
49
91
|
function finalizeSelector(format, { selector , candidate , context , isArbitraryVariant , // Split by the separator, but ignore the separator inside square brackets:
|
|
50
92
|
//
|
|
@@ -87,12 +129,40 @@ base =candidate.split(new RegExp(`\\${(ref1 = context === null || context === vo
|
|
|
87
129
|
node.raws.value = (0, _escapeClassName.default)((0, _unesc.default)(node.raws.value));
|
|
88
130
|
}
|
|
89
131
|
});
|
|
132
|
+
let simpleStart = _postcssSelectorParser.default.comment({
|
|
133
|
+
value: "/*__simple__*/"
|
|
134
|
+
});
|
|
135
|
+
let simpleEnd = _postcssSelectorParser.default.comment({
|
|
136
|
+
value: "/*__simple__*/"
|
|
137
|
+
});
|
|
90
138
|
// We can safely replace the escaped base now, since the `base` section is
|
|
91
139
|
// now in a normalized escaped value.
|
|
92
140
|
ast.walkClasses((node)=>{
|
|
93
|
-
if (node.value
|
|
94
|
-
|
|
141
|
+
if (node.value !== base) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
let parent = node.parent;
|
|
145
|
+
let formatNodes = formatAst.nodes[0].nodes;
|
|
146
|
+
// Perf optimization: if the parent is a single class we can just replace it and be done
|
|
147
|
+
if (parent.nodes.length === 1) {
|
|
148
|
+
node.replaceWith(...formatNodes);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
let simpleSelector = simpleSelectorForNode(node);
|
|
152
|
+
parent.insertBefore(simpleSelector[0], simpleStart);
|
|
153
|
+
parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd);
|
|
154
|
+
for (let child of formatNodes){
|
|
155
|
+
parent.insertBefore(simpleSelector[0], child);
|
|
95
156
|
}
|
|
157
|
+
node.remove();
|
|
158
|
+
// Re-sort the simple selector to ensure it's in the correct order
|
|
159
|
+
simpleSelector = simpleSelectorForNode(simpleStart);
|
|
160
|
+
let firstNode = parent.index(simpleStart);
|
|
161
|
+
parent.nodes.splice(firstNode, simpleSelector.length, ...resortSelector(_postcssSelectorParser.default.selector({
|
|
162
|
+
nodes: simpleSelector
|
|
163
|
+
})).nodes);
|
|
164
|
+
simpleStart.remove();
|
|
165
|
+
simpleEnd.remove();
|
|
96
166
|
});
|
|
97
167
|
// This will make sure to move pseudo's to the correct spot (the end for
|
|
98
168
|
// pseudo elements) because otherwise the selector will never work
|
package/lib/util/negateValue.js
CHANGED
|
@@ -15,7 +15,20 @@ function _default(value) {
|
|
|
15
15
|
if (/^[+-]?(\d+|\d*\.\d+)(e[+-]?\d+)?(%|\w+)?$/.test(value)) {
|
|
16
16
|
return value.replace(/^[+-]?/, (sign)=>sign === "-" ? "" : "-");
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// What functions we support negating numeric values for
|
|
19
|
+
// var() isn't inherently a numeric function but we support it anyway
|
|
20
|
+
// The trigonometric functions are omitted because you'll need to use calc(…) with them _anyway_
|
|
21
|
+
// to produce generally useful results and that will be covered already
|
|
22
|
+
let numericFunctions = [
|
|
23
|
+
"var",
|
|
24
|
+
"calc",
|
|
25
|
+
"min",
|
|
26
|
+
"max",
|
|
27
|
+
"clamp"
|
|
28
|
+
];
|
|
29
|
+
for (const fn of numericFunctions){
|
|
30
|
+
if (value.includes(`${fn}(`)) {
|
|
31
|
+
return `calc(${value} * -1)`;
|
|
32
|
+
}
|
|
20
33
|
}
|
|
21
34
|
}
|
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "default", {
|
|
|
7
7
|
get: ()=>transformThemeValue
|
|
8
8
|
});
|
|
9
9
|
const _postcss = /*#__PURE__*/ _interopRequireDefault(require("postcss"));
|
|
10
|
+
const _isPlainObject = /*#__PURE__*/ _interopRequireDefault(require("./isPlainObject"));
|
|
10
11
|
function _interopRequireDefault(obj) {
|
|
11
12
|
return obj && obj.__esModule ? obj : {
|
|
12
13
|
default: obj
|
|
@@ -23,8 +24,14 @@ function transformThemeValue(themeSection) {
|
|
|
23
24
|
return value;
|
|
24
25
|
};
|
|
25
26
|
}
|
|
27
|
+
if (themeSection === "fontFamily") {
|
|
28
|
+
return (value)=>{
|
|
29
|
+
if (typeof value === "function") value = value({});
|
|
30
|
+
let families = Array.isArray(value) && (0, _isPlainObject.default)(value[1]) ? value[0] : value;
|
|
31
|
+
return Array.isArray(families) ? families.join(", ") : families;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
26
34
|
if ([
|
|
27
|
-
"fontFamily",
|
|
28
35
|
"boxShadow",
|
|
29
36
|
"transitionProperty",
|
|
30
37
|
"transitionDuration",
|
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.db50bbb",
|
|
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
50
|
"cssnano": "^5.1.13",
|
|
51
51
|
"esbuild": "^0.14.54",
|
|
52
|
-
"eslint": "^8.
|
|
52
|
+
"eslint": "^8.23.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
|
@@ -17,6 +17,7 @@ import getModuleDependencies from './lib/getModuleDependencies'
|
|
|
17
17
|
import log from './util/log'
|
|
18
18
|
import packageJson from '../package.json'
|
|
19
19
|
import normalizePath from 'normalize-path'
|
|
20
|
+
import micromatch from 'micromatch'
|
|
20
21
|
import { validateConfig } from './util/validateConfig.js'
|
|
21
22
|
|
|
22
23
|
let env = {
|
|
@@ -837,6 +838,23 @@ async function build() {
|
|
|
837
838
|
}
|
|
838
839
|
|
|
839
840
|
let config = refreshConfig(configPath)
|
|
841
|
+
let contentPatterns = refreshContentPatterns(config)
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* @param {import('../types/config.js').RequiredConfig} config
|
|
845
|
+
* @return {{all: string[], dynamic: string[], static: string[]}}
|
|
846
|
+
**/
|
|
847
|
+
function refreshContentPatterns(config) {
|
|
848
|
+
let globs = extractFileGlobs(config)
|
|
849
|
+
let tasks = fastGlob.generateTasks(globs, { absolute: true })
|
|
850
|
+
let dynamicPatterns = tasks.filter((task) => task.dynamic).flatMap((task) => task.patterns)
|
|
851
|
+
let staticPatterns = tasks.filter((task) => !task.dynamic).flatMap((task) => task.patterns)
|
|
852
|
+
|
|
853
|
+
return {
|
|
854
|
+
all: [...staticPatterns, ...dynamicPatterns],
|
|
855
|
+
dynamic: dynamicPatterns,
|
|
856
|
+
}
|
|
857
|
+
}
|
|
840
858
|
|
|
841
859
|
if (input) {
|
|
842
860
|
contextDependencies.add(path.resolve(input))
|
|
@@ -867,6 +885,7 @@ async function build() {
|
|
|
867
885
|
env.DEBUG && console.time('Resolve config')
|
|
868
886
|
context = null
|
|
869
887
|
config = refreshConfig(configPath)
|
|
888
|
+
contentPatterns = refreshContentPatterns(config)
|
|
870
889
|
env.DEBUG && console.timeEnd('Resolve config')
|
|
871
890
|
|
|
872
891
|
env.DEBUG && console.time('Watch new files')
|
|
@@ -924,7 +943,14 @@ async function build() {
|
|
|
924
943
|
// Restore watching any files that are "removed"
|
|
925
944
|
// 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
945
|
// TODO: An an optimization we should allow removal when the config changes
|
|
927
|
-
watcher.on('unlink', (file) =>
|
|
946
|
+
watcher.on('unlink', (file) => {
|
|
947
|
+
file = normalizePath(file)
|
|
948
|
+
|
|
949
|
+
// Only re-add the file if it's not covered by a dynamic pattern
|
|
950
|
+
if (!micromatch.some([file], contentPatterns.dynamic)) {
|
|
951
|
+
watcher.add(file)
|
|
952
|
+
}
|
|
953
|
+
})
|
|
928
954
|
|
|
929
955
|
// Some applications such as Visual Studio (but not VS Code)
|
|
930
956
|
// will only fire a rename event for atomic writes and not a change event
|
|
@@ -935,11 +961,16 @@ async function build() {
|
|
|
935
961
|
return
|
|
936
962
|
}
|
|
937
963
|
|
|
938
|
-
let watchedPath =
|
|
964
|
+
let watchedPath = meta.watchedPath
|
|
939
965
|
|
|
940
966
|
// Watched path might be the file itself
|
|
941
967
|
// Or the directory it is in
|
|
942
|
-
filePath = watchedPath.endsWith(filePath) ? watchedPath : path.
|
|
968
|
+
filePath = watchedPath.endsWith(filePath) ? watchedPath : path.join(watchedPath, filePath)
|
|
969
|
+
|
|
970
|
+
// Skip this event since the files it is for does not match any of the registered content globs
|
|
971
|
+
if (!micromatch.some([filePath], contentPatterns.all)) {
|
|
972
|
+
return
|
|
973
|
+
}
|
|
943
974
|
|
|
944
975
|
// Skip since we've already queued a rebuild for this file that hasn't happened yet
|
|
945
976
|
if (pendingRebuilds.has(filePath)) {
|
|
@@ -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(),
|
|
@@ -29,6 +29,58 @@ export function formatVariantSelector(current, ...others) {
|
|
|
29
29
|
return current
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Given any node in a selector this gets the "simple" selector it's a part of
|
|
34
|
+
* A simple selector is just a list of nodes without any combinators
|
|
35
|
+
* Technically :is(), :not(), :has(), etc… can have combinators but those are nested
|
|
36
|
+
* inside the relevant node and won't be picked up so they're fine to ignore
|
|
37
|
+
*
|
|
38
|
+
* @param {import('postcss-selector-parser').Node} node
|
|
39
|
+
* @returns {import('postcss-selector-parser').Node[]}
|
|
40
|
+
**/
|
|
41
|
+
function simpleSelectorForNode(node) {
|
|
42
|
+
/** @type {import('postcss-selector-parser').Node[]} */
|
|
43
|
+
let nodes = []
|
|
44
|
+
|
|
45
|
+
// Walk backwards until we hit a combinator node (or the start)
|
|
46
|
+
while (node.prev() && node.prev().type !== 'combinator') {
|
|
47
|
+
node = node.prev()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Now record all non-combinator nodes until we hit one (or the end)
|
|
51
|
+
while (node && node.type !== 'combinator') {
|
|
52
|
+
nodes.push(node)
|
|
53
|
+
node = node.next()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return nodes
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resorts the nodes in a selector to ensure they're in the correct order
|
|
61
|
+
* Tags go before classes, and pseudo classes go after classes
|
|
62
|
+
*
|
|
63
|
+
* @param {import('postcss-selector-parser').Selector} sel
|
|
64
|
+
* @returns {import('postcss-selector-parser').Selector}
|
|
65
|
+
**/
|
|
66
|
+
function resortSelector(sel) {
|
|
67
|
+
sel.sort((a, b) => {
|
|
68
|
+
if (a.type === 'tag' && b.type === 'class') {
|
|
69
|
+
return -1
|
|
70
|
+
} else if (a.type === 'class' && b.type === 'tag') {
|
|
71
|
+
return 1
|
|
72
|
+
} else if (a.type === 'class' && b.type === 'pseudo' && b.value !== ':merge') {
|
|
73
|
+
return -1
|
|
74
|
+
} else if (a.type === 'pseudo' && a.value !== ':merge' && b.type === 'class') {
|
|
75
|
+
return 1
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return sel.index(a) - sel.index(b)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return sel
|
|
82
|
+
}
|
|
83
|
+
|
|
32
84
|
export function finalizeSelector(
|
|
33
85
|
format,
|
|
34
86
|
{
|
|
@@ -88,12 +140,47 @@ export function finalizeSelector(
|
|
|
88
140
|
}
|
|
89
141
|
})
|
|
90
142
|
|
|
143
|
+
let simpleStart = selectorParser.comment({ value: '/*__simple__*/' })
|
|
144
|
+
let simpleEnd = selectorParser.comment({ value: '/*__simple__*/' })
|
|
145
|
+
|
|
91
146
|
// We can safely replace the escaped base now, since the `base` section is
|
|
92
147
|
// now in a normalized escaped value.
|
|
93
148
|
ast.walkClasses((node) => {
|
|
94
|
-
if (node.value
|
|
95
|
-
|
|
149
|
+
if (node.value !== base) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let parent = node.parent
|
|
154
|
+
let formatNodes = formatAst.nodes[0].nodes
|
|
155
|
+
|
|
156
|
+
// Perf optimization: if the parent is a single class we can just replace it and be done
|
|
157
|
+
if (parent.nodes.length === 1) {
|
|
158
|
+
node.replaceWith(...formatNodes)
|
|
159
|
+
return
|
|
96
160
|
}
|
|
161
|
+
|
|
162
|
+
let simpleSelector = simpleSelectorForNode(node)
|
|
163
|
+
parent.insertBefore(simpleSelector[0], simpleStart)
|
|
164
|
+
parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd)
|
|
165
|
+
|
|
166
|
+
for (let child of formatNodes) {
|
|
167
|
+
parent.insertBefore(simpleSelector[0], child)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
node.remove()
|
|
171
|
+
|
|
172
|
+
// Re-sort the simple selector to ensure it's in the correct order
|
|
173
|
+
simpleSelector = simpleSelectorForNode(simpleStart)
|
|
174
|
+
let firstNode = parent.index(simpleStart)
|
|
175
|
+
|
|
176
|
+
parent.nodes.splice(
|
|
177
|
+
firstNode,
|
|
178
|
+
simpleSelector.length,
|
|
179
|
+
...resortSelector(selectorParser.selector({ nodes: simpleSelector })).nodes
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
simpleStart.remove()
|
|
183
|
+
simpleEnd.remove()
|
|
97
184
|
})
|
|
98
185
|
|
|
99
186
|
// This will make sure to move pseudo's to the correct spot (the end for
|
package/src/util/negateValue.js
CHANGED
|
@@ -10,7 +10,15 @@ export default function (value) {
|
|
|
10
10
|
return value.replace(/^[+-]?/, (sign) => (sign === '-' ? '' : '-'))
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// What functions we support negating numeric values for
|
|
14
|
+
// var() isn't inherently a numeric function but we support it anyway
|
|
15
|
+
// The trigonometric functions are omitted because you'll need to use calc(…) with them _anyway_
|
|
16
|
+
// to produce generally useful results and that will be covered already
|
|
17
|
+
let numericFunctions = ['var', 'calc', 'min', 'max', 'clamp']
|
|
18
|
+
|
|
19
|
+
for (const fn of numericFunctions) {
|
|
20
|
+
if (value.includes(`${fn}(`)) {
|
|
21
|
+
return `calc(${value} * -1)`
|
|
22
|
+
}
|
|
15
23
|
}
|
|
16
24
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import postcss from 'postcss'
|
|
2
|
+
import isPlainObject from './isPlainObject'
|
|
2
3
|
|
|
3
4
|
export default function transformThemeValue(themeSection) {
|
|
4
5
|
if (['fontSize', 'outline'].includes(themeSection)) {
|
|
@@ -10,9 +11,16 @@ export default function transformThemeValue(themeSection) {
|
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
if (themeSection === 'fontFamily') {
|
|
15
|
+
return (value) => {
|
|
16
|
+
if (typeof value === 'function') value = value({})
|
|
17
|
+
let families = Array.isArray(value) && isPlainObject(value[1]) ? value[0] : value
|
|
18
|
+
return Array.isArray(families) ? families.join(', ') : families
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
if (
|
|
14
23
|
[
|
|
15
|
-
'fontFamily',
|
|
16
24
|
'boxShadow',
|
|
17
25
|
'transitionProperty',
|
|
18
26
|
'transitionDuration',
|
package/types/config.d.ts
CHANGED
|
@@ -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,
|