Skip to content

[plugin-emotion] escape sequences in css template literals are decoded incorrectly (\\f007 becomes a form feed) #165

Open

Description

@trackoor

Related packages

Describe the bug

unescapeTemplateRaw in packages/emotion/src/common.ts decodes template literal raw text with sequential .replace(...) calls (\\b, \\f, \\n, \\r, \\t, \\v, and \\\\ last):

export function unescapeTemplateRaw(raw: string): string {
  return raw
    .replace(/\\`/g, '`')
    .replace(/\\\$/g, '$')
    .replace(/\\b/g, '\b')
    .replace(/\\f/g, '\f')
    .replace(/\\n/g, '\n')
    .replace(/\\r/g, '\r')
    .replace(/\\t/g, '\t')
    .replace(/\\v/g, '\v')
    .replace(/\\\\/g, '\\')
}

Because \\\\ is replaced last and other JS template escapes (\", \', \uXXXX, \xXX, line continuations) are not handled at all, two classes of escapes break:

  1. An escaped backslash before b, f, n, r, t, or v is decoded as a control character. In css templates for icon fonts (e.g. Font Awesome content: "\\f007"), the raw text is \\f007. .replace(/\\f/g, '\f') matches the second backslash + f, turning \\f007 into \ + U+000C (\f) + 007. In CSS, a backslash followed by a form feed inside a string is a line continuation, so the browser renders 007 instead of U+F007.
  2. Escaped quotes such as \\\" leave the quote backslash unconsumed. In css`content: "\\\""; color: red;` , the raw text \\\" only matches \\\\ on the first two backslashes and leaves \" untouched, producing \" in the cooked string and "content:\"\\\\\"\";color:red;" in the JS output. At runtime the CSS string is content:"\\"", which closes the string early and swallows subsequent declarations.

(Note: this is in unescapeTemplateRaw in common.ts, separate from the css-minify.ts issues in #163.)

// repro.mjs
import { rolldown } from 'rolldown'
import emotionPlugin from '@rolldown/plugin-emotion'

const bundle = await rolldown({
  input: './input.js',
  external: [/^@emotion\//],
  plugins: [emotionPlugin({ sourceMap: false, autoLabel: 'never' })],
})
const { output } = await bundle.generate({ format: 'esm' })
console.log(output[0].code)
// input.js
import { css } from '@emotion/css'
export const icon = css`&::before { content: "\\f007"; }`
export const quote = css`content: "\\\""; color: red;`

Actual output:

import { css } from "@emotion/css";
// input.js
const icon = /* @__PURE__ */ css("&::before{content:\"\\\f007\";}");
const quote = /* @__PURE__ */ css("content:\"\\\\\"\";color:red;");
export { icon, quote };

Expected output (matching untransformed template evaluation and @emotion/babel-plugin 11.13.5):

const icon = /* @__PURE__ */ css("&::before{content:\"\\f007\";}");
const quote = /* @__PURE__ */ css("content:\"\\\"\";color:red;");

Using quasis[i].value.cooked from the parser AST (or decoding escape sequences in a single pass with /\\(...)/g) avoids re-implementing template literal escape decoding via chained replacements.

Reproduction

https://gist.github.com/trackoor/b70684206495019a7aa60f35509c1a76

Steps to reproduce

Run npm install followed by node repro.mjs.

System Info

System:
  OS: Linux 6.1 Debian GNU/Linux 12 (bookworm) 12 (bookworm)
  CPU: (64) x64 Intel(R) Xeon(R) CPU @ 2.80GHz
  Shell: 5.2.15 - /bin/bash
Binaries:
  Node: 26.13.0
  npm: 11.20.0
  pnpm: 12.18.0
npmPackages:
  @rolldown/plugin-emotion: ^0.2.0 => 0.2.0 (also on main 83c059d)
  rolldown: ^1.3.0 => 1.3.0

Used Package Manager

npm

Validations

Credit: https://ucsbplse.github.io/

Activity

  1. added theissue type on Nov 9, 2026
  2. sapphi-red commented on Nov 9, 2026

    @sapphi-red
    Member

    Note for myself: this bug exists in the SWC plugin as well but doesn't in the babel plugin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions