diff --git a/plugins/postcss-light-dark-function/CHANGELOG.md b/plugins/postcss-light-dark-function/CHANGELOG.md index 5d4db80a8..e88243cf9 100644 --- a/plugins/postcss-light-dark-function/CHANGELOG.md +++ b/plugins/postcss-light-dark-function/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Light Dark Function +### Unreleased (patch) + +- Smaller generated CSS. + ### 2.0.0 _August 3, 2024_ diff --git a/plugins/postcss-light-dark-function/dist/index.cjs b/plugins/postcss-light-dark-function/dist/index.cjs index 87c700b29..3ce6808a8 100644 --- a/plugins/postcss-light-dark-function/dist/index.cjs +++ b/plugins/postcss-light-dark-function/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("@csstools/postcss-progressive-custom-properties"),o=require("@csstools/css-tokenizer"),r=require("@csstools/utilities"),t=require("@csstools/css-parser-algorithms");const s="--csstools-color-scheme--dark",n="initial";function toggleNameGenerator(e){return`--csstools-light-dark-toggle--${e}`}const i=/dark/i,a=/light/i;function colorSchemes(e){const r=o.tokenize({css:e});let t=!1,s=!1;return r.forEach((e=>{o.isTokenIdent(e)&&(a.test(e[4].value)?t=!0:i.test(e[4].value)&&(s=!0))})),[t,s]}const c=/^light-dark$/i;function isComma(e){return t.isTokenNode(e)&&o.isTokenComma(e.value)}function parseLightDark(e){if(!t.isFunctionNode(e)||!c.test(e.getName()))return!1;const o=e.value.filter((e=>!t.isWhitespaceNode(e)&&!t.isCommentNode(e)));if(3!==o.length)return!1;let r=o[0];const s=o[1];let n=o[2];if(!r||!s||!n)return!1;if(!isComma(s))return!1;if(isComma(r)||isComma(n))return!1;if(t.isFunctionNode(r)){const e=[r];t.walk(e,(({node:e,parent:o},r)=>{recurseLightDark(e,o,r,!0)})),[r]=e}if(t.isFunctionNode(n)){const e=[n];t.walk(e,(({node:e,parent:o},r)=>{recurseLightDark(e,o,r,!1)})),[n]=e}return[r,n]}function recurseLightDark(e,o,r,s){if("number"!=typeof r)return;const n=parseLightDark(e);if(!n)return;let i=n[s?0:1];if(t.isFunctionNode(i)){const e=[i];t.walk(e,(({node:e,parent:o},r)=>{recurseLightDark(e,o,r,s)})),[i]=e}o.value[r]=i}function transformLightDark(e,r){const n=new Map,i=t.replaceComponentValues(t.parseCommaSeparatedListOfComponentValues(o.tokenize({css:e})),(e=>{const i=parseLightDark(e);if(!i)return;const[a,c]=i,l=r();return n.set(l,`var(${s}) ${a.toString()}`),new t.FunctionNode([o.TokenType.Function,"var(",-1,-1,{value:"var"}],[o.TokenType.CloseParen,")",-1,-1,void 0],[new t.TokenNode([o.TokenType.Ident,l,-1,-1,{value:l}]),new t.TokenNode([o.TokenType.Comma,",",-1,-1,void 0]),new t.WhitespaceNode([[o.TokenType.Whitespace," ",-1,-1,void 0]]),c])}));return{value:t.stringify(i),toggles:n}}const l=/^color-scheme$/i,u=/\blight-dark\(/i,basePlugin=e=>({postcssPlugin:"postcss-light-dark-function",prepare(){let o=0;const currentToggleNameGenerator=()=>toggleNameGenerator(o++);return{postcssPlugin:"postcss-light-dark-function",Declaration(o,{atRule:t,rule:i}){const a=o.parent;if(a){if(l.test(o.prop)){if(a.some((e=>"decl"===e.type&&e.prop===s)))return;const[e,r]=colorSchemes(o.value);if(e&&r){o.cloneBefore({prop:s,value:" "});const e=a.clone();e.removeAll(),e.append(o.clone({prop:s,value:n}));const r=t({name:"media",params:"(prefers-color-scheme: dark)",source:a.source});return r.append(e),void a.after(r)}return r?void o.cloneBefore({prop:s,value:n}):e?void o.cloneBefore({prop:s,value:" "}):void 0}if(u.test(o.value)){if(r.hasFallback(o))return;if(r.hasSupportsAtRuleAncestor(o,u))return;const t=transformLightDark(o.value,currentToggleNameGenerator);if(t.value===o.value)return;for(const[e,r]of t.toggles)o.cloneBefore({prop:e,value:r});if(o.cloneBefore({value:t.value}),o.variable&&o.parent){const e=i({selector:"& *",source:o.source});for(const[r,s]of t.toggles)e.append(o.clone({prop:r,value:s}));e.append(o.clone({value:t.value})),o.parent.append(e)}e?.preserve||o.remove()}}}}}});basePlugin.postcss=!0;const postcssPlugin=o=>{const r=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0},o);return r.enableProgressiveCustomProperties&&r.preserve?{postcssPlugin:"postcss-light-dark-function",plugins:[e(),basePlugin(r)]}:basePlugin(r)};postcssPlugin.postcss=!0,module.exports=postcssPlugin; +"use strict";var e=require("@csstools/postcss-progressive-custom-properties"),o=require("@csstools/css-tokenizer"),r=require("@csstools/utilities"),t=require("@csstools/css-parser-algorithms");const s="--csstools-color-scheme--dark",n="initial";function toggleNameGenerator(e){return`--csstools-light-dark-toggle--${e}`}const a=/dark/i,i=/light/i;function colorSchemes(e){const r=o.tokenize({css:e});let t=!1,s=!1;return r.forEach((e=>{o.isTokenIdent(e)&&(i.test(e[4].value)?t=!0:a.test(e[4].value)&&(s=!0))})),[t,s]}const c=/^light-dark$/i;function isComma(e){return t.isTokenNode(e)&&o.isTokenComma(e.value)}function parseLightDark(e){if(!t.isFunctionNode(e)||!c.test(e.getName()))return!1;const o=e.value.filter((e=>!t.isWhitespaceNode(e)&&!t.isCommentNode(e)));if(3!==o.length)return!1;let r=o[0];const s=o[1];let n=o[2];if(!r||!s||!n)return!1;if(!isComma(s))return!1;if(isComma(r)||isComma(n))return!1;if(t.isFunctionNode(r)){const e=[r];t.walk(e,(({node:e,parent:o},r)=>{recurseLightDark(e,o,r,!0)})),[r]=e}if(t.isFunctionNode(n)){const e=[n];t.walk(e,(({node:e,parent:o},r)=>{recurseLightDark(e,o,r,!1)})),[n]=e}return[r,n]}function recurseLightDark(e,o,r,s){if("number"!=typeof r)return;const n=parseLightDark(e);if(!n)return;let a=n[s?0:1];if(t.isFunctionNode(a)){const e=[a];t.walk(e,(({node:e,parent:o},r)=>{recurseLightDark(e,o,r,s)})),[a]=e}o.value[r]=a}function transformLightDark(e,r){const n=new Map,a=t.replaceComponentValues(t.parseCommaSeparatedListOfComponentValues(o.tokenize({css:e})),(e=>{const a=parseLightDark(e);if(!a)return;const[i,c]=a,l=r();return n.set(l,`var(${s}) ${i.toString()}`),new t.FunctionNode([o.TokenType.Function,"var(",-1,-1,{value:"var"}],[o.TokenType.CloseParen,")",-1,-1,void 0],[new t.TokenNode([o.TokenType.Ident,l,-1,-1,{value:l}]),new t.TokenNode([o.TokenType.Comma,",",-1,-1,void 0]),new t.WhitespaceNode([[o.TokenType.Whitespace," ",-1,-1,void 0]]),c])}));return{value:t.stringify(a),toggles:n}}const l=/^color-scheme$/i,u=/\blight-dark\(/i,basePlugin=e=>({postcssPlugin:"postcss-light-dark-function",prepare(){let o=0;const currentToggleNameGenerator=()=>toggleNameGenerator(o++),t=new Map;return{postcssPlugin:"postcss-light-dark-function",Declaration(o,{atRule:a,rule:i}){const c=o.parent;if(c){if(l.test(o.prop)){if(c.some((e=>"decl"===e.type&&e.prop===s)))return;const[e,r]=colorSchemes(o.value);if(e&&r){o.cloneBefore({prop:s,value:" "});const e=c.clone();e.removeAll(),e.append(o.clone({prop:s,value:n}));const r=a({name:"media",params:"(prefers-color-scheme: dark)",source:c.source});return r.append(e),void c.after(r)}return r?void o.cloneBefore({prop:s,value:n}):e?void o.cloneBefore({prop:s,value:" "}):void 0}if(u.test(o.value)){if(r.hasFallback(o))return;if(r.hasSupportsAtRuleAncestor(o,u))return;const s=transformLightDark(o.value,currentToggleNameGenerator);if(s.value===o.value)return;for(const[e,r]of s.toggles)o.cloneBefore({prop:e,value:r});if(o.cloneBefore({value:s.value}),o.variable&&o.parent){const e=t.get(o.parent)??i({selector:"& *",source:o.source});for(const[r,t]of s.toggles)e.append(o.clone({prop:r,value:t}));e.append(o.clone({value:s.value})),t.has(o.parent)||(o.parent.append(e),t.set(o.parent,e))}e?.preserve||o.remove()}}}}}});basePlugin.postcss=!0;const postcssPlugin=o=>{const r=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0},o);return r.enableProgressiveCustomProperties&&r.preserve?{postcssPlugin:"postcss-light-dark-function",plugins:[e(),basePlugin(r)]}:basePlugin(r)};postcssPlugin.postcss=!0,module.exports=postcssPlugin; diff --git a/plugins/postcss-light-dark-function/dist/index.mjs b/plugins/postcss-light-dark-function/dist/index.mjs index 22decab07..b394554a0 100644 --- a/plugins/postcss-light-dark-function/dist/index.mjs +++ b/plugins/postcss-light-dark-function/dist/index.mjs @@ -1 +1 @@ -import e from"@csstools/postcss-progressive-custom-properties";import{tokenize as r,isTokenIdent as t,isTokenComma as o,TokenType as s}from"@csstools/css-tokenizer";import{hasFallback as n,hasSupportsAtRuleAncestor as a}from"@csstools/utilities";import{isFunctionNode as i,isWhitespaceNode as c,isCommentNode as l,walk as u,isTokenNode as p,replaceComponentValues as f,parseCommaSeparatedListOfComponentValues as v,FunctionNode as g,TokenNode as m,WhitespaceNode as d,stringify as h}from"@csstools/css-parser-algorithms";const k="--csstools-color-scheme--dark",D="initial";function toggleNameGenerator(e){return`--csstools-light-dark-toggle--${e}`}const L=/dark/i,C=/light/i;function colorSchemes(e){const o=r({css:e});let s=!1,n=!1;return o.forEach((e=>{t(e)&&(C.test(e[4].value)?s=!0:L.test(e[4].value)&&(n=!0))})),[s,n]}const P=/^light-dark$/i;function isComma(e){return p(e)&&o(e.value)}function parseLightDark(e){if(!i(e)||!P.test(e.getName()))return!1;const r=e.value.filter((e=>!c(e)&&!l(e)));if(3!==r.length)return!1;let t=r[0];const o=r[1];let s=r[2];if(!t||!o||!s)return!1;if(!isComma(o))return!1;if(isComma(t)||isComma(s))return!1;if(i(t)){const e=[t];u(e,(({node:e,parent:r},t)=>{recurseLightDark(e,r,t,!0)})),[t]=e}if(i(s)){const e=[s];u(e,(({node:e,parent:r},t)=>{recurseLightDark(e,r,t,!1)})),[s]=e}return[t,s]}function recurseLightDark(e,r,t,o){if("number"!=typeof t)return;const s=parseLightDark(e);if(!s)return;let n=s[o?0:1];if(i(n)){const e=[n];u(e,(({node:e,parent:r},t)=>{recurseLightDark(e,r,t,o)})),[n]=e}r.value[t]=n}function transformLightDark(e,t){const o=new Map,n=f(v(r({css:e})),(e=>{const r=parseLightDark(e);if(!r)return;const[n,a]=r,i=t();return o.set(i,`var(${k}) ${n.toString()}`),new g([s.Function,"var(",-1,-1,{value:"var"}],[s.CloseParen,")",-1,-1,void 0],[new m([s.Ident,i,-1,-1,{value:i}]),new m([s.Comma,",",-1,-1,void 0]),new d([[s.Whitespace," ",-1,-1,void 0]]),a])}));return{value:h(n),toggles:o}}const b=/^color-scheme$/i,w=/\blight-dark\(/i,basePlugin=e=>({postcssPlugin:"postcss-light-dark-function",prepare(){let r=0;const currentToggleNameGenerator=()=>toggleNameGenerator(r++);return{postcssPlugin:"postcss-light-dark-function",Declaration(r,{atRule:t,rule:o}){const s=r.parent;if(s){if(b.test(r.prop)){if(s.some((e=>"decl"===e.type&&e.prop===k)))return;const[e,o]=colorSchemes(r.value);if(e&&o){r.cloneBefore({prop:k,value:" "});const e=s.clone();e.removeAll(),e.append(r.clone({prop:k,value:D}));const o=t({name:"media",params:"(prefers-color-scheme: dark)",source:s.source});return o.append(e),void s.after(o)}return o?void r.cloneBefore({prop:k,value:D}):e?void r.cloneBefore({prop:k,value:" "}):void 0}if(w.test(r.value)){if(n(r))return;if(a(r,w))return;const t=transformLightDark(r.value,currentToggleNameGenerator);if(t.value===r.value)return;for(const[e,o]of t.toggles)r.cloneBefore({prop:e,value:o});if(r.cloneBefore({value:t.value}),r.variable&&r.parent){const e=o({selector:"& *",source:r.source});for(const[o,s]of t.toggles)e.append(r.clone({prop:o,value:s}));e.append(r.clone({value:t.value})),r.parent.append(e)}e?.preserve||r.remove()}}}}}});basePlugin.postcss=!0;const postcssPlugin=r=>{const t=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0},r);return t.enableProgressiveCustomProperties&&t.preserve?{postcssPlugin:"postcss-light-dark-function",plugins:[e(),basePlugin(t)]}:basePlugin(t)};postcssPlugin.postcss=!0;export{postcssPlugin as default}; +import e from"@csstools/postcss-progressive-custom-properties";import{tokenize as r,isTokenIdent as t,isTokenComma as o,TokenType as s}from"@csstools/css-tokenizer";import{hasFallback as n,hasSupportsAtRuleAncestor as a}from"@csstools/utilities";import{isFunctionNode as i,isWhitespaceNode as c,isCommentNode as l,walk as u,isTokenNode as p,replaceComponentValues as f,parseCommaSeparatedListOfComponentValues as v,FunctionNode as g,TokenNode as m,WhitespaceNode as d,stringify as h}from"@csstools/css-parser-algorithms";const k="--csstools-color-scheme--dark",D="initial";function toggleNameGenerator(e){return`--csstools-light-dark-toggle--${e}`}const L=/dark/i,C=/light/i;function colorSchemes(e){const o=r({css:e});let s=!1,n=!1;return o.forEach((e=>{t(e)&&(C.test(e[4].value)?s=!0:L.test(e[4].value)&&(n=!0))})),[s,n]}const P=/^light-dark$/i;function isComma(e){return p(e)&&o(e.value)}function parseLightDark(e){if(!i(e)||!P.test(e.getName()))return!1;const r=e.value.filter((e=>!c(e)&&!l(e)));if(3!==r.length)return!1;let t=r[0];const o=r[1];let s=r[2];if(!t||!o||!s)return!1;if(!isComma(o))return!1;if(isComma(t)||isComma(s))return!1;if(i(t)){const e=[t];u(e,(({node:e,parent:r},t)=>{recurseLightDark(e,r,t,!0)})),[t]=e}if(i(s)){const e=[s];u(e,(({node:e,parent:r},t)=>{recurseLightDark(e,r,t,!1)})),[s]=e}return[t,s]}function recurseLightDark(e,r,t,o){if("number"!=typeof t)return;const s=parseLightDark(e);if(!s)return;let n=s[o?0:1];if(i(n)){const e=[n];u(e,(({node:e,parent:r},t)=>{recurseLightDark(e,r,t,o)})),[n]=e}r.value[t]=n}function transformLightDark(e,t){const o=new Map,n=f(v(r({css:e})),(e=>{const r=parseLightDark(e);if(!r)return;const[n,a]=r,i=t();return o.set(i,`var(${k}) ${n.toString()}`),new g([s.Function,"var(",-1,-1,{value:"var"}],[s.CloseParen,")",-1,-1,void 0],[new m([s.Ident,i,-1,-1,{value:i}]),new m([s.Comma,",",-1,-1,void 0]),new d([[s.Whitespace," ",-1,-1,void 0]]),a])}));return{value:h(n),toggles:o}}const b=/^color-scheme$/i,w=/\blight-dark\(/i,basePlugin=e=>({postcssPlugin:"postcss-light-dark-function",prepare(){let r=0;const currentToggleNameGenerator=()=>toggleNameGenerator(r++),t=new Map;return{postcssPlugin:"postcss-light-dark-function",Declaration(r,{atRule:o,rule:s}){const i=r.parent;if(i){if(b.test(r.prop)){if(i.some((e=>"decl"===e.type&&e.prop===k)))return;const[e,t]=colorSchemes(r.value);if(e&&t){r.cloneBefore({prop:k,value:" "});const e=i.clone();e.removeAll(),e.append(r.clone({prop:k,value:D}));const t=o({name:"media",params:"(prefers-color-scheme: dark)",source:i.source});return t.append(e),void i.after(t)}return t?void r.cloneBefore({prop:k,value:D}):e?void r.cloneBefore({prop:k,value:" "}):void 0}if(w.test(r.value)){if(n(r))return;if(a(r,w))return;const o=transformLightDark(r.value,currentToggleNameGenerator);if(o.value===r.value)return;for(const[e,t]of o.toggles)r.cloneBefore({prop:e,value:t});if(r.cloneBefore({value:o.value}),r.variable&&r.parent){const e=t.get(r.parent)??s({selector:"& *",source:r.source});for(const[t,s]of o.toggles)e.append(r.clone({prop:t,value:s}));e.append(r.clone({value:o.value})),t.has(r.parent)||(r.parent.append(e),t.set(r.parent,e))}e?.preserve||r.remove()}}}}}});basePlugin.postcss=!0;const postcssPlugin=r=>{const t=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0},r);return t.enableProgressiveCustomProperties&&t.preserve?{postcssPlugin:"postcss-light-dark-function",plugins:[e(),basePlugin(t)]}:basePlugin(t)};postcssPlugin.postcss=!0;export{postcssPlugin as default}; diff --git a/plugins/postcss-light-dark-function/src/index.ts b/plugins/postcss-light-dark-function/src/index.ts index 84116ff7b..4f4e46498 100644 --- a/plugins/postcss-light-dark-function/src/index.ts +++ b/plugins/postcss-light-dark-function/src/index.ts @@ -1,5 +1,5 @@ import postcssProgressiveCustomProperties from '@csstools/postcss-progressive-custom-properties'; -import type { Plugin, PluginCreator } from 'postcss'; +import type { Container, Plugin, PluginCreator, Rule } from 'postcss'; import { DARK_PROP, OFF, ON, toggleNameGenerator } from './props'; import { colorSchemes } from './color-schemes'; import { hasFallback, hasSupportsAtRuleAncestor } from '@csstools/utilities'; @@ -17,6 +17,8 @@ const basePlugin: PluginCreator = (opts) => { return toggleNameGenerator(counter++); }; + const variableInheritanceRules: Map = new Map(); + return { postcssPlugin: 'postcss-light-dark-function', Declaration(decl, { atRule, rule }): void { @@ -84,7 +86,7 @@ const basePlugin: PluginCreator = (opts) => { decl.cloneBefore({ value: modified.value }); if (decl.variable && decl.parent) { - const variableInheritanceRule = rule({ + const variableInheritanceRule = variableInheritanceRules.get(decl.parent) ?? rule({ selector: '& *', source: decl.source, }); @@ -94,7 +96,11 @@ const basePlugin: PluginCreator = (opts) => { } variableInheritanceRule.append(decl.clone({ value: modified.value })); - decl.parent.append(variableInheritanceRule); + + if (!variableInheritanceRules.has(decl.parent)) { + decl.parent.append(variableInheritanceRule); + variableInheritanceRules.set(decl.parent, variableInheritanceRule); + } } if (!opts?.preserve) { diff --git a/plugins/postcss-light-dark-function/test/_tape.mjs b/plugins/postcss-light-dark-function/test/_tape.mjs index 09ba31c6b..f6956c962 100644 --- a/plugins/postcss-light-dark-function/test/_tape.mjs +++ b/plugins/postcss-light-dark-function/test/_tape.mjs @@ -31,6 +31,9 @@ postcssTape(plugin)({ 'cascade-layers-d': { message: 'supports cascade layers', }, + 'common-patterns-1': { + message: 'support common patterns', + }, ignore: { message: 'ignores values with fallbacks or guard by @supports', }, diff --git a/plugins/postcss-light-dark-function/test/common-patterns-1.css b/plugins/postcss-light-dark-function/test/common-patterns-1.css new file mode 100644 index 000000000..159708f25 --- /dev/null +++ b/plugins/postcss-light-dark-function/test/common-patterns-1.css @@ -0,0 +1,8 @@ +/* multiple color vars in ":root" */ +:root { + color-scheme: light dark; + + --color-red: light-dark(red, pink); + --color-blue: light-dark(blue, cyan); + --color-green: light-dark(green, lime); +} diff --git a/plugins/postcss-light-dark-function/test/common-patterns-1.expect.css b/plugins/postcss-light-dark-function/test/common-patterns-1.expect.css new file mode 100644 index 000000000..6dbed23b8 --- /dev/null +++ b/plugins/postcss-light-dark-function/test/common-patterns-1.expect.css @@ -0,0 +1,36 @@ +/* multiple color vars in ":root" */ +:root { + --csstools-color-scheme--dark: ; + color-scheme: light dark; + + --csstools-light-dark-toggle--0: var(--csstools-color-scheme--dark) red; + + --color-red: var(--csstools-light-dark-toggle--0, pink); + --csstools-light-dark-toggle--1: var(--csstools-color-scheme--dark) blue; + --color-blue: var(--csstools-light-dark-toggle--1, cyan); + --csstools-light-dark-toggle--2: var(--csstools-color-scheme--dark) green; + --color-green: var(--csstools-light-dark-toggle--2, lime); + & * { + + --csstools-light-dark-toggle--0: var(--csstools-color-scheme--dark) red; + + --color-red: var(--csstools-light-dark-toggle--0, pink); + --csstools-light-dark-toggle--1: var(--csstools-color-scheme--dark) blue; + --color-blue: var(--csstools-light-dark-toggle--1, cyan); + --csstools-light-dark-toggle--2: var(--csstools-color-scheme--dark) green; + --color-green: var(--csstools-light-dark-toggle--2, lime); + } +} +@supports (color: light-dark(red, red)) { +:root { + + --color-red: light-dark(red, pink); + --color-blue: light-dark(blue, cyan); + --color-green: light-dark(green, lime); +} +} +@media (prefers-color-scheme: dark) { +:root { + --csstools-color-scheme--dark: initial; +} +}