How to output emoji to console in Node.js (on Windows)?

What you want may not be possible without a change to libuv. When you (or the console) write to stdout or stderr on Windows and the stream is a TTY, libuv does its own conversion from UTF‑8 to UTF‑16. In doing so it explicitly refuses to output surrogate pairs, emitting instead the replacement character U+FFFD � for any codepoint beyond the BMP.

Here’s the culprit in uv/src/win/tty.c:

  /* We wouldn't mind emitting utf-16 surrogate pairs. Too bad, the */
  /* windows console doesn't really support UTF-16, so just emit the */
  /* replacement character. */
  if (utf8_codepoint > 0xffff) {
    utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER;
  }

The throw message appears correctly because Node lets Windows do the conversion from UTF‑8 to UTF‑16 with MultiByteToWideChar() (which does emit surrogate pairs) before writing the message to the console. (See PrintErrorString() in src/node.cc.)

Note: A pull request has been submitted to resolve this issue.


(Disclaimer: I don't have a solution, I explored what makes exception handling special with regards to printing emoji, with the tools I have on Windows 10 -- With some luck that might sched some light on the issue, and perhaps someone will recognize something and come up with a solution)

Looks like Node's exception reporting code for Windows calls to a different Windows API, that happens to support Unicode better.

Let's see with Node 7.10 sources:

ReportException → AppendExceptionLine → PrintErrorString

In PrintErrorString, the Windows-specific section detects output type (tty/console or not): - For non-tty/console context it will print to stderr (e.g. if you redirect to a file) - In a cmd console (with no redirection), it will convert text with MultiByteToWideChar() and then pass that to WriteConsoleW().

If I run your program using ConEmu (easier than getting standard cmd to work with unicode & emoji -- yes I got a bit lazy here), I see something similar as what you saw: console.log fails to print emoji, but the emoji in exception message are printed OK (even the scroll glyph).

If I redirect all output to a file (node test.js > out.txt 2>&1, yes that works in Windows cmd too), I get "clean" Unicode in both cases.

So it seems when a program prints to stdout or stderr in a Windows console, the console does some (bad) reencoding work before printing. When the program uses Windows console API directly (doing the conversion itself with MultiByteToWideChar then write to console with WriteConsoleW()), the console shows the glorious unaltered emoji.

When a JS program uses console API to log stuff, maybe Node could try (on Windows) to detect console and do the same thing as it does for reporting exceptions. See @BrianNixon's answer that explains what is actually happening in libuv.