From 9816a778bb9176127cac563883868f945215f5f2 Mon Sep 17 00:00:00 2001 From: Hunter Tunnicliff Date: Wed, 4 Aug 2021 20:31:04 -0700 Subject: [PATCH 1/5] Replace `color` with `culori` --- package-lock.json | 76 +++++------------------------------ package.json | 2 +- src/util/pluginUtils.js | 9 +---- src/util/withAlphaVariable.js | 72 ++++++++++++++++----------------- 4 files changed, 50 insertions(+), 109 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9663403a2778..b1d3c26bf89b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,8 @@ "bytes": "^3.0.0", "chalk": "^4.1.1", "chokidar": "^3.5.2", - "color": "^3.2.0", "cosmiconfig": "^7.0.0", + "culori": "^0.19.1", "detective": "^5.2.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", @@ -3472,15 +3472,6 @@ "node": ">=0.10.0" } }, - "node_modules/color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.0.tgz", - "integrity": "sha512-4ximSqKXLTQmYLJuvrRHtpOqniR+ASoaVK+Rxdy6ZpfsLvUqtIM7oGGgopRG+O4p9NRv/AfuVD3jsvdxyXqozQ==", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.6.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3497,15 +3488,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/color-string": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", - "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/colord": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/colord/-/colord-2.0.1.tgz", @@ -3866,6 +3848,11 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, + "node_modules/culori": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/culori/-/culori-0.19.1.tgz", + "integrity": "sha512-K/NLpdtNnSQwH2Ru/Fk39wDL40v9PxTBFY6jHQegJDhmBqrE/d9mJB/AD4odSZJml10AlJjZdm6+I9JM3nE/EQ==" + }, "node_modules/data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -9270,19 +9257,6 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -13098,15 +13072,6 @@ "object-visit": "^1.0.0" } }, - "color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.0.tgz", - "integrity": "sha512-4ximSqKXLTQmYLJuvrRHtpOqniR+ASoaVK+Rxdy6ZpfsLvUqtIM7oGGgopRG+O4p9NRv/AfuVD3jsvdxyXqozQ==", - "requires": { - "color-convert": "^2.0.1", - "color-string": "^1.6.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -13120,15 +13085,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "color-string": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", - "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "colord": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/colord/-/colord-2.0.1.tgz", @@ -13402,6 +13358,11 @@ } } }, + "culori": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/culori/-/culori-0.19.1.tgz", + "integrity": "sha512-K/NLpdtNnSQwH2Ru/Fk39wDL40v9PxTBFY6jHQegJDhmBqrE/d9mJB/AD4odSZJml10AlJjZdm6+I9JM3nE/EQ==" + }, "data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -17444,21 +17405,6 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } - } - }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", diff --git a/package.json b/package.json index 99d3c798f1cc..e45f4d0dd5c7 100644 --- a/package.json +++ b/package.json @@ -74,8 +74,8 @@ "bytes": "^3.0.0", "chalk": "^4.1.1", "chokidar": "^3.5.2", - "color": "^3.2.0", "cosmiconfig": "^7.0.0", + "culori": "^0.19.1", "detective": "^5.2.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index 8d67f35e015c..38d015f0011a 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -1,6 +1,6 @@ import selectorParser from 'postcss-selector-parser' import postcss from 'postcss' -import createColor from 'color' +import culori from 'culori' import escapeCommas from './escapeCommas' import { withAlphaValue } from './withAlphaVariable' @@ -203,12 +203,7 @@ function splitAlpha(modifier) { } function isColor(value) { - try { - createColor(value) - return true - } catch (e) { - return false - } + return culori.parse(value) !== undefined } export function asColor(modifier, lookup = {}, tailwindConfig = {}) { diff --git a/src/util/withAlphaVariable.js b/src/util/withAlphaVariable.js index 5080cf908e55..d27b6439ffbf 100644 --- a/src/util/withAlphaVariable.js +++ b/src/util/withAlphaVariable.js @@ -1,25 +1,8 @@ -import createColor from 'color' +import culori from 'culori' import _ from 'lodash' -function hasAlpha(color) { - return ( - color.startsWith('rgba(') || - color.startsWith('hsla(') || - (color.startsWith('#') && color.length === 9) || - (color.startsWith('#') && color.length === 5) - ) -} - -export function toRgba(color) { - const [r, g, b, a] = createColor(color).rgb().array() - - return [r, g, b, a === undefined && hasAlpha(color) ? 1 : a] -} - -export function toHsla(color) { - const [h, s, l, a] = createColor(color).hsl().array() - - return [h, `${s}%`, `${l}%`, a === undefined && hasAlpha(color) ? 1 : a] +function isValidColor(color) { + return culori.parse(color) !== undefined } export function withAlphaValue(color, alphaValue, defaultValue) { @@ -27,13 +10,22 @@ export function withAlphaValue(color, alphaValue, defaultValue) { return color({ opacityValue: alphaValue }) } - try { - const isHSL = color.startsWith('hsl') - const [i, j, k] = isHSL ? toHsla(color) : toRgba(color) - return `${isHSL ? 'hsla' : 'rgba'}(${i}, ${j}, ${k}, ${alphaValue})` - } catch { - return defaultValue + if (isValidColor(color)) { + // Parse color + const parsed = culori.parse(color) + + // Apply alpha value + parsed.alpha = alphaValue + + // Return formatted string + if (parsed.mode === 'hsl') { + return culori.formatHsl(parsed) + } else { + return culori.formatRgb(parsed) + } } + + return defaultValue } export default function withAlphaVariable({ color, property, variable }) { @@ -44,24 +36,32 @@ export default function withAlphaVariable({ color, property, variable }) { } } - try { - const isHSL = color.startsWith('hsl') - - const [i, j, k, a] = isHSL ? toHsla(color) : toRgba(color) + if (isValidColor(color)) { + const { alpha = 1, mode } = culori.parse(color) - if (a !== undefined) { + if (alpha !== 1) { + // Has an alpha value, return color as-is return { [property]: color, } } - return { - [variable]: '1', - [property]: `${isHSL ? 'hsla' : 'rgba'}(${i}, ${j}, ${k}, var(${variable}))`, + let value + if (mode === 'hsl') { + const { h, s, l } = culori.hsl(color) + value = `hsla(${h}, ${s}, ${l}, var(${variable}))` + } else { + const { r, g, b } = culori.rgb(color) + value = `rgba(${r}, ${g}, ${b}, var(${variable}))` } - } catch (error) { + return { - [property]: color, + [variable]: '1', + [property]: value, } } + + return { + [property]: color, + } } From 7d7f6fa792a425adf621a284e036fe22778177e3 Mon Sep 17 00:00:00 2001 From: Hunter Tunnicliff Date: Wed, 4 Aug 2021 20:40:53 -0700 Subject: [PATCH 2/5] Use correct ESM import syntax --- src/util/pluginUtils.js | 2 +- src/util/withAlphaVariable.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index 38d015f0011a..a500f6db10bb 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -1,6 +1,6 @@ import selectorParser from 'postcss-selector-parser' import postcss from 'postcss' -import culori from 'culori' +import * as culori from 'culori' import escapeCommas from './escapeCommas' import { withAlphaValue } from './withAlphaVariable' diff --git a/src/util/withAlphaVariable.js b/src/util/withAlphaVariable.js index d27b6439ffbf..f9dc753b14b0 100644 --- a/src/util/withAlphaVariable.js +++ b/src/util/withAlphaVariable.js @@ -1,4 +1,4 @@ -import culori from 'culori' +import * as culori from 'culori' import _ from 'lodash' function isValidColor(color) { From a582beb228594c3cf9e3729efbb2f01df78b761f Mon Sep 17 00:00:00 2001 From: Hunter Tunnicliff Date: Wed, 4 Aug 2021 20:44:26 -0700 Subject: [PATCH 3/5] Add tests --- tests/withAlphaVariable.test.js | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/withAlphaVariable.test.js b/tests/withAlphaVariable.test.js index bf689ed6a7c5..ebd86a5b8d28 100644 --- a/tests/withAlphaVariable.test.js +++ b/tests/withAlphaVariable.test.js @@ -76,6 +76,15 @@ test('it ignores colors that already have an alpha channel', () => { ).toEqual({ 'background-color': 'rgba(255, 255, 255, 0.5)', }) + expect( + withAlphaVariable({ + color: 'rgba(255 255 255 / 0.5)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'rgba(255 255 255 / 0.5)', + }) expect( withAlphaVariable({ color: 'hsla(240, 100%, 50%, 1)', @@ -94,6 +103,15 @@ test('it ignores colors that already have an alpha channel', () => { ).toEqual({ 'background-color': 'hsla(240, 100%, 50%, 0.5)', }) + expect( + withAlphaVariable({ + color: 'hsl(240 100% 50% / 0.5)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'hsl(240 100% 50% / 0.5)', + }) }) test('it allows a closure to be passed', () => { @@ -130,6 +148,16 @@ test('it transforms rgb and hsl to rgba and hsla', () => { '--tw-bg-opacity': '1', 'background-color': 'rgba(50, 50, 50, var(--tw-bg-opacity))', }) + expect( + withAlphaVariable({ + color: 'rgb(50 50 50)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + '--tw-bg-opacity': '1', + 'background-color': 'rgba(50, 50, 50, var(--tw-bg-opacity))', + }) expect( withAlphaVariable({ color: 'hsl(50, 50%, 50%)', @@ -140,4 +168,14 @@ test('it transforms rgb and hsl to rgba and hsla', () => { '--tw-bg-opacity': '1', 'background-color': 'hsla(50, 50%, 50%, var(--tw-bg-opacity))', }) + expect( + withAlphaVariable({ + color: 'hsl(50 50% 50%)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + '--tw-bg-opacity': '1', + 'background-color': 'hsla(50, 50%, 50%, var(--tw-bg-opacity))', + }) }) From 6df465a34aeca89a5d140126aaf2d6873c9aad11 Mon Sep 17 00:00:00 2001 From: Hunter Tunnicliff Date: Wed, 4 Aug 2021 20:56:04 -0700 Subject: [PATCH 4/5] Fix formatting --- src/util/withAlphaVariable.js | 17 +++++++---------- tests/withAlphaVariable.test.js | 10 ++++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/util/withAlphaVariable.js b/src/util/withAlphaVariable.js index f9dc753b14b0..eede4b894bc5 100644 --- a/src/util/withAlphaVariable.js +++ b/src/util/withAlphaVariable.js @@ -37,23 +37,20 @@ export default function withAlphaVariable({ color, property, variable }) { } if (isValidColor(color)) { - const { alpha = 1, mode } = culori.parse(color) + const parsed = culori.parse(color) - if (alpha !== 1) { + if ('alpha' in parsed) { // Has an alpha value, return color as-is return { [property]: color, } } - let value - if (mode === 'hsl') { - const { h, s, l } = culori.hsl(color) - value = `hsla(${h}, ${s}, ${l}, var(${variable}))` - } else { - const { r, g, b } = culori.rgb(color) - value = `rgba(${r}, ${g}, ${b}, var(${variable}))` - } + const formatFn = parsed.mode === 'hsl' ? 'formatHsl' : 'formatRgb' + const value = culori[formatFn]({ + ...parsed, + alpha: 0.3, + }).replace('0.3)', `var(${variable}))`) return { [variable]: '1', diff --git a/tests/withAlphaVariable.test.js b/tests/withAlphaVariable.test.js index ebd86a5b8d28..34ae4487ae05 100644 --- a/tests/withAlphaVariable.test.js +++ b/tests/withAlphaVariable.test.js @@ -7,6 +7,16 @@ test('it adds the right custom property', () => { '--tw-text-opacity': '1', color: 'rgba(255, 0, 0, var(--tw-text-opacity))', }) + expect( + withAlphaVariable({ + color: 'hsl(240 100% 50%)', + property: 'color', + variable: '--tw-text-opacity', + }) + ).toEqual({ + '--tw-text-opacity': '1', + color: 'hsla(240, 100%, 50%, var(--tw-text-opacity))', + }) }) test('it ignores colors that cannot be parsed', () => { From 79e5227b3607a880ce21efe23b280dd17b84919c Mon Sep 17 00:00:00 2001 From: Hunter Tunnicliff Date: Wed, 4 Aug 2021 21:25:57 -0700 Subject: [PATCH 5/5] Fix handling of CSS variable declarations --- src/util/withAlphaVariable.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/util/withAlphaVariable.js b/src/util/withAlphaVariable.js index eede4b894bc5..1c9e8d48ae91 100644 --- a/src/util/withAlphaVariable.js +++ b/src/util/withAlphaVariable.js @@ -17,11 +17,22 @@ export function withAlphaValue(color, alphaValue, defaultValue) { // Apply alpha value parsed.alpha = alphaValue - // Return formatted string + // Format string + let value if (parsed.mode === 'hsl') { - return culori.formatHsl(parsed) + value = culori.formatHsl(parsed) } else { - return culori.formatRgb(parsed) + value = culori.formatRgb(parsed) + } + + // Correctly apply CSS variable alpha value + if (typeof alphaValue === 'string' && alphaValue.startsWith('var(') && value.endsWith('NaN)')) { + value = value.replace('NaN)', `${alphaValue})`) + } + + // Color could not be formatted correctly + if (!value.includes('NaN')) { + return value } } @@ -49,8 +60,8 @@ export default function withAlphaVariable({ color, property, variable }) { const formatFn = parsed.mode === 'hsl' ? 'formatHsl' : 'formatRgb' const value = culori[formatFn]({ ...parsed, - alpha: 0.3, - }).replace('0.3)', `var(${variable}))`) + alpha: NaN, // intentionally set to `NaN` for replacing + }).replace('NaN)', `var(${variable}))`) return { [variable]: '1',