-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
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
feat: allow .svg files to be inlined #2909
Conversation
Okay, I didn't realise I needed to build before running the tests, so still has the same todo as the original PR:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello, I'm providing a review as this is a feature of interest I've come across while evaluating adopting vite
for projects.
I have a concern about the current implementation here breaking for srcSet
(html) and image-set()
(css) handling. The reference project you've based your PR on from here in the comment has an issue from me a while back where this was an issue an caused breakage.
It would be better to use the upstream package directly rather than duplicating a portion of it here imo. That project provides additional improvements along with a toSrcset()
variant that will keep spaces encoded.
Either way, you need to encode the spaces for these two value types and ideally keep the optimized data url without encoded spaces everywhere else. A related vite
issue that fixed processing for this type of source values is here. Hopefully that helps identify where to address the issue rather than introducing a bug.
Regarding the review comment about dropping ;utf-8
that is mentioned in a codepen blog post by the author of the same referenced package.
// base64 inlined as a string | ||
url = `data:${mime.getType(file)};base64,${content.toString('base64')}` | ||
// svgs can be inlined without base64 | ||
url = file.endsWith('.svg') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An earlier issue I saw mentioned using .SVG
as a workaround for the current exclusion; This PR will continue to process that asset with base64. Perhaps do a case-insensitive check here instead?
Has file
been lower-cased prior to processing?
It doesn't seem so looking at cleanUrl()
, when a maintainer reviews, they'll know if it should be handled here or better suited for lower-casing in cleanUrl()
util for everything instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While /\.svg$/i.test(file)
is more fool-proof, I don't think all-caps file extensions are particularly common?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used plain endsWith
merely to be consistent with the rest of the codebase. If someone ever decides to supports case-insensitive extensions it'll be an easy fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think all-caps file extensions are particularly common?
It shouldn't be common no. Depends if you want to avoid it ever being an issue for users that do though.
Not a big deal, worse that happens is it gets encoded as base64 instead and if that bothers/confuses a user, they raise an issue and someone can point out lowercase extension is only supported or someone adds the fix by lowercasing the filename (only for checking the extension).
If someone ever decides to supports case-insensitive extensions it'll be an easy fix.
I don't know what you mean here? You just use toLowerCase()
on the string.
url = file.toLowerCase().endsWith('.svg')
function specialHexEncode(match: string) { | ||
// Browsers tolerate these characters, and they're frequent | ||
switch (match) { | ||
case '%20': | ||
return ' ' | ||
case '%3D': | ||
return '=' | ||
case '%3A': | ||
return ':' | ||
case '%2F': | ||
return '/' | ||
default: | ||
return match.toLowerCase() // compresses better | ||
} | ||
} | ||
|
||
const doubleQuoteRE = /"/g | ||
const whitespaceRE = /\s+/g | ||
const urlHexPairsRE = /%[\dA-F]{2}/g | ||
|
||
// Adopted from https://github.com/tigt/mini-svg-data-uri | ||
function uriEncodeSvg(content: string) { | ||
const normalizedContent = content | ||
.trim() | ||
.replace(whitespaceRE, ' ') | ||
.replace(doubleQuoteRE, "'") | ||
|
||
return encodeURIComponent(normalizedContent).replace( | ||
urlHexPairsRE, | ||
specialHexEncode | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for the most part the referenced package logic, well a portion of it.
Any reason not to leverage the upstream package directly? It has a few extra improvements you're opting out of here, as well as the support for not breaking srcSet
usage which the implementation you've provided here will fail to support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not go with mini-svg-data-uri
precisely because it does some micro-optimizations that aren't strictly necessary namely conversion from %20
to
, which toSrcset
has to undo as its only function.
However, since the team's sentiment seems to be towards using the upstream package I switched to that. I had to always use toSrcset
though, because there no way to tell whether the Data URI string is going to be used in srcSet
when importing into JavaScript.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to always use
toSrcset
though, because there no way to tell whether the Data URI string is going to be used insrcSet
when importing into JavaScript.
That's fine 👍
If the detection via context of usage isn't available better that it works with all cases where white-space would otherwise cause a breakage (there is more than just srcSet
where SVG can be provided and this would break without encoding spaces).
For anyone that outputs static HTML/CSS, they can probably post-process if they want to convert %20
to
(space) when valid. It can make for decent savings on larger SVG uncompressed(it's often larger than base64 with encoded spaces), but most of the time compression will be in use anyway which closes the gap notably.
For example, an SVG of 1,164 bytes url-encoded (but spaces retained) is 590 bytes gzip level 6, and the space encoded version is 1,494 bytes but only 623 bytes via gzip 6. Base64 equivalent 1,474 bytes uncompressed and 863 bytes gzip 6. Original SVGO optimized SVG is 1,084 bytes as a file (561 bytes gzip 6).
@polarathene Thank you for the review. Would you like to fork this PR and carry it forward? Note that this PR still breaks svg fragments: vite/packages/playground/assets/index.html Lines 83 to 100 in 30ff5a2
See here for the related test suite: vite/packages/playground/assets/__tests__/assets.spec.ts Lines 149 to 167 in 30ff5a2
|
This comment has been minimized.
This comment has been minimized.
Base64 is unnecessary for SVG files: https://github.com/vitejs/vite/issues/1197\#issuecomment-738780169 Also using `Buffer.byteLength` instead of character length when comparing with the `assetsInlineLimit` option. Closes #1204
Would anyone mind approving workflows in this PR? @patak-js @Shinigami92 @antfu |
Please keep in mind that we are humans and therefore need sleep ^^ It's 7:30 AM in the morning right now and I just start my day in around 1-2h |
@Shinigami92 Thank you for the approval, sorry for disturbing everyone, I didn't mean to! |
Did you know you can test locally with |
I did exactly that, however I didn't realize it'd run the same tests twice 🙈 |
@@ -162,7 +162,9 @@ describe('svg fragments', () => { | |||
|
|||
test('from js import', async () => { | |||
const img = await page.$('.svg-frag-import') | |||
expect(await img.getAttribute('src')).toMatch(/svg#icon-heart-view$/) | |||
expect(await img.getAttribute('src')).toMatch( | |||
isBuild ? /svg%3e#icon-heart-view$/ : /svg#icon-heart-view$/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does the %3e
come from, and why only in build mode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aleclarson it's the url-encoded version of >
so the string being matched is svg>#icon-heart-view
, whereas the non-build mode version hasn't had the SVG inlined, thus is still referencing the SVG by filename, instead of the closing svg
tag, it's the svg
extension.
I'm assuming this was tested in a browser that it actually works with the fragment and not just to pass the test though. I know that #
needs to be url-encoded for inlining SVG usually, but perhaps it's not meant to be when appending the fragment identifier after the inlined SVG like you would append it after a file extension? I've never used fragments, but I'm interested to know if that works as intended.
I'd love to have this feature in my project. I see that there is an approval from about a month ago and the branch needs a rebase. Is there anything else that needs to be done before this can be merged? |
We also really need this feature. Seems only to be logical to allow to inline the SVGs among other types of images. Any updates on this? |
For this PR to be merged, someone needs to manually test it with SVG fragments and |
I could do that if you need? do you have any guidelines for testing this type of enhancements? thanks |
Found this PR while searching for some solution to why dynamically From what I can see this PR has been idle for one month now; any news on when this might be merged? EDIT: found a workaround in this thread so no rush even though it would be nice to have it out of the box! |
@princed Are you still working on this? Do you need any help from any of us? Thanks. |
@hornta see here: #2909 (comment) |
why not delegate this to existing plugins? for example, EDIT: not including 10000+ icons, will support adding 10000+ on demand, just install |
because SVGs are assets, therefore
This PR does not use Base64 on SVGs |
I'll try to test it... |
I just mimic the Run With ❯ npm run build
$ vue-tsc --noEmit && vite build
src/App.vue:4:23 - error TS2307: Cannot find module '/src/assets/fragment-ready-4.svg?url#icon-clock-view' or its corresponding type declarations.
4 import topUrlSvg from '/src/assets/fragment-ready-4.svg?url#icon-clock-view';
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Found 1 error.
~/projects/vitejs-vite-y9k5yr
❯ npm run build
$ vue-tsc --noEmit && vite build
vite v2.6.14 building for production...
transforming (1) index.htmlMATCHED: /home/projects/vitejs-vite-y9k5yr/src/assets/fragment-ready-4.svg?url
✓ 12 modules transformed.
dist/assets/fragment-ready-4.edfb3e5c.svg 1.15 KiB
dist/index.html 0.48 KiB
dist/assets/index.5c25b9b4.js 3.68 KiB / gzip: 1.60 KiB
dist/assets/index.f295bcc2.css 0.17 KiB / gzip: 0.13 KiB
dist/assets/vendor.2c023ab2.js 49.20 KiB / gzip: 19.81 KiB
~/projects/vitejs-vite-y9k5yr 6s
❯ |
I'll clone the repo for this PR and check using the modified vite. |
the repro I provide is not a repro for this PR, since this PR only handles the inline svg when there is no let url: string
if (
file.endsWith('.svg') ||
((config.build.lib ||
Buffer.byteLength(content) < config.build.assetsInlineLimit) &&
hash == null)
) {
// svgs can be inlined without base64
url = file.endsWith('.svg')
? // The only difference between the default method and `toSrcset` is that
// the latter encodes spaces as `%20`, so it's safer to always use it
// to support `srcset` use-case even when svg is imported into JavaScript
svgToTinyDataUri.toSrcset(content.toString())
: // base64 inlined as a string
`data:${mime.getType(file)};base64,${content.toString('base64')}` In fact, on dev it is not serving the svg inlined (data-url), so we're not testing the same problem (I'll change also I change import svgFragUrl from './nested/fragment.svg?url'
text('.svg-frag-import-js-url', svgFragUrl)
document.querySelector('.svg-frag-import-url').style = `background: url("${svgFragUrl}") no-repeat;`
import svgFragUrlHash from './nested/fragment.svg?url#icon-heart-view'
text('.svg-frag-import-js-url-hash', svgFragUrlHash)
document.querySelector('.svg-frag-import-url-hash').style = `background: url("${svgFragUrlHash}") no-repeat;` and the html tag: <h2>SVG Fragments via URL JS Import</h2>
<div>
<p>Imported path: <code class="svg-frag-import-js-url"></code></p>
<img class="svg-frag-import-url" alt="" />
</div>
<h2>SVG Fragments via URL HASH JS Import</h2>
<div>
<p>Imported path: <code class="svg-frag-import-js-url-hash"></code></p>
<img class="svg-frag-import-url-hash" alt="" />
</div> Without changing Changing results on: |
I'll test build, I don't know why machine never finish the build from the assets playground, and finish the test. Once done, I'll push to my repo the changes (without the tests) and put the link here. |
Uhmm, the build script is watching, this is why the build never finish... The result for pnpm run build
> test-assets@0.0.0 build F:\work\projects\quini\GitHub\vite\packages\playground\assets
> vite build
vite v2.7.0-beta.4 building for production...
watching for file changes...
build started...
✓ 16 modules transformed.
[vite:css-post] Transform failed with 3 errors:
<stdin>:91:57: error: Expected ")" to end URL token
<stdin>:95:57: error: Expected ")" to end URL token
<stdin>:99:57: error: Expected ")" to end URL token |
I push a draft PR to test this PR: #5658 |
Any update on this? Would be great to get native inline SVG handling in Vite, I'm currently doing some pretty hacky stuff to get them working in Sveltekit |
Do you mean something like this?
|
Any update on this? Inlining them would be great and the proposed hacks don't seem stable to me. |
This is an updated version of #1716 by @aleclarson with the inclusion of SVG URL-escaping. Below is the original PR description:
Base64 is unnecessary for SVG files:
#1197 (comment)
Also using
Buffer.byteLength
instead of character length when comparing with theassetsInlineLimit
option.Closes #1204