Asset pipelinev1.6.1 · MIT

rn-typed-assets

Stop shipping string-based asset paths. Generate the registry your React Native project can actually type-check.

Scan the asset tree, emit stable TypeScript constants and a manifest, then find unused files or rewrite stale references through explicit commands. The package ships no runtime dependencies and takes TypeScript as its only peer.

Live field notesPublic surface

Brittle input

TypeScript JSX
// Before: brittle string-based paths
<Image source={require('../../../assets/toast/info.png')} />
<LottieView source={require('../../utils/loading.json')} />

Typed output

TypeScript JSX
// After: typed named constants
import { Assets, Lotties } from './generated/assets.gen';

<Image source={Assets.toast.info} />
<LottieView source={Lotties.loading} />

Feature surface / 10

What the generator actually ships.

Each entry below maps to a command, a flag, or a file the CLI writes.

01

Zero-config setup

Works out of the box with the default src/assets layout. No configuration required for standard React Native projects.

02

Three built-in asset types

Native support for images (PNG/JPG/WebP), SVGs, and Lottie animations (JSON). Each type gets its own typed export.

03

Deterministic output

Generated files are stable across runs and friendly to code review. Keys are sorted alphabetically for consistent diffs.

04

Collision detection

Files that normalize to the same key (harini-cry.png and harini_cry.png, for example) are caught at generation time with clear errors.

05

Manifest-backed audit

The audit command compares the manifest against the scanned source roots, reports every unreferenced asset, and deletes them on request.

06

Automatic source rewriting

The generate --inplace command rewrites every require() call and stale dotted reference in the project's source files.

07

Content-hash diffing

Each manifest entry carries a SHA-1 hash, enabling the codemod to track files that move or are renamed without content changes.

08

Asset organization

The organize command migrates flat or legacy asset directories into canonical subdirectories (images/, svgs/, lotties/).

09

Fully configurable

Override paths, export names, TypeScript type imports, or add entirely new asset types through rn-typed-assets.config.js.

10

Programmatic API

Every function is exported, so the generation pipeline can run from a build script instead of the CLI.

Generation pipeline / 04

Scan, normalize, build, emit.

The same asset directory produces the same registry on every run, which is what makes the output reviewable in a diff.

  1. 01

    Scan

    For each enabled asset type, recursively list files under the configured rootDir.

  2. 02

    Normalize

    Convert each filename to a stable camelCase key, resolving numeric prefixes and collisions.

  3. 03

    Build the registry tree

    Assemble a nested object tree from path segments, detecting and resolving branch and leaf collisions.

  4. 04

    Emit

    Write assets.gen.ts as a typed const object and assets.manifest.json as a stable index of every key to file mapping.

Example asset tree

Bash
src/assets/
  toast/
    info.png
    warning.png
  coupang/
    harini-cry.png
  lottie/
    loading.json
  svg/
    logo.svg

Key normalization rules

Filenames become identifiers through fixed rules rather than per-project conventions, so two developers regenerating the same tree get the same keys.

FilenameGenerated key
harini-cry.pnghariniCry
camera_guide.pngcameraGuide
Info-Filled.pnginfoFilled
1.pngn1 (numeric prefix → n)
point.png alongside point/ dirpointAsset (collision)

Generated output

Keys are sorted, types are imported per asset type, and the whole object is const so TypeScript narrows every reference.

assets.gen.tsTypeScript
1// src/generated/assets.gen.ts2import type { ImageRequireSource } from 'react-native';3import type { AnimationObject } from 'lottie-react-native';4export type SvgsAssetSource = unknown;5 6export const Assets = {7  coupang: {8    hariniCry: require('../assets/coupang/harini-cry.png') as ImageRequireSource,9  },10  toast: {11    info: require('../assets/toast/info.png') as ImageRequireSource,12    warning: require('../assets/toast/warning.png') as ImageRequireSource,13  },14} as const;15 16export const Lotties = {17  loading: require('../assets/lottie/loading.json') as AnimationObject,18} as const;19 20export const Svgs = {21  logo: require('../assets/svg/logo.svg') as SvgsAssetSource,22} as const;

Quick start / 05

Five steps to a typed registry.

Install as a dev dependency, run the generator, then replace require() paths with named constants.

What the registry changes

Missing assets fail at generation, not at runtime.
Every asset key is an autocompletable TypeScript constant.
Renamed files are re-keyed and their references rewritten.
Unused assets surface in the audit and are removable from it.
  1. STEP / 01

    Install the package

    Add rn-typed-assets as a dev dependency. The project needs TypeScript available; most React Native projects already have it.

    Bash
    npm install --save-dev rn-typed-assets
  2. STEP / 02

    Add scripts to package.json

    Declare the generation, organization, and audit commands once as npm scripts.

    package.jsonJSON
    {
      "scripts": {
        "assets:generate": "rn-typed-assets generate",
        "assets:generate:inplace": "rn-typed-assets generate --inplace",
        "assets:organize": "rn-typed-assets organize src/assets",
        "assets:audit": "rn-typed-assets audit",
        "assets:audit:fix": "rn-typed-assets audit --fix"
      }
    }
  3. STEP / 03

    Run the generator

    Execute the generate command to create assets.gen.ts and assets.manifest.json in src/generated/.

    Bash
    npm run assets:generate
  4. STEP / 04

    Import and use

    Import the typed registry and use named constants instead of require() paths.

    TypeScript JSX
    import { Assets, Lotties, Svgs } from './generated/assets.gen';
    
    // Images
    <Image source={Assets.toast.info} />
    <Image source={Assets.coupang.hariniCry} />
    
    // Lottie animations
    <LottieView source={Lotties.loading} autoPlay loop />
    
    // SVGs (with react-native-svg)
    <SvgUri source={Svgs.logo} />
  5. STEP / 05

    Audit for unused assets

    Run the audit to list unreferenced assets, then delete them in the same command.

    Bash
    npm run assets:audit          # report unused entries
    npm run assets:audit -- --fix # delete unused files and regenerate

