salesforce-ux / theo

Theo is a an abstraction for transforming and formatting Design Tokens

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Typescript output?

StevenLangbroek opened this issue · comments

I see there's no option for outputting typescript, but formats look relatively straightforward to write. Would you be interested in a typescript formatter?

Thanks!

I'd be interested in seeing what that looks like.

I'd be interested in seeing what that looks like.

Initially probably not that different from the common.js one. I'm still exploring Theo though, so depending on how it works it might be interesting to output an enum or union per category.

Yeah, I think Typescript could be interesting if we can output a format that is useful for everyone.

Our design system uses TypeScript and I wanted to leverage autocomplete in VSCode when referencing Theo-generated styles/themes in React components. The quickest solution was to auto-generate typings for the JS module using tsc and its --declaration flag. Example output: https://gist.github.com/petekp/8e706a272b7999e20ea7146b77fd42d8

It only generates the primitive types, e.g. string, number, etc. which has been sufficient for autocompleting theme properties. These annotations could be further refined to specific tokens, but I haven't found this necessary just yet.

@petekp that link gave me a 404.

@trazek Oops, fixed :)

@petekp Could you walk through how you generated the typings for that? tsc --declaration tokens.js doesn't love that it's not a TS file.

@mattfelten I'm currently faking it by registering a custom format (.d.ts) and outputting the style data but wrapped in a typings export declaration; basically gluing together the same output tsc --declaration would emit:

// Convert styles to JS and sort them by category
const categorizeStyles = (styleMap: ImmutableStyleMap) => {
  const styleProps: ThemeStyle[] = styleMap.get('props').toJS()
  return chain(styleProps)
    .sortBy('name')
    .groupBy('category')
    .value()
}

// Convert styles into an easily consumable JS object ("theme")
export const createThemeObjectFromStyles = (
  styleData: ImmutableStyleMap
): Readonly<Theme> => {
  return Object.entries(categorizeStyles(styleData)).reduce(
    (acc, [category, styles]) => ({
      ...acc,
      ...styles.reduce((acc2, style) => {
        return {
          ...acc2,
          [style.name]: style.value,
        }
      }, {}),
    }),
    {}
  )
}

// Create a `.d.ts` file and inject the theme object with typings export declaration
theo.registerFormat(
    'd.ts',
    styleMap =>
      `export declare const _default: 
        ${JSON.stringify(createThemeObjectFromStyles(styleMap))}
      `
  )

Ah that's clever. I ended up registering a new ts format and then running tsc on it.

module.exports = theo => {
	theo.registerFormat(
		"ts",
`
// Source: {{stem meta.file}}
const tokens = {
	{{#each props}}
	{{camelcase name}}: '{{value}}',
	{{/each}}
};
export default tokens;
`
	);
}

@mattfelten Nice! Before my current method I was using my node build script to change the filename from .js->.ts, run tsc --declarations and then change it back to .js 😄

It would be cool to build TS typing support into Theo, perhaps either as an officially supported format on its own (.d.ts), as an option for a .ts format, or perhaps a CLI flag (--tsDeclarations). The latter might be more amenable for custom JS-based formats.

I'll just chime in and say that I don't have any plans to add TypeScript support, but I'd happily accept a PR for it.

I'm down to help implement this. Still wondering what exactly folks would expect from this format. Just definitions? Auto-generated types/interfaces? Thinking optionally outputting .d.ts definitions for the built-in JS format(s) would be a reasonable place to start.

That’s what I would expect. That the output can be packaged and “just work” in typescript projects. I don’t think we would need actual .ts files

I used the following which output a TS declaration which matches the module.js theo format:

const {
  pipe,
  get,
  map,
  camelCase,
  method,
  sortBy,
  join,
} = require("lodash/fp");

const toConstDeclaration = ({ name, value }) =>
  `export declare const ${camelCase(name)} = ${JSON.stringify(value)}`;

module.exports = pipe(
  method("toJS"),
  get("props"),
  sortBy("name"),
  map(toConstDeclaration),
  join(";\n")
);

You can then register this in your setup file:

const typescriptFormat = require("./formats/typescript")

module.exports = theo => {
	theo.registerFormat("d.ts", typescriptFormat)
}