{"id":14620709,"url":"https://github.com/fpapado/svg-use","last_synced_at":"2025-05-15T16:02:44.475Z","repository":{"id":253090776,"uuid":"839846107","full_name":"fpapado/svg-use","owner":"fpapado","description":"Tools and bundler plugins, to ergonomically load SVG files via use[href]","archived":false,"fork":false,"pushed_at":"2025-05-14T21:51:58.000Z","size":1776,"stargazers_count":241,"open_issues_count":12,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-14T22:33:58.742Z","etag":null,"topics":["performance","rollup","svg","svg-icons","vite","webpack"],"latest_commit_sha":null,"homepage":"https://fotis.xyz/posts/introducing-svg-use/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fpapado.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-08-08T12:48:01.000Z","updated_at":"2025-05-12T23:44:51.000Z","dependencies_parsed_at":"2024-09-25T01:47:27.099Z","dependency_job_id":"a796f96d-470c-4606-8367-dd7acf189c36","html_url":"https://github.com/fpapado/svg-use","commit_stats":{"total_commits":102,"total_committers":6,"mean_commits":17.0,"dds":0.6666666666666667,"last_synced_commit":"655eb4d716283234190be34f1c6caedbb61ecac1"},"previous_names":["fpapado/svg-use"],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fpapado%2Fsvg-use","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fpapado%2Fsvg-use/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fpapado%2Fsvg-use/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fpapado%2Fsvg-use/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fpapado","download_url":"https://codeload.github.com/fpapado/svg-use/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254374398,"owners_count":22060609,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["performance","rollup","svg","svg-icons","vite","webpack"],"created_at":"2024-09-09T07:00:36.565Z","updated_at":"2025-05-15T16:02:44.434Z","avatar_url":"https://github.com/fpapado.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# @svg-use\n\nTools and bundler plugins, to load SVG icons as components in JavaScript, via\nSVG's `\u003cuse href\u003e` mechanism. This offers a performant way to reference SVG in\ncomponents, while taking into account **themeing**, **portability**, and **ease\nof use**.\n\n## In a nutshell\n\nWith an input file (`icon.svg`) like this:\n\n```html\n\u003csvg\n  xmlns=\"http://www.w3.org/2000/svg\"\n  width=\"24\"\n  height=\"24\"\n  fill=\"none\"\n  stroke=\"#111\"\n\u003e\n  \u003cline x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"\u003e\u003c/line\u003e\n  \u003cpolyline points=\"12 5 19 12 12 19\"\u003e\u003c/polyline\u003e\n\u003c/svg\u003e\n```\n\nAnd a JS file like this, plus one of the bundler integrations\n([webpack](./packages/webpack/), [rollup](./packages/rollup/),\n[vite](./packages/vite)):\n\n```tsx\nimport { Component as Arrow } from './icon.svg?svgUse';\n\nreturn \u003cArrow color=\"red\" /\u003e;\n```\n\nYou get a component with a performant output, that does not inline the SVG\nitself. This avoids duplication in a document, and keeps the SVG size out of\nyour JS bundles:\n\n```html\n\u003csvg viewBox=\"0 0 24 24\"\u003e\n  \u003cuse\n    href=\"https://my-site.com/assets/icon-someHash.svg#use-href-target\"\n    style=\"--svg-use-href-color: red\"\n  \u003e\u003c/use\u003e\n\u003c/svg\u003e\n```\n\n`icon-someHash.svg` becomes the equivalent of this, and is served as an SVG\nfile:\n\n```html\n\u003csvg\n  xmlns=\"http://www.w3.org/2000/svg\"\n  viewBox=\"0 0 24 24\"\n  fill=\"none\"\n  stroke=\"var(--svg-use-href-color, #111)\"\n  id=\"use-href-target\"\n\u003e\n  \u003cline x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"\u003e\u003c/line\u003e\n  \u003cpolyline points=\"12 5 19 12 12 19\"\u003e\u003c/polyline\u003e\n\u003c/svg\u003e\n```\n\n## Where to go from here\n\nThis is the repository root. To get started, consider\n[reading about the core problem](#the-core-problem) and\n[core solution](#the-core-solution-provided-here) that this package targets.\n\nThen, refer to these links:\n\n- Refer to [@svg-use/webpack](./packages/webpack) for the webpack loader\n- Refer to [@svg-use/vite](./packages/vite) for the Vite plugin\n- Refer to [@svg-use/rollup](./packages/rollup) for the Rollup plugin\n- Refer to [@svg-use/core](./packages/core) for the core logic, which powers the\n  bundler plugins\n- Refer to [@svg-use/react](./packages/react) for the default React wrapper\n  component\n- Refer to [the examples directory](./examples/) for examples of usage with\n  various bundlers and frameworks, as well as\n  [thoughts about how to use this pattern in shared libraries](./examples/shared-library/)\n- Refer to [Contributing](/CONTRIBUTING.md) for how to contribute to this\n  project.\n\n## The core problem\n\nA common technique in the JS (and especially React) ecosystem is converting SVG\nicons to components, so that they can be imported by JS code.\n[One common library for this task is svgr](https://github.com/gregberge/svgr/),\nwhich provides bundler plugins to facilitate converting SVG to JSX. Let's call\nthis approach SVG-in-JS, for the sake of comparison.\n\nThe SVG-in-JS approach is contrasted with referencing the SVG as an asset, and\nusing it in `img[src]` or in `svg \u003e use[href]`. This library provides one such\nalternative.\n\nAt its core, the SVG-in-JS solves a few different issues, in a convenient way.\nAlternatives to it would have to take this problem space.\n\nThe first issue is **theming**. By including the SVG inline, one can use regular\nHTML attributes and CSS selectors, and inherit custom properties easily. Most\noften, this is done to inherit `currentColor`, but other, more bespoke custom\nproperties or theming schemes are also used.\n\nAnother issue **delivery / portability**. By lifting SVG into the realm of JS,\nit can be loaded with ES modules, same as any other JS code. This is not a\nparticularly big deal for applications, which typically use bundlers that are\ncapable of referencing assets in the module graph. However, when it comes to\n**shareable libraries**, this provides a delivery mechanism that works anywhere\nthat JS is supported, without any configuration on behalf of the user.\n\nIn general, referencing assets (such as images or even CSS) from JS is currently\nnot standardised. It is thus hard to ship reusable libraries that depend on\nassets, at least in a general way, which does not assume one specific bundler\nconfig. There are techniques that work with most current bundlers and web\nbrowsers, such as `new URL('path/to/svg.svg', import.meta.url)`. These\ntechniques only solve the delivery problem though, and do not solve the theming\nproblem.\n\nThe SVG-in-JS approach is thus appealing, because it solves real problems in a\nrelatively simple way. However, it also comes with some drawbacks.\n\n### Drawbacks of SVG-in-JS\n\nBy inlining SVGs in JS, we are incurring a number of runtime costs.\n\nIn short:\n\n- Each component's code is parsed multiple times: first as JS, then as HTML/SVG\n  when inserted into the document.\n- Each SVG icon is duplicated in the DOM for every separate instance, bloating\n  the DOM size, and taking time to parse.\n- The size of the SVG icon adds to the JS bundle size. Some common SVG icons can\n  be large, for example country flags with intricate designs. It is easy to\n  accidentally inline large SVGs. This delays meaningful interactivity metrics.\n\n[This article by Jacob 'Kurt' Groß dives into the different drawbacks of SVG-in-JS, as well as different alternatives.](https://kurtextrem.de/)\n\nI believe that the runtime costs are big enough for many common cases, to\nwarrant an alternative.\n\n## The core solution provided here\n\n[SVG provides the `\u003cuse\u003e` element](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use),\nwhich can reference same-origin external SVGs via the `href` attribute.\n\nIf we were to reference an SVG with `use`, it would look like this:\n\n```html\n\u003csvg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"\u003e\n  \u003cuse href=\"https://example.com/icon.svg#someId\" style=\"--color: green;\"\u003e\u003c/use\u003e\n\u003c/svg\u003e\n```\n\nThis library considers the above structure as the compilation target, and\nprovides a toolchain for achieving it.\n\nIn JS, developers would consume this structure like this:\n\n```tsx\n// Using a colocated SVG\nimport { Component as Arrow } from './arrow.svg?svgUse';\n\n// Using an external library; the caller is none the wiser that `use` is used under the hood\nimport { ArrowRight } from 'my-shared-library';\n\nconst MyComponent = () =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003cArrow color=\"currentColor\" /\u003e\n      \u003cArrowRight color=\"green\" /\u003e\n    \u003cdiv\u003e\n  );\n};\n```\n\nTo make the above work, we need a few moving parts:\n\n- a `url` to reference the external SVG by.\n- an `id` to reference the external SVG by (while SVG 2 allows referencing\n  without an id, that part does not seem supported in browsers).\n- a `viewBox`, to allow intrinsic sizing of the outer `svg`.\n- a themeing system, to allow us to customise the referenced SVG. This can be\n  done with `currentColor` (for monochromatic icons) or via CSS properties,\n  which can be inherited by the referenced SVG.\n- a way to pass that information to a \"wrapper\" SVG component, which does the\n  `svg \u003e use[href]` setup.\n- if a CDN is used to host static assets, it gets a bit more complicated, and we\n  need a mechanism to rewrite the URLs, to enable proxying.\n\nThe core thesis is that the above setup is desirable in terms of its runtime\ncharacteristics, but is more tedious to set up than SVG-in-JS, due to the lack\nof a dedicated toolchain.\n\n**`@svg-use` is meant to be exactly that toolchain**, so that developers do not\nhave to worry about the setup.\n\nThe packages are composable and easy to extend. Additionally, type safety and\nuser convenience are key; this should be as (or nearly as) convenient as\nSVG-in-JS.\n\n### Pros and cons of this approach\n\nBy referencing an external asset, we are avoiding the double-parsing and JS\nbundle size costs; we only need to ship a URL and some metadata, as well as a\nwrapper component. We are also reducing the DOM size, since a single `use` is\nsmaller than most icons (and involves fewer elements).\n\n**Themeability** is achieved by the themeing transform, and can often be as\nsimple as passing down `currentColor`. The themeing is done statically, and has\nno runtime cost.\n\nThe **portability** of this approach is good, because you can use the resulting\nSVGs directly, and not just in React. You could even write out the `use[href]`\nmanually if you wanted, or create your own wrapper component, in your framework\nof choice.\n\n**Delivery** of shared libraries can be reliable, using the\n`new URL('path/to/svg', import.meta.url)` pattern.\n[An example with notes is provided for these cases](./examples/shared-library/README.md).\n\nOne downside, is the **lack of CORS** for SVG `use[href]` references. This is a\nreal issue, that can only be reliably solved at the specification level.\nHowever, SVG-in-JS is often used for local and shared-library SVG use-cases,\nwhich are self-hosted, so the lack of CORS is not an issue for replacing them.\n\nIn case you use a CDN for your application assets (including JS), the default\ncomponents provide configuration to rewrite the URLs at runtime, to point them\nto a proxy.\n\nAll that said, there are certainly cases where inlining the SVGs is the better\nor simpler approach, depending on your loading patterns. I do not claim that\nthis library will solve everything, but even if it leads to 80% of the SVGs no\nlonger being inlined in JS \"by default\", it will be a good step.\n\n[Refer to FUTURE.md for developments that might make this approach more ergonomic](./FUTURE.md)\n\n\u003e [!NOTE]  \n\u003e The rest of this document is about diving in to the details, and contributing.\n\u003e Consider [where to go next](#where-to-go-from-here), for links to the concrete\n\u003e plugins and usage examples.\n\n## In depth\n\nAssuming the default configuration and a given bundler plugin.\n\nWhen you write this:\n\n```tsx\nimport { href, id, Component as ArrowIcon } from './arrow.svg?svgUse';\n\nconst MyComponent = () =\u003e {\n  return (\n    \u003cbutton\u003e\n      \u003cArrowIcon color=\"currentColor\" /\u003e\n    \u003c/button\u003e\n  );\n};\n```\n\nA bundler-specific plugin ([webpack](./packages/webpack/),\n[rollup](./packages/rollup/), [vite](./packages/vite)) does the following:\n\n- The plugin resolves `./some-icon.svg` and invokes\n  [`@svg-use/core`](./packages/core)\n  - Core parses `./some-icon.svg`, to ensure that it fulfills the invariants\n  - Core extracts the `id` and `viewBox` of the top-level SVG element\n  - Core runs a \"themeing\" function to turn the SVG element's fills and strokes\n    into configurable CSS custom properties\n  - Core returns the transformed SVG content, and the extracted information\n- The plugin emits the transformed SVG as an asset (using to the bundler's\n  logic), and resolves its would-be URL. The loader stores that URL.\n- The plugin passes the URL to Core, to create a JS module. This is what the\n  userland code ultimately sees.\n  - The module exports the extracted properties, and passes them to a \"component\n    factory\", for convenience.\n- The plugin passes on the JS module to the bundler.\n\nThe ad-hoc JS module is the equivalent of this:\n\n```tsx\nimport { createThemedExternalSvg } from '@svg-use/react';\n\nexport const id = 'use-href-target';\nexport const href = new URL('/assets/some-icon-1234.svg', import.meta.url).href;\nexport const viewBox = '0 0 32 32';\n\n/* createThemedExternalSvg is a component factory function */\nexport const Component = createThemedExternalSvg({ href, id, viewBox });\n```\n\nThis approach combines convenience (being able to just use `Component`), with\ncomposition (being able to use `href` and `id` to build your own wrapper\ncomponents).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffpapado%2Fsvg-use","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffpapado%2Fsvg-use","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffpapado%2Fsvg-use/lists"}