Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SVG content not URL encoded when using dataurl loader #4045

Open
imechZhangLY opened this issue Jan 22, 2025 · 3 comments
Open

SVG content not URL encoded when using dataurl loader #4045

imechZhangLY opened this issue Jan 22, 2025 · 3 comments

Comments

@imechZhangLY
Copy link

Description:

I'm encountering an issue with esbuild when using the dataurl loader for SVG files. The SVG content is not URL encoded, which leads to bugs in certain cases.

Steps to Reproduce:

Use dataurl loader for an SVG file in esbuild.

Use the output as the parameter of a CSS url() function without quotes.

Expected Behavior:

The SVG content should be URL encoded to ensure it works correctly in all use cases.

Actual Behavior:

The SVG content is not URL encoded, causing bugs when used as a parameter in the CSS url function without quotes.

Example

You can find the exmpale on the playground

If we use the output as the following way, it will cause a bug and the background image will not show correctly.

import bg from "./bg.svg"

// the following code will not work.
document.body.style.backgroundImage = `url(${bg})`;
@hyrious
Copy link

hyrious commented Jan 22, 2025

The dataurl loader in JavaScript is meant to be directly assigned to url props like <a>'s href and <img>'s src. For CSS literals you need additional escaping, [possible code by @iconify/utils].

However, on the other hand, esbuild can bundle assets into CSS directly [demo].

@evanw
Copy link
Owner

evanw commented Jan 23, 2025

// the following code will not work.
document.body.style.backgroundImage = `url(${bg})`;

That's true, but it's not clear to me that this is esbuild's fault. There are several problems with this:

  • You could quote the string before passing it to CSS. That's a more principled way to pass arbitrary content to CSS. Quoting the result also generates smaller output code than escaping all of the spaces using %20. It's essentially what esbuild does when it bundles SVG into CSS, as demonstrated above.

  • The MIME type is wrong. You want image/svg+xml, not text/plain. This is happening because esbuild's transform API takes a string as input independent of a file system, and is mainly intended to transform JS and CSS code. While esbuild's transform API could be extended for this use case, I don't think that makes sense because that's not what the transform API is intended to be used for. This would make more sense with the build API, which does look at file types and which would use the correct MIME type. But you'd only do that if you're using esbuild for bundling.

The good news is that you don't need esbuild's transform API for this at all. It's pretty trivial to turn a JS string containing SVG into a CSS background image with a single line of code. Using esbuild for this is an extremely heavy way to do this (and as you pointed out it doesn't even work). Here's some code to do that:

const bg = `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M2.58859 2.71569L2.64645 2.64645C2.82001 2.47288 3.08944 2.4536 3.28431 2.58859L3.35355 2.64645L8 7.293L12.6464 2.64645C12.8417 2.45118 13.1583 2.45118 13.3536 2.64645C13.5488 2.84171 13.5488 3.15829 13.3536 3.35355L8.707 8L13.3536 12.6464C13.5271 12.82 13.5464 13.0894 13.4114 13.2843L13.3536 13.3536C13.18 13.5271 12.9106 13.5464 12.7157 13.4114L12.6464 13.3536L8 8.707L3.35355 13.3536C3.15829 13.5488 2.84171 13.5488 2.64645 13.3536C2.45118 13.1583 2.45118 12.8417 2.64645 12.6464L7.293 8L2.64645 3.35355C2.47288 3.17999 2.4536 2.91056 2.58859 2.71569L2.64645 2.64645L2.58859 2.71569Z" fill="#DD005D"/>
</svg>
`

document.body.style.backgroundImage = `url(data:image/svg+xml;base64,${btoa(bg)})`;

No esbuild required.

@imechZhangLY
Copy link
Author

// the following code will not work.
document.body.style.backgroundImage = url(${bg});

That's true, but it's not clear to me that this is esbuild's fault. There are several problems with this:

  • You could quote the string before passing it to CSS. That's a more principled way to pass arbitrary content to CSS. Quoting the result also generates smaller output code than escaping all of the spaces using %20. It's essentially what esbuild does when it bundles SVG into CSS, as demonstrated above.
  • The MIME type is wrong. You want image/svg+xml, not text/plain. This is happening because esbuild's transform API takes a string as input independent of a file system, and is mainly intended to transform JS and CSS code. While esbuild's transform API could be extended for this use case, I don't think that makes sense because that's not what the transform API is intended to be used for. This would make more sense with the build API, which does look at file types and which would use the correct MIME type. But you'd only do that if you're using esbuild for bundling.

The good news is that you don't need esbuild's transform API for this at all. It's pretty trivial to turn a JS string containing SVG into a CSS background image with a single line of code. Using esbuild for this is an extremely heavy way to do this (and as you pointed out it doesn't even work). Here's some code to do that:

const bg = <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M2.58859 2.71569L2.64645 2.64645C2.82001 2.47288 3.08944 2.4536 3.28431 2.58859L3.35355 2.64645L8 7.293L12.6464 2.64645C12.8417 2.45118 13.1583 2.45118 13.3536 2.64645C13.5488 2.84171 13.5488 3.15829 13.3536 3.35355L8.707 8L13.3536 12.6464C13.5271 12.82 13.5464 13.0894 13.4114 13.2843L13.3536 13.3536C13.18 13.5271 12.9106 13.5464 12.7157 13.4114L12.6464 13.3536L8 8.707L3.35355 13.3536C3.15829 13.5488 2.84171 13.5488 2.64645 13.3536C2.45118 13.1583 2.45118 12.8417 2.64645 12.6464L7.293 8L2.64645 3.35355C2.47288 3.17999 2.4536 2.91056 2.58859 2.71569L2.64645 2.64645L2.58859 2.71569Z" fill="#DD005D"/> </svg>

document.body.style.backgroundImage = url(data:image/svg+xml;base64,${btoa(bg)});
No esbuild required.

@evanw Thanks for your reply. I haven't clearly explained my issue. Quoting the string before passing it to CSS can solve our problem. However, for historical reasons, we haven't been quoting the parameter in the url() function. It worked well with Webpack and url-loader, but when we switched to esbuild, we encountered the issue. According to the W3C, quoting the URL is optional. I believe it's better to encode the SVG content using the bundling tool, as this will reduce issues when switching from other bundling tools to esbuild. You can find more details on https://github.com/[imechZhangLY/esbuild-demo](https://github.com/imechZhangLY/esbuild-demo).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants