Skip to content

Commit 9c6905f

Browse files
committed
fix(core): preserve URL backslash identity while rebasing
1 parent 6c6feac commit 9c6905f

3 files changed

Lines changed: 22 additions & 3 deletions

File tree

‎packages/core/src/compiler/htmlBundler.test.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,6 +1847,21 @@ describe("bundleToSingleHtml", () => {
18471847
expect(styleText(inlined)).toContain(inlinedAs("image/png", "escaped-space-image"));
18481848
});
18491849

1850+
it("keeps raw URL backslashes distinct from encoded filename backslashes in linked CSS", async () => {
1851+
const dir = makeTempProject({
1852+
"index.html": `<link rel="stylesheet" href="css/theme.css"><div data-composition-id="root" data-width="320" data-height="180"></div>`,
1853+
"css/theme.css": String.raw`.raw { background: url("assets\\logo.png"); } .encoded { background: url("assets%5Clogo.png"); }`,
1854+
"css/assets/logo.png": "separator-image",
1855+
"css/assets\\logo.png": "literal-backslash-image",
1856+
});
1857+
const linked = await bundleToSingleHtml(dir, { inlineAssets: false });
1858+
expect(styleText(linked)).toContain('url("css/assets/logo.png")');
1859+
expect(styleText(linked)).toContain('url("css/assets%5Clogo.png")');
1860+
const inlined = await bundleToSingleHtml(dir);
1861+
expect(styleText(inlined)).toContain(inlinedAs("image/png", "separator-image"));
1862+
expect(styleText(inlined)).toContain(inlinedAs("image/png", "literal-backslash-image"));
1863+
});
1864+
18501865
it("removes CSS line continuations before rebasing linked stylesheet URLs", async () => {
18511866
const dir = makeTempProject({
18521867
"index.html": `<link rel="stylesheet" href="css/theme.css"><div data-composition-id="root" data-width="320" data-height="180"></div>`,

‎packages/core/src/compiler/htmlBundler.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ function withCommentsStripped<T>(
140140
return { result, restore };
141141
}
142142

143+
function resolveRelativeUrlPath(fromDir: string, basePath: string): string {
144+
return resolve(fromDir, decodeWellFormedEscapes(basePath.replaceAll("\\", "/")));
145+
}
146+
143147
function rebaseCssUrls(css: string, cssFileDir: string, projectDir: string): string {
144148
const resolvedRoot = resolve(projectDir);
145149
const resolvedDir = resolve(cssFileDir);
@@ -149,7 +153,7 @@ function rebaseCssUrls(css: string, cssFileDir: string, projectDir: string): str
149153
if (!decoded || !isRelativeUrl(decoded)) return full;
150154
const { basePath, suffix } = splitUrlSuffix(decoded);
151155
if (!basePath) return full;
152-
const absolutePath = resolve(resolvedDir, decodeWellFormedEscapes(basePath));
156+
const absolutePath = resolveRelativeUrlPath(resolvedDir, basePath);
153157
const rebased = encodeUrlPath(relative(resolvedRoot, absolutePath).split(sep).join("/"));
154158
if (rebased === basePath) return full;
155159
const escapedSuffix = suffix.replace(
@@ -163,7 +167,7 @@ function rebaseCssUrls(css: string, cssFileDir: string, projectDir: string): str
163167
function rebaseRelativePath(urlValue: string, fromDir: string, toDir: string): string {
164168
const { basePath, suffix } = splitUrlSuffix(urlValue.trim());
165169
if (!basePath) return urlValue;
166-
const absolutePath = resolve(fromDir, decodeWellFormedEscapes(basePath));
170+
const absolutePath = resolveRelativeUrlPath(fromDir, basePath);
167171
const rebased = encodeUrlPath(relative(resolve(toDir), absolutePath).split(sep).join("/"));
168172
return appendSuffixToUrl(rebased, suffix);
169173
}

‎packages/parsers/src/utils/urlPath.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function encodeUrlPath(path: string): string {
3636

3737
export function decodeCssEscapes(text: string): string {
3838
return text
39-
.replace(/\0/g, "\uFFFD")
39+
.replaceAll("\0", "\uFFFD")
4040
.replace(
4141
/\\(?:([0-9a-f]{1,6})(?:\r\n|[ \t\r\n\f])?|(\r\n|[\n\r\f])|([\s\S]))/gi,
4242
(

0 commit comments

Comments
 (0)