Parcel 16 ๐ฆ SVG Handling โ External references & JSX conversion
Parcel treats SVG as a first-class asset type rather than a static binary blob. It parses the SVG’s XML, follows references to other files, rewrites those references to point at the correct output paths, andโwhen configuredโconverts the entire SVG into a JSX component that renders directly in React. The default behavior returns a URL string when you import an SVG. The JSX conversion is an opt-in plugin, and the distinction between the two import modes is the source of most configuration friction.
The external reference handling is automatic. A <use href="fox.svg#path" /> inside an SVG is detected, the referenced file is processed as a dependency, and the href is rewritten to the hashed output filename . The same applies to <image href="photo.jpg" />, <script src="code.js" />, and presentation attributes like fill="url(external.svg#gradient)" . This is the part that works without configuration. The JSX conversion is the part that requires a transformer and a pipeline declaration.
Key point: Parcel’s default SVG import returns a URL. To import an SVG as a React component, install @parcel/transformer-svg-jsx and use a named pipeline (jsx:./icon.svg) in the import statement. The external reference rewriting happens automatically regardless of whether the SVG is imported as a URL or as a component.
Why external references are handled automatically
The dependency graph problem. An SVG is not a single file. It can reference other SVGs (via <use>), raster images (via <image>), stylesheets (via xml-stylesheet), scripts (via <script>), and gradient definitions in other files (via url() in presentation attributes) . If Parcel treated the SVG as an opaque file, those references would break when the referenced files are renamed with content hashes during the build. The automatic processing solves this: every referenced file is added to the dependency graph, processed according to its own type, and the reference in the SVG is rewritten to the final output path .
The hashing interaction. Files referenced from an SVG receive content hashes in their output filenames by default, just like any other Parcel dependency. The SVG’s reference is rewritten to match. This is what makes long-term caching safe: if the referenced image changes, its hash changes, and the SVG’s reference changes with it. The one exception is <a> elements, which Parcel leaves unhashed because those URLs are typically human-readable and need stable names .
The <use> and url() cases. The <use href="fox.svg#path" /> pattern is common for SVG sprites and symbol reuse. Parcel follows the href, processes the referenced SVG, and rewrites the reference to the output filename plus the fragment identifier . The url() function in presentation attributes is handled the same way: fill="url(external.svg#gradient)" resolves the external SVG and rewrites the URL . A known bug in Parcel 2.12.0 breaks filter="url(#filter-2)" because the internal reference is rewritten to a hashed filename without the fragment, but this is a regression rather than the intended behavior .
The trade-off. The automatic reference rewriting assumes the referenced files are part of the same build. A reference to an external URL (https://example.com/icon.svg#path) is not processed. A reference to a file that is not resolvable at build time produces a build error rather than a runtime failure, which is the correct behavior for a bundler but can be surprising when the reference is intentionally dynamic.
a. Importing SVG as a URL (default)
The default import behavior returns a URL string. This is the behavior that works without any configuration, and it is the correct choice when the SVG is used as an <img> source or a CSS background.
// Default import: returns a URL string.
// The SVG is processed as an asset, and the URL points to the output file.
import logo from './logo.svg';
export function Logo() {
return <img src={logo} alt="Logo" />;
}
The new URL() constructor is the more explicit form. It resolves the path relative to the current module and produces an absolute URL at runtime. This is useful when the SVG is used in a context where a string concatenation is neededโa sprite reference, for example.
// Using the URL constructor.
// This resolves the path relative to the module and produces an absolute URL.
const sprite = new URL('./sprite.svg', import.meta.url);
// The sprite URL can be combined with a fragment identifier.
// This is the pattern for SVG sprite usage.
export function Icon({ name }) {
return (
<svg>
<use href={`${sprite}#${name}`} />
</svg>
);
}
The new URL() pattern is important for npm packages built with Parcel. A plain import of an SVG in a library produces a relative path that resolves against the consumer’s page, which breaks when the library is installed in a different project. The new URL(sprite, import.meta.url) form resolves the path relative to the module’s own location, which works regardless of where the library is installed .
b. Importing SVG as a React component (JSX conversion)
The JSX conversion requires the @parcel/transformer-svg-jsx plugin. In Parcel 2.15 and later, this plugin replaces the older @parcel/transformer-svg-react plugin, which used SVGR. The new plugin uses a Rust-based parser and converts the SVG DOM directly to an SWC JavaScript AST . It supports most SVGR configuration options in a .svgrrc file, but custom plugins and some template options are not supported .
# Install the transformer.
yarn add @parcel/transformer-svg-jsx --dev
// .parcelrc โ add a named pipeline for SVG-to-JSX.
// The "..." syntax runs the default SVG transformer first,
// then applies the JSX transformer.
{
"extends": "@parcel/config-default",
"transformers": {
"jsx:*.svg": ["...", "@parcel/transformer-svg-jsx"],
"jsx:*": ["..."]
}
}
// Import using the named pipeline.
// The jsx: prefix tells Parcel to use the JSX transformer.
import Icon from 'jsx:./icon.svg';
export function App() {
return <Icon />;
}
The named pipeline is deliberate. Without it, every SVG import would become a React component, which breaks the <img src={logo} /> pattern. With it, the developer chooses per-import: import logo from './logo.svg' returns a URL, and import Icon from 'jsx:./icon.svg' returns a component. The "jsx:*": ["..."] entry is a workaround for a known bug in the pipeline resolution; without it, the jsx: prefix may not resolve correctly in some configurations .
The TypeScript declaration is necessary for editor support. Without it, TypeScript treats jsx:./icon.svg as an untyped module.
// types/svg.d.ts
declare module "jsx:*.svg" {
import { ComponentType, SVGProps } from "react";
const SVGComponent: ComponentType<SVGProps<SVGSVGElement>>;
export default SVGComponent;
}
Complete Example Session
// ============================================
// PART 1: THE DEFAULT URL IMPORT
// ============================================
// The default import returns a URL string.
// No configuration is required.
import logoUrl from './logo.svg';
export function Header() {
return <img src={logoUrl} alt="Logo" />;
}
// ============================================
// PART 2: THE URL CONSTRUCTOR FOR LIBRARIES
// ============================================
// The new URL() form resolves the path relative to the module.
// This works when the library is installed in a different project.
const spriteUrl = new URL('./icons.svg', import.meta.url);
export function Icon({ name }) {
return (
<svg>
<use href={`${spriteUrl}#${name}`} />
</svg>
);
}
// ============================================
// PART 3: THE JSX TRANSFORMER INSTALLATION
// ============================================
// yarn add @parcel/transformer-svg-jsx --dev
// ============================================
// PART 4: THE .parcelrc CONFIGURATION
// ============================================
// {
// "extends": "@parcel/config-default",
// "transformers": {
// "jsx:*.svg": ["...", "@parcel/transformer-svg-jsx"],
// "jsx:*": ["..."]
// }
// }
// ============================================
// PART 5: THE JSX IMPORT
// ============================================
// The jsx: prefix selects the named pipeline.
// The SVG is converted to a React component.
import StarIcon from 'jsx:./star.svg';
export function Rating({ value }) {
return (
<div>
<StarIcon />
<span>{value}</span>
</div>
);
}
// ============================================
// PART 6: THE TYPESCRIPT DECLARATION
// ============================================
// types/svg.d.ts
// declare module "jsx:*.svg" {
// import { ComponentType, SVGProps } from "react";
// const SVGComponent: ComponentType<SVGProps<SVGSVGElement>>;
// export default SVGComponent;
// }
// Without this, TypeScript reports an error on the import.
// ============================================
// PART 7: THE EXTERNAL REFERENCE IN AN SVG
// ============================================
// icon.svg
// <svg>
// <use href="./symbols.svg#check" />
// </svg>
// Parcel detects the <use href> reference.
// The symbols.svg file is processed as a dependency.
// The href is rewritten to the hashed output path.
// ============================================
// PART 8: THE url() PRESENTATION ATTRIBUTE
// ============================================
// gradient.svg
// <svg>
// <circle fill="url(./defs.svg#gradient)" />
// </svg>
// Parcel follows the url() reference.
// defs.svg is processed and the url() is rewritten.
// ============================================
// PART 9: THE SVGO CONFIGURATION
// ============================================
// .svgrrc
// {
// "icon": true,
// "dimensions": false
// }
//
// The @parcel/transformer-svg-jsx plugin reads .svgrrc.
// Most SVGR options are supported.
// Custom plugins and templates are not supported by the new transformer.
// Use @parcel/transformer-svg-react for those cases.
// ============================================
// PART 10: THE SPRITE PATTERN
// ============================================
// The sprite SVG contains multiple <symbol> elements.
// Importing it as a URL and using <use> is the sprite pattern.
import sprite from './sprite.svg';
export function SpriteIcon({ name }) {
return (
<svg>
{/* The sprite URL is the hashed output path. */}
<use href={`${sprite}#${name}`} />
</svg>
);
}
// The sprite.svg is processed once.
// The URL points to the output file.
// The fragment identifier selects the symbol.
The ten parts covered the default URL import, the new URL() form for libraries, the JSX transformer installation, the .parcelrc pipeline, the TypeScript declaration, the external reference handling, the url() presentation attribute, the SVGR configuration, and the sprite pattern. The through-line is that the URL import works without configuration, and the JSX conversion is an opt-in pipeline that changes the import’s return type.
Quick Reference
SVG import modes
| Import syntax | Return type | Configuration |
|---|---|---|
import logo from './logo.svg' | URL string | None |
new URL('./logo.svg', import.meta.url) | URL object | None |
import Icon from 'jsx:./icon.svg' | React component | @parcel/transformer-svg-jsx + .parcelrc |
import svg from 'bundle-text:./logo.svg' | SVG string | None |
External reference support
| Reference | Supported | Rewritten |
|---|---|---|
<use href="fox.svg#path" /> | Yes | Yes |
<image href="photo.jpg" /> | Yes | Yes |
fill="url(external.svg#gradient)" | Yes | Yes |
<script src="code.js" /> | Yes | Yes |
<a href="page.html" /> | Yes | No hash (stable name) |
Transformer comparison
| Transformer | Engine | SVGR config | Custom plugins |
|---|---|---|---|
@parcel/transformer-svg-jsx | Rust | Most options | Not supported |
@parcel/transformer-svg-react | SVGR (Babel) | Full support | Supported |
Best Practices
โ Do This:
// Use the default import for <img> sources
import logo from './logo.svg'; // โ
// Use new URL() for library sprite references
const sprite = new URL('./sprite.svg', import.meta.url); // โ
// Use the jsx: prefix for React components
import Icon from 'jsx:./icon.svg'; // โ
// Add the TypeScript declaration
declare module "jsx:*.svg" { /* ... */ } // โ
โ Don’t Do This:
// Import an SVG without jsx: and expect a component
import Icon from './icon.svg'; // This is a URL string. // โ
// Use a plain import for a sprite in a library
import sprite from './sprite.svg'; // Relative path breaks. // โ
// Forget the jsx:* pipeline entry
"jsx:*.svg": ["...", "@parcel/transformer-svg-jsx"] // May fail // โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
Icon renders as a string | Import without jsx: prefix | Use import Icon from 'jsx:./icon.svg' |
| Library SVG breaks in consumer | Plain import produces relative URL | Use new URL('./sprite.svg', import.meta.url) |
TypeScript error on jsx: import | Missing module declaration | Add declare module "jsx:*.svg" |
filter="url(#id)" breaks | Known regression in Parcel 2.12.0 | Avoid the affected version or report the bug |
| Custom SVGR plugin ignored | New transformer does not support custom plugins | Use @parcel/transformer-svg-react |
Real-World Examples
1. Default URL import
import logo from './logo.svg';
<img src={logo} alt="Logo" />
2. URL constructor for libraries
const sprite = new URL('./sprite.svg', import.meta.url);
<use href={`${sprite}#check`} />
3. JSX import
import Star from 'jsx:./star.svg';
<Star />
4. Parcelrc pipeline
"jsx:*.svg": ["...", "@parcel/transformer-svg-jsx"]
5. TypeScript declaration
declare module "jsx:*.svg" { const C: React.FC; export default C; }
6. External <use> reference
<use href="fox.svg#path" />
7. External url() reference
<circle fill="url(external.svg#gradient)" />
8. SVGR config
{ "icon": true }
9. Sprite pattern
<use href={`${sprite}#${name}`} />
10. Bundle text import
import svg from 'bundle-text:./logo.svg';
Visual
The Import Mode Decision
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ WHAT DOES THE IMPORT RETURN? โ
โ โ
โ import logo from './logo.svg' โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ URL string โ Default behavior โ
โ โ (hashed output path)โ Works without configuration โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ import Icon from 'jsx:./icon.svg' โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ React component โ Requires @parcel/transformer- โ
โ โ (renders <svg>) โ svg-jsx and .parcelrc pipeline โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ new URL('./sprite.svg', import.meta.url) โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Absolute URL โ Resolves relative to the module โ
โ โ (runtime) โ Works in npm packages โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
External Reference Rewriting
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ BEFORE AND AFTER โ
โ โ
โ icon.svg (source) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ <use href="symbols.svg#check" /> โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ Parcel processes symbols.svg โ
โ and rewrites the reference. โ
โ โ โ
โ โผ โ
โ icon.svg (output) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ <use href="symbols.a1b2c3.svg#check" /> โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The hashed filename is the output path. โ
โ The fragment identifier is preserved. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The JSX Pipeline
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FROM SVG FILE TO REACT COMPONENT โ
โ โ
โ icon.svg โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ "..." (default SVG โ Parses and optimizes the SVG โ
โ โ transformer) โ โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ @parcel/transformer-โ Converts SVG DOM to SWC AST โ
โ โ svg-jsx โ Emits a React component โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ JSX component โ export default function Icon() โ
โ โ โ { return <svg>...</svg>; } โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Default import | Returns a URL string |
| JSX import prefix | jsx: |
| JSX transformer | @parcel/transformer-svg-jsx (Parcel 2.15+) |
| Legacy transformer | @parcel/transformer-svg-react (SVGR-based) |
| External references | Automatically detected and rewritten |
<a> references | Not hashed (stable names) |
new URL() | Recommended for library sprites |
| TypeScript declaration | Required for jsx: imports |
Key takeaways:
- The default SVG import returns a URL, not a component. This is the behavior that works without configuration. To get a React component, the
jsx:prefix and the@parcel/transformer-svg-jsxplugin are required . - External references are processed automatically. The
<use>,<image>,url(), and<script>references inside an SVG are detected, processed as dependencies, and rewritten to the hashed output path . - The
new URL()form is necessary for npm libraries. A plain SVG import in a library produces a relative path that breaks when the package is installed elsewhere.new URL('./sprite.svg', import.meta.url)resolves the path relative to the module, which works in any consumer . - The new JSX transformer is Rust-based and faster.
@parcel/transformer-svg-jsxreplaces the SVGR-based@parcel/transformer-svg-reactin Parcel 2.15. It supports most SVGR options but not custom plugins or templates . - The named pipeline separates URL imports from component imports. Without the
jsx:prefix, every SVG would become a component. With it, the developer chooses per-import, which preserves the<img src={logo} />pattern for static assets .
Remember: Parcel’s SVG handling is two separate systems. The external reference rewriting is automatic and applies to every SVG in the build, regardless of how it is imported. The JSX conversion is an opt-in transformer that changes the import’s return type. The two systems are independent: an SVG imported as a URL still has its internal references rewritten, and an SVG imported as a component still has its external dependencies processed. The configuration work is only for the JSX conversion; the reference handling requires nothing.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!