Skip to content

v3.0.0

Compare
Choose a tag to compare
@dbanksdesign dbanksdesign released this 25 May 20:51
· 148 commits to main since this release

Version 3.0 is now publicly released! We have been working hard the past few months on a number of features and improvements we wanted to combine into a major release. Though we intend that 3.0 to be backwards compatible, we thought it was a good idea to move to a new major version because we are changing a core part of how Style Dictionary works.

If you are starting a new project, you can install Style Dictionary and it will give you the latest version:

npm install --save-dev style-dictionary

If you have an existing project, you can upgrade to 3.0 by updating the version in your package.json file to "style-dictionary": "^3.0.0" and then run npm install or you can use the latest tag to update both your package.json and package-lock.json files:

npm install --save-dev style-dictionary@latest

If you find any bugs or issues, please file tickets so we can get things fixed. We have tested and reviewed 3.0 extensively, but there may be things we missed. Thanks!

Style Properties → Design Tokens

Style Dictionary is moving to the term "design tokens", both in documentation and in code. This has become the industry standard term for a while and it is time we respect that. Until now, Style Dictionary had called these "style properties" or just "properties", with some parts of the documentation also mentioning "design tokens". We want to be consistent with the direction of the community as well as in our documentation and code. We use the terms properties and allProperties in different APIs in Style Dictionary. To be consistent in documentation as well as code, we will be moving to using tokens and allTokens.

Don't worry! This change is backwards-compatible; you will still be able to use properties and allProperties wherever you currently do in your code. If you want, you can update those to tokens and allTokens and everything will work as expected. Moving forward, all examples and documentation will use the term "design tokens" and "tokens" rather than "style properties" and "properties". We do recommend using tokens and allTokens in new code from here on out!

Transitive transforms

This is the big one that required a big re-architecture of how the Style Dictionary build process works.

Up until now the build process looked like this: https://amzn.github.io/style-dictionary/#/build_process
After merging all of the token files, it would iterate through the merged object and transform tokens it found, but only do value transforms on tokens that did not reference another token. The original intent here was that a value of any reference should be the same for all references of it, so we only need to do a value transform once. Then after all tokens are transformed, resolve all aliases/references.

However, we heard from the community there were a number of reasons why someone might want to transform the value of an aliased token.

The new build process is similar, except that it recursively transforms and resolves aliases, only deferring a transform to the next cycle if the token has an unresolved alias. Each pass might reveal tokens that are ready to be transformed in the next pass. Take this example:

{
  "color": {
    "black": { "value": "#000000" },
    "font": {
      "primary": { "value": "{color.black.value}" },
      "input": { "value": "{color.font.primary.value}" }
    }
  }
}

The first pass will transform the color.black token because it doesn't have a reference. It will defer the transforms of color.font.primary and color.font.input because they do have references. Then it will resolve references to color.black.value because it has been transformed.

The second pass will transform color.font.primary because it no longer has a reference. It will then resolve the reference to color.font.primary.value because it has been transformed.

The final pass will transform color.font.input because it no longer has a reference. Now the build is complete because there are no more deferred transforms left.

Use cases this change opens up:

  • Having variable references in outputs
  • Combining values like using HSL for colors
  • Modifying aliases like making a color lighter or darker

Example: https://github.com/amzn/style-dictionary/tree/main/examples/advanced/transitive-transforms

Thanks @mfal!

Output references

#504

This is another big one. This has been one of the first issues we made back in 2017, issue 17! This adds support for outputting references in exported files. This is a bit hard to explain, so let's look at an example. Say you have this very basic set of design tokens:

// tokens.json
{
  "color": {
    "red": { "value": "#ff0000" },
    "danger": { "value": "{color.red.value}" },
    "error": { "value": "{color.danger.value}" }
  }
}

With this configuration:

// config.json
{
  "source": ["tokens.json"]
  "platforms": {
    "css": {
      "transformGroup": "css",
      "files": [{
        "destination": "variables.css",
        "format": "css/variables",
        "options": {
          // Look here 👇
          "outputReferences": true
        }
      }]
    }
  }
}

This would be the output:

:root {
  --color-red: #ff0000;
  --color-danger: var(--color-red);
  --color-error: var(--color-danger);
}

The css variables file now keeps the references you have in your Style Dictionary! This is useful for outputting themeable and dynamic code.

Without outputReferences: true Style Dictionary would resolve all references and the output would be:

