Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .codesandbox/ci.json

This file was deleted.

69 changes: 69 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: CI

on:
push:
branches: ['main']
tags: ['v*']
pull_request:
branches: ['*']

workflow_dispatch:

concurrency:
group: ${{ github.ref }}
cancel-in-progress: true

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: pnpm/action-setup@v5
with:
version: 10
run_install: false

- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm

- run: pnpm install

- run: pnpm validate

- uses: SonarSource/sonarqube-scan-action@v7.0.0
if: "!startsWith(github.ref, 'refs/tags/')"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

publish:
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
needs: validate
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v6

- uses: pnpm/action-setup@v5
with:
version: 10
run_install: false

- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
registry-url: https://registry.npmjs.org

- run: pnpm install

- run: pnpm build

- run: npm install -g npm@11

- run: npm publish --provenance
78 changes: 0 additions & 78 deletions .github/workflows/main.yml

This file was deleted.

63 changes: 63 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# CLAUDE.md

## Project

React component library (`react-inlinesvg`) that loads inline, local, or remote SVGs into React components. Supports data URIs, base64, URL-encoded, inline strings, and remote URLs with automatic caching.

## Stack

- **Package manager:** pnpm
- **React:** supports 16.8–19 (peer dep), dev/tests use React 19
- **TypeScript:** 5.9+, target ES2020
- **Vitest:** 4.x with globals enabled (no need to import `describe`/`it`/`expect`)
- **ESLint:** 9.x (flat config) via `@gilbarbara/eslint-config`
- **Vite plugin:** `@vitejs/plugin-react` 6.x

## Commands

```bash
pnpm run build # Clean + tsup build + fix CJS
pnpm run lint # ESLint with --fix on src/ and test/
pnpm run typecheck # tsc on test/tsconfig.json
pnpm run test # Starts fixture server on :1337, runs vitest with coverage
pnpm run test:watch # Vitest watch mode (requires fixture server: pnpm run start)
pnpm run validate # Full pipeline: lint → typecheck → test → build → size → typevalidation
```

Run a single test file: `npx vitest run test/modules/cache.spec.ts` (needs fixture server for integration tests).

## Architecture

Two entry points: `src/index.tsx` (main component) and `src/provider.tsx` (CacheProvider).

### Core flow

`InlineSVG` component → `useInlineSVG` hook (state machine via `useReducer`) → `CacheStore` for caching → `react-from-dom` for SVG string → React element conversion.

**State machine:** `IDLE` → `LOADING` → `LOADED` → `READY` (or `FAILED`/`UNSUPPORTED`). States defined in `src/config.ts`.

### Key modules (`src/modules/`)

- **useInlineSVG.ts** — Main hook. Handles fetching, inline SVG detection, caching, and lifecycle. Dispatches state transitions.
- **cache.ts** — `CacheStore` class with dual-layer caching: in-memory `Map` + Browser Cache API (persistent). Handles concurrent request deduplication.
- **utils.ts** — SVG DOM processing: parsing, ID uniquification, title/description manipulation, baseURL handling.
- **helpers.ts** — DOM detection, fetch wrapper, SVG support checks.
- **hooks.tsx** — `useMount()` and `usePrevious()` hooks.

### CacheProvider (`src/provider.tsx`)

Optional context provider that creates a persistent `CacheStore` using the Cache API. Without it, a global in-memory `cacheStore` instance is used.

## Testing

- **Framework:** Vitest + jsdom + @testing-library/react
- **Mocking:** `vitest-fetch-mock` for HTTP, `browser-cache-mock` for Cache API
- **Fixtures:** SVG files served by `http-server` on port 1337 from `test/__fixtures__/`
- **Coverage thresholds:** 90% (statements, branches, functions, lines)
- Tests import `cacheStore` directly to clear between tests via `cacheStore.clear()`

## Build

- **Bundler:** tsup → CJS + ESM with `"use client"` banner
- **Size limit:** 10 KB per format
- **Type validation:** `@arethetypeswrong/cli` via `pnpm run typevalidation`
62 changes: 28 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# react-inlinesvg

