design-tokens / community-group

This is the official DTCG repository for the design tokens specification.
https://tr.designtokens.org
Other
1.56k stars 63 forks source link

How to define a token that references a local file? #132

Open CITguy opened 2 years ago

CITguy commented 2 years ago

As an author of design tokens, I would like to define a $value as a relative path to a bundled asset, so that Translation Tools know to treat the value differently than a plain string.

I'm building out a tiered design system, in which I'd like our core package to ship design tokens alongside universal (likely binary) assets (e.g., logos, icons, fonts, etc.).

For example, given the following abridged directory structure:

{package}/
  tokens.json
  assets/
    images/
      icons/
        alert.svg
        bookmark.svg
        ...

I'd like to define an "assets.*" group of tokens, all of which whose value is a relative path from tokens.json to a bundled SVG asset in assets/images/icons/.

($value strings are relative paths from tokens.json)

{
  "assets": {
    "icons": {
      "alert": { "$value": "assets/images/icons/alert.svg" },
      "bookmark": { "$value": "assets/images/icons/bookmark.svg" }
    }
  }
}

The current design tokens format spec doesn't seem to be clear on how to define a $value that is a reference to a file external to the .tokens.json file. My best guess would be to omit $type altogether and set $value to a string (implicit $type: string), but there's no guarantee that a Translation Tool would know to treat the value as a file path.

My Use Case

I ship SVG icons in my core package so that downstream dependencies can take those files and translate them into environment-specific assets.

For example...

  1. core:
    • provides a directory of optimized SVGs to use as inputs for translation tools
    • all tooling used by downstream deps requires a directory of SVGs as their input
  2. core → html:
    • combine SVGs into a sprite sheet using <symbol>
    • <use href="..." /> to consume individual icons
  3. core → css:
    • generate TTF icon font from SVGs
    • generate *.css file to consume individual icons from the font
  4. core → flutter:
    • generate TTF icon font from SVGs
    • generate *.dart code to consume individual icons from the font
CITguy commented 2 years ago

Idea # 1: path type

What if we had a { "$type": "path" }?

This could be used to reference any type of "file" (directory or otherwise), relative to the tokens.json file.

Requirements:

  1. $value MUST begin with ./ (relative)

Example

{
  "assets": {
    "$type": "path",
    "icons": {
      "alert": { 
        "$value": "./assets/images/icons/alert.svg" 
      },
      "bookmark": { 
        "$value": "./assets/images/icons/bookmark.svg" 
      }
    }
  }
}

Advantages

  1. Doesn't restrict directory structure for token package authors.

Challenges

  1. Allows variation of token package file structure
    • requires additional logic/complexity for a translation tool to locate assets

Questions

  1. Are absolute paths necessary?
    • Given the intended portability of the design tokens spec, I can't think of a scenario where I'd need to reference a machine-specific absolute path.
  2. What if a token package author wants to publish assets using a CDN or other remote location?
CITguy commented 2 years ago

Idea # 2: asset type

What if we had a { "$type": "asset" }?

Opting for convention over configuration, assets always exist in a sibling assets/ directory to the tokens.json file. Additionally, "asset" $value will be relative to the assets/ directory.

{package}/
  tokens.json
  assets/
    images/
      icons/
        alert.svg
        bookmark.svg
        ...
    ...

Requirements:

  1. $value MUST NOT begin with assets/ (this is implied, by convention)

Example

{
  "assets": {
    "$type": "asset",
    "icons": {
      "alert": { 
        "$value": "images/icons/alert.svg" 
      },
      "bookmark": { 
        "$value": "images/icons/bookmark.svg" 
      }
    }
  }
}

Advantages

  1. Defines a shallow convention for co-located assets.
    • file structure within assets/ is still free-form, to meet the needs of the token package author

Challenges

  1. tbd...

Questions

  1. What if a token package author wants to publish assets using a CDN or other remote location?
c1rrus commented 2 years ago

You're right, currently the draft spec doesn't have a type for things like assets. As you've noted, just falling back to a string type isn't helpful because there's no expectation for tools to do anything special with them (like guessing they could be file paths and trying to open them). In fact, the spec forbids tools from trying to infer a type from a basic value.

Adding a type for referencing files has been on our to-do list for a while though, so perhaps it's time to finally to do it! :-) In my head I had always imagined to be along the lines of your first idea: the path type. I wonder though if "file" or "asset" would be a more appropriate name for the type. I think the token represents a file, the path is just the technical means of locating that file. I don't have strong feelings about that though.

