| import path from '#path'; | |
| import valueParser from 'postcss-value-parser'; | |
| import normalize from './normalize.js'; | |
| /** | |
| * A `quote` assignment target for value-parser nodes that aren't otherwise | |
| * typed: retags the node as a (possibly unquoted) string node. | |
| * @typedef {{ quote?: string }} QuotedNode | |
| */ | |
| const multiline = /\\[\r\n]/; | |
| // eslint-disable-next-line no-useless-escape | |
| const escapeChars = /([\s\(\)"'])/g; | |
| // Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 | |
| // Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 | |
| const ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/; | |
| // Windows paths like `c:\` | |
| const WINDOWS_PATH_REGEX = /^[a-zA-Z]:\\/; | |
| const dataUrlRegex = /^data:(.*)?,/i; | |
| const extensionRegex = /^.+-extension:\//i; | |
| /** | |
| * Originally in sindresorhus/is-absolute-url | |
| * | |
| * @param {string} url | |
| */ | |
| function isAbsolute(url) { | |
| if (WINDOWS_PATH_REGEX.test(url)) { | |
| return false; | |
| } | |
| return ABSOLUTE_URL_REGEX.test(url); | |
| } | |
| /** | |
| * @param {string} url | |
| * @return {string} | |
| */ | |
| function convert(url) { | |
| if (isAbsolute(url) || url.startsWith('//')) { | |
| let normalizedURL; | |
| try { | |
| normalizedURL = normalize(url); | |
| } catch { | |
| normalizedURL = url; | |
| } | |
| return normalizedURL; | |
| } | |
| // `path.normalize` always returns backslashes on Windows, need replace in `/` | |
| return path.normalize(url).replace(new RegExp('\\' + path.sep, 'g'), '/'); | |
| } | |
| /** | |
| * @param {import('postcss').AtRule} rule | |
| * @return {void} | |
| */ | |
| function transformNamespace(rule) { | |
| rule.params = valueParser(rule.params) | |
| .walk((node) => { | |
| if ( | |
| node.type === 'function' && | |
| node.value.toLowerCase() === 'url' && | |
| node.nodes.length | |
| ) { | |
| /** @type {valueParser.Node} */ (node).type = 'string'; | |
| /** @type {QuotedNode} */ (node).quote = | |
| node.nodes[0].type === 'string' ? node.nodes[0].quote : '"'; | |
| node.value = node.nodes[0].value; | |
| } | |
| if (node.type === 'string') { | |
| node.value = node.value.trim(); | |
| } | |
| return false; | |
| }) | |
| .toString(); | |
| } | |
| /** | |
| * @param {import('postcss').Declaration} decl | |
| * @return {void} | |
| */ | |
| function transformDecl(decl) { | |
| decl.value = valueParser(decl.value) | |
| .walk((node) => { | |
| if (node.type !== 'function' || node.value.toLowerCase() !== 'url') { | |
| return false; | |
| } | |
| node.before = node.after = ''; | |
| if (!node.nodes.length) { | |
| return false; | |
| } | |
| const url = node.nodes[0]; | |
| let escaped; | |
| url.value = url.value.trim().replace(multiline, ''); | |
| // Skip empty URLs | |
| // Empty URL function equals request to current stylesheet where it is declared | |
| if (url.value.length === 0) { | |
| /** @type {QuotedNode} */ (url).quote = ''; | |
| return false; | |
| } | |
| if (dataUrlRegex.test(url.value)) { | |
| return false; | |
| } | |
| if (!extensionRegex.test(url.value)) { | |
| url.value = convert(url.value); | |
| } | |
| if (escapeChars.test(url.value) && url.type === 'string') { | |
| escaped = url.value.replace(escapeChars, '\\$1'); | |
| if (escaped.length < url.value.length + 2) { | |
| url.value = escaped; | |
| /** @type {valueParser.Node} */ (url).type = 'word'; | |
| } | |
| } else { | |
| url.type = 'word'; | |
| } | |
| return false; | |
| }) | |
| .toString(); | |
| } | |
| /** | |
| * @return {import('postcss').Plugin} | |
| */ | |
| function pluginCreator() { | |
| return { | |
| postcssPlugin: 'postcss-normalize-url', | |
| /** | |
| * @param {import('postcss').Root} css | |
| */ | |
| OnceExit(css) { | |
| css.walk((node) => { | |
| if (node.type === 'decl') { | |
| return transformDecl(node); | |
| } else if ( | |
| node.type === 'atrule' && | |
| node.name.toLowerCase() === 'namespace' | |
| ) { | |
| return transformNamespace(node); | |
| } | |
| }); | |
| }, | |
| }; | |
| } | |
| /** @type {true} */ | |
| pluginCreator.postcss = true; | |
| const moduleExports = pluginCreator; | |
| export { moduleExports as default, moduleExports as 'module.exports' }; |