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

@weprodev/react-native-smart-assets

v0.2.0

Published

A smart assets package to loads images and icons smartly

Readme

react-native-smart-assets

A smart, type-safe asset management system for React Native that automatically generates type definitions and provides a simple, intuitive API for loading images and SVG icons.

Features

  • 🎯 Type-Safe: Automatically generates TypeScript definitions for all your assets
  • 🚀 Zero Configuration: Works out of the box with sensible defaults
  • 📦 Asset Registry: Automatic scanning and registration of assets
  • 🎨 SVG Support: Native SVG icon rendering with tinting and sizing
  • 🔄 Variants: Support for density variants (@2x, @3x), dark mode, and platform-specific assets
  • 🌐 Remote Assets: Load remote images with caching and fallback support
  • Preloading: Preload critical assets with progress tracking
  • 🌙 Dark Mode: useAssetTheme automatically switches between -dark / -light asset variants
  • �️ Placeholders: Shimmer, blur, and colour skeletons while images load
  • 🗜️ CLI Optimize: Compress PNG/JPEG assets and warn about oversized files
  • 🔍 CLI Tools: Generate, validate, and get statistics about your assets
  • 👀 Watch Mode: Auto-regenerate types when assets change
  • ⚙️ Expo Config Plugin: Auto-run generate on every expo prebuild — no manual steps
  • 🔒 Babel/Metro Plugin: Catch asset-name typos as build errors with "did you mean?" suggestions

Installation

npm install react-native-smart-assets
# or
yarn add react-native-smart-assets

Quick Start

1. Create Assets Directory

Create an assets directory in your project root and add your images and icons:

assets/
  ├── images/
  │   ├── logo.png
  │   └── background.jpg
  └── icons/
      ├── home.svg
      └── user.svg

2. Generate Asset Registry

Run the CLI tool to generate type-safe asset definitions:

npx react-native-smart-assets generate

This creates an assets/index.ts file with all your assets registered and typed.

3. Initialize Asset Registry

In your app entry point (e.g., App.tsx or index.js):

import { setAssetRegistry } from 'react-native-smart-assets';
import * as Assets from './assets';

setAssetRegistry(Assets.ASSETS, Assets.ASSET_METADATA);

The ASSET_METADATA parameter is optional but recommended as it provides additional information about your assets (type, category, etc.) that can be used for better asset handling and type checking.

4. Use the Asset Component

import { Asset } from 'react-native-smart-assets';

function MyComponent() {
  return (
    <>
      <Asset name="images/logo" size={100} />
      <Asset 
        name="icons/home" 
        size={24} 
        tintColor="#000" 
      />
      <Asset 
        name="images/background" 
        size={{ width: 300, height: 200 }} 
        resizeMode="cover"
      />
    </>
  );
}

Complete Example

Here's a complete example demonstrating all the key features:

import { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, ScrollView } from 'react-native';
import {
  setAssetRegistry,
  Asset,
  useAsset,
  useAssetPreloader,
  preloadRemoteAsset,
  isRemoteUrl,
} from 'react-native-smart-assets';
import * as Assets from './assets';
import type { AssetName } from './assets';

// Initialize the asset registry
setAssetRegistry(Assets.ASSETS, Assets.ASSET_METADATA);

const REMOTE_ASSET_URLS = [
  'https://picsum.photos/200/200?random=1',
  'https://picsum.photos/200/200?random=2',
];

export default function App() {
  const [selectedAsset, setSelectedAsset] = useState<AssetName>('icon');
  const assetInfo = useAsset(selectedAsset);
  const { preload, progress, isLoading, error } = useAssetPreloader();

  // Preload assets on mount
  useEffect(() => {
    preload();
  }, [preload]);

  const handlePreloadSelected = () => {
    preload([selectedAsset]);
  };

  const handlePreloadRemoteAssets = async () => {
    try {
      await Promise.allSettled(
        REMOTE_ASSET_URLS.map((url) => preloadRemoteAsset(url, 10000))
      );
    } catch (err) {
      console.error('Failed to preload remote assets:', err);
    }
  };

  return (
    <ScrollView>
      {/* Local Asset Selection */}
      <View>
        <Text>Select Asset:</Text>
        {Assets.getAllAssetNames().map((name) => (
          <TouchableOpacity
            key={name}
            onPress={() => setSelectedAsset(name)}
          >
            <Text>{name}</Text>
          </TouchableOpacity>
        ))}
      </View>

      {/* Display Selected Asset */}
      <View>
        <Asset<AssetName> name={selectedAsset} size={64} />
        <Text>Asset: {selectedAsset}</Text>
        <Text>Exists: {assetInfo.exists ? 'Yes' : 'No'}</Text>
        <Text>Is SVG: {assetInfo.isSvg ? 'Yes' : 'No'}</Text>
      </View>

      {/* Preloader Controls */}
      <View>
        <TouchableOpacity
          onPress={handlePreloadSelected}
          disabled={isLoading}
        >
          <Text>Preload Selected</Text>
        </TouchableOpacity>
        
        <Text>
          Progress: {progress.loaded} / {progress.total} ({progress.percentage}%)
        </Text>
        
        {isLoading && <Text>Loading...</Text>}
        {error && <Text>Error: {error.message}</Text>}
      </View>

      {/* Remote Assets */}
      <View>
        <Text>Remote Assets:</Text>
        {REMOTE_ASSET_URLS.map((url) => (
          <View key={url}>
            <Asset name={url} size={48} />
            <Text>{isRemoteUrl(url) ? 'Remote' : 'Local'}</Text>
          </View>
        ))}
        
        <TouchableOpacity onPress={handlePreloadRemoteAssets}>
          <Text>Preload Remote Assets</Text>
        </TouchableOpacity>
      </View>

      {/* Asset Grid */}
      <View>
        {Assets.getAllAssetNames().map((name) => (
          <View key={name}>
            <Asset<AssetName> name={name} size={48} />
            <Text>{name}</Text>
          </View>
        ))}
      </View>
    </ScrollView>
  );
}

Usage

Asset Component

The <Asset /> component is the main way to render your assets:

<Asset
  name="images/logo"           // Asset name (type-safe)
  size={24}                    // Size as number (square) or { width, height }
  style={styles.customStyle}   // Additional styles
  tintColor="#FF0000"          // Tint color (for SVG icons)
  resizeMode="contain"         // Image resize mode
  variant="dark"               // Variant (default, dark, light)
  testID="logo"                // Test ID for testing
/>

Size Presets

Use predefined size constants:

import { Asset, AssetSizes } from 'react-native-smart-assets';

<Asset name="icons/home" size={AssetSizes.medium} />
<Asset name="icons/user" size={AssetSizes.large} />

Available presets: xs, sm, md, lg, xl, xxl, small, medium, large, xlarge

Remote Assets

Load remote images directly:

<Asset 
  name="https://example.com/image.png" 
  size={200} 
/>

Asset Preloading

Preload critical assets on app start:

import { useAssetPreloader } from 'react-native-smart-assets';

function App() {
  const { preload, progress, isLoading } = useAssetPreloader([
    'images/logo',
    'images/background',
  ]);

  useEffect(() => {
    preload();
  }, []);

  if (isLoading) {
    return <Text>Loading assets: {progress.percentage}%</Text>;
  }

  return <YourApp />;
}

Dark Mode Assets — useAssetTheme

Automatically resolves the correct asset variant for the current system color scheme. Follows the existing -dark / -light filename convention with zero manual logic.

import { useAssetTheme } from 'react-native-smart-assets';

function Logo() {
  // Returns 'images/logo-dark' in dark mode, 'images/logo' in light mode.
  // Falls back to 'images/logo' gracefully if the -dark variant isn't registered.
  const { name } = useAssetTheme('images/logo');
  return <Asset name={name} size={100} />;
}

Options:

const { name, colorScheme, isDarkVariant } = useAssetTheme('images/logo', {
  darkSuffix: '-dark',   // default — suffix appended in dark mode
  lightSuffix: '-light', // optional — also switch the asset in light mode
  colorScheme: 'dark',   // optional — force a scheme (e.g. Storybook)
});

| Return value | Type | Description | |---|---|---| | name | string | Resolved asset name to pass to <Asset /> | | colorScheme | 'light' \| 'dark' | Currently active scheme | | isDarkVariant | boolean | Whether the dark variant was found and used | | isLightVariant | boolean | Whether the light variant was found and used |

Tip: When the themed variant does not exist in the registry the hook silently falls back to the base name, so <Asset /> shows its own warning.

Asset Placeholders / Skeletons

Show a visual skeleton while an image loads. No effect on SVG assets.

<Asset
  name="images/hero"
  size={{ width: 300, height: 200 }}
  placeholder="shimmer"        // 'shimmer' | 'blur' | 'color' | 'none'
  placeholderColor="#DDE3EC"  // optional base color (default: '#E0E0E0')
/>

| Placeholder | Description | |---|---| | shimmer | Animated bright-band sweep — classic skeleton loading look | | blur | Soft pulsing opacity — suggests hazy content beneath | | color | Static flat background — zero animation overhead | | none | No placeholder (default, preserves existing behaviour) |

The placeholder disappears automatically once onLoad or onError fires. Swapping the name prop resets the loading state instantly.

You can also use the standalone <AssetPlaceholder /> component:

import { AssetPlaceholder } from 'react-native-smart-assets';

<AssetPlaceholder type="shimmer" color="#DDE3EC" style={styles.skeleton} />

Hooks

useAsset(name: string)

Get asset information:

import { useAsset } from 'react-native-smart-assets';

function MyComponent() {
  const { asset, exists, isSvg } = useAsset('images/logo');
  
  if (!exists) {
    return <Text>Asset not found</Text>;
  }
  
  return <Asset name="images/logo" size={100} />;
}

CLI Commands

Generate Asset Registry

npx react-native-smart-assets generate

Options:

  • --assets-dir <path>: Custom assets directory (default: assets)
  • --output-dir <path>: Custom output directory (default: assets)
  • --format <format>: Output format: typescript or javascript (default: typescript)

Validate Assets

npx react-native-smart-assets validate

Checks for:

  • Missing files
  • Invalid file formats
  • Duplicate asset names
  • Missing variants

Get Statistics

npx react-native-smart-assets stats

Shows:

  • Total asset count
  • Images vs SVG breakdown
  • Assets with variants
  • Category distribution

Watch Mode

npx react-native-smart-assets watch

Automatically regenerates the asset registry when files change.

Optimize Assets

Compress PNG and JPEG files directly from the CLI and warn about oversized assets:

npx react-native-smart-assets optimize

Example output:

✓ Compressed icons/logo.png:        240KB → 48KB  (-80%)
✓ Compressed images/background.jpg: 1.2MB → 310KB (-74%)
⚠  images/hero.jpg is 2.4MB after compression — recommended max is 512KB

Saved 1.4MB across 2 file(s)

Options:

  • --quality <0-100>: JPEG / PNG quality (default: 80)
  • --max-size <bytes>: Warn when an asset exceeds this size (default: 524288 = 512 KB)
  • --dry-run: Preview savings without writing any files
  • --assets-dir <path>: Custom assets directory (default: assets)

Note: Real compression requires sharp. Without it the command still runs in analysis-only mode and reports oversized assets, so it's always safe to run in CI:

npm install --save-dev sharp
# or
yarn add --dev sharp

Configuration

Create an assets.config.js file in your project root:

module.exports = {
  assetsDir: 'assets',
  outputDir: 'assets',
  format: 'typescript',
};

Expo Config Plugin

Automatically regenerates the asset registry on every expo prebuild so you never forget to run generate after adding new assets.

Setup

// app.config.js
export default {
  name: 'MyApp',
  slug: 'my-app',
  plugins: [
    [
      'react-native-smart-assets/plugin',
      {
        assetsDir: './src/assets', // where your raw assets live
        outputDir: './src/assets', // where the generated index.ts is written
      },
    ],
  ],
};

