chalk / chalk

🖍 Terminal string styling done right
MIT License
21.96k stars 859 forks source link

Formatting doesn't work when string is read from a file #417

Closed chimericdream closed 4 years ago

chimericdream commented 4 years ago

Node.js version: 14.13.1 Chalk version: 4.1.0

It's entirely possible that I am missing something obvious here, but I haven't been able to figure out what it is. I have a Node.js app that reads text strings from files and outputs them to a connected client. When I output hard-coded strings, Chalk's formatting makes it through to the client just fine, but when the exact same strings are read from the file system, the raw string (including Chalk's format syntax) is displayed instead.

I have reproduced the issue with a minimal repo here.

Here is a screenshot showing what I mean.

chalk-output

chimericdream commented 4 years ago

I have done some more digging, and I am wondering if tagged templates work differently than what I'd expect. With further testing, I have verified that the following example also fails to output ansi-colored text:

const someString = 'This is a {red.bold string} with {blue.bold some color}';

console.log(chalk`${someString}`);

// Output:
// This is a {red.bold string} with {blue.bold some color}

Is this a limitation of JS itself? Is there an alternative or workaround to be able to use chalk in this manner?

papb commented 4 years ago

Yes, tagged templates work differently than you expected. I wouldn't call that behavior a "limitation" though. When you call a template literal such as foo`bar${baz}qux`, this is translated internally into a function call similar to foo(['bar', 'qux'], [baz]) (not exactly this, but enough for you to get an idea). Therefore, when you do chalk`${someString}`, it becomes something like chalk(['', ''], [someString]) (again, not exactly this), and by design (which I think is a correct decision) chalk only interprets "chalk commands" from within the first array (i.e. only from literal strings, not variables).

Is there an alternative or workaround to be able to use chalk in this manner?

Good question. I don't know of any, but perhaps the Chalk team would be willing to accept a feature request for an option such as { dynamicParsing: true } which would also take commands from the variables instead of literals.

Qix- commented 4 years ago

We use .raw string literals, so you have to do a bit of juggling first. This is unlikely to change, but note that it's not officially supported. You're on your own with this :)

const chalk = require('chalk');
const chalkTemplate = msg => { const a = []; a.raw = [msg]; return chalk(a); };

console.log(chalkTemplate('{red hello}')); //-> '\x1b[31mhello\x1b[0m'
chimericdream commented 4 years ago

@Qix- is your sample code an example of what it would take to provide a purely functional API that behaves similarly to the tagged template? If so, I can add a utility to my app that does what I need.

That said, I would obviously prefer if the chalk library itself provided a functional form, but I understand if that's a direction the maintainers prefer not to go. It's one of those cases where "it's trivial to implement yourself" and "it's trivial to simply add to the library" are both valid and understandable.

Qix- commented 4 years ago

Except for any sort of interpolation support, yes. It accepts a single string that is parsed using the template parser.

It's one of those cases where "it's trivial to implement yourself" and "it's trivial to simply add to the library" are both valid and understandable.

Definitely trivial to implement yourself. Using chalk to format files is an edge case, and probably one of the few (if not only) cases where the above stub is really necessary.

Otherwise, using the chalk.red() set of functions directly is more performant when you have "static" strings, and using the tagged template literal syntax is easy to do.

Going to go ahead and close this since it appears the issue has been resolved :) Let me know if there's anything else!

chimericdream commented 4 years ago

I'd like to note that it isn't just strings that come from files in my case, but that is where I originally hit the issue. All of the communication between my app and the client is funneled through a socket, so I was trying to apply the tagged template inside the call to socket.write(...). I was hoping to be able to write code like client.send('some {blue.bold colorized} string') instead of client.send(chalk'some {blue.bold colorized} string') (pretend the backticks are right). The latter isn't difficult, but I expect to have a minimum of hundreds of individually colorized strings throughout my app, and it would have been nice to only need one call to chalk at the actual point where the data is sent to the client.

I'm fine adding my own wrapper for this particular use case, but wanted to point out that strings coming from files is an edge case for me as well. The primary desire was simply to allow passing raw strings around within my app and applying chalk only at the exit point.

Thanks again for your help!