From c10b2d1dcda4d8b5f198dd4dc280a08b64c28823 Mon Sep 17 00:00:00 2001 From: Benjamin Jentsch Date: Thu, 12 Jan 2023 13:58:44 +0100 Subject: [PATCH] postcss-custom-properties: Enable usage of function for preserve option --- plugins/postcss-custom-properties/.tape.mjs | 26 +++ .../postcss-custom-properties/dist/index.cjs | 2 +- .../postcss-custom-properties/dist/index.d.ts | 14 +- .../postcss-custom-properties/dist/index.mjs | 2 +- .../lib/get-custom-properties-from-root.d.ts | 4 +- .../dist/lib/options.d.ts | 14 ++ .../dist/lib/transform-properties.d.ts | 5 +- .../dist/lib/transform-value-ast.d.ts | 3 +- .../postcss-custom-properties/src/index.ts | 26 +-- .../lib/get-custom-properties-from-root.ts | 17 +- .../src/lib/options.ts | 20 +++ .../src/lib/transform-properties.ts | 23 +-- .../src/lib/transform-value-ast.ts | 12 +- .../test/basic.preserve-function.expect.css | 151 ++++++++++++++++++ ...port.preserve-function-filename.expect.css | 7 + 15 files changed, 268 insertions(+), 58 deletions(-) create mode 100644 plugins/postcss-custom-properties/test/basic.preserve-function.expect.css create mode 100644 plugins/postcss-custom-properties/test/import.preserve-function-filename.expect.css diff --git a/plugins/postcss-custom-properties/.tape.mjs b/plugins/postcss-custom-properties/.tape.mjs index 43fa4ae48..c68faef94 100644 --- a/plugins/postcss-custom-properties/.tape.mjs +++ b/plugins/postcss-custom-properties/.tape.mjs @@ -14,6 +14,32 @@ postcssTape(plugin)({ preserve: false } }, + 'basic:preserve-function': { + message: 'supports { preserve: Function } usage', + options: { + /** + * @param {Declaration} d + */ + preserve: (d) => d.value.includes("--color"), + }, + }, + 'import:preserve-function-filename': { + message: 'supports { preserve: Function } usage with filename', + warnings: 1, + plugins: [postcssImport(), plugin({ + /** + * @param {Declaration} declaration + */ + preserve: declaration => { + const cssInputFile = declaration.source.input.file; + + return cssInputFile.endsWith('imported-file.css'); + }, + importFrom: [ + 'test/import-properties.css' + ] + })], + }, 'basic:import': { message: 'supports { importFrom: { customProperties: { ... } } } usage', warnings: 1, diff --git a/plugins/postcss-custom-properties/dist/index.cjs b/plugins/postcss-custom-properties/dist/index.cjs index 9b6aa3390..578c0be78 100644 --- a/plugins/postcss-custom-properties/dist/index.cjs +++ b/plugins/postcss-custom-properties/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("postcss-value-parser"),t=require("path"),r=require("url"),o=require("postcss"),s=require("fs");function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}function i(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(r){if("default"!==r){var o=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,o.get?o:{enumerable:!0,get:function(){return e[r]}})}})),t.default=e,Object.freeze(t)}var a=n(e),c=n(t);const u=/(!\s*)?postcss-custom-properties:\s*off\b/i,l=new WeakMap;function p(e){if(!e||!e.nodes)return!1;if(l.has(e))return l.get(e);const t=e.some((e=>d(e,u)));return l.set(e,t),t}const f=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function m(e){return!!e&&(!!p(e.parent)||d(e.prev(),f))}function d(e,t){return e&&"comment"===e.type&&t.test(e.text)}function w(e,t){const r=new Map,o=new Map;e.nodes.slice().forEach((e=>{if(p(e))return;const s=h(e)?r:g(e)?o:null;s&&(e.nodes.slice().forEach((e=>{if(e.variable&&!m(e)){const{prop:r}=e;s.set(r,e.value),t.preserve||e.remove()}})),!t.preserve&&b(e)&&e.remove())}));const s=new Map;for(const[e,t]of r.entries())s.set(e,a.default(t));for(const[e,t]of o.entries())s.set(e,a.default(t));return s}const v=/^html$/i,y=/^:root$/i,h=e=>"rule"===e.type&&e.selector.split(",").some((e=>v.test(e)))&&Object(e.nodes).length,g=e=>"rule"===e.type&&e.selector.split(",").some((e=>y.test(e)))&&Object(e.nodes).length,b=e=>0===Object(e.nodes).length;function j(e){const t=new Map;if("customProperties"in e)for(const[r,o]of Object.entries(e.customProperties))t.set(r,a.default(o.toString()));if("custom-properties"in e)for(const[r,o]of Object.entries(e["custom-properties"]))t.set(r,a.default(o.toString()));return t}async function O(e){let t;try{t=await(o=e,Promise.resolve().then((function(){return i(require(o))})))}catch(o){t=await function(e){return Promise.resolve().then((function(){return i(require(e))}))}(r.pathToFileURL(e).href)}var o;return j("default"in t?t.default:t)}async function $(e){const t=(await Promise.all(e.map((async e=>{if(e instanceof Promise?e=await e:e instanceof Function&&(e=await e()),"string"==typeof e){const t=c.default.resolve(e);return{type:c.default.extname(t).slice(1).toLowerCase(),from:t}}if("customProperties"in e&&Object(e.customProperties)===e.customProperties)return e;if("custom-properties"in e&&Object(e["custom-properties"])===e["custom-properties"])return e;if("from"in e){const t=c.default.resolve(e.from);let r=e.type;return r||(r=c.default.extname(t).slice(1).toLowerCase()),{type:r,from:t}}return Object.keys(e).length,null})))).filter((e=>!!e)),r=await Promise.all(t.map((async e=>{if("type"in e&&"from"in e){if("css"===e.type||"pcss"===e.type)return await async function(e){const t=await s.promises.readFile(e);return w(o.parse(t,{from:e.toString()}),{preserve:!0})}(e.from);if("js"===e.type||"cjs"===e.type)return await O(e.from);if("mjs"===e.type)return await O(e.from);if("json"===e.type)return await async function(e){return j(await x(e))}(e.from);throw new Error("Invalid source type: "+e.type)}return j(e)}))),n=new Map;return r.forEach((e=>{for(const[t,r]of e.entries())n.set(t,r)})),n}const x=async e=>JSON.parse((await s.promises.readFile(e)).toString());function P(e,t){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((r=>{if(S(r)){const[o,...s]=r.nodes.filter((e=>"div"!==e.type)),{value:n}=o,i=e.nodes.indexOf(r);if(t.has(n)){const r=t.get(n).nodes;!function(e,t,r){const o=new Map(t);o.delete(r),P(e,o)}({nodes:r},t,n),i>-1&&e.nodes.splice(i,1,...r)}else s.length&&(i>-1&&e.nodes.splice(i,1,...r.nodes.slice(r.nodes.indexOf(s[0]))),P(e,t))}else P(r,t)})),e.toString()}const F=/^var$/i,S=e=>"function"===e.type&&F.test(e.value)&&Object(e.nodes).length>0;var k=(e,t,r)=>{if(M(e)&&!m(e)){const o=e.value;let s=P(a.default(o),t);const n=new Set;for(;s.includes("--")&&s.includes("var(")&&!n.has(s);){n.add(s);s=P(a.default(s),t)}if(s!==o){if(function(e,t){if(!e||!e.parent)return!1;let r=!1;const o=e.parent.index(e);return e.parent.each(((s,n)=>s!==e&&(!(n>=o)&&void("decl"===s.type&&s.prop.toLowerCase()===e.prop.toLowerCase()&&s.value===t&&(r=!0))))),r}(e,s))return void(r.preserve||e.remove());if(r.preserve){const t=e.cloneBefore({value:s});E(t)&&(t.raws.value.value=t.value.replace(q,"$1"),t.raws.value.raw=t.raws.value.value+t.raws.value.raw.replace(q,"$2"))}else e.value=s,E(e)&&(e.raws.value.value=e.value.replace(q,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(q,"$2"))}}};const M=e=>!e.variable&&e.value.includes("--")&&e.value.includes("var("),E=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&q.test(e.raws.value.raw),q=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;async function L(e,t,r){"css"===t&&await async function(e,t){const r=`:root {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t${r}: ${t[r]};`),e)),[]).join("\n")}\n}\n`;await s.promises.writeFile(e,r)}(e,r),"scss"===t&&await async function(e,t){const r=`${Object.keys(t).reduce(((e,r)=>{const o=r.replace("--","$");return e.push(`${o}: ${t[r]};`),e}),[]).join("\n")}\n`;await s.promises.writeFile(e,r)}(e,r),"js"===t&&await async function(e,t){const r=`module.exports = {\n\tcustomProperties: {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t\t'${N(r)}': '${N(t[r])}'`),e)),[]).join(",\n")}\n\t}\n};\n`;await s.promises.writeFile(e,r)}(e,r),"json"===t&&await async function(e,t){const r=`${JSON.stringify({"custom-properties":t},null," ")}\n`;await s.promises.writeFile(e,r)}(e,r),"mjs"===t&&await async function(e,t){const r=`export const customProperties = {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t'${N(r)}': '${N(t[r])}'`),e)),[]).join(",\n")}\n};\n`;await s.promises.writeFile(e,r)}(e,r)}function C(e){const t={};for(const[r,o]of e.entries())t[r]=o.toString();return t}const N=e=>e.replace(/\\([\s\S])|(')/g,"\\$1$2").replace(/\n/g,"\\n").replace(/\r/g,"\\r"),T=e=>{const t=!("preserve"in Object(e))||Boolean(e.preserve),r="overrideImportFromWithRoot"in Object(e)&&Boolean(e.overrideImportFromWithRoot),o="disableDeprecationNotice"in Object(e)&&Boolean(e.disableDeprecationNotice);let s=[];Array.isArray(null==e?void 0:e.importFrom)?s=e.importFrom:null!=e&&e.importFrom&&(s=[e.importFrom]);let n=[];Array.isArray(null==e?void 0:e.exportTo)?n=e.exportTo:null!=e&&e.exportTo&&(n=[e.exportTo]);const i=$(s),a=0===s.length&&0===n.length;return{postcssPlugin:"postcss-custom-properties",prepare(){let e=new Map;return a?{Once:r=>{e=w(r,{preserve:t})},Declaration:r=>{k(r,e,{preserve:t})},OnceExit:()=>{e.clear()}}:{Once:async o=>{const s=(await i).entries(),a=w(o,{preserve:t}).entries();if(r)for(const[t,r]of[...s,...a])e.set(t,r);else for(const[t,r]of[...a,...s])e.set(t,r);await function(e,t){return Promise.all(t.map((async t=>{if(t instanceof Function)return void await t(C(e));if("string"==typeof t){const r=c.default.resolve(t),o=c.default.extname(r).slice(1).toLowerCase();return void await L(r,o,C(e))}let r={};if(r="toJSON"in t?t.toJSON(C(e)):C(e),"to"in t){const e=c.default.resolve(t.to);let o=t.type;return o||(o=c.default.extname(e).slice(1).toLowerCase()),void await L(e,o,r)}"customProperties"in t?t.customProperties=r:"custom-properties"in t&&(t["custom-properties"]=r)})))}(e,n)},Declaration:r=>{k(r,e,{preserve:t})},OnceExit:(t,{result:r})=>{!o&&(s.length>0||n.length>0)&&t.warn(r,'"importFrom" and "exportTo" will be removed in a future version of postcss-custom-properties.\nWe are looking for insights and anecdotes on how these features are used so that we can design the best alternative.\nPlease let us know if our proposal will work for you.\nVisit the discussion on github for more details. https://github.com/csstools/postcss-plugins/discussions/192'),e.clear()}}}}};T.postcss=!0,module.exports=T; +"use strict";var e=require("postcss-value-parser"),t=require("path"),r=require("url"),o=require("postcss"),s=require("fs");function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}function i(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(r){if("default"!==r){var o=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,o.get?o:{enumerable:!0,get:function(){return e[r]}})}})),t.default=e,Object.freeze(t)}var a=n(e),c=n(t);const u=/(!\s*)?postcss-custom-properties:\s*off\b/i,l=new WeakMap;function p(e){if(!e||!e.nodes)return!1;if(l.has(e))return l.get(e);const t=e.some((e=>d(e,u)));return l.set(e,t),t}const f=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function m(e){return!!e&&(!!p(e.parent)||d(e.prev(),f))}function d(e,t){return e&&"comment"===e.type&&t.test(e.text)}function v(e,t){const r=new Map,o=new Map,{preserve:s}=t;e.nodes.slice().forEach((e=>{if(p(e))return;const t=h(e)?r:g(e)?o:null;t&&(e.nodes.slice().forEach((e=>{const r=!1===s||"function"==typeof s&&!s(e);if(e.variable&&!m(e)){const{prop:o}=e;t.set(o,e.value),r&&e.remove()}})),!s&&b(e)&&e.remove())}));const n=new Map;for(const[e,t]of r.entries())n.set(e,a.default(t));for(const[e,t]of o.entries())n.set(e,a.default(t));return n}const w=/^html$/i,y=/^:root$/i,h=e=>"rule"===e.type&&e.selector.split(",").some((e=>w.test(e)))&&Object(e.nodes).length,g=e=>"rule"===e.type&&e.selector.split(",").some((e=>y.test(e)))&&Object(e.nodes).length,b=e=>0===Object(e.nodes).length;function j(e){const t=new Map;if("customProperties"in e)for(const[r,o]of Object.entries(e.customProperties))t.set(r,a.default(o.toString()));if("custom-properties"in e)for(const[r,o]of Object.entries(e["custom-properties"]))t.set(r,a.default(o.toString()));return t}async function O(e){let t;try{t=await(o=e,Promise.resolve().then((function(){return i(require(o))})))}catch(o){t=await function(e){return Promise.resolve().then((function(){return i(require(e))}))}(r.pathToFileURL(e).href)}var o;return j("default"in t?t.default:t)}async function $(e){const t=(await Promise.all(e.map((async e=>{if(e instanceof Promise?e=await e:e instanceof Function&&(e=await e()),"string"==typeof e){const t=c.default.resolve(e);return{type:c.default.extname(t).slice(1).toLowerCase(),from:t}}if("customProperties"in e&&Object(e.customProperties)===e.customProperties)return e;if("custom-properties"in e&&Object(e["custom-properties"])===e["custom-properties"])return e;if("from"in e){const t=c.default.resolve(e.from);let r=e.type;return r||(r=c.default.extname(t).slice(1).toLowerCase()),{type:r,from:t}}return Object.keys(e).length,null})))).filter((e=>!!e)),r=await Promise.all(t.map((async e=>{if("type"in e&&"from"in e){if("css"===e.type||"pcss"===e.type)return await async function(e){const t=await s.promises.readFile(e);return v(o.parse(t,{from:e.toString()}),{preserve:!0})}(e.from);if("js"===e.type||"cjs"===e.type)return await O(e.from);if("mjs"===e.type)return await O(e.from);if("json"===e.type)return await async function(e){return j(await x(e))}(e.from);throw new Error("Invalid source type: "+e.type)}return j(e)}))),n=new Map;return r.forEach((e=>{for(const[t,r]of e.entries())n.set(t,r)})),n}const x=async e=>JSON.parse((await s.promises.readFile(e)).toString());function P(e,t){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((r=>{if(S(r)){const[o,...s]=r.nodes.filter((e=>"div"!==e.type)),{value:n}=o,i=e.nodes.indexOf(r);if(t.has(n)){const r=t.get(n).nodes;!function(e,t,r){const o=new Map(t);o.delete(r),P(e,o)}({nodes:r},t,n),i>-1&&e.nodes.splice(i,1,...r)}else s.length&&(i>-1&&e.nodes.splice(i,1,...r.nodes.slice(r.nodes.indexOf(s[0]))),P(e,t))}else P(r,t)})),e.toString()}const F=/^var$/i,S=e=>"function"===e.type&&F.test(e.value)&&Object(e.nodes).length>0;var k=(e,t,r)=>{if(M(e)&&!m(e)){const o=e.value;let s=P(a.default(o),t);const n=new Set;for(;s.includes("--")&&s.includes("var(")&&!n.has(s);){n.add(s);s=P(a.default(s),t)}if(s!==o){if(function(e,t){if(!e||!e.parent)return!1;let r=!1;const o=e.parent.index(e);return e.parent.each(((s,n)=>s!==e&&(!(n>=o)&&void("decl"===s.type&&s.prop.toLowerCase()===e.prop.toLowerCase()&&s.value===t&&(r=!0))))),r}(e,s))return void(r.preserve||e.remove());if(!r.preserve||"function"==typeof r.preserve&&!r.preserve(e))e.value=s,E(e)&&(e.raws.value.value=e.value.replace(q,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(q,"$2"));else if(!0===r.preserve||r.preserve(e)){const t=e.cloneBefore({value:s});E(t)&&(t.raws.value.value=t.value.replace(q,"$1"),t.raws.value.raw=t.raws.value.value+t.raws.value.raw.replace(q,"$2"))}}}};const M=e=>!e.variable&&e.value.includes("--")&&e.value.includes("var("),E=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&q.test(e.raws.value.raw),q=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;async function L(e,t,r){"css"===t&&await async function(e,t){const r=`:root {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t${r}: ${t[r]};`),e)),[]).join("\n")}\n}\n`;await s.promises.writeFile(e,r)}(e,r),"scss"===t&&await async function(e,t){const r=`${Object.keys(t).reduce(((e,r)=>{const o=r.replace("--","$");return e.push(`${o}: ${t[r]};`),e}),[]).join("\n")}\n`;await s.promises.writeFile(e,r)}(e,r),"js"===t&&await async function(e,t){const r=`module.exports = {\n\tcustomProperties: {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t\t'${N(r)}': '${N(t[r])}'`),e)),[]).join(",\n")}\n\t}\n};\n`;await s.promises.writeFile(e,r)}(e,r),"json"===t&&await async function(e,t){const r=`${JSON.stringify({"custom-properties":t},null," ")}\n`;await s.promises.writeFile(e,r)}(e,r),"mjs"===t&&await async function(e,t){const r=`export const customProperties = {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t'${N(r)}': '${N(t[r])}'`),e)),[]).join(",\n")}\n};\n`;await s.promises.writeFile(e,r)}(e,r)}function C(e){const t={};for(const[r,o]of e.entries())t[r]=o.toString();return t}const N=e=>e.replace(/\\([\s\S])|(')/g,"\\$1$2").replace(/\n/g,"\\n").replace(/\r/g,"\\r"),T=e=>{const t=!("preserve"in Object(e))||("function"==typeof e.preserve?e.preserve:Boolean(e.preserve)),r="overrideImportFromWithRoot"in Object(e)&&Boolean(e.overrideImportFromWithRoot),o="disableDeprecationNotice"in Object(e)&&Boolean(e.disableDeprecationNotice);let s=[];Array.isArray(null==e?void 0:e.importFrom)?s=e.importFrom:null!=e&&e.importFrom&&(s=[e.importFrom]);let n=[];Array.isArray(null==e?void 0:e.exportTo)?n=e.exportTo:null!=e&&e.exportTo&&(n=[e.exportTo]);const i=$(s),a=0===s.length&&0===n.length;return{postcssPlugin:"postcss-custom-properties",prepare(){let e=new Map;return a?{Once:r=>{e=v(r,{preserve:t})},Declaration:r=>{k(r,e,{preserve:t})},OnceExit:()=>{e.clear()}}:{Once:async o=>{const s=(await i).entries(),a=v(o,{preserve:t}).entries();if(r)for(const[t,r]of[...s,...a])e.set(t,r);else for(const[t,r]of[...a,...s])e.set(t,r);await function(e,t){return Promise.all(t.map((async t=>{if(t instanceof Function)return void await t(C(e));if("string"==typeof t){const r=c.default.resolve(t),o=c.default.extname(r).slice(1).toLowerCase();return void await L(r,o,C(e))}let r={};if(r="toJSON"in t?t.toJSON(C(e)):C(e),"to"in t){const e=c.default.resolve(t.to);let o=t.type;return o||(o=c.default.extname(e).slice(1).toLowerCase()),void await L(e,o,r)}"customProperties"in t?t.customProperties=r:"custom-properties"in t&&(t["custom-properties"]=r)})))}(e,n)},Declaration:r=>{k(r,e,{preserve:t})},OnceExit:(t,{result:r})=>{!o&&(s.length>0||n.length>0)&&t.warn(r,'"importFrom" and "exportTo" will be removed in a future version of postcss-custom-properties.\nWe are looking for insights and anecdotes on how these features are used so that we can design the best alternative.\nPlease let us know if our proposal will work for you.\nVisit the discussion on github for more details. https://github.com/csstools/postcss-plugins/discussions/192'),e.clear()}}}}};T.postcss=!0,module.exports=T; diff --git a/plugins/postcss-custom-properties/dist/index.d.ts b/plugins/postcss-custom-properties/dist/index.d.ts index f280eee9d..e4ac1e3dc 100644 --- a/plugins/postcss-custom-properties/dist/index.d.ts +++ b/plugins/postcss-custom-properties/dist/index.d.ts @@ -1,16 +1,4 @@ import type { PluginCreator } from 'postcss'; -import type { ImportOptions, ExportOptions } from './lib/options'; -export interface PluginOptions { - /** Do not emit warnings about "importFrom" and "exportTo" deprecations */ - disableDeprecationNotice?: boolean; - /** Determines whether Custom Properties and properties using custom properties should be preserved in their original form. */ - preserve?: boolean; - /** Specifies sources where Custom Properties can be imported from, which might be CSS, JS, and JSON files, functions, and directly passed objects. */ - importFrom?: ImportOptions | Array; - /** Specifies destinations where Custom Properties can be exported to, which might be CSS, JS, and JSON files, functions, and directly passed objects. */ - exportTo?: ExportOptions | Array; - /** Specifies if `importFrom` properties or `:root` properties have priority. */ - overrideImportFromWithRoot?: boolean; -} +import type { PluginOptions } from './lib/options'; declare const creator: PluginCreator; export default creator; diff --git a/plugins/postcss-custom-properties/dist/index.mjs b/plugins/postcss-custom-properties/dist/index.mjs index bd83fedc1..f876d88eb 100644 --- a/plugins/postcss-custom-properties/dist/index.mjs +++ b/plugins/postcss-custom-properties/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-value-parser";import t from"path";import{pathToFileURL as r}from"url";import{parse as o}from"postcss";import{promises as s}from"fs";const n=/(!\s*)?postcss-custom-properties:\s*off\b/i,i=new WeakMap;function a(e){if(!e||!e.nodes)return!1;if(i.has(e))return i.get(e);const t=e.some((e=>u(e,n)));return i.set(e,t),t}const c=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function l(e){return!!e&&(!!a(e.parent)||u(e.prev(),c))}function u(e,t){return e&&"comment"===e.type&&t.test(e.text)}function p(t,r){const o=new Map,s=new Map;t.nodes.slice().forEach((e=>{if(a(e))return;const t=w(e)?o:v(e)?s:null;t&&(e.nodes.slice().forEach((e=>{if(e.variable&&!l(e)){const{prop:o}=e;t.set(o,e.value),r.preserve||e.remove()}})),!r.preserve&&d(e)&&e.remove())}));const n=new Map;for(const[t,r]of o.entries())n.set(t,e(r));for(const[t,r]of s.entries())n.set(t,e(r));return n}const f=/^html$/i,m=/^:root$/i,w=e=>"rule"===e.type&&e.selector.split(",").some((e=>f.test(e)))&&Object(e.nodes).length,v=e=>"rule"===e.type&&e.selector.split(",").some((e=>m.test(e)))&&Object(e.nodes).length,d=e=>0===Object(e.nodes).length;function y(t){const r=new Map;if("customProperties"in t)for(const[o,s]of Object.entries(t.customProperties))r.set(o,e(s.toString()));if("custom-properties"in t)for(const[o,s]of Object.entries(t["custom-properties"]))r.set(o,e(s.toString()));return r}async function h(e){let t;try{t=await import(e)}catch(o){t=await import(r(e).href)}return y("default"in t?t.default:t)}async function g(e){const r=(await Promise.all(e.map((async e=>{if(e instanceof Promise?e=await e:e instanceof Function&&(e=await e()),"string"==typeof e){const r=t.resolve(e);return{type:t.extname(r).slice(1).toLowerCase(),from:r}}if("customProperties"in e&&Object(e.customProperties)===e.customProperties)return e;if("custom-properties"in e&&Object(e["custom-properties"])===e["custom-properties"])return e;if("from"in e){const r=t.resolve(e.from);let o=e.type;return o||(o=t.extname(r).slice(1).toLowerCase()),{type:o,from:r}}return Object.keys(e).length,null})))).filter((e=>!!e)),n=await Promise.all(r.map((async e=>{if("type"in e&&"from"in e){if("css"===e.type||"pcss"===e.type)return await async function(e){const t=await s.readFile(e);return p(o(t,{from:e.toString()}),{preserve:!0})}(e.from);if("js"===e.type||"cjs"===e.type)return await h(e.from);if("mjs"===e.type)return await h(e.from);if("json"===e.type)return await async function(e){return y(await j(e))}(e.from);throw new Error("Invalid source type: "+e.type)}return y(e)}))),i=new Map;return n.forEach((e=>{for(const[t,r]of e.entries())i.set(t,r)})),i}const j=async e=>JSON.parse((await s.readFile(e)).toString());function b(e,t){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((r=>{if($(r)){const[o,...s]=r.nodes.filter((e=>"div"!==e.type)),{value:n}=o,i=e.nodes.indexOf(r);if(t.has(n)){const r=t.get(n).nodes;!function(e,t,r){const o=new Map(t);o.delete(r),b(e,o)}({nodes:r},t,n),i>-1&&e.nodes.splice(i,1,...r)}else s.length&&(i>-1&&e.nodes.splice(i,1,...r.nodes.slice(r.nodes.indexOf(s[0]))),b(e,t))}else b(r,t)})),e.toString()}const O=/^var$/i,$=e=>"function"===e.type&&O.test(e.value)&&Object(e.nodes).length>0;var x=(t,r,o)=>{if(F(t)&&!l(t)){const s=t.value;let n=b(e(s),r);const i=new Set;for(;n.includes("--")&&n.includes("var(")&&!i.has(n);){i.add(n);n=b(e(n),r)}if(n!==s){if(function(e,t){if(!e||!e.parent)return!1;let r=!1;const o=e.parent.index(e);return e.parent.each(((s,n)=>s!==e&&(!(n>=o)&&void("decl"===s.type&&s.prop.toLowerCase()===e.prop.toLowerCase()&&s.value===t&&(r=!0))))),r}(t,n))return void(o.preserve||t.remove());if(o.preserve){const e=t.cloneBefore({value:n});P(e)&&(e.raws.value.value=e.value.replace(S,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(S,"$2"))}else t.value=n,P(t)&&(t.raws.value.value=t.value.replace(S,"$1"),t.raws.value.raw=t.raws.value.value+t.raws.value.raw.replace(S,"$2"))}}};const F=e=>!e.variable&&e.value.includes("--")&&e.value.includes("var("),P=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&S.test(e.raws.value.raw),S=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;async function k(e,t,r){"css"===t&&await async function(e,t){const r=`:root {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t${r}: ${t[r]};`),e)),[]).join("\n")}\n}\n`;await s.writeFile(e,r)}(e,r),"scss"===t&&await async function(e,t){const r=`${Object.keys(t).reduce(((e,r)=>{const o=r.replace("--","$");return e.push(`${o}: ${t[r]};`),e}),[]).join("\n")}\n`;await s.writeFile(e,r)}(e,r),"js"===t&&await async function(e,t){const r=`module.exports = {\n\tcustomProperties: {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t\t'${E(r)}': '${E(t[r])}'`),e)),[]).join(",\n")}\n\t}\n};\n`;await s.writeFile(e,r)}(e,r),"json"===t&&await async function(e,t){const r=`${JSON.stringify({"custom-properties":t},null," ")}\n`;await s.writeFile(e,r)}(e,r),"mjs"===t&&await async function(e,t){const r=`export const customProperties = {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t'${E(r)}': '${E(t[r])}'`),e)),[]).join(",\n")}\n};\n`;await s.writeFile(e,r)}(e,r)}function M(e){const t={};for(const[r,o]of e.entries())t[r]=o.toString();return t}const E=e=>e.replace(/\\([\s\S])|(')/g,"\\$1$2").replace(/\n/g,"\\n").replace(/\r/g,"\\r"),C=e=>{const r=!("preserve"in Object(e))||Boolean(e.preserve),o="overrideImportFromWithRoot"in Object(e)&&Boolean(e.overrideImportFromWithRoot),s="disableDeprecationNotice"in Object(e)&&Boolean(e.disableDeprecationNotice);let n=[];Array.isArray(null==e?void 0:e.importFrom)?n=e.importFrom:null!=e&&e.importFrom&&(n=[e.importFrom]);let i=[];Array.isArray(null==e?void 0:e.exportTo)?i=e.exportTo:null!=e&&e.exportTo&&(i=[e.exportTo]);const a=g(n),c=0===n.length&&0===i.length;return{postcssPlugin:"postcss-custom-properties",prepare(){let e=new Map;return c?{Once:t=>{e=p(t,{preserve:r})},Declaration:t=>{x(t,e,{preserve:r})},OnceExit:()=>{e.clear()}}:{Once:async s=>{const n=(await a).entries(),c=p(s,{preserve:r}).entries();if(o)for(const[t,r]of[...n,...c])e.set(t,r);else for(const[t,r]of[...c,...n])e.set(t,r);await function(e,r){return Promise.all(r.map((async r=>{if(r instanceof Function)return void await r(M(e));if("string"==typeof r){const o=t.resolve(r),s=t.extname(o).slice(1).toLowerCase();return void await k(o,s,M(e))}let o={};if(o="toJSON"in r?r.toJSON(M(e)):M(e),"to"in r){const e=t.resolve(r.to);let s=r.type;return s||(s=t.extname(e).slice(1).toLowerCase()),void await k(e,s,o)}"customProperties"in r?r.customProperties=o:"custom-properties"in r&&(r["custom-properties"]=o)})))}(e,i)},Declaration:t=>{x(t,e,{preserve:r})},OnceExit:(t,{result:r})=>{!s&&(n.length>0||i.length>0)&&t.warn(r,'"importFrom" and "exportTo" will be removed in a future version of postcss-custom-properties.\nWe are looking for insights and anecdotes on how these features are used so that we can design the best alternative.\nPlease let us know if our proposal will work for you.\nVisit the discussion on github for more details. https://github.com/csstools/postcss-plugins/discussions/192'),e.clear()}}}}};C.postcss=!0;export{C as default}; +import e from"postcss-value-parser";import t from"path";import{pathToFileURL as r}from"url";import{parse as o}from"postcss";import{promises as s}from"fs";const n=/(!\s*)?postcss-custom-properties:\s*off\b/i,i=new WeakMap;function a(e){if(!e||!e.nodes)return!1;if(i.has(e))return i.get(e);const t=e.some((e=>u(e,n)));return i.set(e,t),t}const c=/(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function p(e){return!!e&&(!!a(e.parent)||u(e.prev(),c))}function u(e,t){return e&&"comment"===e.type&&t.test(e.text)}function l(t,r){const o=new Map,s=new Map,{preserve:n}=r;t.nodes.slice().forEach((e=>{if(a(e))return;const t=w(e)?o:v(e)?s:null;t&&(e.nodes.slice().forEach((e=>{const r=!1===n||"function"==typeof n&&!n(e);if(e.variable&&!p(e)){const{prop:o}=e;t.set(o,e.value),r&&e.remove()}})),!n&&d(e)&&e.remove())}));const i=new Map;for(const[t,r]of o.entries())i.set(t,e(r));for(const[t,r]of s.entries())i.set(t,e(r));return i}const f=/^html$/i,m=/^:root$/i,w=e=>"rule"===e.type&&e.selector.split(",").some((e=>f.test(e)))&&Object(e.nodes).length,v=e=>"rule"===e.type&&e.selector.split(",").some((e=>m.test(e)))&&Object(e.nodes).length,d=e=>0===Object(e.nodes).length;function y(t){const r=new Map;if("customProperties"in t)for(const[o,s]of Object.entries(t.customProperties))r.set(o,e(s.toString()));if("custom-properties"in t)for(const[o,s]of Object.entries(t["custom-properties"]))r.set(o,e(s.toString()));return r}async function h(e){let t;try{t=await import(e)}catch(o){t=await import(r(e).href)}return y("default"in t?t.default:t)}async function g(e){const r=(await Promise.all(e.map((async e=>{if(e instanceof Promise?e=await e:e instanceof Function&&(e=await e()),"string"==typeof e){const r=t.resolve(e);return{type:t.extname(r).slice(1).toLowerCase(),from:r}}if("customProperties"in e&&Object(e.customProperties)===e.customProperties)return e;if("custom-properties"in e&&Object(e["custom-properties"])===e["custom-properties"])return e;if("from"in e){const r=t.resolve(e.from);let o=e.type;return o||(o=t.extname(r).slice(1).toLowerCase()),{type:o,from:r}}return Object.keys(e).length,null})))).filter((e=>!!e)),n=await Promise.all(r.map((async e=>{if("type"in e&&"from"in e){if("css"===e.type||"pcss"===e.type)return await async function(e){const t=await s.readFile(e);return l(o(t,{from:e.toString()}),{preserve:!0})}(e.from);if("js"===e.type||"cjs"===e.type)return await h(e.from);if("mjs"===e.type)return await h(e.from);if("json"===e.type)return await async function(e){return y(await j(e))}(e.from);throw new Error("Invalid source type: "+e.type)}return y(e)}))),i=new Map;return n.forEach((e=>{for(const[t,r]of e.entries())i.set(t,r)})),i}const j=async e=>JSON.parse((await s.readFile(e)).toString());function b(e,t){return e.nodes&&e.nodes.length&&e.nodes.slice().forEach((r=>{if($(r)){const[o,...s]=r.nodes.filter((e=>"div"!==e.type)),{value:n}=o,i=e.nodes.indexOf(r);if(t.has(n)){const r=t.get(n).nodes;!function(e,t,r){const o=new Map(t);o.delete(r),b(e,o)}({nodes:r},t,n),i>-1&&e.nodes.splice(i,1,...r)}else s.length&&(i>-1&&e.nodes.splice(i,1,...r.nodes.slice(r.nodes.indexOf(s[0]))),b(e,t))}else b(r,t)})),e.toString()}const O=/^var$/i,$=e=>"function"===e.type&&O.test(e.value)&&Object(e.nodes).length>0;var x=(t,r,o)=>{if(F(t)&&!p(t)){const s=t.value;let n=b(e(s),r);const i=new Set;for(;n.includes("--")&&n.includes("var(")&&!i.has(n);){i.add(n);n=b(e(n),r)}if(n!==s){if(function(e,t){if(!e||!e.parent)return!1;let r=!1;const o=e.parent.index(e);return e.parent.each(((s,n)=>s!==e&&(!(n>=o)&&void("decl"===s.type&&s.prop.toLowerCase()===e.prop.toLowerCase()&&s.value===t&&(r=!0))))),r}(t,n))return void(o.preserve||t.remove());if(!o.preserve||"function"==typeof o.preserve&&!o.preserve(t))t.value=n,P(t)&&(t.raws.value.value=t.value.replace(S,"$1"),t.raws.value.raw=t.raws.value.value+t.raws.value.raw.replace(S,"$2"));else if(!0===o.preserve||o.preserve(t)){const e=t.cloneBefore({value:n});P(e)&&(e.raws.value.value=e.value.replace(S,"$1"),e.raws.value.raw=e.raws.value.value+e.raws.value.raw.replace(S,"$2"))}}}};const F=e=>!e.variable&&e.value.includes("--")&&e.value.includes("var("),P=e=>"value"in Object(Object(e.raws).value)&&"raw"in e.raws.value&&S.test(e.raws.value.raw),S=/^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;async function k(e,t,r){"css"===t&&await async function(e,t){const r=`:root {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t${r}: ${t[r]};`),e)),[]).join("\n")}\n}\n`;await s.writeFile(e,r)}(e,r),"scss"===t&&await async function(e,t){const r=`${Object.keys(t).reduce(((e,r)=>{const o=r.replace("--","$");return e.push(`${o}: ${t[r]};`),e}),[]).join("\n")}\n`;await s.writeFile(e,r)}(e,r),"js"===t&&await async function(e,t){const r=`module.exports = {\n\tcustomProperties: {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t\t'${E(r)}': '${E(t[r])}'`),e)),[]).join(",\n")}\n\t}\n};\n`;await s.writeFile(e,r)}(e,r),"json"===t&&await async function(e,t){const r=`${JSON.stringify({"custom-properties":t},null," ")}\n`;await s.writeFile(e,r)}(e,r),"mjs"===t&&await async function(e,t){const r=`export const customProperties = {\n${Object.keys(t).reduce(((e,r)=>(e.push(`\t'${E(r)}': '${E(t[r])}'`),e)),[]).join(",\n")}\n};\n`;await s.writeFile(e,r)}(e,r)}function M(e){const t={};for(const[r,o]of e.entries())t[r]=o.toString();return t}const E=e=>e.replace(/\\([\s\S])|(')/g,"\\$1$2").replace(/\n/g,"\\n").replace(/\r/g,"\\r"),C=e=>{const r=!("preserve"in Object(e))||("function"==typeof e.preserve?e.preserve:Boolean(e.preserve)),o="overrideImportFromWithRoot"in Object(e)&&Boolean(e.overrideImportFromWithRoot),s="disableDeprecationNotice"in Object(e)&&Boolean(e.disableDeprecationNotice);let n=[];Array.isArray(null==e?void 0:e.importFrom)?n=e.importFrom:null!=e&&e.importFrom&&(n=[e.importFrom]);let i=[];Array.isArray(null==e?void 0:e.exportTo)?i=e.exportTo:null!=e&&e.exportTo&&(i=[e.exportTo]);const a=g(n),c=0===n.length&&0===i.length;return{postcssPlugin:"postcss-custom-properties",prepare(){let e=new Map;return c?{Once:t=>{e=l(t,{preserve:r})},Declaration:t=>{x(t,e,{preserve:r})},OnceExit:()=>{e.clear()}}:{Once:async s=>{const n=(await a).entries(),c=l(s,{preserve:r}).entries();if(o)for(const[t,r]of[...n,...c])e.set(t,r);else for(const[t,r]of[...c,...n])e.set(t,r);await function(e,r){return Promise.all(r.map((async r=>{if(r instanceof Function)return void await r(M(e));if("string"==typeof r){const o=t.resolve(r),s=t.extname(o).slice(1).toLowerCase();return void await k(o,s,M(e))}let o={};if(o="toJSON"in r?r.toJSON(M(e)):M(e),"to"in r){const e=t.resolve(r.to);let s=r.type;return s||(s=t.extname(e).slice(1).toLowerCase()),void await k(e,s,o)}"customProperties"in r?r.customProperties=o:"custom-properties"in r&&(r["custom-properties"]=o)})))}(e,i)},Declaration:t=>{x(t,e,{preserve:r})},OnceExit:(t,{result:r})=>{!s&&(n.length>0||i.length>0)&&t.warn(r,'"importFrom" and "exportTo" will be removed in a future version of postcss-custom-properties.\nWe are looking for insights and anecdotes on how these features are used so that we can design the best alternative.\nPlease let us know if our proposal will work for you.\nVisit the discussion on github for more details. https://github.com/csstools/postcss-plugins/discussions/192'),e.clear()}}}}};C.postcss=!0;export{C as default}; diff --git a/plugins/postcss-custom-properties/dist/lib/get-custom-properties-from-root.d.ts b/plugins/postcss-custom-properties/dist/lib/get-custom-properties-from-root.d.ts index 11f31cd31..80a9ab2c8 100644 --- a/plugins/postcss-custom-properties/dist/lib/get-custom-properties-from-root.d.ts +++ b/plugins/postcss-custom-properties/dist/lib/get-custom-properties-from-root.d.ts @@ -1,2 +1,4 @@ +import type { Root } from 'postcss'; import valuesParser from 'postcss-value-parser'; -export default function getCustomPropertiesFromRoot(root: any, opts: any): Map; +import { PluginOptions } from './options'; +export default function getCustomPropertiesFromRoot(root: Root, opts: Pick): Map; diff --git a/plugins/postcss-custom-properties/dist/lib/options.d.ts b/plugins/postcss-custom-properties/dist/lib/options.d.ts index 80d5c83a9..586041960 100644 --- a/plugins/postcss-custom-properties/dist/lib/options.d.ts +++ b/plugins/postcss-custom-properties/dist/lib/options.d.ts @@ -1,3 +1,4 @@ +import { Declaration } from 'postcss'; export type ImportFromSource = { from: string; type?: string; @@ -24,3 +25,16 @@ export type ExportCustomProperties = { export type ExportAsFunction = (ExportCustomProperties: any) => void; export type ExportAsFunctionPromise = (ExportCustomProperties: any) => Promise; export type ExportOptions = ExportToSource | ExportCustomProperties | ExportAsFunction | ExportAsFunctionPromise; +export type PreserveOptions = boolean | ((declaration: Declaration) => boolean); +export interface PluginOptions { + /** Do not emit warnings about "importFrom" and "exportTo" deprecations */ + disableDeprecationNotice?: boolean; + /** Determines whether Custom Properties and properties using custom properties should be preserved in their original form. */ + preserve?: PreserveOptions; + /** Specifies sources where Custom Properties can be imported from, which might be CSS, JS, and JSON files, functions, and directly passed objects. */ + importFrom?: ImportOptions | Array; + /** Specifies destinations where Custom Properties can be exported to, which might be CSS, JS, and JSON files, functions, and directly passed objects. */ + exportTo?: ExportOptions | Array; + /** Specifies if `importFrom` properties or `:root` properties have priority. */ + overrideImportFromWithRoot?: boolean; +} diff --git a/plugins/postcss-custom-properties/dist/lib/transform-properties.d.ts b/plugins/postcss-custom-properties/dist/lib/transform-properties.d.ts index d176d5141..d012884ba 100644 --- a/plugins/postcss-custom-properties/dist/lib/transform-properties.d.ts +++ b/plugins/postcss-custom-properties/dist/lib/transform-properties.d.ts @@ -1,2 +1,5 @@ -declare const _default: (decl: any, customProperties: any, opts: any) => void; +import { ParsedValue } from 'postcss-value-parser'; +import type { PluginOptions } from './options'; +import type { Declaration } from 'postcss'; +declare const _default: (decl: Declaration, customProperties: Map, opts: Pick) => void; export default _default; diff --git a/plugins/postcss-custom-properties/dist/lib/transform-value-ast.d.ts b/plugins/postcss-custom-properties/dist/lib/transform-value-ast.d.ts index 0306fd915..9135a0c1d 100644 --- a/plugins/postcss-custom-properties/dist/lib/transform-value-ast.d.ts +++ b/plugins/postcss-custom-properties/dist/lib/transform-value-ast.d.ts @@ -1 +1,2 @@ -export default function transformValueAST(root: any, customProperties: any): any; +import type { ParsedValue } from 'postcss-value-parser'; +export default function transformValueAST(root: ParsedValue, customProperties: Map): string; diff --git a/plugins/postcss-custom-properties/src/index.ts b/plugins/postcss-custom-properties/src/index.ts index 22b2f1187..3ba903588 100644 --- a/plugins/postcss-custom-properties/src/index.ts +++ b/plugins/postcss-custom-properties/src/index.ts @@ -5,27 +5,15 @@ import getCustomPropertiesFromRoot from './lib/get-custom-properties-from-root'; import getCustomPropertiesFromImports from './lib/get-custom-properties-from-imports'; import transformProperties from './lib/transform-properties'; import writeCustomPropertiesToExports from './lib/write-custom-properties-to-exports'; -import type { ImportOptions, ExportOptions } from './lib/options'; - -export interface PluginOptions { - /** Do not emit warnings about "importFrom" and "exportTo" deprecations */ - disableDeprecationNotice?: boolean; - /** Determines whether Custom Properties and properties using custom properties should be preserved in their original form. */ - preserve?: boolean - - /** Specifies sources where Custom Properties can be imported from, which might be CSS, JS, and JSON files, functions, and directly passed objects. */ - importFrom?: ImportOptions | Array - - /** Specifies destinations where Custom Properties can be exported to, which might be CSS, JS, and JSON files, functions, and directly passed objects. */ - exportTo?: ExportOptions | Array - - /** Specifies if `importFrom` properties or `:root` properties have priority. */ - overrideImportFromWithRoot?: boolean -} +import type { ImportOptions, ExportOptions, PluginOptions } from './lib/options'; const creator: PluginCreator = (opts?: PluginOptions) => { // whether to preserve custom selectors and rules using them - const preserve = 'preserve' in Object(opts) ? Boolean(opts.preserve) : true; + const preserve = 'preserve' in Object(opts) + ? (typeof opts.preserve === 'function' + ? opts.preserve + : Boolean(opts.preserve)) + : true; const overrideImportFromWithRoot = 'overrideImportFromWithRoot' in Object(opts) ? Boolean(opts.overrideImportFromWithRoot) : false; const disableDeprecationNotice = 'disableDeprecationNotice' in Object(opts) ? Boolean(opts.disableDeprecationNotice) : false; @@ -72,7 +60,7 @@ const creator: PluginCreator = (opts?: PluginOptions) => { return { Once: async root => { const importedCustomerProperties = (await customPropertiesPromise).entries(); - const rootCustomProperties = getCustomPropertiesFromRoot(root, { preserve: preserve }).entries(); + const rootCustomProperties = getCustomPropertiesFromRoot(root, { preserve }).entries(); if (overrideImportFromWithRoot) { for (const [name, value] of [...importedCustomerProperties, ...rootCustomProperties]) { diff --git a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-root.ts b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-root.ts index 1ea953d63..2f67bb8a1 100644 --- a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-root.ts +++ b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-root.ts @@ -1,14 +1,18 @@ +import type { Declaration, Root, Rule } from 'postcss'; + import valuesParser from 'postcss-value-parser'; import { isBlockIgnored, isDeclarationIgnored } from './is-ignored'; +import { PluginOptions } from './options'; // return custom selectors from the css root, conditionally removing them -export default function getCustomPropertiesFromRoot(root, opts): Map { +export default function getCustomPropertiesFromRoot(root: Root, opts: Pick): Map { // initialize custom selectors const customPropertiesFromHtmlElement: Map = new Map(); const customPropertiesFromRootPseudo: Map = new Map(); + const { preserve } = opts; // for each html or :root rule - root.nodes.slice().forEach(rule => { + root.nodes.slice().forEach((rule: Rule) => { if (isBlockIgnored(rule)) { return; } @@ -21,7 +25,10 @@ export default function getCustomPropertiesFromRoot(root, opts): Map { + rule.nodes.slice().forEach((decl: Declaration) => { + const declarationCanBeRemoved = preserve === false || + (typeof preserve === 'function' && !preserve(decl)); + if (decl.variable && !isDeclarationIgnored(decl)) { const { prop } = decl; @@ -29,14 +36,14 @@ export default function getCustomPropertiesFromRoot(root, opts): Map, 'custom-properties'?: Record }; @@ -12,3 +13,22 @@ export type ExportCustomProperties = { customProperties?: Record export type ExportAsFunction = (ExportCustomProperties) => void export type ExportAsFunctionPromise = (ExportCustomProperties) => Promise export type ExportOptions = ExportToSource | ExportCustomProperties | ExportAsFunction | ExportAsFunctionPromise; + +export type PreserveOptions = boolean | ((declaration: Declaration) => boolean); + +export interface PluginOptions { + /** Do not emit warnings about "importFrom" and "exportTo" deprecations */ + disableDeprecationNotice?: boolean; + + /** Determines whether Custom Properties and properties using custom properties should be preserved in their original form. */ + preserve?: PreserveOptions; + + /** Specifies sources where Custom Properties can be imported from, which might be CSS, JS, and JSON files, functions, and directly passed objects. */ + importFrom?: ImportOptions | Array + + /** Specifies destinations where Custom Properties can be exported to, which might be CSS, JS, and JSON files, functions, and directly passed objects. */ + exportTo?: ExportOptions | Array + + /** Specifies if `importFrom` properties or `:root` properties have priority. */ + overrideImportFromWithRoot?: boolean +} diff --git a/plugins/postcss-custom-properties/src/lib/transform-properties.ts b/plugins/postcss-custom-properties/src/lib/transform-properties.ts index f0ac25055..6675a6ac2 100644 --- a/plugins/postcss-custom-properties/src/lib/transform-properties.ts +++ b/plugins/postcss-custom-properties/src/lib/transform-properties.ts @@ -1,10 +1,11 @@ -import valuesParser from 'postcss-value-parser'; +import valuesParser, { ParsedValue } from 'postcss-value-parser'; +import type { PluginOptions } from './options'; import transformValueAST from './transform-value-ast'; -import { Declaration } from 'postcss'; +import type { Declaration } from 'postcss'; import { isDeclarationIgnored } from './is-ignored'; // transform custom pseudo selectors with custom selectors -export default (decl, customProperties, opts) => { +export default (decl: Declaration, customProperties: Map, opts: Pick) => { if (isTransformableDecl(decl) && !isDeclarationIgnored(decl)) { const originalValue = decl.value; const valueAST = valuesParser(originalValue); @@ -29,20 +30,20 @@ export default (decl, customProperties, opts) => { return; } - if (opts.preserve) { - const beforeDecl = decl.cloneBefore({ value }); - - if (hasTrailingComment(beforeDecl)) { - beforeDecl.raws.value.value = beforeDecl.value.replace(trailingCommentRegExp, '$1'); - beforeDecl.raws.value.raw = beforeDecl.raws.value.value + beforeDecl.raws.value.raw.replace(trailingCommentRegExp, '$2'); - } - } else { + if (!opts.preserve || (typeof opts.preserve === 'function' && !opts.preserve(decl))) { decl.value = value; if (hasTrailingComment(decl)) { decl.raws.value.value = decl.value.replace(trailingCommentRegExp, '$1'); decl.raws.value.raw = decl.raws.value.value + decl.raws.value.raw.replace(trailingCommentRegExp, '$2'); } + } else if (opts.preserve === true || opts.preserve(decl)) { + const beforeDecl = decl.cloneBefore({ value }); + + if (hasTrailingComment(beforeDecl)) { + beforeDecl.raws.value.value = beforeDecl.value.replace(trailingCommentRegExp, '$1'); + beforeDecl.raws.value.raw = beforeDecl.raws.value.value + beforeDecl.raws.value.raw.replace(trailingCommentRegExp, '$2'); + } } } } diff --git a/plugins/postcss-custom-properties/src/lib/transform-value-ast.ts b/plugins/postcss-custom-properties/src/lib/transform-value-ast.ts index 10d42b10b..bafe85d81 100644 --- a/plugins/postcss-custom-properties/src/lib/transform-value-ast.ts +++ b/plugins/postcss-custom-properties/src/lib/transform-value-ast.ts @@ -1,7 +1,9 @@ -export default function transformValueAST(root, customProperties) { +import type { FunctionNode, Node, ParsedValue } from 'postcss-value-parser'; + +export default function transformValueAST(root: ParsedValue, customProperties: Map) { if (root.nodes && root.nodes.length) { - root.nodes.slice().forEach((child) => { - if (isVarFunction(child)) { + root.nodes.slice().forEach((child: Node & ParsedValue) => { + if (isFunctionNode(child)) { const [propertyNode, ...fallbacks] = child.nodes.filter((node) => node.type !== 'div'); const { value: name } = propertyNode; const index = root.nodes.indexOf(child); @@ -35,7 +37,7 @@ export default function transformValueAST(root, customProperties) { } // reTransform the current ast without a custom property (to prevent recursion) -function reTransformValueAST(root, customProperties, withoutProperty) { +function reTransformValueAST(root, customProperties: Map, withoutProperty) { const nextCustomProperties = new Map(customProperties); nextCustomProperties.delete(withoutProperty); @@ -47,4 +49,4 @@ function reTransformValueAST(root, customProperties, withoutProperty) { const varRegExp = /^var$/i; // whether the node is a var() function -const isVarFunction = node => node.type === 'function' && varRegExp.test(node.value) && Object(node.nodes).length > 0; +const isFunctionNode = (node): node is FunctionNode => node.type === 'function' && varRegExp.test(node.value) && Object(node.nodes).length > 0; diff --git a/plugins/postcss-custom-properties/test/basic.preserve-function.expect.css b/plugins/postcss-custom-properties/test/basic.preserve-function.expect.css new file mode 100644 index 000000000..e8735382e --- /dev/null +++ b/plugins/postcss-custom-properties/test/basic.preserve-function.expect.css @@ -0,0 +1,151 @@ +html { +} + +:root { + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + color: rgb(255, 0, 0); + color: var(--color); +} + +:root, +[data-theme=light] { +} + +.ignore-line { + /* postcss-custom-properties: ignore next */ + color: var(--color); + background-color: blue; + background-color: var(--color-2, blue); +} + +.ignore-block { + /* postcss-custom-properties: off */ + color: var(--color-2, blue); + box-shadow: inset 0 -3px 0 var(--color); + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test { + --skip: gray; + color: rgb(255, 0, 0); + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 rgb(255, 0, 0); + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: 0 10px 20px 30px; +} + +.test--complex_values { + box-shadow: 0 6px 14px 0 color(rgb(255,0,0) a(.15)); +} + +.test--comma_separated_values { + font-family: "Open Sans", sans-serif; +} + +.test--fallback { + color: blue; + color: var(--color-2, blue); +} + +.test--color_w_var { + color: rgb(255, 0, 0); +} + +.test--color_w_vars { + color: hsl(0, 100%, 50%); + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: 1; +} + +.text--calc { + width: calc((100% - 1px) + 10px); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(255, 0, 0) 100%); + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: rgb(255, 0, 0)/*rtl:red*/; + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: #053; +} + +.test--variable-with-url { + order: 1; + background: url("/my/path"); +} + +.test--variable-with-url { + order: 2; + background: url('/my/path'); +} + + +.test--variable-with-url { + order: 3; + background: url(/my/path); +} + + +.test--variable-with-url { + order: 4; + background: url(data:image/png;bm90LWFuZC1pbWFnZQ==); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: 2em; +} + +.test { + font-family: "Helvetica Neue", Arial, sans-serif; +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: 1rem; + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: 2em; + right: 2em; +} diff --git a/plugins/postcss-custom-properties/test/import.preserve-function-filename.expect.css b/plugins/postcss-custom-properties/test/import.preserve-function-filename.expect.css new file mode 100644 index 000000000..05be7c13e --- /dev/null +++ b/plugins/postcss-custom-properties/test/import.preserve-function-filename.expect.css @@ -0,0 +1,7 @@ +:root { + --color: blue; +} + +a { + color: blue; +}