npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pplancq/svg-core

v3.0.1

Published

A functions for asynchronously loading SVG files into the DOM

Readme

@pplancq/svg-core

Lightweight library to asynchronously load and inject SVG files into the DOM, with sanitization, attribute merging, and reactive state management via the Observer pattern.

📝 Description

@pplancq/svg-core provides a class-based architecture to fetch an SVG (from a URL, a local path, or a data URI), sanitize it with DOMPurify, and merge its attributes into an existing SVG element when needed. The library uses the Observer pattern to expose reactive state changes, making it easy to integrate with any UI framework.

Main goals:

  • Load SVGs asynchronously with reactive state (idleloadingsuccess | error).
  • Protect against malicious SVG content using DOMPurify.
  • Provide a simple observer-based API to react to SVG loading events.
  • Deduplicate concurrent requests to the same URL.

⚙️ Installation

npm install @pplancq/svg-core

🚀 Quick start

import { SvgStore } from '@pplancq/svg-core';

// Create a store — the pipeline starts immediately
const store = new SvgStore('https://example.com/my-icon.svg');

// Subscribe to state changes
const unsubscribe = store.subscribe(() => {
  const { status, svgElement, error } = store.getSvgResult();

  if (status === 'loading') {
    console.log('Loading…');
  }

  if (status === 'success') {
    document.body.appendChild(svgElement);
    unsubscribe(); // stop listening once done
  }

  if (status === 'error') {
    console.error('Failed to load SVG:', error);
  }
});

// Read the current state synchronously at any time
const currentState = store.getSvgResult();
console.log(currentState.status); // 'idle' (before the microtask runs)

🔌 API

new SvgStore(src, svgElement?, sanitizeConfig?)

Creates a new store and immediately schedules the loading pipeline.

| Parameter | Type | Required | Description | | ---------------- | ----------------------- | -------- | ------------------------------------------------------------------- | | src | string \| URL | ✅ | URL, path, or data URI (data:image/svg+xml) pointing to the SVG. | | svgElement | SVGSVGElement | ❌ | Existing SVG element to receive the fetched attributes and content. | | sanitizeConfig | SanitizeConfig | ❌ | Custom sanitization options. ⚠️ Only use with trusted sources. |

The pipeline is deferred via queueMicrotask, which guarantees that any subscribe() call placed immediately after construction will receive all state transitions (loading, then success or error).


store.getSvgResult(): SvgState

Returns the current state of the store synchronously.

interface SvgState {
  status: 'idle' | 'loading' | 'success' | 'error';
  svgElement: SVGSVGElement | null;
  error: Error | null;
}

| Status | Meaning | | --------- | ------------------------------------------------- | | idle | Store just created, pipeline not started yet. | | loading | Fetching or decoding the SVG is in progress. | | success | SVG loaded successfully — svgElement is set. | | error | Loading failed — error contains the exception. |


store.subscribe(observer: () => void): () => void

Registers a callback invoked on every state change. Returns an unsubscribe function.

const unsubscribe = store.subscribe(() => {
  console.log(store.getSvgResult().status);
});

// Later, stop receiving notifications:
unsubscribe();

🔒 Custom Sanitization

By default, all SVG content is sanitized using DOMPurify to prevent XSS attacks. Some legitimate SVG features (like animations) may be stripped by the default configuration.

If you control your SVG sources and need to allow specific elements or attributes, pass a SanitizeConfig as the third constructor argument:

import { SvgStore } from '@pplancq/svg-core';

const store = new SvgStore('/animated-spinner.svg', undefined, {
  allowTags: ['animateTransform', 'animate', 'animateMotion'],
  allowAttributes: ['from', 'to', 'dur', 'repeatCount', 'values', 'keyTimes'],
});

Available SanitizeConfig options:

| Option | Type | Description | | -------------------- | ---------- | ---------------------------------------------- | | allowTags | string[] | Additional tag names to allow. | | allowAttributes | string[] | Additional attribute names to allow. | | forbidTags | string[] | Tag names to explicitly forbid. | | forbidAttributes | string[] | Attribute names to explicitly forbid. | | allowDataAttributes| boolean | Allow data-* attributes (default: false). |

