Skip to content

Commit

Permalink
feat: add outputReferencesTransformed utility function (#1154)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorenbroekema authored Apr 12, 2024
1 parent 0b81a08 commit 2da5130
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/mean-panthers-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'style-dictionary': minor
---

Added `outputReferencesTransformed` utility function to pass into outputReferences option, which will not output references for values that have been transitively transformed.
13 changes: 13 additions & 0 deletions __integration__/__snapshots__/outputReferences.test.snap.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,16 @@ css
✔︎ __integration__/build/filteredVariables.css`;
/* end snapshot integration output references should not warn the user if filters out references is prevented with outputReferencesFilter */

snapshots["integration output references should allow using outputReferencesTransformed to not output refs when value has been transitively transformed"] =
`/**
* Do not edit directly
* Generated on Sat, 01 Jan 2000 00:00:00 GMT
*/
:root {
--base: rgb(0,0,0);
--referred: rgba(0,0,0,0.12);
}
`;
/* end snapshot integration output references should allow using outputReferencesTransformed to not output refs when value has been transitively transformed */

55 changes: 55 additions & 0 deletions __integration__/outputReferences.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
*/
import { expect } from 'chai';
import StyleDictionary from 'style-dictionary';
import { fs } from 'style-dictionary/fs';
import { restore, stubMethod } from 'hanbi';
import { buildPath, cleanConsoleOutput } from './_constants.js';
import { resolve } from '../lib/resolve.js';
import { clearOutput } from '../__tests__/__helpers.js';
import { outputReferencesFilter } from '../lib/utils/references/outputReferencesFilter.js';
import { outputReferencesTransformed } from '../lib/utils/index.js';

describe('integration', async () => {
let stub;
Expand All @@ -29,6 +32,58 @@ describe('integration', async () => {
});

describe('output references', async () => {
it('should allow using outputReferencesTransformed to not output refs when value has been transitively transformed', async () => {
restore();
const sd = new StyleDictionary({
tokens: {
base: {
value: 'rgb(0,0,0)',
type: 'color',
},
referred: {
value: 'rgba({base},0.12)',
type: 'color',
},
},
transform: {
'rgb-in-rgba': {
type: 'value',
transitive: true,
matcher: (token) => token.type === 'color',
// quite naive transform to support rgb inside rgba
transformer: (token) => {
const reg = /rgba\((rgb\((\d,\d,\d)\)),((0\.)?\d+?)\)/g;
const match = reg.exec(token.value);
if (match && match[1] && match[2]) {
return token.value.replace(match[1], match[2]);
}
return token.value;
},
},
},
platforms: {
css: {
transforms: ['rgb-in-rgba'],
buildPath,
files: [
{
destination: 'transformedFilteredVariables.css',
format: 'css/variables',
options: {
outputReferences: outputReferencesTransformed,
},
},
],
},
},
});
await sd.buildAllPlatforms();
const output = fs.readFileSync(resolve(`${buildPath}transformedFilteredVariables.css`), {
encoding: 'UTF-8',
});
await expect(output).to.matchSnapshot();
});

it('should warn the user if filters out references briefly', async () => {
const sd = new StyleDictionary({
// we are only testing showFileHeader options so we don't need
Expand Down
2 changes: 1 addition & 1 deletion docs/src/components/sd-playground.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ class SdPlayground extends LitElement {
}

async init() {
await this.initData();
await this.initMonaco();
await this.initData();
this.hasInitializedResolve();
this.currentFile = 'config';
}
Expand Down
9 changes: 9 additions & 0 deletions docs/src/content/docs/reference/Hooks/Formats/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@ You can create custom formats that output references as well. See the [Custom fo
When combining [`filters`](/reference/hooks/filters) with `outputReferences`, it could happen that a token is referencing another token that is getting filtered out.
When that happens, Style Dictionary will throw a warning. However, it is possible to configure `outputReferences` to use [our `outputReferencesFilter` utility function](/reference/utils/references/#outputreferencesfilter), which will prevent tokens that reference other tokens that are filtered out from outputting references, they will output the resolved values instead.

### outputReferences with transitive transforms

When combining [transitive value transforms](/reference/hooks/transforms/#transitive-transforms) with `outputReferences`,
it could happen that a token that contains references has also been transitively transformed.
What this means is that putting back the references in the output would mean we are undoing that work.
In this scenario, it's often preferable not to output a reference.

There is an [`outputReferencesTransformed`](/reference/utils/references/#outputreferencestransformed) utility function that takes care of checking if this happened and not outputting refs for tokens in this scenario.

## File headers

By default Style Dictionary adds a file header comment in the top of files built using built-in formats like this:
Expand Down
129 changes: 128 additions & 1 deletion docs/src/content/docs/reference/Utils/references.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Most notable option for public usage is `usesDtcg`, if set to true, the `resolve

## getReferences

(Whether or not a token value contains references
Whether or not a token value contains references

```javascript title="build-tokens.js"
import StyleDictionary from 'style-dictionary';
Expand Down Expand Up @@ -364,3 +364,130 @@ export default {
},
};
```

## outputReferencesTransformed

An [`outputReferences`](/reference/hooks/formats/#references-in-output-files) function that checks for each token whether
the value has changed through a transitive transform compared to the original value where references are resolved.

```javascript title="build-tokens.js"
import StyleDictionary from 'style-dictionary';
import { outputReferencesTransformed } from 'style-dictionary/utils';

const sd = new StyleDictionary({
tokens: {
base: {
value: '#000',
type: 'color',
},
referred: {
value: 'rgba({base}, 12%)',
type: 'color',
},
},
platforms: {
css: {
transformGroup: 'css',
// see https://github.com/tokens-studio/sd-transforms
// this transform handles rgba(#000, 0.12) -> rgba(0, 0, 0, 0.12)
// as a transitive transform
transforms: ['ts/color/css/hexrgba'],
files: [
{
destination: 'vars.css',
format: 'css/variables',
options: {
outputReferences: outputReferencesTransformed,
},
},
],
},
},
});
```

Output:

```css title="vars.css"
:root {
--base: #000;
--referred: rgba(0, 0, 0, 12%);
}
```

Note that `--referred` is using the resolved value that is a transformed version of `--base` instead of `rgba(var(--base), 12%)` which would be invalid CSS.
This can be verified by setting `outputReferences` to `true` in the demo below.

Live Demo:

~ sd-playground

```json tokens
{
"base": {
"value": "#000",
"type": "color"
},
"referred": {
"value": "rgba({base}, 12%)",
"type": "color"
}
}
```

```js config
import { outputReferencesTransformed } from 'style-dictionary/utils';

export default {
platforms: {
css: {
transformGroup: 'css',
transforms: ['ts/color/css/hexrgba'],
files: [
{
destination: 'vars.css',
format: 'css/variables',
options: {
outputReferences: outputReferencesTransformed,
},
},
],
},
},
};
```

```js script
import StyleDictionary from 'style-dictionary';
import { registerTransforms } from '@tokens-studio/sd-transforms';

// registers 'ts/color/css/hexrgba'
registerTransforms(StyleDictionary);
```

### Combining multiple outputReference utility functions

These utility functions can be quite easily combined together:

```javascript title="build-tokens.js"
import StyleDictionary from 'style-dictionary';
import { outputReferencesFilter, outputReferencesTransformed } from 'style-dictionary/utils';

const sd = new StyleDictionary({
platforms: {
css: {
transformGroup: 'css',
files: [
{
destination: 'vars.css',
format: 'css/variables',
options: {
outputReferences: (token, options) =>
outputReferencesFilter(token, options) && outputReferencesTransformed(token, options),
},
},
],
},
},
});
```
2 changes: 1 addition & 1 deletion docs/src/remark-playground.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const paragraphVisitor = (
let tokensData = serialize({});
let configData = serialize({});
let scriptData = serialize({});
let skipAmount = 2;
let skipAmount = 1;

for (const child of parent.children.slice(index + 1, index + 4)) {
if (child.type !== 'code') break;
Expand Down
2 changes: 2 additions & 0 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import usesReferences from './references/usesReferences.js';
import { getReferences } from './references/getReferences.js';
import { resolveReferences } from './references/resolveReferences.js';
import { outputReferencesFilter } from './references/outputReferencesFilter.js';
import { outputReferencesTransformed } from './references/outputReferencesTransformed.js';
import flattenTokens from './flattenTokens.js';
import { typeDtcgDelegate } from './preprocess.js';

Expand All @@ -24,6 +25,7 @@ export {
getReferences,
resolveReferences,
outputReferencesFilter,
outputReferencesTransformed,
flattenTokens,
typeDtcgDelegate,
};
Expand Down
19 changes: 19 additions & 0 deletions lib/utils/references/outputReferencesTransformed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { resolveReferences } from './resolveReferences.js';

/**
* @typedef {import('../../../types/DesignToken.d.ts').TransformedToken} TransformedToken
* @typedef {import('../../../types/DesignToken.d.ts').Dictionary} Dictionary
*
* @param {TransformedToken} token
* @param {{ dictionary: Dictionary, usesDtcg?: boolean }} dictionary
* @returns
*/
export function outputReferencesTransformed(token, { dictionary, usesDtcg }) {
const originalValue = usesDtcg ? token.original.$value : token.original.value;
const value = usesDtcg ? token.$value : token.value;

// Check if the token's value is the same as if we were resolve references on the original value
// This checks whether the token's value has been transformed e.g. transitive transforms.
// If it has been, that means we should not be outputting refs because this would undo the work of those transforms.
return value === resolveReferences(originalValue, dictionary.tokens, { usesDtcg });
}

0 comments on commit 2da5130

Please sign in to comment.