JavaScript

Terrazzo’s JS plugin generates a resolver API for Node.js clients from your token system. It produces code that is fast but heavy, so it is better-suited for server-side rendering. For client applications, prefer the css-in-js plugin instead.

note

Heads up! The 2.0 plugin has changed quite a lot from Terrazzo 0.x alpha. Read the migrating guide for changes.

Setup

npm i -D @terrazzo/plugin-js
import { defineConfig } from "@terrazzo/cli";
import js from "@terrazzo/plugin-js";

export default defineConfig({
  plugins: [
    js({
      filename: "my-ds.js",
      // optional: only generate outputs for the following modifier contexts
      contexts: {
        theme: ["light", "dark"],
        size: ["sm", "md", "lg"],
      },
    }),
  ],
});

Usage

import { resolver } from "./tokens/my-ds.js";

const lightMd = resolver.apply({ theme: "light", size: "sm" });
lightMd["color.bg"].$type; // "color"
lightMd["color.bg"].$value; // { "colorSpace": "srgb", "components": [1, 1, 1] }

resolver.apply({ foo: "bar" }); // ❌ Invalid input { "foo": "bar" }
tip

Need to work with color? Use color.js’ procedural API for modern, efficient color tools.

Config

Configure options in terrazzo.config.ts:

import { defineConfig } from "@terrazzo/cli";
import js from "@terrazzo/plugin-js";

export default defineConfig({
  plugins: [
    js({
      /* options */
    }),
  ],
});

Options

NameTypeDescription
filenamestringSet to a filename (default: tokens.js).
contextsRecord<string, string[]>Set this to “tree-shake” modifiers. By default, all permutations will be built.
properties(keyof TokenNormalized)[]Only include the specified properties on all tokens. Use to reduce generated filesize and memory impact.

properties

Here are all valid properties, from the TokenNormalized type:

  • $type
  • $description
  • $value
  • $extensions
  • $deprecated
  • id (excluded by default)
  • jsonID (excluded by default)
  • originalValue (excluded by default)
  • source (excluded by default)
  • aliasOf (excluded by default)
  • aliasChain (excluded by default)
  • aliasedBy (excluded by default)
  • dependencies (excluded by default)
  • group (excluded by default)

When to use plugin-js

vs Node.js API

Node.js API is better for the initial build; plugin-js is better for runtime.

  • plugin-js has better type safety meant for JS applications
  • plugin-js frontloads the heavy work of resolution, meant for high-scale on-demand use (i.e. server generation)
    • Example: GitHub Primer, on a 2024 MacBook Air, takes about ~4s to build a single permutation; plugin-js takes 20ms (it’s simply returning the prebuilt tokens)

vs plugin-css-in-js

plugin-css-in-js should be used when pairing with plugin-css.

  • plugin-js doesn’t have access to which CSS variables were generated
  • plugin-js does have access to granular token data and design system information, so plugin-js is better for generating anything non-CSS.

Migrating from 0.x

This plugin got several breaking changes from 0.x.

First, this plugin now always generates .d.ts files alongside .js files without configuration. Use filename instead of js:

import js from "@terrazzo/plugin-js";

export default defineConfig({
  plugins: [
    js({
-     js: "tokens.js",
+     filename: "tokens.js",
-     ts: "tokens.d.ts",
-     json: false, // set to a filename to generate JSON
    }),
  ],
});

Next, if you’re still using legacy $extensions.mode, you’ll want to use tzMode as the context name, e.g.:

import js from "@terrazzo/plugin-js";

export default defineConfig({
  plugins: [
    js({
+     contexts: {
+       'tzMode': ['light', 'dark'],
+     }
    }),
  ],
});

The usage is also different; refer to the appropriate sections for guides on what your new code should look like.