⚠️ Security Warning: Do not use custom sanitization with user-uploaded SVGs or SVGs from untrusted sources.


🔒 Security & CORS

  • Content is sanitized with DOMPurify (SVG profile + filters) to reduce XSS risk.
  • The server must return Content-Type: image/svg+xml — otherwise InvalidSvgError is thrown.
  • For cross-origin requests, the server must allow requests via CORS.

⚡ Performance

  • The SvgFetcher deduplicates simultaneous requests to the same URL (in-flight promise cache) to avoid redundant network requests.
  • For heavy usage (many icons), consider application-side caching or an SVG sprite.

♿ Accessibility

  • The package handles SVG content security, but accessibility (alternative text, roles, titles) depends on how you integrate SVGs in your UI.
  • Consider providing an accessible label (e.g. <title> + aria-labelledby, or aria-hidden) depending on the usage context.

🧪 Development & testing

npm run dev     # build in watch mode
npm run build   # production build
npm test        # run tests (vitest)
npm run lint    # eslint + tsc

Tests use vitest and jsdom to simulate a DOM environment.


🔄 Migration Guide: v2 → v3

Version 3 replaces the functional API with a class-based, Observer-pattern architecture.

What changed

| v2 (functional) | v3 (OOP / Observer) | | --------------------------------------- | ---------------------------------------------------- | | getSvg(src, el?, config?) (async fn) | new SvgStore(src, el?, config?) + .subscribe() | | Returns Promise<SVGSVGElement> | Returns SvgState via getSvgResult() | | One-shot call, no lifecycle events | Reactive state: idle → loading → success \| error | | mergeSvgContent(source, target) | Handled internally — pass svgElement to constructor | | No error state, catch on the promise | state.status === 'error' + state.error |

Before (v2)

import { getSvg } from '@pplancq/svg-core';

try {
  const svgEl = await getSvg('/icon.svg');
  document.body.appendChild(svgEl);
} catch (error) {
  console.error('Failed:', error);
}

// With a target element
const target = document.querySelector('svg#icon');
await getSvg('/icon.svg', target);

// With custom sanitization
const animated = await getSvg('/spinner.svg', undefined, {
  allowTags: ['animateTransform'],
  allowAttributes: ['dur', 'repeatCount'],
});

After (v3)

import { SvgStore } from '@pplancq/svg-core';

// Basic usage
const store = new SvgStore('/icon.svg');
const unsubscribe = store.subscribe(() => {
  const { status, svgElement, error } = store.getSvgResult();
  if (status === 'success') {
    document.body.appendChild(svgElement);
    unsubscribe();
  }
  if (status === 'error') {
    console.error('Failed:', error);
    unsubscribe();
  }
});

// With a target element
const target = document.querySelector('svg#icon');
const store2 = new SvgStore('/icon.svg', target);

// With custom sanitization
const store3 = new SvgStore('/spinner.svg', undefined, {
  allowTags: ['animateTransform'],
  allowAttributes: ['dur', 'repeatCount'],
});

Named exports that were removed

// v2 — no longer available in v3
import { getSvg, mergeSvgContent } from '@pplancq/svg-core'; // ❌ removed

// v3 — new exports
import { SvgStore } from '@pplancq/svg-core';           // ✅
import type { SvgState, SvgStatus, SvgStoreInterface, SanitizeConfig } from '@pplancq/svg-core'; // ✅

🤝 Contributing

Open an issue or a pull request on the repository: https://github.com/pplancq/svg-tools
Respect linting rules (npm run lint) and tests (npm test) before submitting.

📚 Resources & contact

  • Project: https://github.com/pplancq/svg-tools
  • Project site: https://pplancq.github.io/svg-tools/
  • Bugs / requests: https://github.com/pplancq/svg-tools/issues

📜 License

MIT