{"id":165,"date":"2021-10-03T06:41:21","date_gmt":"2021-10-03T06:41:21","guid":{"rendered":"https:\/\/engy.us\/blog\/?p=165"},"modified":"2021-10-03T06:41:21","modified_gmt":"2021-10-03T06:41:21","slug":"efficient-svg-icons-in-web-components-with-webpack-and-svgo","status":"publish","type":"post","link":"https:\/\/engy.us\/blog\/2021\/10\/03\/efficient-svg-icons-in-web-components-with-webpack-and-svgo\/","title":{"rendered":"Efficient SVG icons in Web Components with Webpack and SVGO"},"content":{"rendered":"\n<h2>So many ways to load them<\/h2>\n\n\n\n<p>There are a <a href=\"https:\/\/claude-e-e.medium.com\/ways-to-use-svg-in-your-html-page-dd504660cb37\">lot of different ways to show an SVG on a webpage<\/a>: <code>&lt;img><\/code>, <code>&lt;embed><\/code>, <code>&lt;object><\/code>, <code>&lt;iframe><\/code>, <code>&lt;canvas><\/code> and <code>&lt;svg><\/code> among them. I think for any halfway modern browser there are really only two serious contenders here. Referencing an SVG file:<\/p>\n\n\n\n<p><code>&lt;img src=\"image.svg\" \/><\/code><\/p>\n\n\n\n<p>And embedding the SVG directly into the DOM.<\/p>\n\n\n\n<p><code>&lt;svg>&lt;circle cx=\"50\" cy=\"50\" r=\"40\" \/>&lt;\/svg><\/code><\/p>\n\n\n\n<p>There are some advantages and drawbacks to each. <code>&lt;img><\/code> tags will keep your layouts small and only download the image as it&#8217;s needed. It can be cached by the browser and doesn&#8217;t need to be re-downloaded when your JS bundles update. But you can&#8217;t change their fill with CSS rules, which makes them not ideal for icons, which often need to switch color in dark vs light mode or for high contrast mode.<\/p>\n\n\n\n<p>Conversely, <code>&lt;svg><\/code> tags are malleable to CSS rules, but they are harder to bring in, cache separately and lazy-load.<\/p>\n\n\n\n<p>In our case we support dark and light modes, so we went with <code>&lt;svg><\/code> tags for our icons.<\/p>\n\n\n\n<h2>Should we delay-load?<\/h2>\n\n\n\n<p>Now the question is, how do we include them from our Web Component templates? In React we have SVGR, to turn SVGs into React components that can do fancy stuff like tell Webpack to make a separate bundle with the SVG data and go load that in on-demand.<\/p>\n\n\n\n<p>There is not yet a tool that can do exactly that for Web Components. I was preparing to sit down and write one, but after thinking about it for a bit and consulting with some members of the FAST Element team; I don&#8217;t think it&#8217;s necessary to load them separately.<\/p>\n\n\n\n<ul><li>Properly optimized icon SVGs are tiny, since they are just vector data. Not a lot is being gained by pushing that off to a separate web request and running it through a bunch of code to bring it in.<\/li><li>The icon data is slim enough that it will represent a small fraction of the template size.<\/li><li>Bundled icons never have late pop-in and are never missing. They are treated like just another part of your view template.<\/li><\/ul>\n\n\n\n<h2>How to include them in your template<\/h2>\n\n\n\n<p>Putting them straight into the template is one way:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;button>&lt;svg>&lt;circle cx=\"50\" cy=\"50\" r=\"40\" \/>&lt;\/svg>&lt;\/button><\/code><\/pre>\n\n\n\n<p>That&#8217;s not great because you can&#8217;t re-use them, and if you need to update an icon you have to go find everywhere you used it.<\/p>\n\n\n\n<p>(fun fact: you only need the xmlns attribute on the &lt;svg> tag when it&#8217;s in a file and you can omit it when it&#8217;s embedded in the DOM)<\/p>\n\n\n\n<p>The next impulse is to just export them from some icons module:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export const circleIcon = `&lt;svg>&lt;circle cx=\"50\" cy=\"50\" r=\"40\" \/>&lt;\/svg>`;<\/code><\/pre>\n\n\n\n<p>Then import the string and include in your template:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;button>${circleIcon}&lt;\/button><\/code><\/pre>\n\n\n\n<p>That was what we tried at first. It worked, but it had some drawbacks. Our central icons file soon got quite large, up to 220kb of icons from various things that might eventually be shown at some point in interacting with the app. But we only need ~10kb of that for the first page load. The initial assumption was that Webpack would save us, but it just ended up putting the whole giant module in a critical JS bundle. That means we&#8217;d need to front load every single icon before we could display anything at all. Oops.<\/p>\n\n\n\n<h2>The solution<\/h2>\n\n\n\n<p>We ended up using a combination of raw-loader and <a href=\"https:\/\/github.com\/svg\/svgo-loader\">svgo-loader<\/a> webpack modules. In our webpack config:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>module: {\r\n    rules: &#91;\r\n        { test: \/\\.svg$\/, use: &#91;\r\n            { loader: \"raw-loader\" },\r\n            {\r\n                loader: \"svgo-loader\",\r\n                options: {\r\n                    configFile: false,\r\n                    floatPrecision: 2,\r\n                    plugins: extendDefaultPlugins(&#91;\r\n                        \"removeXMLNS\", \/\/ We can safely remove the XMLNS attribute because we are inlining our SVGs\r\n                        {\r\n                            name: \"removeViewBox\",\r\n                            active: false\r\n                        }\r\n                    ])\r\n                }\r\n            }\r\n        ]},\n        ...<\/code><\/pre>\n\n\n\n<p>Raw-loader means it&#8217;s going to bring the string directly into the module instead of reference it somewhere else.<\/p>\n\n\n\n<p>The SVGO-loader performs a lot of optimizations on the SVGs to slim them down: for example <a href=\"https:\/\/github.com\/svg\/svgo\">removing redundant paths and combining elements<\/a>. So you end up with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import circleIcon from \".\/Circle.svg\";\n\n...\n\n&lt;button>${circleIcon}&lt;\/button><\/code><\/pre>\n\n\n\n<p>The built JS file looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;button>${'&lt;svg>&lt;circle cx=\"50\" cy=\"50\" r=\"40\" \/>&lt;\/svg>'}&lt;\/button><\/code><\/pre>\n\n\n\n<p>Except a bit smaller and more efficient than what was in Circle.svg.<\/p>\n\n\n\n<h2>De-duplication<\/h2>\n\n\n\n<p>A concern here is the size penalty from including the same icon multiple times. One thing to note is that Webpack will, even with the raw-loader re-use a variable if you&#8217;ve used the same SVG more than one time in a single module.<\/p>\n\n\n\n<p>Another technique you can use is that if you have icons that are re-used and that show up together, is to re-export them from a shared icons module, and import them where needed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import circleIcon from \".\/Circle.svg\";\nimport squareIcon from \".\/Square.svg\";\n\nexport { circleIcon, squareIcon };<\/code><\/pre>\n\n\n\n<p>Then you can import and use them as normal strings. Just remember that if you put everything in the same file it won&#8217;t scale very well as Webpack can&#8217;t break the module up.<\/p>\n\n\n\n<h2>TypeScript ANGRY<\/h2>\n\n\n\n<p>If you&#8217;re using TypeScript (which of course you should be) it&#8217;s going to be cross with you. <code>Cannot find module '.\/Circle.svg' or its corresponding type declarations.<\/code> In other words &#8220;What are you doing importing an .svg file? That doesn&#8217;t look like an ES6 module.&#8221;<\/p>\n\n\n\n<p>We need to tell it &#8220;Shhh, shhh, it&#8217;s OK. My buddy Webpack is going to come by and fix everything. When he&#8217;s done, it will look like you&#8217;re importing a string, so don&#8217;t worry about it.&#8221; Translated into TypeScript:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>declare module \"*.svg\" {\r\n    const content: string;\r\n    export default content;\r\n}<\/code><\/pre>\n\n\n\n<p>Then it says &#8220;OHHHH got it, When I see a module ending with .svg, I can assume it&#8217;s going to have a default export with a type of string.&#8221;<\/p>\n\n\n\n<p>Put this type declaration in a package that all the icon users will import from.<\/p>\n\n\n\n<h2>Icon package<\/h2>\n\n\n\n<p>I also made an icon package with all of the .svg files in it:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-attachment-id=\"167\" data-permalink=\"https:\/\/engy.us\/blog\/2021\/10\/03\/efficient-svg-icons-in-web-components-with-webpack-and-svgo\/image-3\/\" data-orig-file=\"https:\/\/i1.wp.com\/engy.us\/blog\/wp-content\/uploads\/2021\/10\/image.png?fit=213%2C177&amp;ssl=1\" data-orig-size=\"213,177\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"image\" data-image-description=\"\" data-medium-file=\"https:\/\/i1.wp.com\/engy.us\/blog\/wp-content\/uploads\/2021\/10\/image.png?fit=213%2C177&amp;ssl=1\" data-large-file=\"https:\/\/i1.wp.com\/engy.us\/blog\/wp-content\/uploads\/2021\/10\/image.png?fit=213%2C177&amp;ssl=1\" loading=\"lazy\" width=\"213\" height=\"177\" src=\"https:\/\/i1.wp.com\/engy.us\/blog\/wp-content\/uploads\/2021\/10\/image.png?resize=213%2C177&#038;ssl=1\" alt=\"\" class=\"wp-image-167\" data-recalc-dims=\"1\"\/><\/figure>\n\n\n\n<p>In package.json you can tell it to include the <code>icons<\/code> folder:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-attachment-id=\"168\" data-permalink=\"https:\/\/engy.us\/blog\/2021\/10\/03\/efficient-svg-icons-in-web-components-with-webpack-and-svgo\/image-1\/\" data-orig-file=\"https:\/\/i2.wp.com\/engy.us\/blog\/wp-content\/uploads\/2021\/10\/image-1.png?fit=137%2C85&amp;ssl=1\" data-orig-size=\"137,85\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"image-1\" data-image-description=\"\" data-medium-file=\"https:\/\/i2.wp.com\/engy.us\/blog\/wp-content\/uploads\/2021\/10\/image-1.png?fit=137%2C85&amp;ssl=1\" data-large-file=\"https:\/\/i2.wp.com\/engy.us\/blog\/wp-content\/uploads\/2021\/10\/image-1.png?fit=137%2C85&amp;ssl=1\" loading=\"lazy\" width=\"137\" height=\"85\" src=\"https:\/\/i2.wp.com\/engy.us\/blog\/wp-content\/uploads\/2021\/10\/image-1.png?resize=137%2C85&#038;ssl=1\" alt=\"\" class=\"wp-image-168\" data-recalc-dims=\"1\"\/><\/figure>\n\n\n\n<p>Then you can import from &#8220;package-name\/icons\/Circle.svg&#8221;<\/p>\n\n\n\n<h2>The result<\/h2>\n\n\n\n<p>After we implemented this icon system, we dropped our critical bundle size by ~210KB, which improved our PLT1 by 100ms.<\/p>\n\n\n\n<p>It&#8217;s also easy to add SVGs as you just check them into the repository and import them as strings, which have been automatically streamlined in the Webpack step.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>So many ways to load them There are a lot of different ways to show an SVG on a webpage: &lt;img>, &lt;embed>, &lt;object>, &lt;iframe>, &lt;canvas> and &lt;svg> among them. I think for any halfway modern browser there are really only two serious contenders here. Referencing an SVG file: &lt;img src=&#8221;image.svg&#8221; \/> And embedding the SVG &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/engy.us\/blog\/2021\/10\/03\/efficient-svg-icons-in-web-components-with-webpack-and-svgo\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Efficient SVG icons in Web Components with Webpack and SVGO&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false},"categories":[1],"tags":[],"jetpack_featured_media_url":"","jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pahBcK-2F","jetpack-related-posts":[{"id":174,"url":"https:\/\/engy.us\/blog\/2021\/12\/11\/aws-cognito-authentication-in-electron\/","url_meta":{"origin":165,"position":0},"title":"AWS Cognito Authentication in Electron","date":"December 11, 2021","format":false,"excerpt":"The AWS Cognito authentication service as of this writing does not officially support the Electron platform. But there is a Javascript SDK for Cognito, as part of AWS Amplify. Others have tried using it on Electron but have run into issues. I ran into several more than what are described\u2026","rel":"","context":"Similar post","img":{"alt_text":"","src":"https:\/\/i2.wp.com\/engy.us\/blog\/wp-content\/uploads\/2021\/12\/image.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":159,"url":"https:\/\/engy.us\/blog\/2021\/05\/25\/how-to-use-individual-code-signing-certificates-to-get-rid-of-smartscreen-warnings\/","url_meta":{"origin":165,"position":1},"title":"How to Use Individual Code Signing Certificates to get rid of SmartScreen warnings","date":"May 25, 2021","format":false,"excerpt":"The problem Windows SmartScreen has for a while been trying to keep malware at bay. One of the ways of doing that is putting up a big scary warning when you try to run anything they haven't validated as safe: You have to click \"More info\" then \"Run anyway\" to\u2026","rel":"","context":"With 5 comments","img":{"alt_text":"","src":"https:\/\/i2.wp.com\/engy.us\/blog\/wp-content\/uploads\/2021\/05\/image.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":88,"url":"https:\/\/engy.us\/blog\/2018\/10\/20\/dark-theme-in-wpf\/","url_meta":{"origin":165,"position":2},"title":"Dark Theme in WPF","date":"October 20, 2018","format":false,"excerpt":"In a recent Windows 10 update a toggle switch was added to allow the user to specify that they wanted \"Dark\" themes in apps: I decided to add support for this to VidCoder. But no updates to WPF were made to make this easy. WPF does having theming support where\u2026","rel":"","context":"With 5 comments","img":{"alt_text":"","src":"https:\/\/i2.wp.com\/engy.us\/blog\/wp-content\/uploads\/2018\/10\/VidCoderDarkExample.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":177,"url":"https:\/\/engy.us\/blog\/2021\/12\/14\/monorepo-with-npm-workspaces-and-typescript\/","url_meta":{"origin":165,"position":3},"title":"Monorepo with NPM Workspaces and Typescript","date":"December 14, 2021","format":false,"excerpt":"I recently needed to set up a project with a monorepo and was searching about for a good guide or example. Nothing I found was quite right. This repo had a rather extensive example but brought in lerna, which I wanted to avoid. This one was a bit simpler but\u2026","rel":"","context":"Similar post","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":25,"url":"https:\/\/engy.us\/blog\/2012\/04\/06\/datetime-and-datetimeoffset-in-net-good-practices-and-common-pitfalls\/","url_meta":{"origin":165,"position":4},"title":"DateTime and DateTimeOffset in .NET: Good practices and common pitfalls","date":"April 6, 2012","format":false,"excerpt":"It becomes necessary to deal with dates and times in most .NET programs. A lot of programs use DateTime but that structure is frought with potential issues when you start serializing, parsing, comparing\u00a0and displaying dates from\u00a0different time zones and cultures. In this post I will go over these issues and\u00a0the\u2026","rel":"","context":"In \".net\"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":79,"url":"https:\/\/engy.us\/blog\/2018\/10\/03\/wpf-databinding-using-dynamicdata\/","url_meta":{"origin":165,"position":5},"title":"WPF Databinding using DynamicData","date":"October 3, 2018","format":false,"excerpt":"My project is MVVM and I had been using ReactiveList<T> as my go-to observable collection for viewmodel properties. But ReactiveUI deprecated ReactiveList in 8.6.1. So I needed to get on to the new recommended library: DynamicData. But there is no direct drop-in replacement you can do like ObservableCollection<T> <-> ReactiveList<T>.\u2026","rel":"","context":"With 2 comments","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/engy.us\/blog\/wp-json\/wp\/v2\/posts\/165"}],"collection":[{"href":"https:\/\/engy.us\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/engy.us\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/engy.us\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/engy.us\/blog\/wp-json\/wp\/v2\/comments?post=165"}],"version-history":[{"count":2,"href":"https:\/\/engy.us\/blog\/wp-json\/wp\/v2\/posts\/165\/revisions"}],"predecessor-version":[{"id":169,"href":"https:\/\/engy.us\/blog\/wp-json\/wp\/v2\/posts\/165\/revisions\/169"}],"wp:attachment":[{"href":"https:\/\/engy.us\/blog\/wp-json\/wp\/v2\/media?parent=165"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/engy.us\/blog\/wp-json\/wp\/v2\/categories?post=165"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/engy.us\/blog\/wp-json\/wp\/v2\/tags?post=165"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}