From 4f5dfca46adbe028bbf83b7ab5fd47b422c68c68 Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Fri, 19 Sep 2025 16:40:44 +0200 Subject: [PATCH 1/7] dedupe font-sizes --- src/index.test.ts | 72 ++++++++++++++++++++++++++++++++++++++++ src/index.ts | 3 +- src/parse-length.test.ts | 6 ++++ src/parse-length.ts | 25 ++++++++++++-- 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 740f5b3..3509378 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -185,6 +185,26 @@ describe('css_to_tokens', () => { }, }) }) + test('handles `0`', () => { + let actual = css_to_tokens(` + .my-design-system { + font-size: 0; + } + `) + expect(actual.font_size).toEqual({ + 'fontSize-c238': { + $type: 'dimension', + $value: { + value: 0, + unit: 'px' + }, + $extensions: { + [EXTENSION_AUTHORED_AS]: '0', + [EXTENSION_USAGE_COUNT]: 1, + } + }, + }) + }) test('outputs a value type when not using rem or px', () => { let actual = css_to_tokens(` @@ -236,6 +256,58 @@ describe('css_to_tokens', () => { }, }) }) + + test('dedupes identical sizes', () => { + let actual = css_to_tokens(` + .my-design-system { + font-size: 16px; + font-size: 16.0PX; + + font-size: 0.5rem; + font-size: .5rem; + + font-size: 0; + font-size: 0px; + font-size: 0rem; + } + `) + expect(actual.font_size).toEqual({ + 'fontSize-171eed': { + $type: 'dimension', + $value: { + value: 16, + unit: 'px' + }, + $extensions: { + [EXTENSION_AUTHORED_AS]: '16.0PX', + // Count is still 1 because we look at the last dicovered value + [EXTENSION_USAGE_COUNT]: 1, + } + }, + 'fontSize-548aa743': { + $type: 'dimension', + $value: { + unit: 'rem', + value: 0.5, + }, + $extensions: { + [EXTENSION_AUTHORED_AS]: '.5rem', + [EXTENSION_USAGE_COUNT]: 1, + }, + }, + 'fontSize-c238': { + $type: 'dimension', + $value: { + unit: 'px', + value: 0, + }, + $extensions: { + [EXTENSION_AUTHORED_AS]: '0rem', + [EXTENSION_USAGE_COUNT]: 1, + }, + }, + }) + }) }) describe('font families', () => { diff --git a/src/index.ts b/src/index.ts index ba928ab..0f1b6ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -124,7 +124,6 @@ export function analysis_to_tokens(analysis: CssAnalysis): Tokens { let unique = get_unique(analysis.values.fontSizes) for (let font_size in unique) { - let name = `fontSize-${hash(font_size)}` let parsed = parse_length(font_size) let extensions = { [EXTENSION_AUTHORED_AS]: font_size, @@ -132,11 +131,13 @@ export function analysis_to_tokens(analysis: CssAnalysis): Tokens { } if (parsed === null) { + let name = `fontSize-${hash(font_size)}` font_sizes[name] = { $value: font_size, $extensions: extensions, } } else { + let name = `fontSize-${hash(parsed.value.toString() + parsed.unit)}` font_sizes[name] = { $type: 'dimension', $value: parsed, diff --git a/src/parse-length.test.ts b/src/parse-length.test.ts index 681fcd0..57eb91f 100644 --- a/src/parse-length.test.ts +++ b/src/parse-length.test.ts @@ -26,6 +26,12 @@ test('absolute size keywords', () => { expect.soft(parse_length('xxx-large')).toEqual({ value: 3, unit: 'rem' }) }) +test('unitless 0', () => { + expect.soft(parse_length('0')).toEqual({ value: 0, unit: 'px' }) + expect.soft(parse_length('0.0')).toEqual({ value: 0, unit: 'px' }) + expect.soft(parse_length('+0')).toEqual({ value: 0, unit: 'px' }) +}) + test('invalid values', () => { expect.soft(parse_length('')).toBeNull() expect.soft(parse_length('1')).toBeNull() diff --git a/src/parse-length.ts b/src/parse-length.ts index ef40c26..1431f0c 100644 --- a/src/parse-length.ts +++ b/src/parse-length.ts @@ -32,10 +32,19 @@ export function parse_length(value: string): DesignTokenLength | null { switch (maybe_length.type) { case 'Dimension': { - if (maybe_length.unit === 'px' || maybe_length.unit === 'rem') { + let unit = maybe_length.unit.toLowerCase() + if (unit === 'px' || unit === 'rem') { + let value = Number(maybe_length.value) + // Always return `0px`, `0`, or `0rem` as `0px` + if (value === 0) { + return { + value: 0, + unit: 'px', + } + } return { - value: Number(maybe_length.value), - unit: maybe_length.unit + value: value, + unit, } } break @@ -51,6 +60,16 @@ export function parse_length(value: string): DesignTokenLength | null { unit: 'rem' } } + break + } + case 'Number': { + if (Number(maybe_length.value) === 0) { + return { + value: 0, + unit: 'px', + } + } + break } } From c2a1c3e5a388807c75f967f940be1361078a8a7d Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Fri, 19 Sep 2025 16:51:02 +0200 Subject: [PATCH 2/7] dedupe line heights --- src/destructure-line-height.test.ts | 6 +++ src/destructure-line-height.ts | 6 ++- src/index.test.ts | 58 +++++++++++++++++++++++++++++ src/index.ts | 5 ++- 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/destructure-line-height.test.ts b/src/destructure-line-height.test.ts index a77ea53..75c6540 100644 --- a/src/destructure-line-height.test.ts +++ b/src/destructure-line-height.test.ts @@ -36,6 +36,12 @@ test('length', () => { expect.soft(destructure_line_height('1e2em')).toEqual({ value: 100, unit: 'em' }) }) +test('zero', () => { + expect.soft(destructure_line_height('0%')).toEqual(0) + expect.soft(destructure_line_height('0px')).toEqual(0) + expect.soft(destructure_line_height('0')).toEqual(0) +}) + test('unprocessable values', () => { expect.soft(destructure_line_height('var(--my-line-height)')).toEqual(null) expect.soft(destructure_line_height('var(--my-line-height, 1.2)')).toEqual(null) diff --git a/src/destructure-line-height.ts b/src/destructure-line-height.ts index d695f21..c7e9425 100644 --- a/src/destructure-line-height.ts +++ b/src/destructure-line-height.ts @@ -21,8 +21,12 @@ export function destructure_line_height(value: string): Length | number | null { switch (maybe_dimension.type) { case 'Dimension': { + let value = Number(maybe_dimension.value) + if (value === 0) { + return 0 + } return { - value: Number(maybe_dimension.value), + value, unit: maybe_dimension.unit } } diff --git a/src/index.test.ts b/src/index.test.ts index 3509378..4d16756 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -352,6 +352,64 @@ describe('css_to_tokens', () => { }) }) + test('dedupes line heights', () => { + let actual = css_to_tokens(` + .my-design-system { + line-height: 1; + line-height: 1.0; + + line-height: 1rem; + line-height: 1.0rem; + + line-height: 1px; + line-height: 1.0px; + + line-height: 0; + line-height: 0.0; + } + `) + expect(actual.line_height).toEqual({ + 'lineHeight-31': { + $type: 'number', + $value: 1, + $extensions: { + [EXTENSION_AUTHORED_AS]: '1.0', + [EXTENSION_USAGE_COUNT]: 1, + } + }, + 'lineHeight-17fec9': { + $type: 'dimension', + $value: { + value: 1, + unit: 'rem' + }, + $extensions: { + [EXTENSION_AUTHORED_AS]: '1.0rem', + [EXTENSION_USAGE_COUNT]: 1, + } + }, + 'lineHeight-c5f9': { + $type: 'dimension', + $value: { + value: 1, + unit: 'px' + }, + $extensions: { + [EXTENSION_AUTHORED_AS]: '1.0px', + [EXTENSION_USAGE_COUNT]: 1, + } + }, + 'lineHeight-30': { + $type: 'number', + $value: 0, + $extensions: { + [EXTENSION_AUTHORED_AS]: '0.0', + [EXTENSION_USAGE_COUNT]: 1, + } + }, + }) + }) + test('outputs a number type when using a number', () => { let actual = css_to_tokens(` .my-design-system { diff --git a/src/index.ts b/src/index.ts index 0f1b6ee..ab1c6ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -170,7 +170,6 @@ export function analysis_to_tokens(analysis: CssAnalysis): Tokens { let unique = get_unique(analysis.values.lineHeights) for (let line_height in unique) { - let name = `lineHeight-${hash(line_height)}` let parsed = destructure_line_height(line_height) let extensions = { [EXTENSION_AUTHORED_AS]: line_height, @@ -178,11 +177,13 @@ export function analysis_to_tokens(analysis: CssAnalysis): Tokens { } if (parsed === null) { + let name = `lineHeight-${hash(line_height)}` line_heights[name] = { $value: line_height, $extensions: extensions, } } else if (typeof parsed === 'number') { + let name = `lineHeight-${hash(parsed)}` line_heights[name] = { $type: 'number', $value: parsed, @@ -190,12 +191,14 @@ export function analysis_to_tokens(analysis: CssAnalysis): Tokens { } } else if (typeof parsed === 'object') { if (parsed.unit === 'px' || parsed.unit === 'rem') { + let name = `lineHeight-${hash(parsed.value.toString() + parsed.unit)}` line_heights[name] = { $type: 'dimension', $value: parsed, $extensions: extensions, } } else { + let name = `lineHeight-${hash(line_height)}` line_heights[name] = { $value: line_height, $extensions: extensions, From 1677bf43a9f0287ee91b84655ba3bda875e22202 Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Mon, 22 Sep 2025 11:01:42 +0200 Subject: [PATCH 3/7] deduplicate colors, line-heights, font-sizes and durations --- src/destructure-line-height.test.ts | 1 + src/index.test.ts | 120 +++++++++++++++++++++++----- src/index.ts | 105 ++++++++++++++++-------- 3 files changed, 172 insertions(+), 54 deletions(-) diff --git a/src/destructure-line-height.test.ts b/src/destructure-line-height.test.ts index 75c6540..3679c68 100644 --- a/src/destructure-line-height.test.ts +++ b/src/destructure-line-height.test.ts @@ -22,6 +22,7 @@ test('percentage', () => { test('number', () => { expect.soft(destructure_line_height('1')).toEqual(1) + expect.soft(destructure_line_height('1.0')).toEqual(1) expect.soft(destructure_line_height('1.1')).toEqual(1.1) expect.soft(destructure_line_height('1e2')).toEqual(100) }) diff --git a/src/index.test.ts b/src/index.test.ts index 4d16756..114392c 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -17,7 +17,7 @@ describe('analysis_to_tokens', () => { let expected = { color: { - 'green-5e0cf03': { + 'green-3fc58faf': { $type: 'color', $value: { colorSpace: 'srgb', @@ -85,7 +85,7 @@ describe('css_to_tokens', () => { } `) expect(actual.color).toEqual({ - 'green-5e0cf03': { + 'green-3fc58faf': { $type: 'color', $value: { colorSpace: 'srgb', @@ -98,7 +98,7 @@ describe('css_to_tokens', () => { [EXTENSION_CSS_PROPERTIES]: ['color', 'border-color'], } }, - 'grey-8139d9b': { + 'grey-d68e53e4': { $type: 'color', $value: { colorSpace: 'srgb', @@ -130,7 +130,7 @@ describe('css_to_tokens', () => { } `) expect(actual.color).toEqual({ - 'black-991c5d52': { + 'black-edec3e7a': { $type: 'color', $value: { colorSpace: 'srgb', @@ -159,9 +159,52 @@ describe('css_to_tokens', () => { test('extensions[css-properties] does not yield a type error', () => { let actual = css_to_tokens('a { color: green; }') // Not so much interested in the test result, more looking that this isn't giving a type error - let properties = actual.color['green-5e0cf03']!['$extensions'][EXTENSION_CSS_PROPERTIES] + let properties = actual.color['green-3fc58faf']!['$extensions'][EXTENSION_CSS_PROPERTIES] expect(properties).toEqual(['color']) }) + + test('deduplicates colors', () => { + let actual = css_to_tokens(` + .my-design-system { + color: green; + border-color: rgb(0, 128, 0); + background-color: #008000; + color: #008000; + } + .alpha { + border-color: rgb(0 128 0 / 0.5); + border-color: rgb(0 128 0 / .5); + } + `) + expect(actual.color).toEqual({ + 'green-3fc58faf': { + $type: 'color', + $value: { + colorSpace: 'srgb', + components: [0, 0.5019607843137255, 0], + alpha: 1, + }, + $extensions: { + [EXTENSION_AUTHORED_AS]: 'green', + [EXTENSION_USAGE_COUNT]: 4, + [EXTENSION_CSS_PROPERTIES]: ['color', 'border-color', 'background-color'], + } + }, + 'green-64a061f5': { + $type: 'color', + $value: { + colorSpace: 'srgb', + components: [0, 0.5019607843137255, 0], + alpha: 0.5, + }, + $extensions: { + [EXTENSION_AUTHORED_AS]: 'rgb(0 128 0 / 0.5)', + [EXTENSION_USAGE_COUNT]: 2, + [EXTENSION_CSS_PROPERTIES]: ['border-color'], + } + }, + }) + }) }) describe('font sizes', () => { @@ -279,9 +322,9 @@ describe('css_to_tokens', () => { unit: 'px' }, $extensions: { - [EXTENSION_AUTHORED_AS]: '16.0PX', + [EXTENSION_AUTHORED_AS]: '16px', // Count is still 1 because we look at the last dicovered value - [EXTENSION_USAGE_COUNT]: 1, + [EXTENSION_USAGE_COUNT]: 2, } }, 'fontSize-548aa743': { @@ -291,8 +334,8 @@ describe('css_to_tokens', () => { value: 0.5, }, $extensions: { - [EXTENSION_AUTHORED_AS]: '.5rem', - [EXTENSION_USAGE_COUNT]: 1, + [EXTENSION_AUTHORED_AS]: '0.5rem', + [EXTENSION_USAGE_COUNT]: 2, }, }, 'fontSize-c238': { @@ -302,8 +345,8 @@ describe('css_to_tokens', () => { value: 0, }, $extensions: { - [EXTENSION_AUTHORED_AS]: '0rem', - [EXTENSION_USAGE_COUNT]: 1, + [EXTENSION_AUTHORED_AS]: '0', + [EXTENSION_USAGE_COUNT]: 3, }, }, }) @@ -373,8 +416,8 @@ describe('css_to_tokens', () => { $type: 'number', $value: 1, $extensions: { - [EXTENSION_AUTHORED_AS]: '1.0', - [EXTENSION_USAGE_COUNT]: 1, + [EXTENSION_AUTHORED_AS]: '1', + [EXTENSION_USAGE_COUNT]: 2, } }, 'lineHeight-17fec9': { @@ -384,8 +427,8 @@ describe('css_to_tokens', () => { unit: 'rem' }, $extensions: { - [EXTENSION_AUTHORED_AS]: '1.0rem', - [EXTENSION_USAGE_COUNT]: 1, + [EXTENSION_AUTHORED_AS]: '1rem', + [EXTENSION_USAGE_COUNT]: 2, } }, 'lineHeight-c5f9': { @@ -395,16 +438,16 @@ describe('css_to_tokens', () => { unit: 'px' }, $extensions: { - [EXTENSION_AUTHORED_AS]: '1.0px', - [EXTENSION_USAGE_COUNT]: 1, + [EXTENSION_AUTHORED_AS]: '1px', + [EXTENSION_USAGE_COUNT]: 2, } }, 'lineHeight-30': { $type: 'number', $value: 0, $extensions: { - [EXTENSION_AUTHORED_AS]: '0.0', - [EXTENSION_USAGE_COUNT]: 1, + [EXTENSION_AUTHORED_AS]: '0', + [EXTENSION_USAGE_COUNT]: 2, } }, }) @@ -623,7 +666,7 @@ describe('css_to_tokens', () => { } `) expect(actual.duration).toEqual({ - 'duration-452f2b3b': { + 'duration-f9e24f32': { $value: 'var(--test)', $extensions: { [EXTENSION_AUTHORED_AS]: 'var(--test)', @@ -632,6 +675,43 @@ describe('css_to_tokens', () => { }, }) }) + + test('dedupes values', () => { + let actual = css_to_tokens(` + .my-design-system { + animation-duration: 100ms; + animation-duration: 0.1s; + animation-duration: .1s; + + animation-duration: 1S; + animation-duration: 1000MS; + } + `) + expect(actual.duration).toEqual({ + 'duration-bdf1': { + $type: 'duration', + $value: { + value: 100, + unit: 'ms' + }, + $extensions: { + [EXTENSION_AUTHORED_AS]: '100ms', + [EXTENSION_USAGE_COUNT]: 3, + } + }, + 'duration-17005f': { + $type: 'duration', + $value: { + value: 1000, + unit: 'ms' + }, + $extensions: { + [EXTENSION_AUTHORED_AS]: '1S', + [EXTENSION_USAGE_COUNT]: 2, + } + }, + }) + }) }) describe('easing', () => { diff --git a/src/index.ts b/src/index.ts index ab1c6ba..56d27cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -95,23 +95,35 @@ export function analysis_to_tokens(analysis: CssAnalysis): Tokens { let color_token = color_to_token(color) if (color_token !== null) { - let name = `${color_dict.get(group)}-${hash(color)}` + let name = `${color_dict.get(group)}-${hash([ + color_token.colorSpace, + ...color_token.components, + color_token.alpha, + ].join(''))}` let items_per_context = analysis.values.colors.itemsPerContext as ItemsPerContext let properties = Object.entries(items_per_context).reduce((acc, [property, collection]) => { if (color in collection.unique || (collection.uniqueWithLocations && color in collection.uniqueWithLocations)) { - acc.push(property) + acc.add(property) } return acc - }, [] as Array) + }, new Set() as Set) - colors[name] = { - $type: 'color', - $value: color_token, - $extensions: { - [EXTENSION_AUTHORED_AS]: color, - [EXTENSION_USAGE_COUNT]: get_count(unique[color]!), - [EXTENSION_CSS_PROPERTIES]: properties, + if (colors[name]) { + colors[name].$extensions[EXTENSION_CSS_PROPERTIES] = Array.from( + new Set([...colors[name].$extensions[EXTENSION_CSS_PROPERTIES], ...properties]) + ) + colors[name].$extensions[EXTENSION_USAGE_COUNT] += get_count(unique[color!]!) + } + else { + colors[name] = { + $type: 'color', + $value: color_token, + $extensions: { + [EXTENSION_AUTHORED_AS]: color, + [EXTENSION_USAGE_COUNT]: get_count(unique[color]!), + [EXTENSION_CSS_PROPERTIES]: Array.from(properties), + } } } } @@ -136,12 +148,18 @@ export function analysis_to_tokens(analysis: CssAnalysis): Tokens { $value: font_size, $extensions: extensions, } - } else { + } + else { let name = `fontSize-${hash(parsed.value.toString() + parsed.unit)}` - font_sizes[name] = { - $type: 'dimension', - $value: parsed, - $extensions: extensions, + if (font_sizes[name]) { + font_sizes[name].$extensions[EXTENSION_USAGE_COUNT] += extensions[EXTENSION_USAGE_COUNT] + } + else { + font_sizes[name] = { + $type: 'dimension', + $value: parsed, + $extensions: extensions, + } } } } @@ -182,22 +200,35 @@ export function analysis_to_tokens(analysis: CssAnalysis): Tokens { $value: line_height, $extensions: extensions, } - } else if (typeof parsed === 'number') { + } + else if (typeof parsed === 'number') { let name = `lineHeight-${hash(parsed)}` - line_heights[name] = { - $type: 'number', - $value: parsed, - $extensions: extensions, + if (line_heights[name]) { + line_heights[name].$extensions[EXTENSION_USAGE_COUNT] += extensions[EXTENSION_USAGE_COUNT] } - } else if (typeof parsed === 'object') { - if (parsed.unit === 'px' || parsed.unit === 'rem') { - let name = `lineHeight-${hash(parsed.value.toString() + parsed.unit)}` + else { line_heights[name] = { - $type: 'dimension', + $type: 'number', $value: parsed, $extensions: extensions, } - } else { + } + } + else if (typeof parsed === 'object') { + if (parsed.unit === 'px' || parsed.unit === 'rem') { + let name = `lineHeight-${hash(parsed.value.toString() + parsed.unit)}` + if (line_heights[name]) { + line_heights[name].$extensions[EXTENSION_USAGE_COUNT] += extensions[EXTENSION_USAGE_COUNT] + } + else { + line_heights[name] = { + $type: 'dimension', + $value: parsed, + $extensions: extensions, + } + } + } + else { let name = `lineHeight-${hash(line_height)}` line_heights[name] = { $value: line_height, @@ -273,23 +304,29 @@ export function analysis_to_tokens(analysis: CssAnalysis): Tokens { for (let duration in unique) { let parsed = convert_duration(duration) let is_valid = parsed < Number.MAX_SAFE_INTEGER - 1 - let name = hash(parsed.toString()) let extensions = { [EXTENSION_AUTHORED_AS]: duration, [EXTENSION_USAGE_COUNT]: get_count(unique[duration]!), } if (is_valid) { - durations[`duration-${name}`] = { - $type: 'duration', - $value: { - value: parsed, - unit: 'ms' - }, - $extensions: extensions, + let name = `duration-${hash(parsed.toString())}` + if (durations[name]) { + durations[name].$extensions[EXTENSION_USAGE_COUNT] += extensions[EXTENSION_USAGE_COUNT] + } + else { + durations[name] = { + $type: 'duration', + $value: { + value: parsed, + unit: 'ms' + }, + $extensions: extensions, + } } } else { - durations[`duration-${name}`] = { + let name = `duration-${hash('invalid' + parsed.toString())}` + durations[name] = { $value: duration, $extensions: extensions, } From dada6cec676f807dfcf99c2904db2193e00038de Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Mon, 22 Sep 2025 11:20:02 +0200 Subject: [PATCH 4/7] BREAKING: target node v22+, rewrite some stuff --- package.json | 2 +- src/destructure-line-height.test.ts | 3 +++ src/index.test.ts | 33 ++++++++++++++--------------- src/index.ts | 28 ++++++++++++------------ tsconfig.json | 4 ++-- 5 files changed, 36 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index e5b7f6e..3790988 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "default": "./dist/css-design-tokens.js" }, "engines": { - "node": ">=18" + "node": ">=22" }, "scripts": { "test": "vitest run", diff --git a/src/destructure-line-height.test.ts b/src/destructure-line-height.test.ts index 3679c68..cadd21a 100644 --- a/src/destructure-line-height.test.ts +++ b/src/destructure-line-height.test.ts @@ -39,8 +39,11 @@ test('length', () => { test('zero', () => { expect.soft(destructure_line_height('0%')).toEqual(0) + expect.soft(destructure_line_height('0.0%')).toEqual(0) expect.soft(destructure_line_height('0px')).toEqual(0) + expect.soft(destructure_line_height('0.0px')).toEqual(0) expect.soft(destructure_line_height('0')).toEqual(0) + expect.soft(destructure_line_height('0.0')).toEqual(0) }) test('unprocessable values', () => { diff --git a/src/index.test.ts b/src/index.test.ts index 114392c..114aa12 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -323,7 +323,6 @@ describe('css_to_tokens', () => { }, $extensions: { [EXTENSION_AUTHORED_AS]: '16px', - // Count is still 1 because we look at the last dicovered value [EXTENSION_USAGE_COUNT]: 2, } }, @@ -640,10 +639,10 @@ describe('css_to_tokens', () => { describe('duration', () => { test('outputs a token when using a valid duration', () => { let actual = css_to_tokens(` - .my-design-system { - animation-duration: 1s; - } - `) + .my-design-system { + animation-duration: 1s; + } + `) expect(actual.duration).toEqual({ 'duration-17005f': { $type: 'duration', @@ -661,10 +660,10 @@ describe('css_to_tokens', () => { test('outputs an unparsed token when using an invalid duration', () => { let actual = css_to_tokens(` - .my-design-system { - animation-duration: var(--test); - } - `) + .my-design-system { + animation-duration: var(--test); + } + `) expect(actual.duration).toEqual({ 'duration-f9e24f32': { $value: 'var(--test)', @@ -678,15 +677,15 @@ describe('css_to_tokens', () => { test('dedupes values', () => { let actual = css_to_tokens(` - .my-design-system { - animation-duration: 100ms; - animation-duration: 0.1s; - animation-duration: .1s; + .my-design-system { + animation-duration: 100ms; + animation-duration: 0.1s; + animation-duration: .1s; - animation-duration: 1S; - animation-duration: 1000MS; - } - `) + animation-duration: 1S; + animation-duration: 1000MS; + } + `) expect(actual.duration).toEqual({ 'duration-bdf1': { $type: 'duration', diff --git a/src/index.ts b/src/index.ts index 56d27cf..d6595e4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -93,27 +93,27 @@ export function analysis_to_tokens(analysis: CssAnalysis): Tokens { for (let [group, group_colors] of color_groups) { for (let color of group_colors) { let color_token = color_to_token(color) + let count = get_count(unique[color]!) if (color_token !== null) { - let name = `${color_dict.get(group)}-${hash([ - color_token.colorSpace, - ...color_token.components, - color_token.alpha, - ].join(''))}` + let { colorSpace, components, alpha } = color_token + let name = `${color_dict.get(group)}-${hash([colorSpace, ...components, alpha].join(''))}` let items_per_context = analysis.values.colors.itemsPerContext as ItemsPerContext - let properties = Object.entries(items_per_context).reduce((acc, [property, collection]) => { - if (color in collection.unique || (collection.uniqueWithLocations && color in collection.uniqueWithLocations)) { - acc.add(property) + let new_properties = Object.entries(items_per_context).reduce((acc, [property, collection]) => { + if (color in collection.unique) { + return acc.add(property) + } + if (collection.uniqueWithLocations && color in collection.uniqueWithLocations) { + return acc.add(property) } return acc }, new Set() as Set) if (colors[name]) { - colors[name].$extensions[EXTENSION_CSS_PROPERTIES] = Array.from( - new Set([...colors[name].$extensions[EXTENSION_CSS_PROPERTIES], ...properties]) - ) - colors[name].$extensions[EXTENSION_USAGE_COUNT] += get_count(unique[color!]!) + let old_properties = colors[name].$extensions[EXTENSION_CSS_PROPERTIES] + colors[name].$extensions[EXTENSION_CSS_PROPERTIES] = Array.from(new Set(old_properties).union(new_properties)) + colors[name].$extensions[EXTENSION_USAGE_COUNT] += count } else { colors[name] = { @@ -121,8 +121,8 @@ export function analysis_to_tokens(analysis: CssAnalysis): Tokens { $value: color_token, $extensions: { [EXTENSION_AUTHORED_AS]: color, - [EXTENSION_USAGE_COUNT]: get_count(unique[color]!), - [EXTENSION_CSS_PROPERTIES]: Array.from(properties), + [EXTENSION_USAGE_COUNT]: count, + [EXTENSION_CSS_PROPERTIES]: Array.from(new_properties), } } } diff --git a/tsconfig.json b/tsconfig.json index 59e2b2c..71d9f99 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { // Base options: "skipLibCheck": true, - "target": "es2022", + "target": "esnext", "verbatimModuleSyntax": true, "allowJs": true, "checkJs": true, @@ -17,7 +17,7 @@ "noEmit": true, // Code runs in the DOM "lib": [ - "ES2022", + "esnext", "DOM", "DOM.Iterable" ], From 961086d57bff4a9fca973909ea8382d17bb4da82 Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Mon, 22 Sep 2025 11:25:03 +0200 Subject: [PATCH 5/7] rm more tsconfig --- tsconfig.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 71d9f99..b7c8dd9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,11 +15,8 @@ "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, "noEmit": true, - // Code runs in the DOM "lib": [ "esnext", - "DOM", - "DOM.Iterable" ], }, "include": [ From 86ee0ea4bd0cee78f2df48772d9bda5022cc04e1 Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Mon, 22 Sep 2025 11:33:31 +0200 Subject: [PATCH 6/7] upgrade GH actions to v5 for Node 24 --- .github/workflows/release.yml | 8 ++++---- .github/workflows/test.yml | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a04e471..05222a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,8 +11,8 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 - run: npm install --ignore-scripts --no-audit --no-fund - run: npm test @@ -20,8 +20,8 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 with: cache: 'npm' registry-url: https://registry.npmjs.org/ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index adf4214..daa57b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,9 +14,9 @@ jobs: name: Unit tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: cache: "npm" - run: npm install --ignore-scripts --no-audit --no-fund @@ -26,9 +26,9 @@ jobs: name: Check types runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: cache: "npm" - run: npm install --ignore-scripts --no-audit --no-fund @@ -39,9 +39,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: cache: "npm" - run: npm install --ignore-scripts --no-audit --no-fund From dcb49771b721742782572042639774d4b30de89f Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Mon, 22 Sep 2025 11:44:04 +0200 Subject: [PATCH 7/7] fix CI node versions --- .github/workflows/release.yml | 10 +++++++--- .github/workflows/test.yml | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 05222a0..d9ba9dc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,10 @@ jobs: steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 - - run: npm install --ignore-scripts --no-audit --no-fund + with: + cache: "npm" + node-version: 22 + - run: npm ci --ignore-scripts --no-audit --no-fund - run: npm test publish-npm: @@ -23,9 +26,10 @@ jobs: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 with: - cache: 'npm' + node-version: 22 + cache: "npm" registry-url: https://registry.npmjs.org/ - - run: npm install --ignore-scripts --no-audit --no-fund + - run: npm ci --ignore-scripts --no-audit --no-fund - run: npm run build - run: npm publish --public env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index daa57b0..91ca824 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,7 @@ jobs: uses: actions/setup-node@v5 with: cache: "npm" + node-version: 22 - run: npm install --ignore-scripts --no-audit --no-fund - run: npm test @@ -31,6 +32,7 @@ jobs: uses: actions/setup-node@v5 with: cache: "npm" + node-version: 22 - run: npm install --ignore-scripts --no-audit --no-fund - run: npm run check @@ -44,6 +46,7 @@ jobs: uses: actions/setup-node@v5 with: cache: "npm" + node-version: 22 - run: npm install --ignore-scripts --no-audit --no-fund - name: Build package run: npm run build