You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
3.6 KiB
179 lines
3.6 KiB
'use strict'; |
|
const valueParser = require('postcss-value-parser'); |
|
const mappings = require('./lib/map'); |
|
|
|
/** |
|
* @param {unknown} item |
|
* @param {number} index |
|
* @return {boolean} |
|
*/ |
|
function evenValues(item, index) { |
|
return index % 2 === 0; |
|
} |
|
|
|
const repeatKeywords = new Set(mappings.values()); |
|
|
|
/** |
|
* @param {valueParser.Node} node |
|
* @return {boolean} |
|
*/ |
|
function isCommaNode(node) { |
|
return node.type === 'div' && node.value === ','; |
|
} |
|
|
|
/** |
|
* @param {valueParser.Node} node |
|
* @return {boolean} |
|
*/ |
|
function isVariableFunctionNode(node) { |
|
if (node.type !== 'function') { |
|
return false; |
|
} |
|
|
|
return ['var', 'env'].includes(node.value.toLowerCase()); |
|
} |
|
|
|
/** |
|
* @param {string} value |
|
* @return {string} |
|
*/ |
|
function transform(value) { |
|
const parsed = valueParser(value); |
|
|
|
if (parsed.nodes.length === 1) { |
|
return value; |
|
} |
|
/** @type {{start: number?, end: number?}[]} */ |
|
const ranges = []; |
|
let rangeIndex = 0; |
|
let shouldContinue = true; |
|
|
|
parsed.nodes.forEach((node, index) => { |
|
// After comma (`,`) follows next background |
|
if (isCommaNode(node)) { |
|
rangeIndex += 1; |
|
shouldContinue = true; |
|
|
|
return; |
|
} |
|
|
|
if (!shouldContinue) { |
|
return; |
|
} |
|
|
|
// After separator (`/`) follows `background-size` values |
|
// Avoid them |
|
if (node.type === 'div' && node.value === '/') { |
|
shouldContinue = false; |
|
|
|
return; |
|
} |
|
|
|
if (!ranges[rangeIndex]) { |
|
ranges[rangeIndex] = { |
|
start: null, |
|
end: null, |
|
}; |
|
} |
|
|
|
// Do not try to be processed `var and `env` function inside background |
|
if (isVariableFunctionNode(node)) { |
|
shouldContinue = false; |
|
ranges[rangeIndex].start = null; |
|
ranges[rangeIndex].end = null; |
|
|
|
return; |
|
} |
|
|
|
const isRepeatKeyword = |
|
node.type === 'word' && repeatKeywords.has(node.value.toLowerCase()); |
|
|
|
if (ranges[rangeIndex].start === null && isRepeatKeyword) { |
|
ranges[rangeIndex].start = index; |
|
ranges[rangeIndex].end = index; |
|
|
|
return; |
|
} |
|
|
|
if (ranges[rangeIndex].start !== null) { |
|
if (node.type === 'space') { |
|
return; |
|
} else if (isRepeatKeyword) { |
|
ranges[rangeIndex].end = index; |
|
|
|
return; |
|
} |
|
|
|
return; |
|
} |
|
}); |
|
|
|
ranges.forEach((range) => { |
|
if (range.start === null) { |
|
return; |
|
} |
|
|
|
const nodes = parsed.nodes.slice( |
|
range.start, |
|
/** @type {number} */ (range.end) + 1 |
|
); |
|
|
|
if (nodes.length !== 3) { |
|
return; |
|
} |
|
const key = nodes |
|
.filter(evenValues) |
|
.map((n) => n.value.toLowerCase()) |
|
.toString(); |
|
|
|
const match = mappings.get(key); |
|
|
|
if (match) { |
|
nodes[0].value = match; |
|
nodes[1].value = nodes[2].value = ''; |
|
} |
|
}); |
|
|
|
return parsed.toString(); |
|
} |
|
|
|
/** |
|
* @type {import('postcss').PluginCreator<void>} |
|
* @return {import('postcss').Plugin} |
|
*/ |
|
function pluginCreator() { |
|
return { |
|
postcssPlugin: 'postcss-normalize-repeat-style', |
|
prepare() { |
|
const cache = new Map(); |
|
return { |
|
OnceExit(css) { |
|
css.walkDecls( |
|
/^(background(-repeat)?|(-\w+-)?mask-repeat)$/i, |
|
(decl) => { |
|
const value = decl.value; |
|
|
|
if (!value) { |
|
return; |
|
} |
|
|
|
if (cache.has(value)) { |
|
decl.value = cache.get(value); |
|
|
|
return; |
|
} |
|
|
|
const result = transform(value); |
|
|
|
decl.value = result; |
|
cache.set(value, result); |
|
} |
|
); |
|
}, |
|
}; |
|
}, |
|
}; |
|
} |
|
|
|
pluginCreator.postcss = true; |
|
module.exports = pluginCreator;
|
|
|