tailwindcss 3.3.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  - Nothing yet!
11
11
 
12
+ ## [3.3.1] - 2023-03-30
13
+
14
+ ### Fixed
15
+
16
+ - Try resolving `config.default` before `config` to ensure the config file is resolved correctly ([#10898](https://github.com/tailwindlabs/tailwindcss/pull/10898))
17
+ - Pull pseudo elements outside of `:is` and `:has` when using `@apply` ([#10903](https://github.com/tailwindlabs/tailwindcss/pull/10903))
18
+ - Update the types for the `safelist` config ([#10901](https://github.com/tailwindlabs/tailwindcss/pull/10901))
19
+ - Fix `@tailwindcss/line-clamp` warning ([#10915](https://github.com/tailwindlabs/tailwindcss/pull/10915), [#10919](https://github.com/tailwindlabs/tailwindcss/pull/10919))
20
+ - Fix `process` is not defined error ([#10919](https://github.com/tailwindlabs/tailwindcss/pull/10919))
21
+
12
22
  ## [3.3.0] - 2023-03-27
13
23
 
14
24
  ### Added
@@ -2211,7 +2221,8 @@ No release notes
2211
2221
 
2212
2222
  - Everything!
2213
2223
 
2214
- [unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.0...HEAD
2224
+ [unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.1...HEAD
2225
+ [3.3.1]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.0...v3.3.1
2215
2226
  [3.3.0]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.7...v3.3.0
2216
2227
  [3.2.7]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.6...v3.2.7
2217
2228
  [3.2.6]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.5...v3.2.6
@@ -11,6 +11,7 @@ const _postcssSelectorParser = /*#__PURE__*/ _interopRequireDefault(require("pos
11
11
  const _generateRules = require("./generateRules");
12
12
  const _escapeClassName = /*#__PURE__*/ _interopRequireDefault(require("../util/escapeClassName"));
13
13
  const _applyImportantSelector = require("../util/applyImportantSelector");
14
+ const _formatVariantSelectorJs = require("../util/formatVariantSelector.js");
14
15
  function _interopRequireDefault(obj) {
15
16
  return obj && obj.__esModule ? obj : {
16
17
  default: obj
@@ -487,6 +488,15 @@ function processApply(root, context, localCache) {
487
488
  rule.walkDecls((d)=>{
488
489
  d.important = meta.important || important1;
489
490
  });
491
+ // Move pseudo elements to the end of the selector (if necessary)
492
+ let selector = (0, _postcssSelectorParser.default)().astSync(rule.selector);
493
+ selector.each((sel)=>{
494
+ let [pseudoElements] = (0, _formatVariantSelectorJs.collectPseudoElements)(sel);
495
+ if (pseudoElements.length > 0) {
496
+ sel.nodes.push(...pseudoElements.sort(_formatVariantSelectorJs.sortSelector));
497
+ }
498
+ });
499
+ rule.selector = selector.toString();
490
500
  });
491
501
  }
492
502
  // It could be that the node we were inserted was removed because the class didn't match
@@ -28,9 +28,13 @@ function lazyJiti() {
28
28
  });
29
29
  }
30
30
  function loadConfig(path) {
31
- try {
32
- return path ? require(path) : {};
33
- } catch {
34
- return lazyJiti()(path);
35
- }
31
+ let config = function() {
32
+ try {
33
+ return path ? require(path) : {};
34
+ } catch {
35
+ return lazyJiti()(path);
36
+ }
37
+ }();
38
+ var _config_default;
39
+ return (_config_default = config.default) !== null && _config_default !== void 0 ? _config_default : config;
36
40
  }
@@ -25,11 +25,16 @@ function _interopRequireDefault(obj) {
25
25
  };
26
26
  }
27
27
  let OXIDE_DEFAULT_ENABLED = _packageJson.default.tailwindcss.engine === "oxide";
28
- const env = {
28
+ const env = typeof process !== "undefined" ? {
29
29
  NODE_ENV: process.env.NODE_ENV,
30
30
  DEBUG: resolveDebug(process.env.DEBUG),
31
31
  ENGINE: _packageJson.default.tailwindcss.engine,
32
32
  OXIDE: resolveBoolean(process.env.OXIDE, OXIDE_DEFAULT_ENABLED)
33
+ } : {
34
+ NODE_ENV: "production",
35
+ DEBUG: false,
36
+ ENGINE: _packageJson.default.tailwindcss.engine,
37
+ OXIDE: OXIDE_DEFAULT_ENABLED
33
38
  };
34
39
  const contextMap = new Map();
35
40
  const configContextMap = new Map();
@@ -6,17 +6,32 @@ Object.defineProperty(exports, "applyImportantSelector", {
6
6
  enumerable: true,
7
7
  get: ()=>applyImportantSelector
8
8
  });
9
- const _splitAtTopLevelOnly = require("./splitAtTopLevelOnly");
10
- function applyImportantSelector(selector, important) {
11
- let matches = /^(.*?)(:before|:after|::[\w-]+)(\)*)$/g.exec(selector);
12
- if (!matches) return `${important} ${wrapWithIs(selector)}`;
13
- let [, before, pseudo, brackets] = matches;
14
- return `${important} ${wrapWithIs(before + brackets)}${pseudo}`;
9
+ const _postcssSelectorParser = /*#__PURE__*/ _interopRequireDefault(require("postcss-selector-parser"));
10
+ const _formatVariantSelectorJs = require("./formatVariantSelector.js");
11
+ function _interopRequireDefault(obj) {
12
+ return obj && obj.__esModule ? obj : {
13
+ default: obj
14
+ };
15
15
  }
16
- function wrapWithIs(selector) {
17
- let parts = (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(selector, " ");
18
- if (parts.length === 1 && parts[0].startsWith(":is(") && parts[0].endsWith(")")) {
19
- return selector;
20
- }
21
- return `:is(${selector})`;
16
+ function applyImportantSelector(selector, important) {
17
+ let sel = (0, _postcssSelectorParser.default)().astSync(selector);
18
+ sel.each((sel)=>{
19
+ // Wrap with :is if it's not already wrapped
20
+ let isWrapped = sel.nodes[0].type === "pseudo" && sel.nodes[0].value === ":is" && sel.nodes.every((node)=>node.type !== "combinator");
21
+ if (!isWrapped) {
22
+ sel.nodes = [
23
+ _postcssSelectorParser.default.pseudo({
24
+ value: ":is",
25
+ nodes: [
26
+ sel.clone()
27
+ ]
28
+ })
29
+ ];
30
+ }
31
+ let [pseudoElements] = (0, _formatVariantSelectorJs.collectPseudoElements)(sel);
32
+ if (pseudoElements.length > 0) {
33
+ sel.nodes.push(...pseudoElements.sort(_formatVariantSelectorJs.sortSelector));
34
+ }
35
+ });
36
+ return `${important} ${sel.toString()}`;
22
37
  }
@@ -12,7 +12,9 @@ _export(exports, {
12
12
  formatVariantSelector: ()=>formatVariantSelector,
13
13
  eliminateIrrelevantSelectors: ()=>eliminateIrrelevantSelectors,
14
14
  finalizeSelector: ()=>finalizeSelector,
15
- handleMergePseudo: ()=>handleMergePseudo
15
+ handleMergePseudo: ()=>handleMergePseudo,
16
+ collectPseudoElements: ()=>collectPseudoElements,
17
+ sortSelector: ()=>sortSelector
16
18
  });
17
19
  const _postcssSelectorParser = /*#__PURE__*/ _interopRequireDefault(require("postcss-selector-parser"));
18
20
  const _unesc = /*#__PURE__*/ _interopRequireDefault(require("postcss-selector-parser/dist/util/unesc"));
@@ -202,9 +204,9 @@ function finalizeSelector(current, formats, { context , candidate , base }) {
202
204
  });
203
205
  // Move pseudo elements to the end of the selector (if necessary)
204
206
  selector.each((sel)=>{
205
- let pseudoElements = collectPseudoElements(sel);
207
+ let [pseudoElements] = collectPseudoElements(sel);
206
208
  if (pseudoElements.length > 0) {
207
- sel.nodes.push(pseudoElements.sort(sortSelector));
209
+ sel.nodes.push(...pseudoElements.sort(sortSelector));
208
210
  }
209
211
  });
210
212
  return selector.toString();
@@ -279,41 +281,38 @@ let pseudoElementExceptions = [
279
281
  "::-webkit-scrollbar-corner",
280
282
  "::-webkit-resizer"
281
283
  ];
282
- /**
283
- * This will make sure to move pseudo's to the correct spot (the end for
284
- * pseudo elements) because otherwise the selector will never work
285
- * anyway.
286
- *
287
- * E.g.:
288
- * - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
289
- * - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
290
- *
291
- * `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
292
- *
293
- * @param {Selector} selector
294
- **/ function collectPseudoElements(selector) {
284
+ function collectPseudoElements(selector, force = false) {
295
285
  /** @type {Node[]} */ let nodes = [];
296
- for (let node of selector.nodes){
297
- if (isPseudoElement(node)) {
286
+ let seenPseudoElement = null;
287
+ for (let node of [
288
+ ...selector.nodes
289
+ ]){
290
+ if (isPseudoElement(node, force)) {
298
291
  nodes.push(node);
299
292
  selector.removeChild(node);
293
+ seenPseudoElement = node.value;
294
+ } else if (seenPseudoElement !== null) {
295
+ if (pseudoElementExceptions.includes(seenPseudoElement) && isPseudoClass(node, force)) {
296
+ nodes.push(node);
297
+ selector.removeChild(node);
298
+ } else {
299
+ seenPseudoElement = null;
300
+ }
300
301
  }
301
302
  if (node === null || node === void 0 ? void 0 : node.nodes) {
302
- nodes.push(...collectPseudoElements(node));
303
+ let hasPseudoElementRestrictions = node.type === "pseudo" && (node.value === ":is" || node.value === ":has");
304
+ let [collected, seenPseudoElementInSelector] = collectPseudoElements(node, force || hasPseudoElementRestrictions);
305
+ if (seenPseudoElementInSelector) {
306
+ seenPseudoElement = seenPseudoElementInSelector;
307
+ }
308
+ nodes.push(...collected);
303
309
  }
304
310
  }
305
- return nodes;
311
+ return [
312
+ nodes,
313
+ seenPseudoElement
314
+ ];
306
315
  }
307
- // This will make sure to move pseudo's to the correct spot (the end for
308
- // pseudo elements) because otherwise the selector will never work
309
- // anyway.
310
- //
311
- // E.g.:
312
- // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
313
- // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
314
- //
315
- // `::before:hover` doesn't work, which means that we can make it work
316
- // for you by flipping the order.
317
316
  function sortSelector(a, z) {
318
317
  // Both nodes are non-pseudo's so we can safely ignore them and keep
319
318
  // them in the same order.
@@ -334,8 +333,11 @@ function sortSelector(a, z) {
334
333
  // ::file-selector-button) to the right.
335
334
  return isPseudoElement(a) - isPseudoElement(z);
336
335
  }
337
- function isPseudoElement(node) {
336
+ function isPseudoElement(node, force = false) {
338
337
  if (node.type !== "pseudo") return false;
339
- if (pseudoElementExceptions.includes(node.value)) return false;
338
+ if (pseudoElementExceptions.includes(node.value) && !force) return false;
340
339
  return node.value.startsWith("::") || pseudoElementsBC.includes(node.value);
341
340
  }
341
+ function isPseudoClass(node, force) {
342
+ return node.type === "pseudo" && !isPseudoElement(node, force);
343
+ }
@@ -276,19 +276,5 @@ function normalizeConfig(config) {
276
276
  break;
277
277
  }
278
278
  }
279
- // Warn if the line-clamp plugin is installed
280
- if (config.plugins.length > 0) {
281
- let plugin;
282
- try {
283
- plugin = require("@tailwindcss/line-clamp");
284
- } catch {}
285
- if (plugin && config.plugins.includes(plugin)) {
286
- _log.default.warn("line-clamp-in-core", [
287
- "As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.",
288
- "Remove it from the `plugins` array in your configuration to eliminate this warning."
289
- ]);
290
- config.plugins = config.plugins.filter((p)=>p !== plugin);
291
- }
292
- }
293
279
  return config;
294
280
  }
@@ -20,5 +20,16 @@ function validateConfig(config) {
20
20
  "https://tailwindcss.com/docs/content-configuration"
21
21
  ]);
22
22
  }
23
+ // Warn if the line-clamp plugin is installed
24
+ try {
25
+ let plugin = require("@tailwindcss/line-clamp");
26
+ if (config.plugins.includes(plugin)) {
27
+ _log.default.warn("line-clamp-in-core", [
28
+ "As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.",
29
+ "Remove it from the `plugins` array in your configuration to eliminate this warning."
30
+ ]);
31
+ config.plugins = config.plugins.filter((p)=>p !== plugin);
32
+ }
33
+ } catch {}
23
34
  return config;
24
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailwindcss",
3
- "version": "3.3.0",
3
+ "version": "3.3.1",
4
4
  "description": "A utility-first CSS framework for rapidly building custom user interfaces.",
5
5
  "license": "MIT",
6
6
  "main": "lib/index.js",
@@ -4,6 +4,7 @@ import parser from 'postcss-selector-parser'
4
4
  import { resolveMatches } from './generateRules'
5
5
  import escapeClassName from '../util/escapeClassName'
6
6
  import { applyImportantSelector } from '../util/applyImportantSelector'
7
+ import { collectPseudoElements, sortSelector } from '../util/formatVariantSelector.js'
7
8
 
8
9
  /** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
9
10
 
@@ -562,6 +563,17 @@ function processApply(root, context, localCache) {
562
563
  rule.walkDecls((d) => {
563
564
  d.important = meta.important || important
564
565
  })
566
+
567
+ // Move pseudo elements to the end of the selector (if necessary)
568
+ let selector = parser().astSync(rule.selector)
569
+ selector.each((sel) => {
570
+ let [pseudoElements] = collectPseudoElements(sel)
571
+ if (pseudoElements.length > 0) {
572
+ sel.nodes.push(...pseudoElements.sort(sortSelector))
573
+ }
574
+ })
575
+
576
+ rule.selector = selector.toString()
565
577
  })
566
578
  }
567
579
 
@@ -19,9 +19,13 @@ function lazyJiti() {
19
19
  }
20
20
 
21
21
  export function loadConfig(path: string): Config {
22
- try {
23
- return path ? require(path) : {}
24
- } catch {
25
- return lazyJiti()(path)
26
- }
22
+ let config = (function () {
23
+ try {
24
+ return path ? require(path) : {}
25
+ } catch {
26
+ return lazyJiti()(path)
27
+ }
28
+ })()
29
+
30
+ return config.default ?? config
27
31
  }
@@ -1,12 +1,21 @@
1
1
  import pkg from '../../package.json'
2
2
  let OXIDE_DEFAULT_ENABLED = pkg.tailwindcss.engine === 'oxide'
3
3
 
4
- export const env = {
5
- NODE_ENV: process.env.NODE_ENV,
6
- DEBUG: resolveDebug(process.env.DEBUG),
7
- ENGINE: pkg.tailwindcss.engine,
8
- OXIDE: resolveBoolean(process.env.OXIDE, OXIDE_DEFAULT_ENABLED),
9
- }
4
+ export const env =
5
+ typeof process !== 'undefined'
6
+ ? {
7
+ NODE_ENV: process.env.NODE_ENV,
8
+ DEBUG: resolveDebug(process.env.DEBUG),
9
+ ENGINE: pkg.tailwindcss.engine,
10
+ OXIDE: resolveBoolean(process.env.OXIDE, OXIDE_DEFAULT_ENABLED),
11
+ }
12
+ : {
13
+ NODE_ENV: 'production',
14
+ DEBUG: false,
15
+ ENGINE: pkg.tailwindcss.engine,
16
+ OXIDE: OXIDE_DEFAULT_ENABLED,
17
+ }
18
+
10
19
  export const contextMap = new Map()
11
20
  export const configContextMap = new Map()
12
21
  export const contextSourcesMap = new Map()
@@ -1,19 +1,30 @@
1
- import { splitAtTopLevelOnly } from './splitAtTopLevelOnly'
1
+ import parser from 'postcss-selector-parser'
2
+ import { collectPseudoElements, sortSelector } from './formatVariantSelector.js'
2
3
 
3
4
  export function applyImportantSelector(selector, important) {
4
- let matches = /^(.*?)(:before|:after|::[\w-]+)(\)*)$/g.exec(selector)
5
- if (!matches) return `${important} ${wrapWithIs(selector)}`
5
+ let sel = parser().astSync(selector)
6
6
 
7
- let [, before, pseudo, brackets] = matches
8
- return `${important} ${wrapWithIs(before + brackets)}${pseudo}`
9
- }
7
+ sel.each((sel) => {
8
+ // Wrap with :is if it's not already wrapped
9
+ let isWrapped =
10
+ sel.nodes[0].type === 'pseudo' &&
11
+ sel.nodes[0].value === ':is' &&
12
+ sel.nodes.every((node) => node.type !== 'combinator')
10
13
 
11
- function wrapWithIs(selector) {
12
- let parts = splitAtTopLevelOnly(selector, ' ')
14
+ if (!isWrapped) {
15
+ sel.nodes = [
16
+ parser.pseudo({
17
+ value: ':is',
18
+ nodes: [sel.clone()],
19
+ }),
20
+ ]
21
+ }
13
22
 
14
- if (parts.length === 1 && parts[0].startsWith(':is(') && parts[0].endsWith(')')) {
15
- return selector
16
- }
23
+ let [pseudoElements] = collectPseudoElements(sel)
24
+ if (pseudoElements.length > 0) {
25
+ sel.nodes.push(...pseudoElements.sort(sortSelector))
26
+ }
27
+ })
17
28
 
18
- return `:is(${selector})`
29
+ return `${important} ${sel.toString()}`
19
30
  }
@@ -246,9 +246,9 @@ export function finalizeSelector(current, formats, { context, candidate, base })
246
246
 
247
247
  // Move pseudo elements to the end of the selector (if necessary)
248
248
  selector.each((sel) => {
249
- let pseudoElements = collectPseudoElements(sel)
249
+ let [pseudoElements] = collectPseudoElements(sel)
250
250
  if (pseudoElements.length > 0) {
251
- sel.nodes.push(pseudoElements.sort(sortSelector))
251
+ sel.nodes.push(...pseudoElements.sort(sortSelector))
252
252
  }
253
253
  })
254
254
 
@@ -351,23 +351,45 @@ let pseudoElementExceptions = [
351
351
  * `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
352
352
  *
353
353
  * @param {Selector} selector
354
+ * @param {boolean} force
354
355
  **/
355
- function collectPseudoElements(selector) {
356
+ export function collectPseudoElements(selector, force = false) {
356
357
  /** @type {Node[]} */
357
358
  let nodes = []
359
+ let seenPseudoElement = null
358
360
 
359
- for (let node of selector.nodes) {
360
- if (isPseudoElement(node)) {
361
+ for (let node of [...selector.nodes]) {
362
+ if (isPseudoElement(node, force)) {
361
363
  nodes.push(node)
362
364
  selector.removeChild(node)
365
+ seenPseudoElement = node.value
366
+ } else if (seenPseudoElement !== null) {
367
+ if (pseudoElementExceptions.includes(seenPseudoElement) && isPseudoClass(node, force)) {
368
+ nodes.push(node)
369
+ selector.removeChild(node)
370
+ } else {
371
+ seenPseudoElement = null
372
+ }
363
373
  }
364
374
 
365
375
  if (node?.nodes) {
366
- nodes.push(...collectPseudoElements(node))
376
+ let hasPseudoElementRestrictions =
377
+ node.type === 'pseudo' && (node.value === ':is' || node.value === ':has')
378
+
379
+ let [collected, seenPseudoElementInSelector] = collectPseudoElements(
380
+ node,
381
+ force || hasPseudoElementRestrictions
382
+ )
383
+
384
+ if (seenPseudoElementInSelector) {
385
+ seenPseudoElement = seenPseudoElementInSelector
386
+ }
387
+
388
+ nodes.push(...collected)
367
389
  }
368
390
  }
369
391
 
370
- return nodes
392
+ return [nodes, seenPseudoElement]
371
393
  }
372
394
 
373
395
  // This will make sure to move pseudo's to the correct spot (the end for
@@ -380,7 +402,7 @@ function collectPseudoElements(selector) {
380
402
  //
381
403
  // `::before:hover` doesn't work, which means that we can make it work
382
404
  // for you by flipping the order.
383
- function sortSelector(a, z) {
405
+ export function sortSelector(a, z) {
384
406
  // Both nodes are non-pseudo's so we can safely ignore them and keep
385
407
  // them in the same order.
386
408
  if (a.type !== 'pseudo' && z.type !== 'pseudo') {
@@ -404,9 +426,13 @@ function sortSelector(a, z) {
404
426
  return isPseudoElement(a) - isPseudoElement(z)
405
427
  }
406
428
 
407
- function isPseudoElement(node) {
429
+ function isPseudoElement(node, force = false) {
408
430
  if (node.type !== 'pseudo') return false
409
- if (pseudoElementExceptions.includes(node.value)) return false
431
+ if (pseudoElementExceptions.includes(node.value) && !force) return false
410
432
 
411
433
  return node.value.startsWith('::') || pseudoElementsBC.includes(node.value)
412
434
  }
435
+
436
+ function isPseudoClass(node, force) {
437
+ return node.type === 'pseudo' && !isPseudoElement(node, force)
438
+ }
@@ -297,22 +297,5 @@ export function normalizeConfig(config) {
297
297
  }
298
298
  }
299
299
 
300
- // Warn if the line-clamp plugin is installed
301
- if (config.plugins.length > 0) {
302
- let plugin
303
- try {
304
- plugin = require('@tailwindcss/line-clamp')
305
- } catch {}
306
-
307
- if (plugin && config.plugins.includes(plugin)) {
308
- log.warn('line-clamp-in-core', [
309
- 'As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.',
310
- 'Remove it from the `plugins` array in your configuration to eliminate this warning.',
311
- ])
312
-
313
- config.plugins = config.plugins.filter((p) => p !== plugin)
314
- }
315
- }
316
-
317
300
  return config
318
301
  }
@@ -9,5 +9,18 @@ export function validateConfig(config) {
9
9
  ])
10
10
  }
11
11
 
12
+ // Warn if the line-clamp plugin is installed
13
+ try {
14
+ let plugin = require('@tailwindcss/line-clamp')
15
+ if (config.plugins.includes(plugin)) {
16
+ log.warn('line-clamp-in-core', [
17
+ 'As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.',
18
+ 'Remove it from the `plugins` array in your configuration to eliminate this warning.',
19
+ ])
20
+
21
+ config.plugins = config.plugins.filter((p) => p !== plugin)
22
+ }
23
+ } catch {}
24
+
12
25
  return config
13
26
  }
package/types/config.d.ts CHANGED
@@ -46,12 +46,7 @@ type PrefixConfig = string
46
46
  type SeparatorConfig = string
47
47
 
48
48
  // Safelist related config
49
- type SafelistConfig =
50
- | string[]
51
- | {
52
- pattern: RegExp
53
- variants?: string[]
54
- }[]
49
+ type SafelistConfig = (string | { pattern: RegExp; variants?: string[] })[]
55
50
 
56
51
  // Blocklist related config
57
52
  type BlocklistConfig = string[]