tailwindcss 0.0.0-insiders.d72b277 → 0.0.0-insiders.d9bc25d

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
@@ -10,6 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
  - Prevent nesting plugin from breaking other plugins ([#7563](https://github.com/tailwindlabs/tailwindcss/pull/7563))
11
11
  - Recursively collapse adjacent rules ([#7565](https://github.com/tailwindlabs/tailwindcss/pull/7565))
12
12
  - Allow default ring color to be a function ([#7587](https://github.com/tailwindlabs/tailwindcss/pull/7587))
13
+ - Preserve source maps for generated CSS ([#7588](https://github.com/tailwindlabs/tailwindcss/pull/7588))
14
+ - Split box shadows on top-level commas only ([#7479](https://github.com/tailwindlabs/tailwindcss/pull/7479))
15
+ - Use local user CSS cache for `@apply` ([#7524](https://github.com/tailwindlabs/tailwindcss/pull/7524))
16
+
17
+ ### Changed
18
+
19
+ - Replace `chalk` with `picocolors` ([#6039](https://github.com/tailwindlabs/tailwindcss/pull/6039))
13
20
 
14
21
  ## [3.0.23] - 2022-02-16
15
22
 
@@ -24,7 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
31
 
25
32
  ### Fixed
26
33
 
27
- - Temporarily move postcss to dependencies ([#7424](https://github.com/tailwindlabs/tailwindcss/pull/7424))
34
+ - Temporarily move `postcss` to dependencies ([#7424](https://github.com/tailwindlabs/tailwindcss/pull/7424))
28
35
 
29
36
  ## [3.0.21] - 2022-02-10
30
37
 
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  exports.flagEnabled = flagEnabled;
6
6
  exports.issueFlagNotices = issueFlagNotices;
7
7
  exports.default = void 0;
8
- var _chalk = _interopRequireDefault(require("chalk"));
8
+ var _picocolors = _interopRequireDefault(require("picocolors"));
9
9
  var _log = _interopRequireDefault(require("./util/log"));
10
10
  function _interopRequireDefault(obj) {
11
11
  return obj && obj.__esModule ? obj : {
@@ -47,7 +47,7 @@ function issueFlagNotices(config) {
47
47
  return;
48
48
  }
49
49
  if (experimentalFlagsEnabled(config).length > 0) {
50
- let changes = experimentalFlagsEnabled(config).map((s)=>_chalk.default.yellow(s)
50
+ let changes = experimentalFlagsEnabled(config).map((s)=>_picocolors.default.yellow(s)
51
51
  ).join(', ');
52
52
  _log.default.warn('experimental-flags-enabled', [
53
53
  `You have enabled experimental features: ${changes}`,
@@ -13,7 +13,7 @@ function _interopRequireDefault(obj) {
13
13
  default: obj
14
14
  };
15
15
  }
16
- function extractClasses(node) {
16
+ /** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */ function extractClasses(node) {
17
17
  let classes = new Set();
18
18
  let container = _postcss.default.root({
19
19
  nodes: [
@@ -40,7 +40,114 @@ function prefix(context, selector) {
40
40
  let prefix1 = context.tailwindConfig.prefix;
41
41
  return typeof prefix1 === 'function' ? prefix1(selector) : prefix1 + selector;
42
42
  }
43
- function buildApplyCache(applyCandidates, context) {
43
+ function* pathToRoot(node) {
44
+ yield node;
45
+ while(node.parent){
46
+ yield node.parent;
47
+ node = node.parent;
48
+ }
49
+ }
50
+ /**
51
+ * Only clone the node itself and not its children
52
+ *
53
+ * @param {*} node
54
+ * @param {*} overrides
55
+ * @returns
56
+ */ function shallowClone(node, overrides = {}) {
57
+ let children = node.nodes;
58
+ node.nodes = [];
59
+ let tmp = node.clone(overrides);
60
+ node.nodes = children;
61
+ return tmp;
62
+ }
63
+ /**
64
+ * Clone just the nodes all the way to the top that are required to represent
65
+ * this singular rule in the tree.
66
+ *
67
+ * For example, if we have CSS like this:
68
+ * ```css
69
+ * @media (min-width: 768px) {
70
+ * @supports (display: grid) {
71
+ * .foo {
72
+ * display: grid;
73
+ * grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
74
+ * }
75
+ * }
76
+ *
77
+ * @supports (backdrop-filter: blur(1px)) {
78
+ * .bar {
79
+ * backdrop-filter: blur(1px);
80
+ * }
81
+ * }
82
+ *
83
+ * .baz {
84
+ * color: orange;
85
+ * }
86
+ * }
87
+ * ```
88
+ *
89
+ * And we're cloning `.bar` it'll return a cloned version of what's required for just that single node:
90
+ *
91
+ * ```css
92
+ * @media (min-width: 768px) {
93
+ * @supports (backdrop-filter: blur(1px)) {
94
+ * .bar {
95
+ * backdrop-filter: blur(1px);
96
+ * }
97
+ * }
98
+ * }
99
+ * ```
100
+ *
101
+ * @param {import('postcss').Node} node
102
+ */ function nestedClone(node) {
103
+ for (let parent of pathToRoot(node)){
104
+ if (node === parent) {
105
+ continue;
106
+ }
107
+ if (parent.type === 'root') {
108
+ break;
109
+ }
110
+ node = shallowClone(parent, {
111
+ nodes: [
112
+ node
113
+ ]
114
+ });
115
+ }
116
+ return node;
117
+ }
118
+ /**
119
+ * @param {import('postcss').Root} root
120
+ */ function buildLocalApplyCache(root, context) {
121
+ /** @type {ApplyCache} */ let cache = new Map();
122
+ let highestOffset = context.layerOrder.user >> 4n;
123
+ root.walkRules((rule, idx)=>{
124
+ // Ignore rules generated by Tailwind
125
+ for (let node of pathToRoot(rule)){
126
+ var ref;
127
+ if (((ref = node.raws.tailwind) === null || ref === void 0 ? void 0 : ref.layer) !== undefined) {
128
+ return;
129
+ }
130
+ }
131
+ // Clone what's required to represent this singular rule in the tree
132
+ let container = nestedClone(rule);
133
+ for (let className of extractClasses(rule)){
134
+ let list = cache.get(className) || [];
135
+ cache.set(className, list);
136
+ list.push([
137
+ {
138
+ layer: 'user',
139
+ sort: BigInt(idx) + highestOffset,
140
+ important: false
141
+ },
142
+ container,
143
+ ]);
144
+ }
145
+ });
146
+ return cache;
147
+ }
148
+ /**
149
+ * @returns {ApplyCache}
150
+ */ function buildApplyCache(applyCandidates, context) {
44
151
  for (let candidate of applyCandidates){
45
152
  if (context.notClassCache.has(candidate) || context.applyClassCache.has(candidate)) {
46
153
  continue;
@@ -62,6 +169,39 @@ function buildApplyCache(applyCandidates, context) {
62
169
  }
63
170
  return context.applyClassCache;
64
171
  }
172
+ /**
173
+ * Build a cache only when it's first used
174
+ *
175
+ * @param {() => ApplyCache} buildCacheFn
176
+ * @returns {ApplyCache}
177
+ */ function lazyCache(buildCacheFn) {
178
+ let cache = null;
179
+ return {
180
+ get: (name)=>{
181
+ cache = cache || buildCacheFn();
182
+ return cache.get(name);
183
+ },
184
+ has: (name)=>{
185
+ cache = cache || buildCacheFn();
186
+ return cache.has(name);
187
+ }
188
+ };
189
+ }
190
+ /**
191
+ * Take a series of multiple caches and merge
192
+ * them so they act like one large cache
193
+ *
194
+ * @param {ApplyCache[]} caches
195
+ * @returns {ApplyCache}
196
+ */ function combineCaches(caches) {
197
+ return {
198
+ get: (name)=>caches.flatMap((cache)=>cache.get(name) || []
199
+ )
200
+ ,
201
+ has: (name)=>caches.some((cache)=>cache.has(name)
202
+ )
203
+ };
204
+ }
65
205
  function extractApplyCandidates(params) {
66
206
  let candidates = params.split(/[\s\t\n]+/g);
67
207
  if (candidates[candidates.length - 1] === '!important') {
@@ -75,7 +215,7 @@ function extractApplyCandidates(params) {
75
215
  false
76
216
  ];
77
217
  }
78
- function processApply(root, context) {
218
+ function processApply(root, context, localCache) {
79
219
  let applyCandidates = new Set();
80
220
  // Collect all @apply rules and candidates
81
221
  let applies = [];
@@ -89,7 +229,10 @@ function processApply(root, context) {
89
229
  // Start the @apply process if we have rules with @apply in them
90
230
  if (applies.length > 0) {
91
231
  // Fill up some caches!
92
- let applyClassCache = buildApplyCache(applyCandidates, context);
232
+ let applyClassCache = combineCaches([
233
+ localCache,
234
+ buildApplyCache(applyCandidates, context)
235
+ ]);
93
236
  /**
94
237
  * When we have an apply like this:
95
238
  *
@@ -129,7 +272,10 @@ function processApply(root, context) {
129
272
  // Collect all apply candidates and their rules
130
273
  for (let apply of applies){
131
274
  let candidates = perParentApplies.get(apply.parent) || [];
132
- perParentApplies.set(apply.parent, candidates);
275
+ perParentApplies.set(apply.parent, [
276
+ candidates,
277
+ apply.source
278
+ ]);
133
279
  let [applyCandidates, important] = extractApplyCandidates(apply.params);
134
280
  if (apply.parent.type === 'atrule') {
135
281
  if (apply.parent.name === 'screen') {
@@ -158,12 +304,12 @@ function processApply(root, context) {
158
304
  ]);
159
305
  }
160
306
  }
161
- for (const [parent, candidates] of perParentApplies){
307
+ for (const [parent, [candidates, atApplySource]] of perParentApplies){
162
308
  let siblings = [];
163
309
  for (let [applyCandidate, important, rules] of candidates){
164
- for (let [meta, node] of rules){
310
+ for (let [meta, node1] of rules){
165
311
  let parentClasses = extractClasses(parent);
166
- let nodeClasses = extractClasses(node);
312
+ let nodeClasses = extractClasses(node1);
167
313
  // Add base utility classes from the @apply node to the list of
168
314
  // classes to check whether it intersects and therefore results in a
169
315
  // circular dependency or not.
@@ -190,14 +336,18 @@ function processApply(root, context) {
190
336
  let intersects = parentClasses.some((selector)=>nodeClasses.includes(selector)
191
337
  );
192
338
  if (intersects) {
193
- throw node.error(`You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`);
339
+ throw node1.error(`You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`);
194
340
  }
195
341
  let root = _postcss.default.root({
196
342
  nodes: [
197
- node.clone()
343
+ node1.clone()
198
344
  ]
199
345
  });
200
- let canRewriteSelector = node.type !== 'atrule' || node.type === 'atrule' && node.name !== 'keyframes';
346
+ // Make sure every node in the entire tree points back at the @apply rule that generated it
347
+ root.walk((node)=>{
348
+ node.source = atApplySource;
349
+ });
350
+ let canRewriteSelector = node1.type !== 'atrule' || node1.type === 'atrule' && node1.name !== 'keyframes';
201
351
  if (canRewriteSelector) {
202
352
  root.walkRules((rule)=>{
203
353
  // Let's imagine you have the following structure:
@@ -270,11 +420,14 @@ function processApply(root, context) {
270
420
  }
271
421
  }
272
422
  // Do it again, in case we have other `@apply` rules
273
- processApply(root, context);
423
+ processApply(root, context, localCache);
274
424
  }
275
425
  }
276
426
  function expandApplyAtRules(context) {
277
427
  return (root)=>{
278
- processApply(root, context);
428
+ // Build a cache of the user's CSS so we can use it to resolve classes used by @apply
429
+ let localCache = lazyCache(()=>buildLocalApplyCache(root, context)
430
+ );
431
+ processApply(root, context, localCache);
279
432
  };
280
433
  }
@@ -191,19 +191,25 @@ function expandTailwindAtRules(context) {
191
191
  layerNodes.base.before((0, _cloneNodes).default([
192
192
  ...baseNodes,
193
193
  ...defaultNodes
194
- ], layerNodes.base.source));
194
+ ], layerNodes.base.source, {
195
+ layer: 'base'
196
+ }));
195
197
  layerNodes.base.remove();
196
198
  }
197
199
  if (layerNodes.components) {
198
200
  layerNodes.components.before((0, _cloneNodes).default([
199
201
  ...componentNodes
200
- ], layerNodes.components.source));
202
+ ], layerNodes.components.source, {
203
+ layer: 'components'
204
+ }));
201
205
  layerNodes.components.remove();
202
206
  }
203
207
  if (layerNodes.utilities) {
204
208
  layerNodes.utilities.before((0, _cloneNodes).default([
205
209
  ...utilityNodes
206
- ], layerNodes.utilities.source));
210
+ ], layerNodes.utilities.source, {
211
+ layer: 'utilities'
212
+ }));
207
213
  layerNodes.utilities.remove();
208
214
  }
209
215
  // We do post-filtering to not alter the emitted order of the variants
@@ -219,10 +225,14 @@ function expandTailwindAtRules(context) {
219
225
  return true;
220
226
  });
221
227
  if (layerNodes.variants) {
222
- layerNodes.variants.before((0, _cloneNodes).default(variantNodes, layerNodes.variants.source));
228
+ layerNodes.variants.before((0, _cloneNodes).default(variantNodes, layerNodes.variants.source, {
229
+ layer: 'variants'
230
+ }));
223
231
  layerNodes.variants.remove();
224
232
  } else if (variantNodes.length > 0) {
225
- root.append((0, _cloneNodes).default(variantNodes, root.source));
233
+ root.append((0, _cloneNodes).default(variantNodes, root.source, {
234
+ layer: 'variants'
235
+ }));
226
236
  }
227
237
  // If we've got a utility layer and no utilities are generated there's likely something wrong
228
238
  const hasUtilityVariants = variantNodes.some((node)=>{
@@ -115,7 +115,9 @@ function resolveDefaultsAtRules({ tailwindConfig }) {
115
115
  continue;
116
116
  }
117
117
  for (let [, selectors] of selectorGroups){
118
- let universalRule = _postcss.default.rule();
118
+ let universalRule = _postcss.default.rule({
119
+ source: universal.source
120
+ });
119
121
  universalRule.selectors = [
120
122
  ...selectors
121
123
  ];
@@ -124,7 +126,9 @@ function resolveDefaultsAtRules({ tailwindConfig }) {
124
126
  universal.before(universalRule);
125
127
  }
126
128
  } else {
127
- let universalRule = _postcss.default.rule();
129
+ let universalRule = _postcss.default.rule({
130
+ source: universal.source
131
+ });
128
132
  universalRule.selectors = [
129
133
  '*',
130
134
  '::before',
@@ -245,21 +245,6 @@ function buildPluginApi(tailwindConfig, context, { variantList , variantMap , of
245
245
  // Preserved for backwards compatibility but not used in v3.0+
246
246
  return [];
247
247
  },
248
- addUserCss (userCss) {
249
- for (let [identifier, rule] of withIdentifiers(userCss)){
250
- let offset = offsets.user++;
251
- if (!context.candidateRuleMap.has(identifier)) {
252
- context.candidateRuleMap.set(identifier, []);
253
- }
254
- context.candidateRuleMap.get(identifier).push([
255
- {
256
- sort: offset,
257
- layer: 'user'
258
- },
259
- rule
260
- ]);
261
- }
262
- },
263
248
  addBase (base) {
264
249
  for (let [identifier, rule] of withIdentifiers(base)){
265
250
  let prefixedIdentifier = prefixIdentifier(identifier, {});
@@ -533,16 +518,6 @@ function collectLayerPlugins(root) {
533
518
  layerRule.remove();
534
519
  }
535
520
  });
536
- root.walkRules((rule)=>{
537
- // At this point it is safe to include all the left-over css from the
538
- // user's css file. This is because the `@tailwind` and `@layer` directives
539
- // will already be handled and will be removed from the css tree.
540
- layerPlugins.push(function({ addUserCss }) {
541
- addUserCss(rule, {
542
- respectPrefix: false
543
- });
544
- });
545
- });
546
521
  return layerPlugins;
547
522
  }
548
523
  function resolvePlugins(context, root) {
@@ -119,7 +119,7 @@ function resolveChangedFiles(candidateFiles, fileModifiedMap) {
119
119
  return changedFiles;
120
120
  }
121
121
  function setupTrackingContext(configOrPath) {
122
- return ({ tailwindDirectives , registerDependency , applyDirectives })=>{
122
+ return ({ tailwindDirectives , registerDependency })=>{
123
123
  return (root, result)=>{
124
124
  let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = getTailwindConfig(configOrPath);
125
125
  let contextDependencies = new Set(configDependencies);
@@ -129,7 +129,7 @@ function setupTrackingContext(configOrPath) {
129
129
  // being part of this trigger too, but it's tough because it's impossible
130
130
  // for a layer in one file to end up in the actual @tailwind rule in
131
131
  // another file since independent sources are effectively isolated.
132
- if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
132
+ if (tailwindDirectives.size > 0) {
133
133
  // Add current css file as a context dependencies.
134
134
  contextDependencies.add(result.opts.from);
135
135
  // Add all css @import dependencies as context dependencies.
@@ -146,7 +146,7 @@ function setupTrackingContext(configOrPath) {
146
146
  // We may want to think about `@layer` being part of this trigger too, but it's tough
147
147
  // because it's impossible for a layer in one file to end up in the actual @tailwind rule
148
148
  // in another file since independent sources are effectively isolated.
149
- if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
149
+ if (tailwindDirectives.size > 0) {
150
150
  let fileModifiedMap = (0, _setupContextUtils).getFileModifiedMap(context);
151
151
  // Add template paths as postcss dependencies.
152
152
  for (let fileOrGlob of candidateFiles){
@@ -3,11 +3,22 @@ Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
5
  exports.default = cloneNodes;
6
- function cloneNodes(nodes, source) {
6
+ function cloneNodes(nodes, source = undefined, raws = undefined) {
7
7
  return nodes.map((node)=>{
8
8
  let cloned = node.clone();
9
9
  if (source !== undefined) {
10
10
  cloned.source = source;
11
+ if ('walk' in cloned) {
12
+ cloned.walk((child)=>{
13
+ child.source = source;
14
+ });
15
+ }
16
+ }
17
+ if (raws !== undefined) {
18
+ cloned.raws.tailwind = {
19
+ ...cloned.raws.tailwind,
20
+ ...raws
21
+ };
11
22
  }
12
23
  return cloned;
13
24
  });
package/lib/util/log.js CHANGED
@@ -4,27 +4,27 @@ Object.defineProperty(exports, "__esModule", {
4
4
  });
5
5
  exports.dim = dim;
6
6
  exports.default = void 0;
7
- var _chalk = _interopRequireDefault(require("chalk"));
7
+ var _picocolors = _interopRequireDefault(require("picocolors"));
8
8
  function _interopRequireDefault(obj) {
9
9
  return obj && obj.__esModule ? obj : {
10
10
  default: obj
11
11
  };
12
12
  }
13
13
  let alreadyShown = new Set();
14
- function log(chalk, messages, key) {
14
+ function log(type, messages, key) {
15
15
  if (process.env.JEST_WORKER_ID !== undefined) return;
16
16
  if (key && alreadyShown.has(key)) return;
17
17
  if (key) alreadyShown.add(key);
18
18
  console.warn('');
19
- messages.forEach((message)=>console.warn(chalk, '-', message)
19
+ messages.forEach((message)=>console.warn(type, '-', message)
20
20
  );
21
21
  }
22
22
  function dim(input) {
23
- return _chalk.default.dim(input);
23
+ return _picocolors.default.dim(input);
24
24
  }
25
25
  var _default = {
26
26
  info (key, messages) {
27
- log(_chalk.default.bold.cyan('info'), ...Array.isArray(key) ? [
27
+ log(_picocolors.default.bold(_picocolors.default.cyan('info')), ...Array.isArray(key) ? [
28
28
  key
29
29
  ] : [
30
30
  messages,
@@ -32,7 +32,7 @@ var _default = {
32
32
  ]);
33
33
  },
34
34
  warn (key, messages) {
35
- log(_chalk.default.bold.yellow('warn'), ...Array.isArray(key) ? [
35
+ log(_picocolors.default.bold(_picocolors.default.yellow('warn')), ...Array.isArray(key) ? [
36
36
  key
37
37
  ] : [
38
38
  messages,
@@ -40,7 +40,7 @@ var _default = {
40
40
  ]);
41
41
  },
42
42
  risk (key, messages) {
43
- log(_chalk.default.bold.magenta('risk'), ...Array.isArray(key) ? [
43
+ log(_picocolors.default.bold(_picocolors.default.magenta('risk')), ...Array.isArray(key) ? [
44
44
  key
45
45
  ] : [
46
46
  messages,
@@ -11,13 +11,53 @@ let KEYWORDS = new Set([
11
11
  'revert',
12
12
  'unset'
13
13
  ]);
14
- let COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
15
- ;
16
14
  let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead.
17
15
  ;
18
16
  let LENGTH = /^-?(\d+|\.\d+)(.*?)$/g;
17
+ let SPECIALS = /[(),]/g;
18
+ /**
19
+ * This splits a string on top-level commas.
20
+ *
21
+ * Regex doesn't support recursion (at least not the JS-flavored version).
22
+ * So we have to use a tiny state machine to keep track of paren vs comma
23
+ * placement. Before we'd only exclude commas from the inner-most nested
24
+ * set of parens rather than any commas that were not contained in parens
25
+ * at all which is the intended behavior here.
26
+ *
27
+ * Expected behavior:
28
+ * var(--a, 0 0 1px rgb(0, 0, 0)), 0 0 1px rgb(0, 0, 0)
29
+ * ─┬─ ┬ ┬ ┬
30
+ * x x x ╰──────── Split because top-level
31
+ * ╰──────────────┴──┴───────────── Ignored b/c inside >= 1 levels of parens
32
+ *
33
+ * @param {string} input
34
+ */ function* splitByTopLevelCommas(input) {
35
+ SPECIALS.lastIndex = -1;
36
+ let depth = 0;
37
+ let lastIndex = 0;
38
+ let found = false;
39
+ // Find all parens & commas
40
+ // And only split on commas if they're top-level
41
+ for (let match of input.matchAll(SPECIALS)){
42
+ if (match[0] === '(') depth++;
43
+ if (match[0] === ')') depth--;
44
+ if (match[0] === ',' && depth === 0) {
45
+ found = true;
46
+ yield input.substring(lastIndex, match.index);
47
+ lastIndex = match.index + match[0].length;
48
+ }
49
+ }
50
+ // Provide the last segment of the string if available
51
+ // Otherwise the whole string since no commas were found
52
+ // This mirrors the behavior of string.split()
53
+ if (found) {
54
+ yield input.substring(lastIndex);
55
+ } else {
56
+ yield input;
57
+ }
58
+ }
19
59
  function parseBoxShadowValue(input) {
20
- let shadows = input.split(COMMA);
60
+ let shadows = Array.from(splitByTopLevelCommas(input));
21
61
  return shadows.map((shadow)=>{
22
62
  let value = shadow.trim();
23
63
  let result = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailwindcss",
3
- "version": "0.0.0-insiders.d72b277",
3
+ "version": "0.0.0-insiders.d9bc25d",
4
4
  "description": "A utility-first CSS framework for rapidly building custom user interfaces.",
5
5
  "license": "MIT",
6
6
  "main": "lib/index.js",
@@ -52,7 +52,8 @@
52
52
  "jest-diff": "^27.5.1",
53
53
  "prettier": "^2.5.1",
54
54
  "prettier-plugin-tailwindcss": "^0.1.7",
55
- "rimraf": "^3.0.0"
55
+ "rimraf": "^3.0.0",
56
+ "source-map-js": "^1.0.2"
56
57
  },
57
58
  "peerDependencies": {
58
59
  "autoprefixer": "^10.0.2",
@@ -60,7 +61,6 @@
60
61
  },
61
62
  "dependencies": {
62
63
  "arg": "^5.0.1",
63
- "chalk": "^4.1.2",
64
64
  "chokidar": "^3.5.3",
65
65
  "color-name": "^1.1.4",
66
66
  "cosmiconfig": "^7.0.1",
@@ -71,7 +71,8 @@
71
71
  "glob-parent": "^6.0.2",
72
72
  "is-glob": "^4.0.3",
73
73
  "normalize-path": "^3.0.0",
74
- "object-hash": "^2.2.0",
74
+ "object-hash": "^3.0.0",
75
+ "picocolors": "^1.0.0",
75
76
  "postcss": "^8.4.6",
76
77
  "postcss-js": "^4.0.0",
77
78
  "postcss-load-config": "^3.1.3",
@@ -1,4 +1,4 @@
1
- import chalk from 'chalk'
1
+ import colors from 'picocolors'
2
2
  import log from './util/log'
3
3
 
4
4
  let defaults = {
@@ -41,7 +41,7 @@ export function issueFlagNotices(config) {
41
41
 
42
42
  if (experimentalFlagsEnabled(config).length > 0) {
43
43
  let changes = experimentalFlagsEnabled(config)
44
- .map((s) => chalk.yellow(s))
44
+ .map((s) => colors.yellow(s))
45
45
  .join(', ')
46
46
 
47
47
  log.warn('experimental-flags-enabled', [
@@ -5,6 +5,8 @@ import { resolveMatches } from './generateRules'
5
5
  import bigSign from '../util/bigSign'
6
6
  import escapeClassName from '../util/escapeClassName'
7
7
 
8
+ /** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
9
+
8
10
  function extractClasses(node) {
9
11
  let classes = new Set()
10
12
  let container = postcss.root({ nodes: [node.clone()] })
@@ -35,6 +37,131 @@ function prefix(context, selector) {
35
37
  return typeof prefix === 'function' ? prefix(selector) : prefix + selector
36
38
  }
37
39
 
40
+ function* pathToRoot(node) {
41
+ yield node
42
+ while (node.parent) {
43
+ yield node.parent
44
+ node = node.parent
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Only clone the node itself and not its children
50
+ *
51
+ * @param {*} node
52
+ * @param {*} overrides
53
+ * @returns
54
+ */
55
+ function shallowClone(node, overrides = {}) {
56
+ let children = node.nodes
57
+ node.nodes = []
58
+
59
+ let tmp = node.clone(overrides)
60
+
61
+ node.nodes = children
62
+
63
+ return tmp
64
+ }
65
+
66
+ /**
67
+ * Clone just the nodes all the way to the top that are required to represent
68
+ * this singular rule in the tree.
69
+ *
70
+ * For example, if we have CSS like this:
71
+ * ```css
72
+ * @media (min-width: 768px) {
73
+ * @supports (display: grid) {
74
+ * .foo {
75
+ * display: grid;
76
+ * grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
77
+ * }
78
+ * }
79
+ *
80
+ * @supports (backdrop-filter: blur(1px)) {
81
+ * .bar {
82
+ * backdrop-filter: blur(1px);
83
+ * }
84
+ * }
85
+ *
86
+ * .baz {
87
+ * color: orange;
88
+ * }
89
+ * }
90
+ * ```
91
+ *
92
+ * And we're cloning `.bar` it'll return a cloned version of what's required for just that single node:
93
+ *
94
+ * ```css
95
+ * @media (min-width: 768px) {
96
+ * @supports (backdrop-filter: blur(1px)) {
97
+ * .bar {
98
+ * backdrop-filter: blur(1px);
99
+ * }
100
+ * }
101
+ * }
102
+ * ```
103
+ *
104
+ * @param {import('postcss').Node} node
105
+ */
106
+ function nestedClone(node) {
107
+ for (let parent of pathToRoot(node)) {
108
+ if (node === parent) {
109
+ continue
110
+ }
111
+
112
+ if (parent.type === 'root') {
113
+ break
114
+ }
115
+
116
+ node = shallowClone(parent, {
117
+ nodes: [node],
118
+ })
119
+ }
120
+
121
+ return node
122
+ }
123
+
124
+ /**
125
+ * @param {import('postcss').Root} root
126
+ */
127
+ function buildLocalApplyCache(root, context) {
128
+ /** @type {ApplyCache} */
129
+ let cache = new Map()
130
+
131
+ let highestOffset = context.layerOrder.user >> 4n
132
+
133
+ root.walkRules((rule, idx) => {
134
+ // Ignore rules generated by Tailwind
135
+ for (let node of pathToRoot(rule)) {
136
+ if (node.raws.tailwind?.layer !== undefined) {
137
+ return
138
+ }
139
+ }
140
+
141
+ // Clone what's required to represent this singular rule in the tree
142
+ let container = nestedClone(rule)
143
+
144
+ for (let className of extractClasses(rule)) {
145
+ let list = cache.get(className) || []
146
+ cache.set(className, list)
147
+
148
+ list.push([
149
+ {
150
+ layer: 'user',
151
+ sort: BigInt(idx) + highestOffset,
152
+ important: false,
153
+ },
154
+ container,
155
+ ])
156
+ }
157
+ })
158
+
159
+ return cache
160
+ }
161
+
162
+ /**
163
+ * @returns {ApplyCache}
164
+ */
38
165
  function buildApplyCache(applyCandidates, context) {
39
166
  for (let candidate of applyCandidates) {
40
167
  if (context.notClassCache.has(candidate) || context.applyClassCache.has(candidate)) {
@@ -62,6 +189,43 @@ function buildApplyCache(applyCandidates, context) {
62
189
  return context.applyClassCache
63
190
  }
64
191
 
192
+ /**
193
+ * Build a cache only when it's first used
194
+ *
195
+ * @param {() => ApplyCache} buildCacheFn
196
+ * @returns {ApplyCache}
197
+ */
198
+ function lazyCache(buildCacheFn) {
199
+ let cache = null
200
+
201
+ return {
202
+ get: (name) => {
203
+ cache = cache || buildCacheFn()
204
+
205
+ return cache.get(name)
206
+ },
207
+ has: (name) => {
208
+ cache = cache || buildCacheFn()
209
+
210
+ return cache.has(name)
211
+ },
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Take a series of multiple caches and merge
217
+ * them so they act like one large cache
218
+ *
219
+ * @param {ApplyCache[]} caches
220
+ * @returns {ApplyCache}
221
+ */
222
+ function combineCaches(caches) {
223
+ return {
224
+ get: (name) => caches.flatMap((cache) => cache.get(name) || []),
225
+ has: (name) => caches.some((cache) => cache.has(name)),
226
+ }
227
+ }
228
+
65
229
  function extractApplyCandidates(params) {
66
230
  let candidates = params.split(/[\s\t\n]+/g)
67
231
 
@@ -72,7 +236,7 @@ function extractApplyCandidates(params) {
72
236
  return [candidates, false]
73
237
  }
74
238
 
75
- function processApply(root, context) {
239
+ function processApply(root, context, localCache) {
76
240
  let applyCandidates = new Set()
77
241
 
78
242
  // Collect all @apply rules and candidates
@@ -90,7 +254,7 @@ function processApply(root, context) {
90
254
  // Start the @apply process if we have rules with @apply in them
91
255
  if (applies.length > 0) {
92
256
  // Fill up some caches!
93
- let applyClassCache = buildApplyCache(applyCandidates, context)
257
+ let applyClassCache = combineCaches([localCache, buildApplyCache(applyCandidates, context)])
94
258
 
95
259
  /**
96
260
  * When we have an apply like this:
@@ -140,7 +304,7 @@ function processApply(root, context) {
140
304
  for (let apply of applies) {
141
305
  let candidates = perParentApplies.get(apply.parent) || []
142
306
 
143
- perParentApplies.set(apply.parent, candidates)
307
+ perParentApplies.set(apply.parent, [candidates, apply.source])
144
308
 
145
309
  let [applyCandidates, important] = extractApplyCandidates(apply.params)
146
310
 
@@ -178,7 +342,7 @@ function processApply(root, context) {
178
342
  }
179
343
  }
180
344
 
181
- for (const [parent, candidates] of perParentApplies) {
345
+ for (const [parent, [candidates, atApplySource]] of perParentApplies) {
182
346
  let siblings = []
183
347
 
184
348
  for (let [applyCandidate, important, rules] of candidates) {
@@ -220,6 +384,12 @@ function processApply(root, context) {
220
384
  }
221
385
 
222
386
  let root = postcss.root({ nodes: [node.clone()] })
387
+
388
+ // Make sure every node in the entire tree points back at the @apply rule that generated it
389
+ root.walk((node) => {
390
+ node.source = atApplySource
391
+ })
392
+
223
393
  let canRewriteSelector =
224
394
  node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')
225
395
 
@@ -296,12 +466,15 @@ function processApply(root, context) {
296
466
  }
297
467
 
298
468
  // Do it again, in case we have other `@apply` rules
299
- processApply(root, context)
469
+ processApply(root, context, localCache)
300
470
  }
301
471
  }
302
472
 
303
473
  export default function expandApplyAtRules(context) {
304
474
  return (root) => {
305
- processApply(root, context)
475
+ // Build a cache of the user's CSS so we can use it to resolve classes used by @apply
476
+ let localCache = lazyCache(() => buildLocalApplyCache(root, context))
477
+
478
+ processApply(root, context, localCache)
306
479
  }
307
480
  }
@@ -204,17 +204,29 @@ export default function expandTailwindAtRules(context) {
204
204
  // Replace any Tailwind directives with generated CSS
205
205
 
206
206
  if (layerNodes.base) {
207
- layerNodes.base.before(cloneNodes([...baseNodes, ...defaultNodes], layerNodes.base.source))
207
+ layerNodes.base.before(
208
+ cloneNodes([...baseNodes, ...defaultNodes], layerNodes.base.source, {
209
+ layer: 'base',
210
+ })
211
+ )
208
212
  layerNodes.base.remove()
209
213
  }
210
214
 
211
215
  if (layerNodes.components) {
212
- layerNodes.components.before(cloneNodes([...componentNodes], layerNodes.components.source))
216
+ layerNodes.components.before(
217
+ cloneNodes([...componentNodes], layerNodes.components.source, {
218
+ layer: 'components',
219
+ })
220
+ )
213
221
  layerNodes.components.remove()
214
222
  }
215
223
 
216
224
  if (layerNodes.utilities) {
217
- layerNodes.utilities.before(cloneNodes([...utilityNodes], layerNodes.utilities.source))
225
+ layerNodes.utilities.before(
226
+ cloneNodes([...utilityNodes], layerNodes.utilities.source, {
227
+ layer: 'utilities',
228
+ })
229
+ )
218
230
  layerNodes.utilities.remove()
219
231
  }
220
232
 
@@ -234,10 +246,18 @@ export default function expandTailwindAtRules(context) {
234
246
  })
235
247
 
236
248
  if (layerNodes.variants) {
237
- layerNodes.variants.before(cloneNodes(variantNodes, layerNodes.variants.source))
249
+ layerNodes.variants.before(
250
+ cloneNodes(variantNodes, layerNodes.variants.source, {
251
+ layer: 'variants',
252
+ })
253
+ )
238
254
  layerNodes.variants.remove()
239
255
  } else if (variantNodes.length > 0) {
240
- root.append(cloneNodes(variantNodes, root.source))
256
+ root.append(
257
+ cloneNodes(variantNodes, root.source, {
258
+ layer: 'variants',
259
+ })
260
+ )
241
261
  }
242
262
 
243
263
  // If we've got a utility layer and no utilities are generated there's likely something wrong
@@ -120,7 +120,9 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
120
120
  }
121
121
 
122
122
  for (let [, selectors] of selectorGroups) {
123
- let universalRule = postcss.rule()
123
+ let universalRule = postcss.rule({
124
+ source: universal.source,
125
+ })
124
126
 
125
127
  universalRule.selectors = [...selectors]
126
128
 
@@ -128,7 +130,9 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
128
130
  universal.before(universalRule)
129
131
  }
130
132
  } else {
131
- let universalRule = postcss.rule()
133
+ let universalRule = postcss.rule({
134
+ source: universal.source,
135
+ })
132
136
 
133
137
  universalRule.selectors = ['*', '::before', '::after']
134
138
 
@@ -230,17 +230,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
230
230
  // Preserved for backwards compatibility but not used in v3.0+
231
231
  return []
232
232
  },
233
- addUserCss(userCss) {
234
- for (let [identifier, rule] of withIdentifiers(userCss)) {
235
- let offset = offsets.user++
236
-
237
- if (!context.candidateRuleMap.has(identifier)) {
238
- context.candidateRuleMap.set(identifier, [])
239
- }
240
-
241
- context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
242
- }
243
- },
244
233
  addBase(base) {
245
234
  for (let [identifier, rule] of withIdentifiers(base)) {
246
235
  let prefixedIdentifier = prefixIdentifier(identifier, {})
@@ -521,15 +510,6 @@ function collectLayerPlugins(root) {
521
510
  }
522
511
  })
523
512
 
524
- root.walkRules((rule) => {
525
- // At this point it is safe to include all the left-over css from the
526
- // user's css file. This is because the `@tailwind` and `@layer` directives
527
- // will already be handled and will be removed from the css tree.
528
- layerPlugins.push(function ({ addUserCss }) {
529
- addUserCss(rule, { respectPrefix: false })
530
- })
531
- })
532
-
533
513
  return layerPlugins
534
514
  }
535
515
 
@@ -112,7 +112,7 @@ function resolveChangedFiles(candidateFiles, fileModifiedMap) {
112
112
  // source path), or set up a new one (including setting up watchers and registering
113
113
  // plugins) then return it
114
114
  export default function setupTrackingContext(configOrPath) {
115
- return ({ tailwindDirectives, registerDependency, applyDirectives }) => {
115
+ return ({ tailwindDirectives, registerDependency }) => {
116
116
  return (root, result) => {
117
117
  let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] =
118
118
  getTailwindConfig(configOrPath)
@@ -125,7 +125,7 @@ export default function setupTrackingContext(configOrPath) {
125
125
  // being part of this trigger too, but it's tough because it's impossible
126
126
  // for a layer in one file to end up in the actual @tailwind rule in
127
127
  // another file since independent sources are effectively isolated.
128
- if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
128
+ if (tailwindDirectives.size > 0) {
129
129
  // Add current css file as a context dependencies.
130
130
  contextDependencies.add(result.opts.from)
131
131
 
@@ -153,7 +153,7 @@ export default function setupTrackingContext(configOrPath) {
153
153
  // We may want to think about `@layer` being part of this trigger too, but it's tough
154
154
  // because it's impossible for a layer in one file to end up in the actual @tailwind rule
155
155
  // in another file since independent sources are effectively isolated.
156
- if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
156
+ if (tailwindDirectives.size > 0) {
157
157
  let fileModifiedMap = getFileModifiedMap(context)
158
158
 
159
159
  // Add template paths as postcss dependencies.
@@ -1,9 +1,22 @@
1
- export default function cloneNodes(nodes, source) {
1
+ export default function cloneNodes(nodes, source = undefined, raws = undefined) {
2
2
  return nodes.map((node) => {
3
3
  let cloned = node.clone()
4
4
 
5
5
  if (source !== undefined) {
6
6
  cloned.source = source
7
+
8
+ if ('walk' in cloned) {
9
+ cloned.walk((child) => {
10
+ child.source = source
11
+ })
12
+ }
13
+ }
14
+
15
+ if (raws !== undefined) {
16
+ cloned.raws.tailwind = {
17
+ ...cloned.raws.tailwind,
18
+ ...raws,
19
+ }
7
20
  }
8
21
 
9
22
  return cloned
package/src/util/log.js CHANGED
@@ -1,29 +1,29 @@
1
- import chalk from 'chalk'
1
+ import colors from 'picocolors'
2
2
 
3
3
  let alreadyShown = new Set()
4
4
 
5
- function log(chalk, messages, key) {
5
+ function log(type, messages, key) {
6
6
  if (process.env.JEST_WORKER_ID !== undefined) return
7
7
 
8
8
  if (key && alreadyShown.has(key)) return
9
9
  if (key) alreadyShown.add(key)
10
10
 
11
11
  console.warn('')
12
- messages.forEach((message) => console.warn(chalk, '-', message))
12
+ messages.forEach((message) => console.warn(type, '-', message))
13
13
  }
14
14
 
15
15
  export function dim(input) {
16
- return chalk.dim(input)
16
+ return colors.dim(input)
17
17
  }
18
18
 
19
19
  export default {
20
20
  info(key, messages) {
21
- log(chalk.bold.cyan('info'), ...(Array.isArray(key) ? [key] : [messages, key]))
21
+ log(colors.bold(colors.cyan('info')), ...(Array.isArray(key) ? [key] : [messages, key]))
22
22
  },
23
23
  warn(key, messages) {
24
- log(chalk.bold.yellow('warn'), ...(Array.isArray(key) ? [key] : [messages, key]))
24
+ log(colors.bold(colors.yellow('warn')), ...(Array.isArray(key) ? [key] : [messages, key]))
25
25
  },
26
26
  risk(key, messages) {
27
- log(chalk.bold.magenta('risk'), ...(Array.isArray(key) ? [key] : [messages, key]))
27
+ log(colors.bold(colors.magenta('risk')), ...(Array.isArray(key) ? [key] : [messages, key]))
28
28
  },
29
29
  }
@@ -1,10 +1,58 @@
1
1
  let KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
2
- let COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
3
2
  let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead.
4
3
  let LENGTH = /^-?(\d+|\.\d+)(.*?)$/g
5
4
 
5
+ let SPECIALS = /[(),]/g
6
+
7
+ /**
8
+ * This splits a string on top-level commas.
9
+ *
10
+ * Regex doesn't support recursion (at least not the JS-flavored version).
11
+ * So we have to use a tiny state machine to keep track of paren vs comma
12
+ * placement. Before we'd only exclude commas from the inner-most nested
13
+ * set of parens rather than any commas that were not contained in parens
14
+ * at all which is the intended behavior here.
15
+ *
16
+ * Expected behavior:
17
+ * var(--a, 0 0 1px rgb(0, 0, 0)), 0 0 1px rgb(0, 0, 0)
18
+ * ─┬─ ┬ ┬ ┬
19
+ * x x x ╰──────── Split because top-level
20
+ * ╰──────────────┴──┴───────────── Ignored b/c inside >= 1 levels of parens
21
+ *
22
+ * @param {string} input
23
+ */
24
+ function* splitByTopLevelCommas(input) {
25
+ SPECIALS.lastIndex = -1
26
+
27
+ let depth = 0
28
+ let lastIndex = 0
29
+ let found = false
30
+
31
+ // Find all parens & commas
32
+ // And only split on commas if they're top-level
33
+ for (let match of input.matchAll(SPECIALS)) {
34
+ if (match[0] === '(') depth++
35
+ if (match[0] === ')') depth--
36
+ if (match[0] === ',' && depth === 0) {
37
+ found = true
38
+
39
+ yield input.substring(lastIndex, match.index)
40
+ lastIndex = match.index + match[0].length
41
+ }
42
+ }
43
+
44
+ // Provide the last segment of the string if available
45
+ // Otherwise the whole string since no commas were found
46
+ // This mirrors the behavior of string.split()
47
+ if (found) {
48
+ yield input.substring(lastIndex)
49
+ } else {
50
+ yield input
51
+ }
52
+ }
53
+
6
54
  export function parseBoxShadowValue(input) {
7
- let shadows = input.split(COMMA)
55
+ let shadows = Array.from(splitByTopLevelCommas(input))
8
56
  return shadows.map((shadow) => {
9
57
  let value = shadow.trim()
10
58
  let result = { raw: value }