From d738eed789ba45758a8a919823c6f0986230720b Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sun, 9 Jan 2022 13:37:33 +0100 Subject: [PATCH 1/6] postcss-custom-properties: fix types --- plugins/postcss-custom-properties/.tape.js | 29 +--- .../postcss-custom-properties/CHANGELOG.md | 5 + plugins/postcss-custom-properties/index.d.ts | 39 ----- .../postcss-custom-properties/package.json | 2 +- .../postcss-custom-properties/src/index.js | 54 ------- .../postcss-custom-properties/src/index.ts | 87 +++++++++++ .../lib/get-custom-properties-from-imports.js | 110 ------------- .../lib/get-custom-properties-from-imports.ts | 141 +++++++++++++++++ ....js => get-custom-properties-from-root.ts} | 19 ++- .../src/lib/{is-ignored.js => is-ignored.ts} | 4 +- .../src/lib/options.ts | 14 ++ ...-properties.js => transform-properties.ts} | 0 ...rm-value-ast.js => transform-value-ast.ts} | 14 +- ... => write-custom-properties-to-exports.ts} | 144 ++++++++++-------- .../postcss-custom-properties/test/basic.css | 4 + .../test/basic.expect.css | 4 + .../test/basic.import-is-empty.expect.css | 4 + .../test/basic.import-override.expect.css | 4 + .../test/basic.import.expect.css | 4 + .../test/basic.preserve.expect.css | 4 + .../postcss-custom-properties/tsconfig.json | 10 ++ rollup/presets/package-typescript.js | 2 +- 22 files changed, 391 insertions(+), 307 deletions(-) delete mode 100644 plugins/postcss-custom-properties/index.d.ts delete mode 100755 plugins/postcss-custom-properties/src/index.js create mode 100755 plugins/postcss-custom-properties/src/index.ts delete mode 100644 plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.js create mode 100644 plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts rename plugins/postcss-custom-properties/src/lib/{get-custom-properties-from-root.js => get-custom-properties-from-root.ts} (74%) rename plugins/postcss-custom-properties/src/lib/{is-ignored.js => is-ignored.ts} (84%) create mode 100644 plugins/postcss-custom-properties/src/lib/options.ts rename plugins/postcss-custom-properties/src/lib/{transform-properties.js => transform-properties.ts} (100%) rename plugins/postcss-custom-properties/src/lib/{transform-value-ast.js => transform-value-ast.ts} (76%) rename plugins/postcss-custom-properties/src/lib/{write-custom-properties-to-exports.js => write-custom-properties-to-exports.ts} (50%) create mode 100644 plugins/postcss-custom-properties/tsconfig.json diff --git a/plugins/postcss-custom-properties/.tape.js b/plugins/postcss-custom-properties/.tape.js index c5593174f..99353af0c 100644 --- a/plugins/postcss-custom-properties/.tape.js +++ b/plugins/postcss-custom-properties/.tape.js @@ -1,4 +1,5 @@ const importWithProps = require('./test/import'); +const assert = require('assert').strict; module.exports = { 'basic': { @@ -207,9 +208,7 @@ module.exports = { global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.scss', 'utf8'); }, after() { - if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.scss', 'utf8')) { - throw new Error('The original file did not match the freshly exported copy'); - } + assert.strictEqual(global.__exportPropertiesString, require('fs').readFileSync('test/export-properties.scss', 'utf8')); } }, 'basic:export-json': { @@ -223,9 +222,7 @@ module.exports = { global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.json', 'utf8'); }, after() { - if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.json', 'utf8')) { - throw new Error('The original file did not match the freshly exported copy'); - } + assert.strictEqual(global.__exportPropertiesString, require('fs').readFileSync('test/export-properties.json', 'utf8')); } }, 'basic:export-js': { @@ -239,9 +236,7 @@ module.exports = { global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.js', 'utf8'); }, after() { - if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.js', 'utf8')) { - throw new Error('The original file did not match the freshly exported copy'); - } + assert.strictEqual(global.__exportPropertiesString, require('fs').readFileSync('test/export-properties.js', 'utf8')); } }, 'basic:export-mjs': { @@ -255,9 +250,7 @@ module.exports = { global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.mjs', 'utf8'); }, after() { - if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.mjs', 'utf8')) { - throw new Error('The original file did not match the freshly exported copy'); - } + assert.strictEqual(global.__exportPropertiesString, require('fs').readFileSync('test/export-properties.mjs', 'utf8')); } }, 'basic:export-css': { @@ -271,9 +264,7 @@ module.exports = { global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.css', 'utf8'); }, after() { - if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.css', 'utf8')) { - throw new Error('The original file did not match the freshly exported copy'); - } + assert.strictEqual(global.__exportPropertiesString, require('fs').readFileSync('test/export-properties.css', 'utf8')); } }, 'basic:export-css-to': { @@ -287,9 +278,7 @@ module.exports = { global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.css', 'utf8'); }, after() { - if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.css', 'utf8')) { - throw new Error('The original file did not match the freshly exported copy'); - } + assert.strictEqual(global.__exportPropertiesString, require('fs').readFileSync('test/export-properties.css', 'utf8')); } }, 'basic:export-css-to-type': { @@ -303,9 +292,7 @@ module.exports = { global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.css', 'utf8'); }, after() { - if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.css', 'utf8')) { - throw new Error('The original file did not match the freshly exported copy'); - } + assert.strictEqual(global.__exportPropertiesString, require('fs').readFileSync('test/export-properties.css', 'utf8')); } }, 'basic:import-is-empty': { diff --git a/plugins/postcss-custom-properties/CHANGELOG.md b/plugins/postcss-custom-properties/CHANGELOG.md index 6448b28fb..838d82df8 100755 --- a/plugins/postcss-custom-properties/CHANGELOG.md +++ b/plugins/postcss-custom-properties/CHANGELOG.md @@ -1,5 +1,10 @@ # Changes to PostCSS Custom Properties +### Unreleased + +- Converted to typescript +- Correct typings for plugin options + ### 12.0.4 (January 7, 2022) - Fixed an issue that was causing synchronous mode to not being able to pick and transform properties that were added as part of the PostCSS flow. ([#132](https://github.com/csstools/postcss-plugins/issues/132)) diff --git a/plugins/postcss-custom-properties/index.d.ts b/plugins/postcss-custom-properties/index.d.ts deleted file mode 100644 index af2437344..000000000 --- a/plugins/postcss-custom-properties/index.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type * as PostCSS from 'postcss' - -type ImportExportObject = { customProperties: {} } -type ImportExportFunction = (customProperties?: {}) => ImportExportObject -type ImportExportFilepath = string - -type ImportExport = ImportExportFilepath | ImportExportObject | ImportExportFunction | (ImportExportFilepath | ImportExportObject | ImportExportFunction)[] - -export interface PluginOptions { - /** 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?: ImportExport - - /** Specifies destinations where Custom Properties can be exported to, which might be CSS, JS, and JSON files, functions, and directly passed objects. */ - exportTo?: ImportExport -} - -export interface Plugin { - (options?: PluginOptions): { - postcssPlugin: 'postcss-custom-properties', - prepare({ root }: { root: any }): ( - | { - Declaration: (decl: any) => void; - Once?: undefined; - } - | { - Once: (root: any) => Promise; - Declaration: (decl: any) => void; - } - ) - }, - postcss: true -} - -declare const plugin: Plugin - -export default plugin diff --git a/plugins/postcss-custom-properties/package.json b/plugins/postcss-custom-properties/package.json index 47ece39f6..79d5f02e1 100644 --- a/plugins/postcss-custom-properties/package.json +++ b/plugins/postcss-custom-properties/package.json @@ -11,7 +11,7 @@ "bugs": "https://github.com/csstools/postcss-plugins/issues", "main": "dist/index.cjs", "module": "dist/index.mjs", - "types": "index.d.ts", + "types": "dist/index.d.ts", "files": [ "CHANGELOG.md", "LICENSE.md", diff --git a/plugins/postcss-custom-properties/src/index.js b/plugins/postcss-custom-properties/src/index.js deleted file mode 100755 index cc750fc91..000000000 --- a/plugins/postcss-custom-properties/src/index.js +++ /dev/null @@ -1,54 +0,0 @@ -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'; - -const creator = opts => { - // whether to preserve custom selectors and rules using them - const preserve = 'preserve' in Object(opts) ? Boolean(opts.preserve) : true; - - // sources to import custom selectors from - const importFrom = [].concat(Object(opts).importFrom || []); - - // destinations to export custom selectors to - const exportTo = [].concat(Object(opts).exportTo || []); - - // promise any custom selectors are imported - const customPropertiesPromise = getCustomPropertiesFromImports(importFrom); - - let customProperties; - - // whether to return synchronous function if no asynchronous operations are requested - const canReturnSyncFunction = importFrom.length === 0 && exportTo.length === 0; - - return { - postcssPlugin: 'postcss-custom-properties', - prepare () { - if (canReturnSyncFunction) { - return { - Once: (root) => { - customProperties = getCustomPropertiesFromRoot(root, { preserve }); - }, - Declaration: (decl) => transformProperties(decl, customProperties, { preserve }), - }; - } else { - return { - Once: async root => { - customProperties = Object.assign( - {}, - getCustomPropertiesFromRoot(root, { preserve }), - await customPropertiesPromise, - ); - - await writeCustomPropertiesToExports(customProperties, exportTo); - }, - Declaration: (decl) => transformProperties(decl, customProperties, { preserve }), - }; - } - }, - }; -}; - -creator.postcss = true; - -export default creator; diff --git a/plugins/postcss-custom-properties/src/index.ts b/plugins/postcss-custom-properties/src/index.ts new file mode 100755 index 000000000..b568c78f3 --- /dev/null +++ b/plugins/postcss-custom-properties/src/index.ts @@ -0,0 +1,87 @@ +import type { PluginCreator } from 'postcss'; +import type valuesParser from 'postcss-value-parser'; + +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 { + /** 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 +} + +const creator: PluginCreator = (opts?: PluginOptions) => { + // whether to preserve custom selectors and rules using them + const preserve = 'preserve' in Object(opts) ? Boolean(opts.preserve) : true; + + // sources to import custom selectors from + let importFrom: Array = []; + if (Array.isArray(opts?.importFrom)) { + importFrom = opts.importFrom; + } else if (opts?.importFrom) { + importFrom = [opts.importFrom]; + } + + // destinations to export custom selectors to + let exportTo: Array = []; + if (Array.isArray(opts?.exportTo)) { + exportTo = opts.exportTo; + } else if (opts?.exportTo) { + exportTo = [opts.exportTo]; + } + + // promise any custom selectors are imported + const customPropertiesPromise = getCustomPropertiesFromImports(importFrom); + + let customProperties: Map = new Map(); + + // whether to return synchronous function if no asynchronous operations are requested + const canReturnSyncFunction = importFrom.length === 0 && exportTo.length === 0; + + return { + postcssPlugin: 'postcss-custom-properties', + prepare () { + if (canReturnSyncFunction) { + return { + Once: (root) => { + customProperties = getCustomPropertiesFromRoot(root, { preserve }); + }, + Declaration: (decl) => { + transformProperties(decl, customProperties, { preserve }); + }, + }; + } else { + return { + Once: async root => { + const rootCustomProperties = getCustomPropertiesFromRoot(root, { preserve: preserve }); + for (const [name, value] of rootCustomProperties.entries()) { + customProperties.set(name, value); + } + + const importedCustomerProperties = await customPropertiesPromise; + for (const [name, value] of importedCustomerProperties.entries()) { + customProperties.set(name, value); + } + + await writeCustomPropertiesToExports(customProperties, exportTo); + }, + Declaration: (decl) => { + transformProperties(decl, customProperties, { preserve }); + }, + }; + } + }, + }; +}; + +creator.postcss = true; + +export default creator; diff --git a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.js b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.js deleted file mode 100644 index fa3656f58..000000000 --- a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.js +++ /dev/null @@ -1,110 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { parse } from 'postcss'; -import valuesParser from 'postcss-value-parser'; -import getCustomPropertiesFromRoot from './get-custom-properties-from-root'; - -/* Get Custom Properties from CSS File -/* ========================================================================== */ - -async function getCustomPropertiesFromCSSFile(from) { - const css = await readFile(from); - const root = parse(css, { from }); - - return getCustomPropertiesFromRoot(root, { preserve: true }); -} - -/* Get Custom Properties from Object -/* ========================================================================== */ - -function getCustomPropertiesFromObject(object) { - const customProperties = Object.assign( - {}, - Object(object).customProperties, - Object(object)['custom-properties'], - ); - - for (const key in customProperties) { - customProperties[key] = valuesParser(String(customProperties[key])); - } - - return customProperties; -} - -/* Get Custom Properties from JSON file -/* ========================================================================== */ - -async function getCustomPropertiesFromJSONFile(from) { - const object = await readJSON(from); - - return getCustomPropertiesFromObject(object); -} - -/* Get Custom Properties from JS file -/* ========================================================================== */ - -async function getCustomPropertiesFromJSFile(from) { - const object = await import(from); - - return getCustomPropertiesFromObject(object); -} - -/* Get Custom Properties from Imports -/* ========================================================================== */ - -export default function getCustomPropertiesFromImports(sources) { - return sources.map(source => { - if (source instanceof Promise) { - return source; - } else if (source instanceof Function) { - return source(); - } - - // read the source as an object - const opts = source === Object(source) ? source : { from: String(source) }; - - // skip objects with Custom Properties - if (opts.customProperties || opts['custom-properties']) { - return opts; - } - - // source pathname - const from = path.resolve(String(opts.from || '')); - - // type of file being read from - const type = (opts.type || path.extname(from).slice(1)).toLowerCase(); - - return { type, from }; - }).reduce(async (customProperties, source) => { - const { type, from } = await source; - - if (type === 'css' || type === 'pcss') { - return Object.assign(await customProperties, await getCustomPropertiesFromCSSFile(from)); - } - - if (type === 'js') { - return Object.assign(await customProperties, await getCustomPropertiesFromJSFile(from)); - } - - if (type === 'json') { - return Object.assign(await customProperties, await getCustomPropertiesFromJSONFile(from)); - } - - return Object.assign(await customProperties, await getCustomPropertiesFromObject(await source)); - }, {}); -} - -/* Helper utilities -/* ========================================================================== */ - -const readFile = from => new Promise((resolve, reject) => { - fs.readFile(from, 'utf8', (error, result) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); -}); - -const readJSON = async from => JSON.parse(await readFile(from)); diff --git a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts new file mode 100644 index 000000000..b1cbb8747 --- /dev/null +++ b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts @@ -0,0 +1,141 @@ +import { promises as fsp } from 'fs'; +import path from 'path'; +import { parse } from 'postcss'; +import valuesParser from 'postcss-value-parser'; +import getCustomPropertiesFromRoot from './get-custom-properties-from-root'; +import type { ImportCustomProperties, ImportOptions } from './options'; + +/* Get Custom Properties from CSS File +/* ========================================================================== */ + +async function getCustomPropertiesFromCSSFile(from) { + const css = await fsp.readFile(from); + const root = parse(css, { from : from.toString() }); + + return getCustomPropertiesFromRoot(root, { preserve: true }); +} + +/* Get Custom Properties from Object +/* ========================================================================== */ + +function getCustomPropertiesFromObject(object: ImportCustomProperties): Map { + const out: Map = new Map(); + + if ('customProperties' in object) { + for (const [name, value] of Object.entries(object.customProperties)) { + out.set(name, valuesParser(value.toString())); + } + } + + if ('custom-properties' in object) { + for (const [name, value] of Object.entries(object['custom-properties'])) { + out.set(name, valuesParser(value.toString())); + } + } + + return out; +} + +/* Get Custom Properties from JSON file +/* ========================================================================== */ + +async function getCustomPropertiesFromJSONFile(from): Promise> { + const object = await readJSON(from); + + return getCustomPropertiesFromObject(object); +} + +/* Get Custom Properties from JS file +/* ========================================================================== */ + +async function getCustomPropertiesFromJSFile(from): Promise> { + const object = await import(from); + + return getCustomPropertiesFromObject(object); +} + +/* Get Custom Properties from Imports +/* ========================================================================== */ + +export default async function getCustomPropertiesFromImports(sources: Array): Promise> { + const sourceData = (await Promise.all(sources.map(async (source) => { + if (source instanceof Promise) { + source = await source; + } else if (source instanceof Function) { + source = await source(); + } + + if (typeof source === 'string') { + const fromPath = path.resolve(source); + const type = path.extname(fromPath).slice(1).toLowerCase(); + + return { + type: type, + from: fromPath, + }; + } + + if ('customProperties' in source && Object(source.customProperties) === source.customProperties) { + return source; + } + + if ('custom-properties' in source && Object(source['custom-properties']) === source['custom-properties']) { + return source; + } + + if ('from' in source) { + const fromPath = path.resolve(source.from); + let type = source.type; + if (!type) { + type = path.extname(fromPath).slice(1).toLowerCase(); + } + return { + type: type, + from: fromPath, + }; + } + + if (Object.keys(source).length === 0) { + // Empty `importFrom` object. + return null; + } + + throw new Error(`Invalid source object: ${source}`); + }))).filter((x) => { + return !!x; + }); + + const data: Array> = await Promise.all(sourceData.map(async (partialData) => { + if (('type' in partialData) && ('from' in partialData)) { + if (partialData.type === 'css' || partialData.type === 'pcss' || partialData.type === 'postcss') { + return await getCustomPropertiesFromCSSFile(partialData.from); + } + + if (partialData.type === 'js') { + return await getCustomPropertiesFromJSFile(partialData.from); + } + + if (partialData.type === 'json') { + return await getCustomPropertiesFromJSONFile(partialData.from); + } + + throw new Error('Invalid source type: ' + partialData.type); + } + + return getCustomPropertiesFromObject(partialData); + })); + + const out: Map = new Map(); + data.forEach((partialData) => { + for (const [name, value] of partialData.entries()) { + out.set(name, value); + } + }); + + return out; +} + +/* Helper utilities +/* ========================================================================== */ + +const readJSON = async from => JSON.parse((await fsp.readFile(from)).toString()); diff --git a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-root.js b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-root.ts similarity index 74% rename from plugins/postcss-custom-properties/src/lib/get-custom-properties-from-root.js rename to plugins/postcss-custom-properties/src/lib/get-custom-properties-from-root.ts index 7b81d6ae6..8484dfa06 100644 --- a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-root.js +++ b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-root.ts @@ -2,10 +2,11 @@ import valuesParser from 'postcss-value-parser'; import { isBlockIgnored } from './is-ignored'; // return custom selectors from the css root, conditionally removing them -export default function getCustomPropertiesFromRoot(root, opts) { +export default function getCustomPropertiesFromRoot(root, opts): Map { // initialize custom selectors - const customPropertiesFromHtmlElement = {}; - const customPropertiesFromRootPseudo = {}; + const customPropertiesFromHtmlElement: Map = new Map(); + const customPropertiesFromRootPseudo: Map = new Map(); + const out: Map = new Map(); // for each html or :root rule root.nodes.slice().forEach(rule => { @@ -22,7 +23,7 @@ export default function getCustomPropertiesFromRoot(root, opts) { const { prop } = decl; // write the parsed value to the custom property - customPropertiesObject[prop] = valuesParser(decl.value); + customPropertiesObject.set(prop, valuesParser(decl.value)); // conditionally remove the custom property declaration if (!opts.preserve) { @@ -38,8 +39,16 @@ export default function getCustomPropertiesFromRoot(root, opts) { } }); + for (const [name, value] of customPropertiesFromHtmlElement.entries()) { + out.set(name, value); + } + + for (const [name, value] of customPropertiesFromRootPseudo.entries()) { + out.set(name, value); + } + // return all custom properties, preferring :root properties over html properties - return { ...customPropertiesFromHtmlElement, ...customPropertiesFromRootPseudo }; + return out; } // match html and :root rules diff --git a/plugins/postcss-custom-properties/src/lib/is-ignored.js b/plugins/postcss-custom-properties/src/lib/is-ignored.ts similarity index 84% rename from plugins/postcss-custom-properties/src/lib/is-ignored.js rename to plugins/postcss-custom-properties/src/lib/is-ignored.ts index 3bcc7e777..e1c5ccc5c 100644 --- a/plugins/postcss-custom-properties/src/lib/is-ignored.js +++ b/plugins/postcss-custom-properties/src/lib/is-ignored.ts @@ -1,12 +1,12 @@ function isBlockIgnored(ruleOrDeclaration) { - var rule = ruleOrDeclaration.selector ? + const rule = ruleOrDeclaration.selector ? ruleOrDeclaration : ruleOrDeclaration.parent; return /(!\s*)?postcss-custom-properties:\s*off\b/i.test(rule.toString()); } function isRuleIgnored(rule) { - var previous = rule.prev(); + const previous = rule.prev(); return Boolean(isBlockIgnored(rule) || previous && diff --git a/plugins/postcss-custom-properties/src/lib/options.ts b/plugins/postcss-custom-properties/src/lib/options.ts new file mode 100644 index 000000000..6db403f7f --- /dev/null +++ b/plugins/postcss-custom-properties/src/lib/options.ts @@ -0,0 +1,14 @@ + +export type ImportFromSource = { from: string, type?: string } | string; +export type ImportCustomProperties = { customProperties?: Record, 'custom-properties'?: Record }; +export type ImportAsFunction = () => ImportFromSource | ImportCustomProperties +export type ImportAsPromise = Promise +export type ImportAsFunctionPromise = () => Promise +export type ImportOptions = ImportFromSource | ImportCustomProperties | ImportAsFunction | ImportAsPromise | ImportAsFunctionPromise; + +export type ExportJSONFunction = (customProperties?: Record) => Record; +export type ExportToSource = { to: string, type?: string, toJSON: ExportJSONFunction } | string; +export type ExportCustomProperties = { customProperties?: Record, 'custom-properties'?: Record, toJSON: ExportJSONFunction }; +export type ExportAsFunction = (ExportCustomProperties) => void +export type ExportAsFunctionPromise = (ExportCustomProperties) => Promise +export type ExportOptions = ExportToSource | ExportCustomProperties | ExportAsFunction | ExportAsFunctionPromise; diff --git a/plugins/postcss-custom-properties/src/lib/transform-properties.js b/plugins/postcss-custom-properties/src/lib/transform-properties.ts similarity index 100% rename from plugins/postcss-custom-properties/src/lib/transform-properties.js rename to plugins/postcss-custom-properties/src/lib/transform-properties.ts diff --git a/plugins/postcss-custom-properties/src/lib/transform-value-ast.js b/plugins/postcss-custom-properties/src/lib/transform-value-ast.ts similarity index 76% rename from plugins/postcss-custom-properties/src/lib/transform-value-ast.js rename to plugins/postcss-custom-properties/src/lib/transform-value-ast.ts index 0070bdb4a..d14346492 100644 --- a/plugins/postcss-custom-properties/src/lib/transform-value-ast.js +++ b/plugins/postcss-custom-properties/src/lib/transform-value-ast.ts @@ -6,12 +6,12 @@ export default function transformValueAST(root, customProperties) { const { value: name } = propertyNode; const index = root.nodes.indexOf(child); - if (name in Object(customProperties)) { + if (customProperties.has(name)) { // Direct match of a custom property to a parsed value - const nodes = customProperties[name].nodes; + const nodes = customProperties.get(name).nodes; // Re-transform nested properties without given one to avoid circular from keeping this forever - retransformValueAST({ nodes }, customProperties, name); + reTransformValueAST({ nodes }, customProperties, name); if (index > -1) { root.nodes.splice(index, 1, ...nodes); @@ -34,11 +34,11 @@ export default function transformValueAST(root, customProperties) { return root.toString(); } -// retransform the current ast without a custom property (to prevent recursion) -function retransformValueAST(root, customProperties, withoutProperty) { - const nextCustomProperties = Object.assign({}, customProperties); +// reTransform the current ast without a custom property (to prevent recursion) +function reTransformValueAST(root, customProperties, withoutProperty) { + const nextCustomProperties = new Map(customProperties); - delete nextCustomProperties[withoutProperty]; + nextCustomProperties.delete(withoutProperty); return transformValueAST(root, nextCustomProperties); } diff --git a/plugins/postcss-custom-properties/src/lib/write-custom-properties-to-exports.js b/plugins/postcss-custom-properties/src/lib/write-custom-properties-to-exports.ts similarity index 50% rename from plugins/postcss-custom-properties/src/lib/write-custom-properties-to-exports.js rename to plugins/postcss-custom-properties/src/lib/write-custom-properties-to-exports.ts index 58924cbba..bbd0b1cb6 100644 --- a/plugins/postcss-custom-properties/src/lib/write-custom-properties-to-exports.js +++ b/plugins/postcss-custom-properties/src/lib/write-custom-properties-to-exports.ts @@ -1,5 +1,8 @@ -import fs from 'fs'; +import type valuesParser from 'postcss-value-parser'; + +import { promises as fsp } from 'fs'; import path from 'path'; +import type { ExportOptions } from './options'; /* Write Custom Properties to CSS File /* ========================================================================== */ @@ -12,7 +15,7 @@ async function writeCustomPropertiesToCssFile(to, customProperties) { }, []).join('\n'); const css = `:root {\n${cssContent}\n}\n`; - await writeFile(to, css); + await fsp.writeFile(to, css); } /* Write Custom Properties to SCSS File @@ -27,7 +30,7 @@ async function writeCustomPropertiesToScssFile(to, customProperties) { }, []).join('\n'); const scss = `${scssContent}\n`; - await writeFile(to, scss); + await fsp.writeFile(to, scss); } /* Write Custom Properties to JSON file @@ -39,7 +42,7 @@ async function writeCustomPropertiesToJsonFile(to, customProperties) { }, null, ' '); const json = `${jsonContent}\n`; - await writeFile(to, json); + await fsp.writeFile(to, json); } /* Write Custom Properties to Common JS file @@ -53,7 +56,7 @@ async function writeCustomPropertiesToCjsFile(to, customProperties) { }, []).join(',\n'); const js = `module.exports = {\n\tcustomProperties: {\n${jsContents}\n\t}\n};\n`; - await writeFile(to, js); + await fsp.writeFile(to, js); } /* Write Custom Properties to Module JS file @@ -67,83 +70,90 @@ async function writeCustomPropertiesToMjsFile(to, customProperties) { }, []).join(',\n'); const mjs = `export const customProperties = {\n${mjsContents}\n};\n`; - await writeFile(to, mjs); + await fsp.writeFile(to, mjs); } /* Write Custom Properties to Exports /* ========================================================================== */ -export default function writeCustomPropertiesToExports(customProperties, destinations) { +export default function writeCustomPropertiesToExports(customProperties, destinations: Array) { return Promise.all(destinations.map(async destination => { if (destination instanceof Function) { - await destination(defaultCustomPropertiesToJSON(customProperties)); + await destination(defaultCustomPropertiesToJSONObject(customProperties)); + return; + } + + if (typeof destination === 'string') { + const toPath = path.resolve(destination); + const type = path.extname(toPath).slice(1).toLowerCase(); + + await writePropertiesToFile(toPath, type, defaultCustomPropertiesToJSONObject(customProperties)); + return; + } + + // transformer for Custom Properties into a JSON-compatible object + let customPropertiesAsJSONObject = {}; + if ('toJSON' in destination) { + customPropertiesAsJSONObject = destination.toJSON(defaultCustomPropertiesToJSONObject(customProperties)); } else { - // read the destination as an object - const opts = destination === Object(destination) ? destination : { to: String(destination) }; - - // transformer for Custom Properties into a JSON-compatible object - const toJSON = opts.toJSON || defaultCustomPropertiesToJSON; - - if ('customProperties' in opts) { - // write directly to an object as customProperties - opts.customProperties = toJSON(customProperties); - } else if ('custom-properties' in opts) { - // write directly to an object as custom-properties - opts['custom-properties'] = toJSON(customProperties); - } else { - // destination pathname - const to = String(opts.to || ''); - - // type of file being written to - const type = (opts.type || path.extname(opts.to).slice(1)).toLowerCase(); - - // transformed Custom Properties - const customPropertiesJSON = toJSON(customProperties); - - if (type === 'css') { - await writeCustomPropertiesToCssFile(to, customPropertiesJSON); - } - - if (type === 'scss') { - await writeCustomPropertiesToScssFile(to, customPropertiesJSON); - } - - if (type === 'js') { - await writeCustomPropertiesToCjsFile(to, customPropertiesJSON); - } - - if (type === 'json') { - await writeCustomPropertiesToJsonFile(to, customPropertiesJSON); - } - - if (type === 'mjs') { - await writeCustomPropertiesToMjsFile(to, customPropertiesJSON); - } + customPropertiesAsJSONObject = defaultCustomPropertiesToJSONObject(customProperties); + } + + if ('to' in destination) { + const toPath = path.resolve(destination.to); + let type = destination.type; + if (!type) { + type = path.extname(toPath).slice(1).toLowerCase(); } + + await writePropertiesToFile(toPath, type, customPropertiesAsJSONObject); + return; + } + + if ('customProperties' in destination) { + // write directly to an object as customProperties + destination.customProperties = customPropertiesAsJSONObject; + } else if ('custom-properties' in destination) { + // write directly to an object as custom-properties + destination['custom-properties'] = customPropertiesAsJSONObject; } })); } +async function writePropertiesToFile(to: string, type: string, customProperties: Record) { + if (type === 'css') { + await writeCustomPropertiesToCssFile(to, customProperties); + } + + if (type === 'scss') { + await writeCustomPropertiesToScssFile(to, customProperties); + } + + if (type === 'js') { + await writeCustomPropertiesToCjsFile(to, customProperties); + } + + if (type === 'json') { + await writeCustomPropertiesToJsonFile(to, customProperties); + } + + if (type === 'mjs') { + await writeCustomPropertiesToMjsFile(to, customProperties); + } +} + /* Helper utilities /* ========================================================================== */ -const defaultCustomPropertiesToJSON = customProperties => { - return Object.keys(customProperties).reduce((customPropertiesJSON, key) => { - const valueNodes = customProperties[key]; - customPropertiesJSON[key] = valueNodes.toString(); +function defaultCustomPropertiesToJSONObject(customProperties: Map): Record { + const out = {}; + for (const [name, value] of customProperties.entries()) { + out[name] = value.toString(); + } - return customPropertiesJSON; - }, {}); -}; - -const writeFile = (to, text) => new Promise((resolve, reject) => { - fs.writeFile(to, text, error => { - if (error) { - reject(error); - } else { - resolve(); - } - }); -}); + return out; +} -const escapeForJS = string => string.replace(/\\([\s\S])|(')/g, '\\$1$2').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); +const escapeForJS = (string) => { + return string.replace(/\\([\s\S])|(')/g, '\\$1$2').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); +}; diff --git a/plugins/postcss-custom-properties/test/basic.css b/plugins/postcss-custom-properties/test/basic.css index f6e3ac9c5..b8ff02ae4 100644 --- a/plugins/postcss-custom-properties/test/basic.css +++ b/plugins/postcss-custom-properties/test/basic.css @@ -125,3 +125,7 @@ html { order: 4; background: var(--url-4); } + +.no-prototype-collisions { + color: var(toString); +} diff --git a/plugins/postcss-custom-properties/test/basic.expect.css b/plugins/postcss-custom-properties/test/basic.expect.css index 8532e0511..e4de33c12 100644 --- a/plugins/postcss-custom-properties/test/basic.expect.css +++ b/plugins/postcss-custom-properties/test/basic.expect.css @@ -144,3 +144,7 @@ html { background: url(data:image/png;bm90LWFuZC1pbWFnZQ==); background: var(--url-4); } + +.no-prototype-collisions { + color: var(toString); +} diff --git a/plugins/postcss-custom-properties/test/basic.import-is-empty.expect.css b/plugins/postcss-custom-properties/test/basic.import-is-empty.expect.css index 8532e0511..e4de33c12 100644 --- a/plugins/postcss-custom-properties/test/basic.import-is-empty.expect.css +++ b/plugins/postcss-custom-properties/test/basic.import-is-empty.expect.css @@ -144,3 +144,7 @@ html { background: url(data:image/png;bm90LWFuZC1pbWFnZQ==); background: var(--url-4); } + +.no-prototype-collisions { + color: var(toString); +} diff --git a/plugins/postcss-custom-properties/test/basic.import-override.expect.css b/plugins/postcss-custom-properties/test/basic.import-override.expect.css index 3cd943cfd..1b5aa8f77 100644 --- a/plugins/postcss-custom-properties/test/basic.import-override.expect.css +++ b/plugins/postcss-custom-properties/test/basic.import-override.expect.css @@ -97,3 +97,7 @@ order: 4; background: url(data:image/png;bm90LWFuZC1pbWFnZQ==); } + +.no-prototype-collisions { + color: var(toString); +} diff --git a/plugins/postcss-custom-properties/test/basic.import.expect.css b/plugins/postcss-custom-properties/test/basic.import.expect.css index 4b31a16a6..415669f52 100644 --- a/plugins/postcss-custom-properties/test/basic.import.expect.css +++ b/plugins/postcss-custom-properties/test/basic.import.expect.css @@ -145,3 +145,7 @@ html { background: url(data:image/png;bm90LWFuZC1pbWFnZQ==); background: var(--url-4); } + +.no-prototype-collisions { + color: var(toString); +} diff --git a/plugins/postcss-custom-properties/test/basic.preserve.expect.css b/plugins/postcss-custom-properties/test/basic.preserve.expect.css index 16bfadb7a..654781d93 100644 --- a/plugins/postcss-custom-properties/test/basic.preserve.expect.css +++ b/plugins/postcss-custom-properties/test/basic.preserve.expect.css @@ -97,3 +97,7 @@ order: 4; background: url(data:image/png;bm90LWFuZC1pbWFnZQ==); } + +.no-prototype-collisions { + color: var(toString); +} diff --git a/plugins/postcss-custom-properties/tsconfig.json b/plugins/postcss-custom-properties/tsconfig.json new file mode 100644 index 000000000..f0655e674 --- /dev/null +++ b/plugins/postcss-custom-properties/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declarationDir": ".", + "module": "es2020" + }, + "include": ["./src/**/*"], + "exclude": ["dist"], +} diff --git a/rollup/presets/package-typescript.js b/rollup/presets/package-typescript.js index f0b6942c3..0f57512a7 100644 --- a/rollup/presets/package-typescript.js +++ b/rollup/presets/package-typescript.js @@ -20,7 +20,7 @@ export function packageTypescript() { exclude: 'node_modules/**', presets: packageBabelPreset, }), - terser(), + // terser(), ], }, ]; From e2a7335d3dbf1fdc09976dd894003474473d3ebf Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sun, 9 Jan 2022 14:47:42 +0100 Subject: [PATCH 2/6] cleanup --- plugins/postcss-custom-properties/.tape.js | 11 +++++++++++ plugins/postcss-custom-properties/package.json | 7 +++++++ .../lib/get-custom-properties-from-imports.ts | 17 ++++++++++++----- .../postcss-custom-properties/test/_import.mjs | 11 +++++++++++ .../postcss-custom-properties/test/_require.cjs | 7 +++++++ .../test/import-properties-2.cjs | 6 ++++++ .../test/import-properties-2.js | 4 ++-- .../test/import-properties.cjs | 6 ++++++ .../test/import-properties.js | 4 ++-- .../test/import-properties.mjs | 6 ++++++ rollup/presets/package-typescript.js | 2 +- 11 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 plugins/postcss-custom-properties/test/import-properties-2.cjs create mode 100644 plugins/postcss-custom-properties/test/import-properties.cjs create mode 100644 plugins/postcss-custom-properties/test/import-properties.mjs diff --git a/plugins/postcss-custom-properties/.tape.js b/plugins/postcss-custom-properties/.tape.js index 99353af0c..d6f3b132e 100644 --- a/plugins/postcss-custom-properties/.tape.js +++ b/plugins/postcss-custom-properties/.tape.js @@ -81,6 +81,17 @@ module.exports = { expect: 'basic.import.expect.css', result: 'basic.import.result.css' }, + 'basic:import-cjs': { + message: 'supports { importFrom: "test/import-properties{-2}?.cjs" } usage', + options: { + importFrom: [ + 'test/import-properties.cjs', + 'test/import-properties-2.cjs' + ] + }, + expect: 'basic.import.expect.css', + result: 'basic.import.result.css' + }, 'basic:import-css': { message: 'supports { importFrom: "test/import-properties{-2}?.css" } usage', options: { diff --git a/plugins/postcss-custom-properties/package.json b/plugins/postcss-custom-properties/package.json index 79d5f02e1..e1ec1edf6 100644 --- a/plugins/postcss-custom-properties/package.json +++ b/plugins/postcss-custom-properties/package.json @@ -12,6 +12,13 @@ "main": "dist/index.cjs", "module": "dist/index.mjs", "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs", + "default": "./dist/index.mjs" + } + }, "files": [ "CHANGELOG.md", "LICENSE.md", diff --git a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts index b1cbb8747..647131333 100644 --- a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts +++ b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts @@ -1,9 +1,11 @@ -import { promises as fsp } from 'fs'; -import path from 'path'; -import { parse } from 'postcss'; -import valuesParser from 'postcss-value-parser'; import getCustomPropertiesFromRoot from './get-custom-properties-from-root'; +import path from 'path'; import type { ImportCustomProperties, ImportOptions } from './options'; +import valuesParser from 'postcss-value-parser'; +import vm from 'vm'; +import { parse } from 'postcss'; +import { promises as fsp } from 'fs'; +import { createRequire } from 'module'; /* Get Custom Properties from CSS File /* ========================================================================== */ @@ -111,7 +113,12 @@ export default async function getCustomPropertiesFromImports(sources: Array Date: Sun, 9 Jan 2022 14:53:34 +0100 Subject: [PATCH 3/6] update change log --- plugins/postcss-custom-properties/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/postcss-custom-properties/CHANGELOG.md b/plugins/postcss-custom-properties/CHANGELOG.md index 838d82df8..20d33bf02 100755 --- a/plugins/postcss-custom-properties/CHANGELOG.md +++ b/plugins/postcss-custom-properties/CHANGELOG.md @@ -4,6 +4,7 @@ - Converted to typescript - Correct typings for plugin options +- Allow `.mjs` in `importFrom` ### 12.0.4 (January 7, 2022) From b2970556f51ebb119e9a1a8b49c79c747dd0ead1 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sun, 9 Jan 2022 15:12:54 +0100 Subject: [PATCH 4/6] more fixes --- plugins/postcss-custom-properties/CHANGELOG.md | 1 + .../src/lib/get-custom-properties-from-imports.ts | 2 +- .../src/lib/get-custom-properties-from-root.ts | 6 +----- plugins/postcss-custom-properties/test/basic.css | 5 +++++ plugins/postcss-custom-properties/test/basic.expect.css | 6 ++++++ .../test/basic.import-is-empty.expect.css | 6 ++++++ .../test/basic.import-override.expect.css | 4 ++++ .../postcss-custom-properties/test/basic.import.expect.css | 6 ++++++ .../test/basic.preserve.expect.css | 4 ++++ .../postcss-custom-properties/test/export-properties.css | 1 + plugins/postcss-custom-properties/test/export-properties.js | 1 + .../postcss-custom-properties/test/export-properties.json | 1 + .../postcss-custom-properties/test/export-properties.mjs | 1 + .../postcss-custom-properties/test/export-properties.scss | 1 + 14 files changed, 39 insertions(+), 6 deletions(-) diff --git a/plugins/postcss-custom-properties/CHANGELOG.md b/plugins/postcss-custom-properties/CHANGELOG.md index 20d33bf02..dbd38afe9 100755 --- a/plugins/postcss-custom-properties/CHANGELOG.md +++ b/plugins/postcss-custom-properties/CHANGELOG.md @@ -5,6 +5,7 @@ - Converted to typescript - Correct typings for plugin options - Allow `.mjs` in `importFrom` +- Fix unicode support in custom property names ### 12.0.4 (January 7, 2022) diff --git a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts index 647131333..61480a910 100644 --- a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts +++ b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts @@ -109,7 +109,7 @@ export default async function getCustomPropertiesFromImports(sources: Array> = await Promise.all(sourceData.map(async (partialData) => { if (('type' in partialData) && ('from' in partialData)) { - if (partialData.type === 'css' || partialData.type === 'pcss' || partialData.type === 'postcss') { + if (partialData.type === 'css' || partialData.type === 'pcss') { return await getCustomPropertiesFromCSSFile(partialData.from); } 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 8484dfa06..e177478fa 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 @@ -19,7 +19,7 @@ export default function getCustomPropertiesFromRoot(root, opts): Map { - if (isCustomDecl(decl) && !isBlockIgnored(decl)) { + if (decl.variable && !isBlockIgnored(decl)) { const { prop } = decl; // write the parsed value to the custom property @@ -54,14 +54,10 @@ export default function getCustomPropertiesFromRoot(root, opts): Map node.type === 'rule' && node.selector.split(',').some(item => htmlSelectorRegExp.test(item)) && Object(node.nodes).length; const isRootRule = node => node.type === 'rule' && node.selector.split(',').some(item => rootSelectorRegExp.test(item)) && Object(node.nodes).length; -// whether the node is an custom property -const isCustomDecl = node => node.type === 'decl' && customPropertyRegExp.test(node.prop); - // whether the node is a parent without children const isEmptyParent = node => Object(node.nodes).length === 0; diff --git a/plugins/postcss-custom-properties/test/basic.css b/plugins/postcss-custom-properties/test/basic.css index b8ff02ae4..0c8deab3d 100644 --- a/plugins/postcss-custom-properties/test/basic.css +++ b/plugins/postcss-custom-properties/test/basic.css @@ -19,6 +19,7 @@ html { --url-2: url('/my/path'); --url-3: url(/my/path); --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; color: var(--color); } @@ -129,3 +130,7 @@ html { .no-prototype-collisions { color: var(toString); } + +.test-unicode { + color: var(--✅-size); +} diff --git a/plugins/postcss-custom-properties/test/basic.expect.css b/plugins/postcss-custom-properties/test/basic.expect.css index e4de33c12..e7c5743ff 100644 --- a/plugins/postcss-custom-properties/test/basic.expect.css +++ b/plugins/postcss-custom-properties/test/basic.expect.css @@ -19,6 +19,7 @@ html { --url-2: url('/my/path'); --url-3: url(/my/path); --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; color: rgb(255, 0, 0); color: var(--color); } @@ -148,3 +149,8 @@ html { .no-prototype-collisions { color: var(toString); } + +.test-unicode { + color: 2em; + color: var(--✅-size); +} diff --git a/plugins/postcss-custom-properties/test/basic.import-is-empty.expect.css b/plugins/postcss-custom-properties/test/basic.import-is-empty.expect.css index e4de33c12..e7c5743ff 100644 --- a/plugins/postcss-custom-properties/test/basic.import-is-empty.expect.css +++ b/plugins/postcss-custom-properties/test/basic.import-is-empty.expect.css @@ -19,6 +19,7 @@ html { --url-2: url('/my/path'); --url-3: url(/my/path); --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; color: rgb(255, 0, 0); color: var(--color); } @@ -148,3 +149,8 @@ html { .no-prototype-collisions { color: var(toString); } + +.test-unicode { + color: 2em; + color: var(--✅-size); +} diff --git a/plugins/postcss-custom-properties/test/basic.import-override.expect.css b/plugins/postcss-custom-properties/test/basic.import-override.expect.css index 1b5aa8f77..e078da1ce 100644 --- a/plugins/postcss-custom-properties/test/basic.import-override.expect.css +++ b/plugins/postcss-custom-properties/test/basic.import-override.expect.css @@ -101,3 +101,7 @@ .no-prototype-collisions { color: var(toString); } + +.test-unicode { + color: 2em; +} diff --git a/plugins/postcss-custom-properties/test/basic.import.expect.css b/plugins/postcss-custom-properties/test/basic.import.expect.css index 415669f52..c09bd2234 100644 --- a/plugins/postcss-custom-properties/test/basic.import.expect.css +++ b/plugins/postcss-custom-properties/test/basic.import.expect.css @@ -19,6 +19,7 @@ html { --url-2: url('/my/path'); --url-3: url(/my/path); --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; color: rgb(255, 0, 0); color: var(--color); } @@ -149,3 +150,8 @@ html { .no-prototype-collisions { color: var(toString); } + +.test-unicode { + color: 2em; + color: var(--✅-size); +} diff --git a/plugins/postcss-custom-properties/test/basic.preserve.expect.css b/plugins/postcss-custom-properties/test/basic.preserve.expect.css index 654781d93..a78e89b2e 100644 --- a/plugins/postcss-custom-properties/test/basic.preserve.expect.css +++ b/plugins/postcss-custom-properties/test/basic.preserve.expect.css @@ -101,3 +101,7 @@ .no-prototype-collisions { color: var(toString); } + +.test-unicode { + color: 2em; +} diff --git a/plugins/postcss-custom-properties/test/export-properties.css b/plugins/postcss-custom-properties/test/export-properties.css index 57c2332ff..6c5bc15ff 100644 --- a/plugins/postcss-custom-properties/test/export-properties.css +++ b/plugins/postcss-custom-properties/test/export-properties.css @@ -15,5 +15,6 @@ --url-2: url('/my/path'); --url-3: url(/my/path); --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; --theme-color: #053; } diff --git a/plugins/postcss-custom-properties/test/export-properties.js b/plugins/postcss-custom-properties/test/export-properties.js index b863d35dc..4a881c6cd 100644 --- a/plugins/postcss-custom-properties/test/export-properties.js +++ b/plugins/postcss-custom-properties/test/export-properties.js @@ -16,6 +16,7 @@ module.exports = { '--url-2': 'url(\'/my/path\')', '--url-3': 'url(/my/path)', '--url-4': 'url(data:image/png;bm90LWFuZC1pbWFnZQ==)', + '--✅-size': '2em', '--theme-color': '#053' } }; diff --git a/plugins/postcss-custom-properties/test/export-properties.json b/plugins/postcss-custom-properties/test/export-properties.json index 93752ccf7..723f042ba 100644 --- a/plugins/postcss-custom-properties/test/export-properties.json +++ b/plugins/postcss-custom-properties/test/export-properties.json @@ -16,6 +16,7 @@ "--url-2": "url('/my/path')", "--url-3": "url(/my/path)", "--url-4": "url(data:image/png;bm90LWFuZC1pbWFnZQ==)", + "--✅-size": "2em", "--theme-color": "#053" } } diff --git a/plugins/postcss-custom-properties/test/export-properties.mjs b/plugins/postcss-custom-properties/test/export-properties.mjs index 5814fe2e0..30e030b94 100644 --- a/plugins/postcss-custom-properties/test/export-properties.mjs +++ b/plugins/postcss-custom-properties/test/export-properties.mjs @@ -15,5 +15,6 @@ export const customProperties = { '--url-2': 'url(\'/my/path\')', '--url-3': 'url(/my/path)', '--url-4': 'url(data:image/png;bm90LWFuZC1pbWFnZQ==)', + '--✅-size': '2em', '--theme-color': '#053' }; diff --git a/plugins/postcss-custom-properties/test/export-properties.scss b/plugins/postcss-custom-properties/test/export-properties.scss index f1874c60b..a920224bb 100644 --- a/plugins/postcss-custom-properties/test/export-properties.scss +++ b/plugins/postcss-custom-properties/test/export-properties.scss @@ -14,4 +14,5 @@ $url-1: url("/my/path"); $url-2: url('/my/path'); $url-3: url(/my/path); $url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); +$✅-size: 2em; $theme-color: #053; From 79ed169de233ac4b2ec649d6ac165aede0f50a8d Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sun, 9 Jan 2022 15:30:25 +0100 Subject: [PATCH 5/6] add overrideImportFromWithRoot option --- plugins/postcss-custom-properties/.tape.js | 19 ++++ .../postcss-custom-properties/CHANGELOG.md | 3 +- plugins/postcss-custom-properties/README.md | 11 ++ .../postcss-custom-properties/src/index.ts | 22 ++-- .../basic.import-override.inverse.expect.css | 107 ++++++++++++++++++ 5 files changed, 154 insertions(+), 8 deletions(-) create mode 100644 plugins/postcss-custom-properties/test/basic.import-override.inverse.expect.css diff --git a/plugins/postcss-custom-properties/.tape.js b/plugins/postcss-custom-properties/.tape.js index d6f3b132e..f69475d26 100644 --- a/plugins/postcss-custom-properties/.tape.js +++ b/plugins/postcss-custom-properties/.tape.js @@ -165,6 +165,25 @@ module.exports = { expect: 'basic.import-override.expect.css', result: 'basic.import-override.result.css' }, + 'basic:import-override:inverse': { + message: 'importFrom with { preserve: false, overrideImportFromWithRoot: true } should override importFrom properties', + options: { + preserve: false, + overrideImportFromWithRoot: true, + importFrom: { + customProperties: { + '--color': 'rgb(0, 0, 0)', + '--color-2': 'yellow', + '--ref-color': 'var(--color)', + '--margin': '0 10px 20px 30px', + '--shadow-color': 'rgb(0,0,0)', + '--z-index': 10 + } + } + }, + expect: 'basic.import-override.inverse.expect.css', + result: 'basic.import-override.inverse.result.css' + }, 'basic:export': { message: 'supports { exportTo: { customProperties: { ... } } } usage', options: { diff --git a/plugins/postcss-custom-properties/CHANGELOG.md b/plugins/postcss-custom-properties/CHANGELOG.md index dbd38afe9..026b65f0a 100755 --- a/plugins/postcss-custom-properties/CHANGELOG.md +++ b/plugins/postcss-custom-properties/CHANGELOG.md @@ -2,9 +2,10 @@ ### Unreleased +- Add `overrideImportFromWithRoot` option +- Allow `.mjs` in `importFrom` - Converted to typescript - Correct typings for plugin options -- Allow `.mjs` in `importFrom` - Fix unicode support in custom property names ### 12.0.4 (January 7, 2022) diff --git a/plugins/postcss-custom-properties/README.md b/plugins/postcss-custom-properties/README.md index b87ff7e25..b0ff2167d 100755 --- a/plugins/postcss-custom-properties/README.md +++ b/plugins/postcss-custom-properties/README.md @@ -137,6 +137,17 @@ postcssCustomProperties({ See example imports written in [CSS](test/import-properties.css), [JS](test/import-properties.js), and [JSON](test/import-properties.json). +### overrideImportFromWithRoot + +The `overrideImportFromWithRoot` option determines if properties added via `importFrom` are overridden by properties that exist in `:root`. +Defaults to `false`. + +```js +postcssCustomProperties({ + overrideImportFromWithRoot: true +}); +``` + ### exportTo The `exportTo` option specifies destinations where Custom Properties can be exported diff --git a/plugins/postcss-custom-properties/src/index.ts b/plugins/postcss-custom-properties/src/index.ts index b568c78f3..3ca5c3e8c 100755 --- a/plugins/postcss-custom-properties/src/index.ts +++ b/plugins/postcss-custom-properties/src/index.ts @@ -16,11 +16,16 @@ export interface PluginOptions { /** 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 + } 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 overrideImportFromWithRoot = 'overrideImportFromWithRoot' in Object(opts) ? Boolean(opts.overrideImportFromWithRoot) : false; // sources to import custom selectors from let importFrom: Array = []; @@ -61,14 +66,17 @@ const creator: PluginCreator = (opts?: PluginOptions) => { } else { return { Once: async root => { - const rootCustomProperties = getCustomPropertiesFromRoot(root, { preserve: preserve }); - for (const [name, value] of rootCustomProperties.entries()) { - customProperties.set(name, value); - } + const importedCustomerProperties = (await customPropertiesPromise).entries(); + const rootCustomProperties = getCustomPropertiesFromRoot(root, { preserve: preserve }).entries(); - const importedCustomerProperties = await customPropertiesPromise; - for (const [name, value] of importedCustomerProperties.entries()) { - customProperties.set(name, value); + if (overrideImportFromWithRoot) { + for (const [name, value] of [...importedCustomerProperties, ...rootCustomProperties]) { + customProperties.set(name, value); + } + } else { + for (const [name, value] of [...rootCustomProperties, ...importedCustomerProperties]) { + customProperties.set(name, value); + } } await writeCustomPropertiesToExports(customProperties, exportTo); diff --git a/plugins/postcss-custom-properties/test/basic.import-override.inverse.expect.css b/plugins/postcss-custom-properties/test/basic.import-override.inverse.expect.css new file mode 100644 index 000000000..167bb6693 --- /dev/null +++ b/plugins/postcss-custom-properties/test/basic.import-override.inverse.expect.css @@ -0,0 +1,107 @@ +:root { + color: rgb(255, 0, 0); +} + +.ignore-line { + /* postcss-custom-properties: ignore next */ + color: var(--color); + background-color: yellow; +} + +.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); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 rgb(255, 0, 0); +} + +.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: yellow; +} + +.test--color_w_var { + color: rgb(255, 0, 0); +} + +.test--color_w_vars { + color: hsl(0, 100%, 50%); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: 10; +} + +.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%); +} + +.test--loose-formatting { + color: rgb(255, 0, 0)/*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; +} From 082fad38e38f65616a9b5582c902f93a3c6a4030 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sun, 9 Jan 2022 16:23:47 +0100 Subject: [PATCH 6/6] cleanup --- plugin-packs/postcss-preset-env/.tape.js | 21 +++++++++++++++++++ .../test/import.ch88.expect.css | 14 +++++++++++++ .../lib/get-custom-properties-from-imports.ts | 2 -- 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 plugin-packs/postcss-preset-env/test/import.ch88.expect.css diff --git a/plugin-packs/postcss-preset-env/.tape.js b/plugin-packs/postcss-preset-env/.tape.js index ace290674..d7a04f40a 100644 --- a/plugin-packs/postcss-preset-env/.tape.js +++ b/plugin-packs/postcss-preset-env/.tape.js @@ -235,6 +235,27 @@ module.exports = { stage: 0 } }, + 'import:ch88': { + message: 'supports { browsers: "chrome >= 88", importFrom: { customMedia, customProperties, customSelectors, environmentVariables } } usage', + options: { + browsers: 'chrome >= 88', + importFrom: { + customMedia: { + '--narrow-window': '(max-width: env(--sm))' + }, + customProperties: { + '--order': '1' + }, + customSelectors: { + ':--heading': 'h1, h2, h3, h4, h5, h6' + }, + environmentVariables: { + '--sm': '40rem' + } + }, + stage: 0 + } + }, 'basic:export': { message: 'supports { stage: 0 } usage', options: { diff --git a/plugin-packs/postcss-preset-env/test/import.ch88.expect.css b/plugin-packs/postcss-preset-env/test/import.ch88.expect.css new file mode 100644 index 000000000..1ec7a1ea9 --- /dev/null +++ b/plugin-packs/postcss-preset-env/test/import.ch88.expect.css @@ -0,0 +1,14 @@ +.test-custom-properties { + order: var(--order); +} + +@media (max-width: 40rem) { + .test-custom-media-queries { + order: 2; + } +} + +h1.test-custom-selectors,h2.test-custom-selectors,h3.test-custom-selectors,h4.test-custom-selectors,h5.test-custom-selectors,h6.test-custom-selectors { + order: 3; + } + diff --git a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts index 61480a910..811baba31 100644 --- a/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts +++ b/plugins/postcss-custom-properties/src/lib/get-custom-properties-from-imports.ts @@ -2,10 +2,8 @@ import getCustomPropertiesFromRoot from './get-custom-properties-from-root'; import path from 'path'; import type { ImportCustomProperties, ImportOptions } from './options'; import valuesParser from 'postcss-value-parser'; -import vm from 'vm'; import { parse } from 'postcss'; import { promises as fsp } from 'fs'; -import { createRequire } from 'module'; /* Get Custom Properties from CSS File /* ========================================================================== */