From 0ce8a0ffcd64412108248799920638863facdd0e Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sat, 2 Sep 2023 12:52:15 +0200 Subject: [PATCH] postcss-rebase-url : custom props handling --- plugins/postcss-rebase-url/CHANGELOG.md | 4 +++ plugins/postcss-rebase-url/dist/index.cjs | 2 +- plugins/postcss-rebase-url/dist/index.mjs | 2 +- plugins/postcss-rebase-url/src/index.ts | 28 ++++++++++++++++++- plugins/postcss-rebase-url/test/basic.css | 1 + .../postcss-rebase-url/test/basic.expect.css | 18 ++++++++++++ .../postcss-rebase-url/test/imports/props.css | 17 +++++++++++ 7 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 plugins/postcss-rebase-url/test/imports/props.css diff --git a/plugins/postcss-rebase-url/CHANGELOG.md b/plugins/postcss-rebase-url/CHANGELOG.md index 6dc3b318c..34784498e 100644 --- a/plugins/postcss-rebase-url/CHANGELOG.md +++ b/plugins/postcss-rebase-url/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Rebase URL +### Unreleased (patch) + +- Fix handling of typed vs untyped custom properties that contain url values. + ### 1.0.0 _August 28, 2023_ diff --git a/plugins/postcss-rebase-url/dist/index.cjs b/plugins/postcss-rebase-url/dist/index.cjs index d251e479e..0a83fdc55 100644 --- a/plugins/postcss-rebase-url/dist/index.cjs +++ b/plugins/postcss-rebase-url/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("@csstools/css-tokenizer"),r=require("@csstools/css-parser-algorithms"),t=require("path");const s=/^([-_a-z0-9]+:)?\/\//i;function rebase(e,r,i){if(e.startsWith("data:"))return!1;if(s.test(e))return!1;if(e.startsWith("/"))return e;if(e.startsWith("#"))return e;try{const r=new URL(e);if(r.port||r.protocol)return!1}catch{}const o=t.posix.resolve(t.posix.join(r,e));return t.posix.relative(i,o)}function serializeString(e){let r="";for(const t of e){const e=t.codePointAt(0);if(void 0!==e)switch(e){case 0:r+=String.fromCodePoint(65533);break;case 127:r+=`\\${e.toString(16)}`;break;case 34:case 39:case 92:r+=`\\${t}`;break;default:if(1<=e&&e<=31){r+=`\\${e.toString(16)} `;break}r+=t}else r+=String.fromCodePoint(65533)}return r}function normalizedDir(e){return t.parse(t.resolve(e.trim())).dir.split(t.sep).join(t.posix.sep)}const i=/url\(/i,o=/url/i,creator=()=>({postcssPlugin:"postcss-rebase-url",prepare(){const t=new WeakSet;return{Declaration(s,{result:n}){var a;if(t.has(s))return;const{from:u}=n.opts;if(!u)return;if(null==(a=s.source)||!a.input.from)return;if(!i.test(s.value))return;const l=normalizedDir(u),c=s.source.input.from.trim();if(!c)return;const f=normalizedDir(c),p=r.parseCommaSeparatedListOfComponentValues(e.tokenize({css:s.value})),v=r.replaceComponentValues(p,(t=>{if(r.isTokenNode(t)&&t.value[0]===e.TokenType.URL){const e=rebase(t.value[4].value.trim(),f,l);if(e)return t.value[4].value=e,t.value[1]=`url(${serializeString(e)})`,t}if(r.isFunctionNode(t)&&o.test(t.getName()))for(const s of t.value)if(!r.isWhitespaceNode(s)&&!r.isCommentNode(s)&&r.isTokenNode(s)&&s.value[0]===e.TokenType.String){const e=rebase(s.value[4].value.trim(),f,l);if(e)return s.value[4].value=e,s.value[1]=`"${serializeString(e)}"`,t;break}})),m=r.stringify(v);m!==s.value&&(s.value=m,t.add(s))}}}});creator.postcss=!0,module.exports=creator; +"use strict";var e=require("@csstools/css-tokenizer"),r=require("@csstools/css-parser-algorithms"),t=require("path");const s=/^([-_a-z0-9]+:)?\/\//i;function rebase(e,r,i){if(e.startsWith("data:"))return!1;if(s.test(e))return!1;if(e.startsWith("/"))return e;if(e.startsWith("#"))return e;try{const r=new URL(e);if(r.port||r.protocol)return!1}catch{}const o=t.posix.resolve(t.posix.join(r,e));return t.posix.relative(i,o)}function serializeString(e){let r="";for(const t of e){const e=t.codePointAt(0);if(void 0!==e)switch(e){case 0:r+=String.fromCodePoint(65533);break;case 127:r+=`\\${e.toString(16)}`;break;case 34:case 39:case 92:r+=`\\${t}`;break;default:if(1<=e&&e<=31){r+=`\\${e.toString(16)} `;break}r+=t}else r+=String.fromCodePoint(65533)}return r}function normalizedDir(e){return t.parse(t.resolve(e.trim())).dir.split(t.sep).join(t.posix.sep)}const i=/url\(/i,o=/url/i,creator=()=>({postcssPlugin:"postcss-rebase-url",prepare(){const t=new WeakSet,s=new Set;return{Once(e){e.walkAtRules(/property/i,(e=>{if(!e.nodes)return;const r=e.nodes.find((e=>{if("decl"===e.type&&/syntax/i.test(e.prop))return!0}));r&&//i.test(r.value)&&s.add(e.params.trim())}))},Declaration(n,{result:a}){var u;if(t.has(n))return;if(n.variable&&!s.has(n.prop))return;const{from:l}=a.opts;if(!l)return;if(null==(u=n.source)||!u.input.from)return;if(!i.test(n.value))return;const c=normalizedDir(l),p=n.source.input.from.trim();if(!p)return;const f=normalizedDir(p),d=r.parseCommaSeparatedListOfComponentValues(e.tokenize({css:n.value})),v=r.replaceComponentValues(d,(t=>{if(r.isTokenNode(t)&&t.value[0]===e.TokenType.URL){const e=rebase(t.value[4].value.trim(),f,c);if(e)return t.value[4].value=e,t.value[1]=`url(${serializeString(e)})`,t}if(r.isFunctionNode(t)&&o.test(t.getName()))for(const s of t.value)if(!r.isWhitespaceNode(s)&&!r.isCommentNode(s)&&r.isTokenNode(s)&&s.value[0]===e.TokenType.String){const e=rebase(s.value[4].value.trim(),f,c);if(e)return s.value[4].value=e,s.value[1]=`"${serializeString(e)}"`,t;break}})),m=r.stringify(v);m!==n.value&&(n.value=m,t.add(n))}}}});creator.postcss=!0,module.exports=creator; diff --git a/plugins/postcss-rebase-url/dist/index.mjs b/plugins/postcss-rebase-url/dist/index.mjs index defc23960..2354e48be 100644 --- a/plugins/postcss-rebase-url/dist/index.mjs +++ b/plugins/postcss-rebase-url/dist/index.mjs @@ -1 +1 @@ -import{tokenize as r,TokenType as e}from"@csstools/css-tokenizer";import{parseCommaSeparatedListOfComponentValues as t,replaceComponentValues as s,isTokenNode as i,isFunctionNode as o,isWhitespaceNode as a,isCommentNode as n,stringify as u}from"@csstools/css-parser-algorithms";import l from"path";const c=/^([-_a-z0-9]+:)?\/\//i;function rebase(r,e,t){if(r.startsWith("data:"))return!1;if(c.test(r))return!1;if(r.startsWith("/"))return r;if(r.startsWith("#"))return r;try{const e=new URL(r);if(e.port||e.protocol)return!1}catch{}const s=l.posix.resolve(l.posix.join(e,r));return l.posix.relative(t,s)}function serializeString(r){let e="";for(const t of r){const r=t.codePointAt(0);if(void 0!==r)switch(r){case 0:e+=String.fromCodePoint(65533);break;case 127:e+=`\\${r.toString(16)}`;break;case 34:case 39:case 92:e+=`\\${t}`;break;default:if(1<=r&&r<=31){e+=`\\${r.toString(16)} `;break}e+=t}else e+=String.fromCodePoint(65533)}return e}function normalizedDir(r){return l.parse(l.resolve(r.trim())).dir.split(l.sep).join(l.posix.sep)}const f=/url\(/i,p=/url/i,creator=()=>({postcssPlugin:"postcss-rebase-url",prepare(){const l=new WeakSet;return{Declaration(c,{result:v}){var m;if(l.has(c))return;const{from:d}=v.opts;if(!d)return;if(null==(m=c.source)||!m.input.from)return;if(!f.test(c.value))return;const g=normalizedDir(d),b=c.source.input.from.trim();if(!b)return;const S=normalizedDir(b),h=t(r({css:c.value})),z=s(h,(r=>{if(i(r)&&r.value[0]===e.URL){const e=rebase(r.value[4].value.trim(),S,g);if(e)return r.value[4].value=e,r.value[1]=`url(${serializeString(e)})`,r}if(o(r)&&p.test(r.getName()))for(const t of r.value)if(!a(t)&&!n(t)&&i(t)&&t.value[0]===e.String){const e=rebase(t.value[4].value.trim(),S,g);if(e)return t.value[4].value=e,t.value[1]=`"${serializeString(e)}"`,r;break}})),k=u(z);k!==c.value&&(c.value=k,l.add(c))}}}});creator.postcss=!0;export{creator as default}; +import{tokenize as r,TokenType as e}from"@csstools/css-tokenizer";import{parseCommaSeparatedListOfComponentValues as t,replaceComponentValues as s,isTokenNode as i,isFunctionNode as o,isWhitespaceNode as a,isCommentNode as n,stringify as u}from"@csstools/css-parser-algorithms";import l from"path";const c=/^([-_a-z0-9]+:)?\/\//i;function rebase(r,e,t){if(r.startsWith("data:"))return!1;if(c.test(r))return!1;if(r.startsWith("/"))return r;if(r.startsWith("#"))return r;try{const e=new URL(r);if(e.port||e.protocol)return!1}catch{}const s=l.posix.resolve(l.posix.join(e,r));return l.posix.relative(t,s)}function serializeString(r){let e="";for(const t of r){const r=t.codePointAt(0);if(void 0!==r)switch(r){case 0:e+=String.fromCodePoint(65533);break;case 127:e+=`\\${r.toString(16)}`;break;case 34:case 39:case 92:e+=`\\${t}`;break;default:if(1<=r&&r<=31){e+=`\\${r.toString(16)} `;break}e+=t}else e+=String.fromCodePoint(65533)}return e}function normalizedDir(r){return l.parse(l.resolve(r.trim())).dir.split(l.sep).join(l.posix.sep)}const f=/url\(/i,p=/url/i,creator=()=>({postcssPlugin:"postcss-rebase-url",prepare(){const l=new WeakSet,c=new Set;return{Once(r){r.walkAtRules(/property/i,(r=>{if(!r.nodes)return;const e=r.nodes.find((r=>{if("decl"===r.type&&/syntax/i.test(r.prop))return!0}));e&&//i.test(e.value)&&c.add(r.params.trim())}))},Declaration(v,{result:m}){var d;if(l.has(v))return;if(v.variable&&!c.has(v.prop))return;const{from:g}=m.opts;if(!g)return;if(null==(d=v.source)||!d.input.from)return;if(!f.test(v.value))return;const b=normalizedDir(g),S=v.source.input.from.trim();if(!S)return;const h=normalizedDir(S),k=t(r({css:v.value})),z=s(k,(r=>{if(i(r)&&r.value[0]===e.URL){const e=rebase(r.value[4].value.trim(),h,b);if(e)return r.value[4].value=e,r.value[1]=`url(${serializeString(e)})`,r}if(o(r)&&p.test(r.getName()))for(const t of r.value)if(!a(t)&&!n(t)&&i(t)&&t.value[0]===e.String){const e=rebase(t.value[4].value.trim(),h,b);if(e)return t.value[4].value=e,t.value[1]=`"${serializeString(e)}"`,r;break}})),x=u(z);x!==v.value&&(v.value=x,l.add(v))}}}});creator.postcss=!0;export{creator as default}; diff --git a/plugins/postcss-rebase-url/src/index.ts b/plugins/postcss-rebase-url/src/index.ts index 796adacfc..5a078108b 100644 --- a/plugins/postcss-rebase-url/src/index.ts +++ b/plugins/postcss-rebase-url/src/index.ts @@ -1,4 +1,4 @@ -import type { PluginCreator } from 'postcss'; +import type { PluginCreator, Declaration } from 'postcss'; import { TokenType, tokenize } from '@csstools/css-tokenizer'; import { isCommentNode, isFunctionNode, isTokenNode, isWhitespaceNode, parseCommaSeparatedListOfComponentValues, replaceComponentValues, stringify } from '@csstools/css-parser-algorithms'; import { rebase } from './rebase'; @@ -16,13 +16,39 @@ const creator: PluginCreator = () => { postcssPlugin: 'postcss-rebase-url', prepare() { const visited = new WeakSet(); + const registeredPropsWithURL_Type = new Set(); return { + Once(root) { + root.walkAtRules(/property/i, (atRule) => { + if (!atRule.nodes) { + return; + } + + const syntaxDescriptor = atRule.nodes.find((x) => { + if (x.type === 'decl' && /syntax/i.test(x.prop)) { + return true; + } + }) as Declaration | undefined; + + if (!syntaxDescriptor) { + return; + } + + if (//i.test(syntaxDescriptor.value)) { + registeredPropsWithURL_Type.add(atRule.params.trim()); + } + }); + }, Declaration(decl, { result }) { if (visited.has(decl)) { return; } + if (decl.variable && !registeredPropsWithURL_Type.has(decl.prop)) { + return; + } + const { from: fromEntryPoint } = result.opts; if (!fromEntryPoint) { return; diff --git a/plugins/postcss-rebase-url/test/basic.css b/plugins/postcss-rebase-url/test/basic.css index 806cb9f7f..078b70818 100644 --- a/plugins/postcss-rebase-url/test/basic.css +++ b/plugins/postcss-rebase-url/test/basic.css @@ -1,4 +1,5 @@ @import "imports/basic.css"; +@import "imports/props.css"; .root { background: url(./images/green.png); diff --git a/plugins/postcss-rebase-url/test/basic.expect.css b/plugins/postcss-rebase-url/test/basic.expect.css index e0d3cb681..a7f75f6c6 100644 --- a/plugins/postcss-rebase-url/test/basic.expect.css +++ b/plugins/postcss-rebase-url/test/basic.expect.css @@ -35,6 +35,24 @@ background: url("node_modules://images/green.png"); } +@property --background-image--strictly-typed { + syntax: ' | none'; + inherits: true; + initial-value: none; +} + +@property --background-image--loosely-typed { + syntax: '*'; + inherits: true; + initial-value: none; +} + +.box { + --background-image--strictly-typed: url(imports/green.png#1); + --background-image--loosely-typed: url(./green.png#2); + --background-image--untyped: url(./green.png#3); +} + .root { background: url(images/green.png); } diff --git a/plugins/postcss-rebase-url/test/imports/props.css b/plugins/postcss-rebase-url/test/imports/props.css new file mode 100644 index 000000000..e5345dbf8 --- /dev/null +++ b/plugins/postcss-rebase-url/test/imports/props.css @@ -0,0 +1,17 @@ +@property --background-image--strictly-typed { + syntax: ' | none'; + inherits: true; + initial-value: none; +} + +@property --background-image--loosely-typed { + syntax: '*'; + inherits: true; + initial-value: none; +} + +.box { + --background-image--strictly-typed: url(./green.png#1); + --background-image--loosely-typed: url(./green.png#2); + --background-image--untyped: url(./green.png#3); +}