:root {
  --color-red: #ff0000;
  --color-danger: #ff0000;
  --color-error: #ff0000;
}

Not all formats use the outputReferences option because that file format might not support it (like JSON for example). The current list of formats that handle outputReferences:

  • css/variables
  • scss/variables
  • less/variables
  • compose/object
  • android/resources
  • ios-swift/class.swift
  • ios-swift/enum.swift
  • flutter/class.dart

If you have custom formats you can make use of this feature too! The dictionary object that is passed as an argument to the formatter function has 2 new methods on it: usesReference() and getReference() which you can use to get the reference name. Here is an example of that:

StyleDictionary.registerFormat({
  name: `myCustomFormat`,
  formatter: function(dictionary) {
    return dictionary.allProperties.map(token => {
      let value = JSON.stringify(token.value);
      // the `dictionary` object now has `usesReference()` and
      // `getReference()` methods. `usesReference()` will return true if
      // the value has a reference in it. `getReference()` will return
      // the reference to the whole token so that you can access its
      // name or any other attributes.
      if (dictionary.usesReference(token.original.value)) {
        const reference = dictionary.getReference(token.original.value);
        value = reference.name;
      }
      return `export const ${token.name} = ${value};`
    }).join(`\n`)
  }
})

Example: https://github.com/amzn/style-dictionary/tree/main/examples/advanced/variables-in-outputs

Custom parser support

#429

We are pretty excited about this. Until now you could only define your design tokens in either JSON, JSON5, or plain Node modules. The addition of custom parser support allows you to define your tokens in any language you like! The most obvious use-case is to use YAML which has a cleaner and less verbose syntax than JSON. Now, the sky is the limit. Your source of truth can be in any file format you like as long as you can parse it into an object that Style Dictionary can understand. You register a custom parser the same way as you register a custom transform or format. A parser consists of a pattern to match against files, similar to the test attribute in a loader in Webpack, and a parse function which gets the file path and its contents and is expected to return an object.

Adding filePath and isSource entries on tokens

#356

Style Dictionary adds some metadata on each token before any transforms take place in order to give more context for transforming and formatting tokens. For example, this design token:

{
  "color": {
    "red": { "value": "#ff0000" }
  }
}

Turns into this:

{
  "color": {
    "red": {
      "value": "#ff0000",
      "name": "red", // adds a default 'name', which is the object key
      "path": ["color","red"], // object path
      "original": {
        "value": "#ff0000"
      } // copies the original object so you always have a clean copy
    }
  }
}

We are adding 2 new pieces of metadata to each token:

  • filePath: A string representing the absolute path of the file that defines the token. This will help with debugging and if you want to output files based on the source files.
  • isSource: A boolean representing if this file was defined as ‘source’ in the configuration as opposed to ‘include’ (or directly setting the ‘properties’ object). This can also help filtering tokens from output files you don't want to include.

Thanks @7studio!

Format helpers

Style Dictionary comes with built-in formats to generate basic files like CSS, Sass, and Less variables. We know that we can't build formats to fit everyone's exact needs, so you can write your own formats to output exactly what you want. However, one common ask was to use 90% of a built-in format, but tweak small parts of it. Before 3.0, the only way to do that would be to copy and paste the format source code from Style Dictionary into your own custom format and then make your tweaks.

In 3.0, we have added format helpers. Format helpers are functions that the built-in formats use for things like formatting each token definition line, sorting the tokens, and displaying a file header comment. These functions are now part of the public API, which you can access with StyleDictionary.formatHelpers. Format helpers should make it simple to build custom formats.

const StyleDictionary = require('style-dictionary');

const { formattedVariables } = StyleDictionary.formatHelpers;

module.exports = {
  format: {
    myFormat: ({ dictionary, options, file }) => {
      const { outputReferences } = options;
      return formattedVariables({
        dictionary,
        outputReferences,
        formatting: {
          prefix: '$',
          separator: ':',
          suffix: ';'
        }
      });
    }
  }
}

Example: https://github.com/amzn/style-dictionary/tree/main/examples/advanced/format-helpers

Updated format method arguments

#533

We wanted to make the format method signature easier to work with by not relying on positional arguments and instead used named arguments in the form of an object that can be destructured. But we also didn't want to break anyone's custom formats so we did it in a backwards compatible way. We did this by overloading the first argument sent to the formatter method, which is the dictionary object.

