esbuild icon indicating copy to clipboard operation
esbuild copied to clipboard

SVG content not URL encoded when using dataurl loader

Open imechZhangLY opened this issue 11 months ago • 3 comments

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})`;

imechZhangLY avatar Jan 22 '25 10:01 imechZhangLY

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].

hyrious avatar Jan 22 '25 11:01 hyrious

// 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 avatar Jan 23 '25 22:01 evanw

// 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.

imechZhangLY avatar Jan 24 '25 04:01 imechZhangLY