CLI reference / 03

Generate, organize, audit.

Three commands cover the whole surface. Each one lists its options and the default it falls back to when the flag is absent.

generate

Scan asset directories and emit assets.gen.ts + assets.manifest.json.

FlagDescriptionDefault
--types <types>Comma-separated asset typesimage,svg,lottie
--inplaceRewrite source files after regenfalse
--root <path>Project root directorycwd
--config <path>Path to config file./rn-typed-assets.config.js

Examples

Bash
rn-typed-assets generate
rn-typed-assets generate --inplace
rn-typed-assets generate --types=image,lottie

organize

Move asset files into canonical subdirectories, then regenerate and rewrite sources.

Creates: images/, svgs/, lotties/ subdirectories

FlagDescriptionDefault
<assetsDir>Path to the asset root to organize(required)
--types <types>Asset types to moveimage,svg,lottie

Examples

Bash
rn-typed-assets organize src/assets

audit

Compare manifest against actual source-file usages.

FlagDescriptionDefault
--types <types>Asset types to auditimage,svg,lottie
--fixDelete unused files and regeneratefalse

Examples

Bash
rn-typed-assets audit
rn-typed-assets audit --fix
rn-typed-assets audit --types=image

Configuration / optional

Zero config by default, explicit when overridden.

The defaults cover the standard layout. A config file at the project root replaces only the fields it names, so omitting it accepts every default.

Full configuration

Every field below is an override. The commented value is what the CLI uses when the field is absent.

rn-typed-assets.config.jsJavaScript
1// rn-typed-assets.config.js2module.exports = {3  // Where to write assets.gen.ts and assets.manifest.json4  // Default: 'src/generated'5  outputDir: 'src/generated',6 7  // Directories and entry files scanned by the audit command8  // Default: ['src', 'App.tsx', 'index.js']9  sourceRoots: ['src', 'App.tsx', 'index.js'],10 11  // Per-type configuration (all fields are optional overrides)12  types: {13    image: {14      rootDir: 'src/assets',              // scan root15      extensions: ['.png', '.jpg', '.jpeg', '.webp'],16      exportName: 'Assets',               // export const Assets = ...17      typeImport: {18        typeName: 'ImageRequireSource',   // TypeScript type name19        from: 'react-native',             // import source20      },21    },22    svg: {23      rootDir: 'src/assets/svg',24      extensions: ['.svg'],25      exportName: 'Svgs',26      inlineType: 'unknown',  // emits: export type SvgsAssetSource = unknown27    },28    lottie: {29      rootDir: 'src/assets/lottie',30      extensions: ['.json'],31      exportName: 'Lotties',32      typeImport: {33        typeName: 'AnimationObject',34        from: 'lottie-react-native',35      },36    },37  },38};

Adding custom types

Any type outside the three defaults can be declared under types. The audit command discovers it automatically through the exportName to type reverse map.

JavaScript
// Adding a custom font asset type
module.exports = {
  types: {
    font: {
      rootDir: 'src/assets/fonts',
      extensions: ['.ttf', '.otf'],
      exportName: 'Fonts',
      inlineType: 'string',  // emits: export type FontsAssetSource = string
    },
  },
};

Configuration options

outputDir
Directory for generated files
sourceRoots
Files and directories scanned by the audit command
rootDir
Asset scan root for each type
extensions
File extensions to include
exportName
Name of the exported constant
typeImport / inlineType
TypeScript type configuration

CI integration / 02

Fail the build when the manifest drifts.

Regenerate in CI and diff the output, or regenerate on commit and stage it. Both catch the same drift between the manifest and the filesystem.

GitHub Actions

Regenerates the registry in CI and fails the job when the committed output differs from what the current asset tree produces.

YAML
# .github/workflows/ci.yml
- name: Verify asset manifest is up to date
  run: |
    npm run assets:generate
    git diff --exit-code src/generated/

Husky pre-commit

Regenerates and stages the registry on every commit through a Husky git hook, so the manifest never lags the filesystem.

Bash
# .husky/pre-commit
npm run assets:generate
git add src/generated/assets.gen.ts src/generated/assets.manifest.json

Asset pipeline / ready

Replace the next brittle asset path before it ships.

Install the generator as a development dependency, generate the registry, and let TypeScript carry the asset names from there.