TypeScript config (app.config.ts):

import type { ExpoConfig } from 'expo/config';
import type { SmartAssetsPluginOptions } from 'react-native-smart-assets/plugin';

const pluginOptions: SmartAssetsPluginOptions = {
  assetsDir: './src/assets',
  outputDir: './src/assets',
};

const config: ExpoConfig = {
  name: 'MyApp',
  slug: 'my-app',
  plugins: [['react-native-smart-assets/plugin', pluginOptions]],
};

export default config;

How it works

Run prebuild as usual — generation happens automatically:

npx expo prebuild

You'll see this in the output:

[react-native-smart-assets] Running asset registry generation…
  assetsDir : /your/project/src/assets
  outputDir : /your/project/src/assets
✓ [react-native-smart-assets] Registry generated — 12 asset(s)
  Output: /your/project/src/assets/index.ts

Options

| Option | Type | Default | Description | |---|---|---|---| | assetsDir | string | './assets' | Directory containing your raw asset files | | outputDir | string | Same as assetsDir | Directory where the generated registry is written | | format | 'typescript' \| 'javascript' | 'typescript' | Output file format | | failOnError | boolean | false | true = hard-fail the build on errors (recommended for CI) |


Babel / Metro Plugin — Compile-Time Validation

Validates asset names at build time instead of runtime. Typos in <Asset name="…" /> or getAsset("…") surface as build errors with helpful suggestions — before the app even launches.

// ❌ Build error: Asset "icons/hom" not found.
//    Did you mean "icons/home"?
<Asset name="icons/hom" size={24} />

// ✅ Fine
<Asset name="icons/home" size={24} />

Setup

Add the plugin to your babel.config.js:

// babel.config.js
module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: [
    [
      'react-native-smart-assets/babel-plugin',
      {
        registryPath: './src/assets/index.ts', // path to generated registry
        mode: 'error',                          // 'error' | 'warn' | 'off'
      },
    ],
  ],
};

After changing babel.config.js clear Metro's cache once:

npx expo start --clear
# or
npx react-native start --reset-cache

What gets validated

| Pattern | Example | |---|---| | JSX name prop | <Asset name="icons/hom" /> | | getAsset() first argument | getAsset('images/lgoo') | | hasAsset() first argument | hasAsset('icons/ghost') |

Dynamic values (e.g. name={myVar}) are silently skipped — only string literals are checked.

Options

| Option | Type | Default | Description | |---|---|---|---| | registryPath | string | './assets/index.ts' | Path to the generated asset registry | | mode | 'error' \| 'warn' \| 'off' | 'error' | How to report unknown asset names | | assetComponents | string[] | ['Asset'] | JSX components whose name prop is validated | | assetFunctions | string[] | ['getAsset', 'hasAsset'] | Function calls whose first argument is validated |

Recommended mode per environment

// babel.config.js
const isDev = process.env.NODE_ENV === 'development';

module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: [
    [
      'react-native-smart-assets/babel-plugin',
      {
        registryPath: './src/assets/index.ts',
        mode: isDev ? 'warn' : 'error', // warn locally, hard-fail in CI
      },
    ],
  ],
};

Custom component names

plugins: [
  ['react-native-smart-assets/babel-plugin', {
    registryPath: './src/assets/index.ts',
    mode: 'error',
    assetComponents: ['Asset', 'AppIcon', 'SmartImage'],
    assetFunctions:  ['getAsset', 'hasAsset', 'useAsset'],
  }],
],

Recommended Workflow

expo prebuild        ← Expo plugin auto-generates the registry
      ↓
Metro bundler starts ← Babel plugin reads the fresh registry
      ↓
<Asset name="typo"> ← caught immediately as a build error

For local development, you can also keep the file watcher running:

# Terminal 1 — regenerate registry whenever assets change
npx react-native-smart-assets watch --assets-dir ./src/assets

