amzn / style-dictionary

A build system for creating cross-platform styles.
https://amzn.github.io/style-dictionary/#/
Apache License 2.0
3.72k stars 524 forks source link

[Question] is it possible to selectively output references? #882

Open KenjiCrosland opened 1 year ago

KenjiCrosland commented 1 year ago

I'm looking to use 3.0 outputReferences but there are several tokens we filter out of our main output file. Is it possible to only output the references for non-filtered tokens?

For example, lets say we have an _options folder whose values get used in our final output file but the values of those options are not in the output file. When I use outputReferences these option token names get referenced even though they aren't in the final outputted file. Could we have the values of these instead?

woylie commented 1 year ago

We're having the same situation. In our case, we define the base colors in one file, and then define one additional color file for each theme. The theme color files reference the base colors, and the base color file is filtered out from the final output.

The ideal behavior for this situation would be:

nahiyankhan commented 1 year ago

Very much doable! On our end, we have a custom CTI that has a layer field which could be base, semantic, or component. In our build config we then have a filter that essentially makes sure token.attributes.layer != base.

woylie commented 1 year ago

Very much doable! On our end, we have a custom CTI that has a layer field which could be base, semantic, or component. In our build config we then have a filter that essentially makes sure token.attributes.layer != base.

That's not the issue, we're doing that as well. The issue is that references to the filtered tokens are still included in the output.

For example:

base.json

{
  "color": {
    "base": {
      "white": { "value": "#ffffff" }
    }
  }
}

some.theme.json

{
  "color": {
    "background": { "value": "{color.base.white}" },
    "button": {
      "background": { "value": "{color.background.white}" }
    }
  }
}

Now if you filter base.json from the output and set outputReferences to true:

{
  platforms: {
    css: {
      transformGroup: "css",
      files: [{
        destination: "variables.css",
        format: "css/variables",
        filter: "baseColorFilter",
        options: {
          outputReferences: true
        }
      }]
    }
  }
}

The desired output would be:

--color-background: #ffffff;
--color-button-background: --color-background;

So references would only be used if the referenced value is part of the output. Right now, this doesn't work, though, because Styledict tries to use references even if the referenced value is not included in the final output, and since it cannot resolve the references, the build will fail.

nahiyankhan commented 1 year ago

@woylie You are right, I misinterpreted the problem. My bad! I appreciate your example. I get the issue now. Just thought more about it and have a likely solution.

The gist is, outputReferences (and the subsequent functions usesReference and formattedVariables) doesn't take into account if a referenced token is filtered out to your point. But we can bypass that quirk by defining a customFormat where we pre-process the dictionary object to remove references (and keep the literal value intact) to filtered tokens.

My example is specific to css/variables and tokens that are base but I think we can make that smarter to sync up with whatever we are filtering on.

Key part:

const resolveBaseReferences = (dictionary) => {
  dictionary.allTokens.map(token => {
    if (token.original.value.includes('color.base.')) {
      token.original.value = token.value
    }
    return token
  })
  return dictionary
}

StyleDictionary.registerFormat({
  name: 'custom/css/variables',
  formatter: function({dictionary, file, options}) {
    const selector = options.selector ? options.selector : `:root`;
    const { outputReferences } = options;
    dictionary = resolveBaseReferences(dictionary)
    return fileHeader({file}) +
      `${selector} {\n` +
      formattedVariables({format: 'css', dictionary, outputReferences}) +
      '\n}\n';
  }
})

This does give the desired output:

--color-background: #ffffff;
--color-button-background: --color-background;

Even though I have a base.json with color.base.white in it thats referenced by color.background

Example repo - style-dictionary-output-references-demo

jorenbroekema commented 8 months ago

I would classify it as a bug that outputReferences on a format-level does not take into account filters (also on format-level). It definitely should imo, and perhaps outputReferences besides just "true" or "false" should also accept a Function where you can selectively decide to output references or not, per token basis.

lukasoppermann commented 2 months ago

We are running into the same issue.

We have base tokens for colors and those are referenced in semantic tokens. We don't want the base tokens to be used, so neither the tokens nor references to base tokens should be in the output.

However, component tokens reference semantic tokens. We want those to use css variables and keep the references as this allows us to overwrite semantic tokens and those changes would propagate to component tokens.

jorenbroekema commented 2 months ago

Ah yeah we can use this issue for what we started talking about on here https://github.com/amzn/style-dictionary/issues/1021#issuecomment-2031769455

My comment in October wasn't entirely correct because filtering tokens that are relied upon through references when using outputReferences 'true' do give a warning in the console so that's decent, but it doesn't exactly help you fix the problem, selectively not outputting refs for tokens that rely on tokens that are filtered out would be a good fix. So that gives us a pretty good use case for justifying the feature I suggested in that other issue: disabling outputting refs for specific tokens, in this case any token that contains references that would be filtered out.

I'll add this issue to the v4 board

lukasoppermann commented 2 months ago

So @jorenbroekema this would still mean changing outputReferencea to accept a function or Boolean right?

jorenbroekema commented 2 months ago

Yup! should come with a little demo on how you can use that to not output refs for tokens that contain refs that are filtered out, the getReferences() util will come in handy for that I think, or marking the token somehow as "contains refs that are filtered out"

jorenbroekema commented 2 months ago

https://github.com/amzn/style-dictionary/pull/1145