terrazzoapp / terrazzo

Use DTCG tokens in CI and code

Home Page:https://terrazzo.app

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nested plugins

jbarreiros opened this issue · comments

Hi. First, thank you for authoring Cobalt. Coming from Style Dictionary, I appreciate the attention to the upcoming design tokens format specification.

The following is more of a question, not a bug.

We like to use Storybook to showcase our design tokens, including examples. My initial thought was to use the JS plugin to export a JSON file. What I'm noticing, is that composite tokens like shadow keep their original shape, and are not transformed into a computed value (e.g. CSS).

"shadow.active": [
  {
    "offsetX": "0",
    "offsetY": "2px",
    "blur": "2px",
    "spread": "0",
    "color": "rgb(0 0 0 / 24%)",
    "inset": false
  }
],

vs

"shadow.active": [ "0 2px 2px 0 rgb(0 0 0 / 24%)"],

It totally makes sense why the JSON format would not include transformed values.

Therefore, I set out to write a custom plugin to generate a JSON file that provided values transformed for CSS. Ideally, I wanted to somehow leverage the official CSS plugin since it already contains all that juicy logic to transform tokens into CSS.

The challenge is that a plugin bundles the transformation and formatting logic in a single step. Neither piece can be imported and used separately. To leverage an existing plugin, you let it do its thing and return the formatted output, then you use regex to adjust the output into something else. For example:

import pluginCSS from '@cobalt-ui/plugin-css'

export default function pluginObjectStyles(options = {}) {
  return {
    name: 'plugin-css-object-styles',
    async build({ tokens, metadata }) {
      const raw = await pluginCSS(options).build({ tokens, metadata })
      const { filename, contents: css } = raw[0]

      const cssAsJs = /* ... a bunch of regex to convert contents to an exported JS object */
      /*
      E.g.
      :root {
        --color-red: red;
      }
      to...
      export const tokens = {
        "--color-red": "red"
      }
      */

      return [
        {
          filename,
          contents: cssAsJs,
        },
      ]
    },
  }
}

Is that the recommended approach to reusing plugins? Should transformation and formatting be separate steps that could be imported and utilized separately? Curious how you would approach this? Thanks.

Apologies, I didn't dig deep enough into the source code.

After taking another look, I found that plugin-css exports the defaultTransformer function, which is exactly what I was looking for.

import { defaultTransformer as pluginCssDefaultTransformer } from '@cobalt-ui/plugin-css'

export default {
  ...
  plugins: [
    pluginJS({
      js: false,
      json: true,
      transform: (token) => {
        return pluginCssDefaultTransformer(token, { colorFormat: 'hex', prefix: '' })
      },
    }),
  ]
}

Woot!

Yeah this is a great question, and highlights some assumptions I’ve made that may not be always applicable to all scenarios.

One core design focus of the W3C Design Tokens spec is to try and outline a truly universal token format, which means not leaning too heavily on web. Even though Cobalt, being built in Node.js, skews heavily towards web itself, I’ve tried not to accidentally treat web as a first-class citizen. Native app plugins (Swift, Kotlin, etc) are currently missing from Cobalt. But with the JSON output I’d still one day like to handle that usecase to some degree.

All that to say, though there’s still a lot of work to do on Cobalt in general, I’ve tried to be careful in limiting CSS transformation to only the CSS plugin. Though you could make an argument that the JS plugin should have some overlap because those languages do in the DOM.

I also wanted to thank you for pointing out the conflation of transformation + formatting in the same plugin step. While I don’t really know that it’s a problem to solve for right now, it’s still an important callout as a design decision. And something to pay attention to if it causes any future friction.

Glad you found a workaround! I’d recommend exactly the approach you’ve taken. No right or wrong, of course, but that seems like the simplest way to manage things.

Also an automatic token docs builder for Cobalt is something I’m working on right now, and hope to have it shareable by the end of the year.