# Terminal 2 — normal Metro dev server
npx expo start

Asset Variants

Density Variants

React Native automatically picks the right density variant:

assets/
  ├── icon.png      (1x)
  ├── [email protected]   (2x)
  └── [email protected]   (3x)

Dark Mode Variants

Use -dark suffix for dark mode assets:

assets/
  ├── icon.svg
  └── icon-dark.svg

Platform Variants

Use platform-specific extensions:

assets/
  ├── icon.ios.png
  └── icon.android.png

API Reference

Components

Asset

Main component for rendering assets.

Props:

| Prop | Type | Default | Description | |---|---|---|---| | name | string | — | Asset name (type-safe from generated types) | | size | number \| { width, height } | — | Asset dimensions | | style | StyleProp<ImageStyle> | — | Additional styles | | tintColor | string | — | Tint color (SVG only) | | resizeMode | ImageResizeMode | 'contain' | Image resize mode | | variant | 'default' \| 'dark' \| 'light' | 'default' | Asset variant | | placeholder | 'shimmer' \| 'blur' \| 'color' \| 'none' | 'none' | Loading placeholder style | | placeholderColor | string | '#E0E0E0' | Placeholder base color | | testID | string | — | Test identifier |

AssetPlaceholder

Standalone skeleton component, useful when you need a placeholder outside of <Asset />.

Props:

| Prop | Type | Default | Description | |---|---|---|---| | type | 'shimmer' \| 'blur' \| 'color' \| 'none' | — | Visual style | | color | string | '#E0E0E0' | Base color | | style | ViewStyle | — | Additional styles | | testID | string | — | Test identifier |

Hooks

useAsset(name: string)

Returns asset information.

Returns:

  • asset: any — The raw asset object
  • exists: boolean — Whether the asset is registered
  • isSvg: boolean — Whether the asset is an SVG

useAssetTheme(baseName: string, options?)

Resolves the correct asset variant for the current system color scheme.

Options:

  • colorScheme?: 'light' | 'dark' — Override the detected scheme
  • darkSuffix?: string — Suffix for dark variants (default: '-dark')
  • lightSuffix?: string — Suffix for light variants (default: undefined)

Returns:

  • name: string — Resolved asset name (ready to pass to <Asset />)
  • colorScheme: 'light' | 'dark' — Currently active scheme
  • isDarkVariant: boolean — Whether the dark variant was resolved
  • isLightVariant: boolean — Whether the light variant was resolved

useAssetPreloader(assetNames?: string[])

Preloads assets with progress tracking.

Returns:

  • preload: (names?: string[]) => Promise<void> - Preload function
  • progress: { loaded: number; total: number; percentage: number } - Progress info
  • isLoading: boolean - Loading state
  • error: Error | null - Error if any

Utilities

setAssetRegistry(registry: AssetRegistry, metadata?: AssetMetadataMap)

Initialize the asset registry with generated assets.

Parameters:

  • registry: AssetRegistry - The asset registry object (typically Assets.ASSETS)
  • metadata?: AssetMetadataMap - Optional metadata object (typically Assets.ASSET_METADATA) containing asset information like type, category, etc.

getAsset(name: string)

Get an asset by name.

hasAsset(name: string)

Check if an asset exists.

getAllAssetNames(): string[]

Get all registered asset names.

AssetSizes

Predefined size constants.

getSizePreset(preset: AssetSizePreset): number

Get a size preset value.

getResponsiveSize(baseSize: number, scale?: number): number

Calculate responsive size.

TypeScript Support

The generated assets/index.ts file includes:

  • AssetName type with all your asset names
  • ASSETS constant with all asset imports
  • ASSET_METADATA with asset information
  • Helper functions for type-safe asset access

Plugin type exports

import type { SmartAssetsPluginOptions } from 'react-native-smart-assets/plugin';
import type { BabelPluginOptions }       from 'react-native-smart-assets/babel-plugin';

Contributing

See the Contributing Guide for details.

License

MIT


Made with ❤️ for the React Native community