Dynamic call of chalk with typescript

1.5k Views Asked by At

I am using typescript and want to call the chalk method dynamically, see my code :

import chalk from 'chalk';

const color: string = "red";
const message: string = "My Title";
const light: boolean = false;

const colorName = `bg${capitalize(color)}${light ? 'Bright' : ''}`;
console.log(chalk[colorName](message));

So, the color function takes as a value the color of the background of the message. The message is the content and the light is the boolean used to know if the background should have its bright version used.
The problem is that typescript throws this error :

Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'ChalkInstance'.

on the chalk[colorName] part.

I already tried to put (chalk[colorName] as keyof ChalkInstance). And already looked at this post (which is the basically the same problem). Obviously, the solutions answered aren't working with my case.

3

There are 3 best solutions below

0
On BEST ANSWER

I found the answer to my question myself. The problem was that the colorName variable was of type any and chalk couldn't recognize it as an index of the chalk class. Here the value of the colorName variable could contain all the value of the backgroun colors whether they were bright or not.
I then had two solutions :

  • Hard code all the values of the colors (which would take a long time)
  • Find the type representing the colors and explicit set as the type of the colorName variable

My solution is close from the second idea, however, the Chalk library doesn't provide a type for the colors. However, we can import the explicit list of all the BackgroundColors. Then, we only have to asign the type of these keywors as the type of the colorName variable.

import { BackgroundColor } from 'chalk';

import chalk from 'chalk';

const color: string = "red";
const message: string = "My Title";
const light: boolean = false;

const colorName = `bg${capitalize(color)}${light ? 'Bright' : ''}` as typeof BackgroundColor;
console.log(chalk[colorName](message));

PS: Note that the type of the variabkes color, message and light are hard-coded. On the application, these values are dynamic, that's a way to make it more clear.

4
On

There are a few things you need to do.

  1. Remove the redundant explicit types from your variables color, message, and light. Doing so is not only unnecessary, it actually interferes with TypeScript properly inferring the literal value of those types. In other words, if you define const color = "red"; TypeScript will know, because it is a const, that it will never change and it can always be treated as the string literal "red" instead of the more generic string. (As a general rule, you should never explicitly define the type of a const variable.)
const color = "red";
const message = "My Title";
const light = false;
  1. Make sure your capitalize() function properly defines its return type. In this case, there is actually an awesome built-in utility type Capitalize<> which you can use here. In combination with a TypeScript generic, you can define the function in such a way that TypeScript knows that e.g. if "red" is what goes in, "Red" is what comes out.
function capitalize<S extends string>(c: S) {
  return c.replace(/\b\w/g, firstLetter => firstLetter.toUpperCase()) as Capitalize<S>;
}
  1. Use the as const assertion when you define colorName (and any other similar variables you may need to define). If you don't do this, the type of colorName will be inferred as string, which is no good for indexing chalk. With as const, you are basically telling TypeScript to treat the resulting expression as a literal string value. In this case, the type of colorName becomes "bgRedBright" | "bgRed", both of which are valid indices of chalk.
const colorName = `bg${capitalize(color)}${light ? 'Bright' : ''}` as const;
                                                                   ^^^^^^^^

When you put it all together:

import chalk from 'chalk';

const color = "red";
const message = "My Title";
const light = false;

function capitalize<S extends string>(c: S) {
  return c.replace(/\b\w/g, firstLetter => firstLetter.toUpperCase()) as Capitalize<S>;
}

const colorName = `bg${capitalize(color)}${light ? 'Bright' : ''}` as const;

console.log(chalk[colorName](message));

Edit: It's possible in the future that you would need to define your color variable in such a way that it is dynamic and not just a hard coded literal "red" value. In that case you need a way to satisfy TypeScript that color will always be something that is valid given all the other inferences that we just set up.

Here, we actually do want to explicitly define a type for certain variables, particularly let and function arguments. Fortunately, chalk provides a very useful type for this case, ForegroundColor which is essentially all of the valid "base" colors and happen to be compatible with BackgroundColor when put into the form `bg${ForegroundColor`, which is exactly what we need here.

import chalk, { ForegroundColor } from 'chalk';

let color: ForegroundColor = "red";
color = "blue";

We could even improve our capitalize() function by more strictly controlling what the type of the argument can be:

function capitalize<S extends ForegroundColor>(c: S) {
  return c.replace(/\b\w/g, firstLetter => firstLetter.toUpperCase()) as Capitalize<S>;
}

Another playground example that puts those further improvements into practice.

0
On
I recommend Colors.ts for TypeScript

npm install colorts

import 'colorts/lib/string';

console.log('hello'.green);                       // outputs green text
console.log('i like cake and pies'.underline.red) // outputs red underlined text
console.log('inverse the color'.inverse);         // inverses the color


https://github.com/shaselle/colors.ts