[![NPM version](https://badge.fury.io/js/react-inlinesvg.svg)](https://www.npmjs.com/package/react-inlinesvg) [![CI](https://github.com/gilbarbara/react-inlinesvg/actions/workflows/main.yml/badge.svg)](https://github.com/gilbarbara/react-inlinesvg/actions/workflows/main.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=gilbarbara_react-inlinesvg&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=gilbarbara_react-inlinesvg) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=gilbarbara_react-inlinesvg&metric=coverage)](https://sonarcloud.io/summary/new_code?id=gilbarbara_react-inlinesvg)
[![NPM version](https://badge.fury.io/js/react-inlinesvg.svg)](https://www.npmjs.com/package/react-inlinesvg) [![CI](https://github.com/gilbarbara/react-inlinesvg/actions/workflows/ci.yml/badge.svg)](https://github.com/gilbarbara/react-inlinesvg/actions/workflows/ci.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=gilbarbara_react-inlinesvg&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=gilbarbara_react-inlinesvg) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=gilbarbara_react-inlinesvg&metric=coverage)](https://sonarcloud.io/summary/new_code?id=gilbarbara_react-inlinesvg)

Load inline, local, or remote SVGs in your React components.

Expand All @@ -11,8 +11,7 @@ View the [demo](https://codesandbox.io/s/github/gilbarbara/react-inlinesvg/tree/
- 🏖 **Easy to use:** Just set the `src`
- 🛠 **Flexible:** Personalize the options to fit your needs
- ⚡️ **Smart:** Async requests will be cached.
- 🚀 **SSR:** Render a loader until the DOM is available
- 🟦 **Typescript:** Nicely typed
- 🚀 **SSR:** Safe for server-side rendering

## Usage

Expand All @@ -23,14 +22,13 @@ npm i react-inlinesvg
And import it into your code:

```tsx
import React from 'react';
import SVG from 'react-inlinesvg';

export default function App() {
return (
<main>
<SVG
src="https://cdn.svgporn.com/logos/react.svg"
src="https://cdn.svglogos.dev/logos/react.svg"
width={128}
height="auto"
title="React"
Expand All @@ -42,12 +40,15 @@ export default function App() {

## Props

**src** {string} - **required**.
The SVG file you want to load. It can be a require, URL, or a string (base64 or URL encoded).
_If you are using create-react-app and your file resides in the `public` directory, you can use the path directly without require._
**src** {string} - **required**.
The SVG to load. It accepts:

**baseURL** {string}
An URL to prefix each ID in case you use the `<base>` tag and `uniquifyIDs`.
- A URL or path to an SVG file (absolute or relative, including from a bundler import)
- A data URI (base64 or URL-encoded)
- A raw SVG string

**baseURL** {string}
A URL to prepend to `url()` references inside the SVG when using `uniquifyIDs`. Required if your page uses an HTML `<base>` tag.

**children** {ReactNode}
The fallback content in case of a fetch error or unsupported browser.
Expand All @@ -58,18 +59,17 @@ The fallback content in case of a fetch error or unsupported browser.
</SVG>
```

**cacheRequests** {boolean} ▶︎ `true`
Cache remote SVGs.
Starting in version 4.x, you can also cache the files permanently, read more [below](#caching).
**cacheRequests** {boolean} ▶︎ `true`
Cache remote SVGs in memory. When used with the [CacheProvider](#caching), requests are also persisted in the browser cache.

**description** {string}
A description for your SVG. It will override an existing `<desc>` tag.

**fetchOptions** {RequestInit}
Custom options for the [request](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request).

**innerRef** {React.Ref}
Set a ref in SVGElement.
**innerRef** {React.Ref\<SVGElement | null>}
Set a ref on the SVG element.

>The SVG is processed and parsed so the ref won't be set on the initial render.
You can use the `onLoad` callback to get and use the ref instead.
Expand All @@ -92,22 +92,14 @@ This will receive a single argument with:
}
```

- or an `InlineSVGError`, which has the following properties:

```typescript
{
name: 'InlineSVGError';
data: object; // optional
message: string;
}
```
- or an `Error` for issues like missing `src`, unsupported browser, or invalid SVG content.

**onLoad** {function}.
A callback to be invoked upon successful load.
This will receive 2 arguments: the `src` prop and an `isCached` boolean

**preProcessor** {function} ▶︎ `string`
A function to process the contents of the SVG text before parsing.
**preProcessor** {function}
A function to pre-process the SVG string before parsing. Receives the SVG string and must return a string.

**title** {string | null}
A title for your SVG. It will override an existing `<title>` tag.
Expand All @@ -123,7 +115,7 @@ Create unique IDs for each icon.

### Example

```jsx
```tsx
<SVG
baseURL="/home"
cacheRequests={true}
Expand All @@ -132,7 +124,7 @@ Create unique IDs for each icon.
onError={(error) => console.log(error.message)}
onLoad={(src, isCached) => console.log(src, isCached)}
preProcessor={(code) => code.replace(/fill=".*?"/g, 'fill="currentColor"')}
src="https://cdn.svgporn.com/logos/react.svg"
src="https://cdn.svglogos.dev/logos/react.svg"
title="React"
uniqueHash="a1f8d1"
uniquifyIDs={true}
Expand All @@ -144,7 +136,7 @@ Create unique IDs for each icon.
You can use the browser's cache to store the SVGs permanently.
To set it up, wrap your app with the cache provider:

```typescript
```tsx
import { createRoot } from 'react-dom/client';
import CacheProvider from 'react-inlinesvg/provider';
import App from './App';
Expand All @@ -156,21 +148,23 @@ createRoot(document.getElementById('root')!).render(
);
```

The `CacheProvider` accepts an optional `name` prop to customize the cache storage name.

> Be aware of the limitations of the [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache).

## Browser Support

Any browsers that support inlining [SVGs](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg) and [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) will work.
Any browser that supports inlining [SVGs](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg) and [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) will work.

If you need to support legacy browsers, include a polyfill for `fetch` and `Number.isNaN` in your app.
If you need to support legacy browsers, include a polyfill for `fetch` in your app.

## CORS

If you are loading remote SVGs, you must ensure it has [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) support.
If you are loading remote SVGs, you must ensure they have [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) headers.

## Why do you need this package?

One of the reasons SVGs are awesome is that you can style them with CSS.
One reason SVGs are awesome is that you can style them with CSS.
Unfortunately, this is not useful in practice because the style element has to be in the same document. This leaves you with three bad options:

1. Embed the CSS in the SVG document
Expand All @@ -192,7 +186,7 @@ But there's an alternative that sidesteps these issues: load the SVG with a GET

### Note

The SVG [`<use>`](http://css-tricks.com/svg-use-external-source) element can be used to achieve something similar to this component. See [this article](http://taye.me/blog/svg/a-guide-to-svg-use-elements) for more information and [this table](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use#Browser_compatibility) for browser support and caveats.
The SVG [`<use>`](https://css-tricks.com/svg-use-external-source/) element can be used to achieve something similar to this component. See [this article](https://taye.me/blog/svg/a-guide-to-svg-use-elements) for more information and [this table](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use#Browser_compatibility) for browser support and caveats.

## Credits

Expand Down
Loading
Loading