tailwindcss 3.2.1 → 3.2.3
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 +25 -1
- package/lib/cli/build/plugin.js +3 -3
- package/lib/cli/build/watching.js +73 -11
- package/lib/css/preflight.css +2 -0
- package/lib/lib/content.js +14 -4
- package/lib/lib/defaultExtractor.js +1 -1
- package/lib/lib/expandApplyAtRules.js +34 -15
- package/lib/lib/expandTailwindAtRules.js +1 -1
- package/lib/lib/generateRules.js +2 -2
- package/lib/lib/setupContextUtils.js +18 -7
- package/lib/util/formatVariantSelector.js +3 -3
- package/lib/util/pluginUtils.js +13 -0
- package/lib/util/resolveConfig.js +3 -6
- package/package.json +7 -7
- package/peers/index.js +634 -579
- package/src/cli/build/plugin.js +2 -2
- package/src/cli/build/watching.js +101 -11
- package/src/css/preflight.css +2 -0
- package/src/lib/content.js +16 -8
- package/src/lib/defaultExtractor.js +1 -1
- package/src/lib/expandApplyAtRules.js +35 -15
- package/src/lib/generateRules.js +10 -7
- package/src/lib/setupContextUtils.js +19 -8
- package/src/util/formatVariantSelector.js +2 -2
- package/src/util/pluginUtils.js +17 -0
- package/src/util/resolveConfig.js +3 -7
package/src/cli/build/plugin.js
CHANGED
|
@@ -195,8 +195,8 @@ let state = {
|
|
|
195
195
|
return file !== null && typeof file === 'object'
|
|
196
196
|
})
|
|
197
197
|
|
|
198
|
-
for (let { raw:
|
|
199
|
-
content.push({ content, extension })
|
|
198
|
+
for (let { raw: htmlContent, extension = 'html' } of rawContent) {
|
|
199
|
+
content.push({ content: htmlContent, extension })
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
return content
|
|
@@ -8,6 +8,14 @@ import path from 'path'
|
|
|
8
8
|
|
|
9
9
|
import { readFileWithRetries } from './utils.js'
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* The core idea of this watcher is:
|
|
13
|
+
* 1. Whenever a file is added, changed, or renamed we queue a rebuild
|
|
14
|
+
* 2. Perform as few rebuilds as possible by batching them together
|
|
15
|
+
* 3. Coalesce events that happen in quick succession to avoid unnecessary rebuilds
|
|
16
|
+
* 4. Ensure another rebuild happens _if_ changed while a rebuild is in progress
|
|
17
|
+
*/
|
|
18
|
+
|
|
11
19
|
/**
|
|
12
20
|
*
|
|
13
21
|
* @param {*} args
|
|
@@ -42,27 +50,93 @@ export function createWatcher(args, { state, rebuild }) {
|
|
|
42
50
|
: false,
|
|
43
51
|
})
|
|
44
52
|
|
|
53
|
+
// A queue of rebuilds, file reads, etc… to run
|
|
45
54
|
let chain = Promise.resolve()
|
|
46
|
-
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A list of files that have been changed since the last rebuild
|
|
58
|
+
*
|
|
59
|
+
* @type {{file: string, content: () => Promise<string>, extension: string}[]}
|
|
60
|
+
*/
|
|
47
61
|
let changedContent = []
|
|
48
62
|
|
|
63
|
+
/**
|
|
64
|
+
* A list of files for which a rebuild has already been queued.
|
|
65
|
+
* This is used to prevent duplicate rebuilds when multiple events are fired for the same file.
|
|
66
|
+
* The rebuilt file is cleared from this list when it's associated rebuild has _started_
|
|
67
|
+
* This is because if the file is changed during a rebuild it won't trigger a new rebuild which it should
|
|
68
|
+
**/
|
|
69
|
+
let pendingRebuilds = new Set()
|
|
70
|
+
|
|
71
|
+
let _timer
|
|
72
|
+
let _reject
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Rebuilds the changed files and resolves when the rebuild is
|
|
76
|
+
* complete regardless of whether it was successful or not
|
|
77
|
+
*/
|
|
78
|
+
async function rebuildAndContinue() {
|
|
79
|
+
let changes = changedContent.splice(0)
|
|
80
|
+
|
|
81
|
+
// There are no changes to rebuild so we can just do nothing
|
|
82
|
+
if (changes.length === 0) {
|
|
83
|
+
return Promise.resolve()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Clear all pending rebuilds for the about-to-be-built files
|
|
87
|
+
changes.forEach((change) => pendingRebuilds.delete(change.file))
|
|
88
|
+
|
|
89
|
+
// Resolve the promise even when the rebuild fails
|
|
90
|
+
return rebuild(changes).then(
|
|
91
|
+
() => {},
|
|
92
|
+
() => {}
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
49
96
|
/**
|
|
50
97
|
*
|
|
51
98
|
* @param {*} file
|
|
52
99
|
* @param {(() => Promise<string>) | null} content
|
|
100
|
+
* @returns {Promise<void>}
|
|
53
101
|
*/
|
|
54
102
|
function recordChangedFile(file, content = null) {
|
|
55
103
|
file = path.resolve(file)
|
|
56
104
|
|
|
57
|
-
|
|
105
|
+
// Applications like Vim/Neovim fire both rename and change events in succession for atomic writes
|
|
106
|
+
// In that case rebuild has already been queued by rename, so can be skipped in change
|
|
107
|
+
if (pendingRebuilds.has(file)) {
|
|
108
|
+
return Promise.resolve()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Mark that a rebuild of this file is going to happen
|
|
112
|
+
// It MUST happen synchronously before the rebuild is queued for this to be effective
|
|
113
|
+
pendingRebuilds.add(file)
|
|
58
114
|
|
|
59
115
|
changedContent.push({
|
|
60
116
|
file,
|
|
61
|
-
content,
|
|
117
|
+
content: content ?? (() => fs.promises.readFile(file, 'utf8')),
|
|
62
118
|
extension: path.extname(file).slice(1),
|
|
63
119
|
})
|
|
64
120
|
|
|
65
|
-
|
|
121
|
+
if (_timer) {
|
|
122
|
+
clearTimeout(_timer)
|
|
123
|
+
_reject()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// If a rebuild is already in progress we don't want to start another one until the 10ms timer has expired
|
|
127
|
+
chain = chain.then(
|
|
128
|
+
() =>
|
|
129
|
+
new Promise((resolve, reject) => {
|
|
130
|
+
_timer = setTimeout(resolve, 10)
|
|
131
|
+
_reject = reject
|
|
132
|
+
})
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
// Resolves once this file has been rebuilt (or the rebuild for this file has failed)
|
|
136
|
+
// This queues as many rebuilds as there are changed files
|
|
137
|
+
// But those rebuilds happen after some delay
|
|
138
|
+
// And will immediately resolve if there are no changes
|
|
139
|
+
chain = chain.then(rebuildAndContinue, rebuildAndContinue)
|
|
66
140
|
|
|
67
141
|
return chain
|
|
68
142
|
}
|
|
@@ -107,18 +181,34 @@ export function createWatcher(args, { state, rebuild }) {
|
|
|
107
181
|
return
|
|
108
182
|
}
|
|
109
183
|
|
|
184
|
+
// We'll go ahead and add the file to the pending rebuilds list here
|
|
185
|
+
// It'll be removed when the rebuild starts unless the read fails
|
|
186
|
+
// which will be taken care of as well
|
|
110
187
|
pendingRebuilds.add(filePath)
|
|
111
188
|
|
|
112
|
-
|
|
113
|
-
let content
|
|
114
|
-
|
|
189
|
+
async function enqueue() {
|
|
115
190
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
191
|
+
// We need to read the file as early as possible outside of the chain
|
|
192
|
+
// because it may be gone by the time we get to it. doing the read
|
|
193
|
+
// immediately increases the chance that the file is still there
|
|
194
|
+
let content = await readFileWithRetries(path.resolve(filePath))
|
|
195
|
+
|
|
196
|
+
if (content === undefined) {
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// This will push the rebuild onto the chain
|
|
201
|
+
// @ts-ignore: TypeScript isn't picking up that content is a string here
|
|
202
|
+
await recordChangedFile(filePath, () => content)
|
|
203
|
+
} catch {
|
|
204
|
+
// If reading the file fails, it's was probably a deleted temporary file
|
|
205
|
+
// So we can ignore it and no rebuild is needed
|
|
119
206
|
}
|
|
207
|
+
}
|
|
120
208
|
|
|
121
|
-
|
|
209
|
+
enqueue().then(() => {
|
|
210
|
+
// If the file read fails we still need to make sure the file isn't stuck in the pending rebuilds list
|
|
211
|
+
pendingRebuilds.delete(filePath)
|
|
122
212
|
})
|
|
123
213
|
})
|
|
124
214
|
|
package/src/css/preflight.css
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
2. Prevent adjustments of font size after orientation changes in iOS.
|
|
23
23
|
3. Use a more readable tab size.
|
|
24
24
|
4. Use the user's configured `sans` font-family by default.
|
|
25
|
+
5. Use the user's configured `sans` font-feature-settings by default.
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
28
|
html {
|
|
@@ -30,6 +31,7 @@ html {
|
|
|
30
31
|
-moz-tab-size: 4; /* 3 */
|
|
31
32
|
tab-size: 4; /* 3 */
|
|
32
33
|
font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */
|
|
34
|
+
font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings', normal); /* 5 */
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
/*
|
package/src/lib/content.js
CHANGED
|
@@ -94,17 +94,18 @@ function parseFilePath(filePath, ignore) {
|
|
|
94
94
|
* @returns {ContentPath}
|
|
95
95
|
*/
|
|
96
96
|
function resolveGlobPattern(contentPath) {
|
|
97
|
-
contentPath.pattern = contentPath.glob
|
|
98
|
-
? `${contentPath.base}/${contentPath.glob}`
|
|
99
|
-
: contentPath.base
|
|
100
|
-
|
|
101
|
-
contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern
|
|
102
|
-
|
|
103
97
|
// This is required for Windows support to properly pick up Glob paths.
|
|
104
98
|
// Afaik, this technically shouldn't be needed but there's probably
|
|
105
99
|
// some internal, direct path matching with a normalized path in
|
|
106
100
|
// a package which can't handle mixed directory separators
|
|
107
|
-
|
|
101
|
+
let base = normalizePath(contentPath.base)
|
|
102
|
+
|
|
103
|
+
// If the user's file path contains any special characters (like parens) for instance fast-glob
|
|
104
|
+
// is like "OOOH SHINY" and treats them as such. So we have to escape the base path to fix this
|
|
105
|
+
base = fastGlob.escapePath(base)
|
|
106
|
+
|
|
107
|
+
contentPath.pattern = contentPath.glob ? `${base}/${contentPath.glob}` : base
|
|
108
|
+
contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern
|
|
108
109
|
|
|
109
110
|
return contentPath
|
|
110
111
|
}
|
|
@@ -195,7 +196,14 @@ function resolveChangedFiles(candidateFiles, fileModifiedMap) {
|
|
|
195
196
|
let prevModified = fileModifiedMap.has(file) ? fileModifiedMap.get(file) : -Infinity
|
|
196
197
|
let modified = fs.statSync(file).mtimeMs
|
|
197
198
|
|
|
198
|
-
|
|
199
|
+
// This check is intentionally >= because we track the last modified time of context dependencies
|
|
200
|
+
// earier in the process and we want to make sure we don't miss any changes that happen
|
|
201
|
+
// when a context dependency is also a content dependency
|
|
202
|
+
// Ideally, we'd do all this tracking at one time but that is a larger refactor
|
|
203
|
+
// than we want to commit to right now, so this is a decent compromise.
|
|
204
|
+
// This should be sufficient because file modification times will be off by at least
|
|
205
|
+
// 1ms (the precision of fstat in Node) in most cases if they exist and were changed.
|
|
206
|
+
if (modified >= prevModified) {
|
|
199
207
|
changedFiles.add(file)
|
|
200
208
|
fileModifiedMap.set(file, modified)
|
|
201
209
|
}
|
|
@@ -346,22 +346,42 @@ function processApply(root, context, localCache) {
|
|
|
346
346
|
})
|
|
347
347
|
})
|
|
348
348
|
|
|
349
|
-
// Sort tag names before class names
|
|
349
|
+
// Sort tag names before class names (but only sort each group (separated by a combinator)
|
|
350
|
+
// separately and not in total)
|
|
350
351
|
// This happens when replacing `.bar` in `.foo.bar` with a tag like `section`
|
|
351
|
-
for (
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
} else
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
return 1
|
|
352
|
+
for (let sel of replaced) {
|
|
353
|
+
let groups = [[]]
|
|
354
|
+
for (let node of sel.nodes) {
|
|
355
|
+
if (node.type === 'combinator') {
|
|
356
|
+
groups.push(node)
|
|
357
|
+
groups.push([])
|
|
358
|
+
} else {
|
|
359
|
+
let last = groups[groups.length - 1]
|
|
360
|
+
last.push(node)
|
|
361
361
|
}
|
|
362
|
+
}
|
|
362
363
|
|
|
363
|
-
|
|
364
|
-
|
|
364
|
+
sel.nodes = []
|
|
365
|
+
|
|
366
|
+
for (let group of groups) {
|
|
367
|
+
if (Array.isArray(group)) {
|
|
368
|
+
group.sort((a, b) => {
|
|
369
|
+
if (a.type === 'tag' && b.type === 'class') {
|
|
370
|
+
return -1
|
|
371
|
+
} else if (a.type === 'class' && b.type === 'tag') {
|
|
372
|
+
return 1
|
|
373
|
+
} else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) {
|
|
374
|
+
return -1
|
|
375
|
+
} else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') {
|
|
376
|
+
return 1
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return 0
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
sel.nodes = sel.nodes.concat(group)
|
|
384
|
+
}
|
|
365
385
|
}
|
|
366
386
|
|
|
367
387
|
sel.replaceWith(...replaced)
|
|
@@ -382,7 +402,7 @@ function processApply(root, context, localCache) {
|
|
|
382
402
|
|
|
383
403
|
if (apply.parent.type === 'atrule') {
|
|
384
404
|
if (apply.parent.name === 'screen') {
|
|
385
|
-
|
|
405
|
+
let screenType = apply.parent.params
|
|
386
406
|
|
|
387
407
|
throw apply.error(
|
|
388
408
|
`@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates
|
|
@@ -414,7 +434,7 @@ function processApply(root, context, localCache) {
|
|
|
414
434
|
}
|
|
415
435
|
}
|
|
416
436
|
|
|
417
|
-
for (
|
|
437
|
+
for (let [parent, [candidates, atApplySource]] of perParentApplies) {
|
|
418
438
|
let siblings = []
|
|
419
439
|
|
|
420
440
|
for (let [applyCandidate, important, rules] of candidates) {
|
package/src/lib/generateRules.js
CHANGED
|
@@ -3,7 +3,7 @@ import selectorParser from 'postcss-selector-parser'
|
|
|
3
3
|
import parseObjectStyles from '../util/parseObjectStyles'
|
|
4
4
|
import isPlainObject from '../util/isPlainObject'
|
|
5
5
|
import prefixSelector from '../util/prefixSelector'
|
|
6
|
-
import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils'
|
|
6
|
+
import { updateAllClasses, filterSelectorsForClass, getMatchingTypes } from '../util/pluginUtils'
|
|
7
7
|
import log from '../util/log'
|
|
8
8
|
import * as sharedState from './sharedState'
|
|
9
9
|
import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
|
|
@@ -116,12 +116,15 @@ function applyImportant(matches, classCandidate) {
|
|
|
116
116
|
for (let [meta, rule] of matches) {
|
|
117
117
|
let container = postcss.root({ nodes: [rule.clone()] })
|
|
118
118
|
container.walkRules((r) => {
|
|
119
|
-
r.selector = updateAllClasses(
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
r.selector = updateAllClasses(
|
|
120
|
+
filterSelectorsForClass(r.selector, classCandidate),
|
|
121
|
+
(className) => {
|
|
122
|
+
if (className === classCandidate) {
|
|
123
|
+
return `!${className}`
|
|
124
|
+
}
|
|
125
|
+
return className
|
|
122
126
|
}
|
|
123
|
-
|
|
124
|
-
})
|
|
127
|
+
)
|
|
125
128
|
r.walkDecls((d) => (d.important = true))
|
|
126
129
|
})
|
|
127
130
|
result.push([{ ...meta, important: true }, container.nodes[0]])
|
|
@@ -210,7 +213,7 @@ function applyVariant(variant, matches, context) {
|
|
|
210
213
|
let container = postcss.root({ nodes: [rule.clone()] })
|
|
211
214
|
|
|
212
215
|
for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples) {
|
|
213
|
-
let clone = containerFromArray ?? container.clone()
|
|
216
|
+
let clone = (containerFromArray ?? container).clone()
|
|
214
217
|
let collectedFormats = []
|
|
215
218
|
|
|
216
219
|
function prepareBackup() {
|
|
@@ -528,7 +528,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|
|
528
528
|
variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
|
|
529
529
|
if (typeof variantFunction !== 'string') {
|
|
530
530
|
// Safelist public API functions
|
|
531
|
-
return (api) => {
|
|
531
|
+
return (api = {}) => {
|
|
532
532
|
let { args, modifySelectors, container, separator, wrap, format } = api
|
|
533
533
|
let result = variantFunction(
|
|
534
534
|
Object.assign(
|
|
@@ -581,11 +581,13 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|
|
581
581
|
|
|
582
582
|
api.addVariant(
|
|
583
583
|
isSpecial ? `${variant}${key}` : `${variant}-${key}`,
|
|
584
|
-
({ args, container }) =>
|
|
585
|
-
variantFn(
|
|
584
|
+
({ args, container }) => {
|
|
585
|
+
return variantFn(
|
|
586
586
|
value,
|
|
587
|
-
modifiersEnabled ? { modifier: args
|
|
588
|
-
)
|
|
587
|
+
modifiersEnabled ? { modifier: args?.modifier, container } : { container }
|
|
588
|
+
)
|
|
589
|
+
},
|
|
590
|
+
|
|
589
591
|
{
|
|
590
592
|
...options,
|
|
591
593
|
value,
|
|
@@ -601,13 +603,17 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|
|
601
603
|
api.addVariant(
|
|
602
604
|
variant,
|
|
603
605
|
({ args, container }) => {
|
|
604
|
-
if (args
|
|
606
|
+
if (args?.value === sharedState.NONE && !hasDefault) {
|
|
605
607
|
return null
|
|
606
608
|
}
|
|
607
609
|
|
|
608
610
|
return variantFn(
|
|
609
|
-
args
|
|
610
|
-
|
|
611
|
+
args?.value === sharedState.NONE
|
|
612
|
+
? options.values.DEFAULT
|
|
613
|
+
: // Falling back to args if it is a string, otherwise '' for older intellisense
|
|
614
|
+
// (JetBrains) plugins.
|
|
615
|
+
args?.value ?? (typeof args === 'string' ? args : ''),
|
|
616
|
+
modifiersEnabled ? { modifier: args?.modifier, container } : { container }
|
|
611
617
|
)
|
|
612
618
|
},
|
|
613
619
|
{
|
|
@@ -819,6 +825,7 @@ function registerPlugins(plugins, context) {
|
|
|
819
825
|
if (checks.length > 0) {
|
|
820
826
|
let patternMatchingCount = new Map()
|
|
821
827
|
let prefixLength = context.tailwindConfig.prefix.length
|
|
828
|
+
let checkImportantUtils = checks.some((check) => check.pattern.source.includes('!'))
|
|
822
829
|
|
|
823
830
|
for (let util of classList) {
|
|
824
831
|
let utils = Array.isArray(util)
|
|
@@ -855,6 +862,10 @@ function registerPlugins(plugins, context) {
|
|
|
855
862
|
]
|
|
856
863
|
}
|
|
857
864
|
|
|
865
|
+
if (checkImportantUtils && options?.respectImportant) {
|
|
866
|
+
classes = [...classes, ...classes.map((cls) => '!' + cls)]
|
|
867
|
+
}
|
|
868
|
+
|
|
858
869
|
return classes
|
|
859
870
|
})()
|
|
860
871
|
: [util]
|
|
@@ -69,9 +69,9 @@ function resortSelector(sel) {
|
|
|
69
69
|
return -1
|
|
70
70
|
} else if (a.type === 'class' && b.type === 'tag') {
|
|
71
71
|
return 1
|
|
72
|
-
} else if (a.type === 'class' && b.type === 'pseudo' && b.value
|
|
72
|
+
} else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) {
|
|
73
73
|
return -1
|
|
74
|
-
} else if (a.type === 'pseudo' && a.value
|
|
74
|
+
} else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') {
|
|
75
75
|
return 1
|
|
76
76
|
}
|
|
77
77
|
|
package/src/util/pluginUtils.js
CHANGED
|
@@ -37,6 +37,23 @@ export function updateAllClasses(selectors, updateClass) {
|
|
|
37
37
|
return result
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export function filterSelectorsForClass(selectors, classCandidate) {
|
|
41
|
+
let parser = selectorParser((selectors) => {
|
|
42
|
+
selectors.each((sel) => {
|
|
43
|
+
const containsClass = sel.nodes.some(
|
|
44
|
+
(node) => node.type === 'class' && node.value === classCandidate
|
|
45
|
+
)
|
|
46
|
+
if (!containsClass) {
|
|
47
|
+
sel.remove()
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
let result = parser.processSync(selectors)
|
|
53
|
+
|
|
54
|
+
return result
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
function resolveArbitraryValue(modifier, validate) {
|
|
41
58
|
if (!isArbitraryValue(modifier)) {
|
|
42
59
|
return undefined
|
|
@@ -16,10 +16,6 @@ function isFunction(input) {
|
|
|
16
16
|
return typeof input === 'function'
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function isObject(input) {
|
|
20
|
-
return typeof input === 'object' && input !== null
|
|
21
|
-
}
|
|
22
|
-
|
|
23
19
|
function mergeWith(target, ...sources) {
|
|
24
20
|
let customizer = sources.pop()
|
|
25
21
|
|
|
@@ -28,7 +24,7 @@ function mergeWith(target, ...sources) {
|
|
|
28
24
|
let merged = customizer(target[k], source[k])
|
|
29
25
|
|
|
30
26
|
if (merged === undefined) {
|
|
31
|
-
if (
|
|
27
|
+
if (isPlainObject(target[k]) && isPlainObject(source[k])) {
|
|
32
28
|
target[k] = mergeWith({}, target[k], source[k], customizer)
|
|
33
29
|
} else {
|
|
34
30
|
target[k] = source[k]
|
|
@@ -103,12 +99,12 @@ function mergeThemes(themes) {
|
|
|
103
99
|
|
|
104
100
|
function mergeExtensionCustomizer(merged, value) {
|
|
105
101
|
// When we have an array of objects, we do want to merge it
|
|
106
|
-
if (Array.isArray(merged) &&
|
|
102
|
+
if (Array.isArray(merged) && isPlainObject(merged[0])) {
|
|
107
103
|
return merged.concat(value)
|
|
108
104
|
}
|
|
109
105
|
|
|
110
106
|
// When the incoming value is an array, and the existing config is an object, prepend the existing object
|
|
111
|
-
if (Array.isArray(value) &&
|
|
107
|
+
if (Array.isArray(value) && isPlainObject(value[0]) && isPlainObject(merged)) {
|
|
112
108
|
return [merged, ...value]
|
|
113
109
|
}
|
|
114
110
|
|