Repository navigation
Description
Related packages
- plugin-babel
- plugin-emotion
- plugin-jsx-remove-attributes
- plugin-styled-jsx
- plugin-transform-imports
- oxc-unshadowed-visitor
- pluginutils
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:
- An escaped backslash before
b,f,n,r,t, orvis decoded as a control character. Incsstemplates for icon fonts (e.g. Font Awesomecontent: "\\f007"), the raw text is\\f007..replace(/\\f/g, '\f')matches the second backslash +f, turning\\f007into\+ U+000C (\f) +007. In CSS, a backslash followed by a form feed inside a string is a line continuation, so the browser renders007instead of U+F007. - Escaped quotes such as
\\\"leave the quote backslash unconsumed. Incss`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 iscontent:"\\"", 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.0Used Package Manager
npm
Validations
- Follow our Code of Conduct
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion.
- The provided reproduction is a minimal reproducible example of the bug.
Credit: https://ucsbplse.github.io/
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Effort
Activity
sapphi-red commentedon Nov 9, 2026
Note for myself: this bug exists in the SWC plugin as well but doesn't in the babel plugin