Old way:

StyleDictionary.registerFormat({
  name: 'myCustomFormat',
  formatter: (dictionary, platform, file) {
    // ...
  }
});

New way:

StyleDictionary.registerFormat({
  name: 'myCustomFormat',
  formatter: ({dictionary, platform, file, options}) {
    // ...
  }
});

The old way will continue to work, but we recommend changing to the new way at some point. For a full list of the information sent to the formatter function, see: https://github.com/amzn/style-dictionary/blob/3.0/docs/formats.md#creating-formats

You might also notice a new part of the information sent to the formatter method: options. This object will a merged version of an options object at the platform-level configuration and file-level configuration where the file takes precedence. This allows for a cascading of configuration from the platform to the file and so you don't have to repeat the same options for multiple files like the showFileHeader option.

{
  "source": ["tokens/**/*.json"],
  "platforms": {
    "css": {
      "options": {
        "showFileHeader": false,
        // ouputReferences is a new option added in 3.0!
        "outputReferences": true
      },
      "transformGroup": "css",
      "files": [{
        // this file will inherit the platform options above
        "destination": "variables.css",
        "format": "css/variables"
      },{
        // this file overrides the platform options
        "destination": "variablesWithoutReferences.css",
        "format": "css/variables",
        "options": {
          "showFileHeader": true,
          "outputReferences": false
        }
      }]
    }
  }
}

Previously, there wasn't much convention around adding extra configuration at the platform and file-level for transforms, formats, and actions. We want to start correcting that a bit by using an options object at the file and platform level.

Custom file headers

When we started Style Dictionary, our intention was that the code it generates would not be checked into version control. To make that very apparent we added a code comment at the top of every built-in format that says it is a generated file with a timestamp of when it was generated. Over the years we have seen many different use-cases and patterns emerge from using Style Dictionary. One of those is actually checking in generated files to version control and using a pull request to review and verify the results. Having a time-stamped comment in generated files now becomes a bit of an obstacle. In the principle of extensibility and customization, you can now define your own file header comments for output files! This allows you to remove the timestamp if you want, write a custom message, use the version number of the package, or even use a hash of the source so that it only changes when the source changes.

You can use the custom file headers on any built-in format, or even use them in a custom format with the formatHelper.fileHeader function.

module.exports = {
  //...
  fileHeader: {
    myFileHeader: (defaultMessage) => {
      return [
        ...defaultMessage,
        'hello, world!'
      ]
    }
  },

  platforms: {
    css: {
      transformGroup: `css`,
      files: [{
        destination: `variables.css`,
        format: `css/variables`,
        fileHeader: `myFileHeader`
      }]
    }
  }
}

Example: https://github.com/amzn/style-dictionary/tree/main/examples/advanced/custom-file-header

Typescript support

#410

Style Dictionary has Typescript types now! We have added support both for generating typescript files with the typescript/es6-declarations and typescript/module-declarations formats, as well as type definitions for using the Style Dictionary module in a Typescript environment. The source code of Style Dictionary remains in plain Javascript for now, but many people have been wanting to use Style Dictionary in Typescript so as an interim solution we added just the type definitions. As an added bonus, the type definitions also help Javascript Intellisense in VSCode even if you aren't using Typescript in your project!

Thanks @AndrewLeedham!

More built-ins

We added support for: Jetpack Compose, React Native, and Stylus with built-in transforms, transform groups, and formats. Style Dictionary is built to be customized and extended, so any language or platform can be supported with customization. We do want to offer a core set of generic transforms and formats to get you started so that you don't have to write custom code for everything. If you think we are missing something, please let us know! Here are the formats, transforms, and transformGroups we added in 3.0:

Formats

Transforms

Transform Groups

Bug fixes

Other features

Better testing

To have higher confidence in the end-to-end experience of Style Dictionary we have added integration tests that run Style Dictionary as real users would. Integration tests are in integration and use snapshot testing to ensure the output of Style Dictionary from input, transforms, and formats remains the same and valid output.

We have also added more unit tests as well for some features we have added and bugs we have found. We added:

  • 28 test suites
  • 174 tests
  • 69 snapshots

Dropping support for older versions of node

#441

To be honest, we should have done this way sooner. Those versions of Node are no longer even in long-term support or maintenance. Hopefully this change should not affect anyone as you should all be on more recent versions of Node anyways.