In terms of the specifics, I prefer your first idea of just specifying a relative path. In fact, why not allow any kind of relative path? For example:

{
  "icons": {
    "alert": {
      "$type": "file",
      "$value": "./path/to/file.svg"   
    },
    "bookmark": {
      "$type": "file",
      "$value": "../a/different/file.svg"
    }
  }
}

The spec intentionally avoids requiring tokens or groups to have certain names, so in a similar vein, I don't think we should be mandating that people keep assets in directory with a specific name or location.

CITguy commented 2 years ago

"$type": "file" makes sense to me for a File token.

My Concern

My biggest concern with the definition is that someone will eventually have a need to centralize assets on a public CDN or via an API.

CDN

In the CDN case, the File token would be a valid URL, given that most of the time you don't want to restrict access to files on a CDN.

{
  "icons": {
    "$type": "file",
    "alert": {
      "$value": "https://cdn.foobar.com/icons/alert.svg"
    },
    "bookmark": {
      "$value": "https://cdn.foobar.com/icons/bookmark.svg"
    }
  }
}

API

A file path could be defined as an API path (e.g., /icons/alert.svg), which isn't technically a valid file path or URL, but for many cases this can be mitigated by defining File tokens as URLs to an API endpoint (e.g., https://api.foobar.com/icons/alert.svg).

It's possible that the API system may have access control strategies in place to restrict access to certain resources, but any access controls applied should be documented by the API itself (out of scope for the Design Tokens spec).

{
  "icons": {
    "$type": "file",
    "alert": {
      "$value": "https://api.foobar.com/icons/alert.svg"
    },
    "bookmark": {
      "$value": "https://api.foobar.com/icons/bookmark.svg"
    }
  }
}

My Recommendation

I'd recommend that File tokens be documented such that their value must satisfy one of the following conditions:

  1. a relative path from the tokens JSON to a local asset (i.e., starts with ./ or ../)
    • "./foo.svg", "../foo.svg", "../foo/bar.svg"
    • Best if shipping design tokens as a package/bundle (e.g., ZIP, npm, etc.)
  2. a valid URL (as defined by the URL Living Standard)
    • Best if shipping design tokens as an API or if assets are provided via CDN.
romainmenke commented 2 years ago

It might be best to avoid URL's for now. Downloading files always has security implications and it is easier to judge if this is harmful when the specification and the ecosystem has matured a bit.

In the mean time any design token file needs to be distributed between multiple persons. I don't think it will be likely that you can share a design token file but can not also share an asset file.

Only allowing file paths should be sufficient for now.

drwpow commented 11 months ago

This is an old issue, but I’d love to see this revisited, as icons are an important part of every DS and I do think this is a core token type.

I’m strongly in favor of @CITguy’s most recent proposal (especially the URL Living Standard bit) and think it would work well.


Downloading files always has security implication

To reference how the web, a traditionally insecure platform, has handled it: the link tag marks a reference to a local or remote resource. It marks a relationship to the source to designate what’s done with it. And while a few built-in types are understood and executed (stylesheet for CSS, shortcut icon for favicons, etc.), many are simply ignored.

Perhaps there could be some hint as part of the schema, e.g:

{
  "icons": {
    "$type": "file",
    "alert": {
      "$value": {
        "rel": "icon",
        "url": "https://cdn.foobar.com/icons/alert.svg"
      }
    }
  }
}

Note: rel is a bad name; just using it as a placeholder

Perhaps there are a small number of rels that are recommended, such as:

And the rest can be up to tooling to respect or ignore (similar to how $extensions work).

This hopefully solves some of the security issues, because many systems can do basic checks on this (e.g. “I was expecting rel: "icon" but the server returned a mimetype of application/zip so I’ll throw an error). This could also be more extensible than having 1 token type for icons, images, etc.

ilikescience commented 10 months ago

I think this could actually be as simple as having a url type which follows exactly the whatwg standard. Urls can be relative paths, which would be combined with a base path (which might be a file path for local files) by the parser. We can provide a standard (like RFC 3986 for resolving relative paths.

As for the security of URLs, I think we get into trouble when we start talking about a translator actually fetching something on the url. The original comment's author mentioned wanting a token translator to combine svgs into a sprite sheet or generate a font file; to me this seems like a job for another tool, not a token translator.