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

@framjet/spa-config

v0.1.3

Published

Universal configuration loader for SPAs that works seamlessly across Node.js, browsers, Vite, Webpack, Deno, and Bun. Automatically detects runtime environments and loads config from appropriate sources with type-safe getters and smart fallbacks.

Readme

@framjet/spa-config

A universal configuration loader for Single Page Applications that works seamlessly across different JavaScript runtimes and bundlers. Automatically detects your environment (Node.js, Browser, Vite, Webpack, Deno, Bun) and loads configuration from the appropriate sources.

Features

  • 🌐 Universal Runtime Support: Works in Node.js, browsers, Deno, Bun, and modern bundlers
  • 🔧 Auto-Detection: Automatically detects your runtime environment
  • 📦 Bundler Integration: Built-in support for Vite, Webpack, and other modern bundlers
  • 🔒 Type Safety: Full TypeScript support with custom configuration interfaces
  • 🎯 Smart Fallbacks: Tries multiple environment variable sources and common prefixes
  • 🚀 Zero Dependencies: Lightweight with no external dependencies
  • 🔄 Caching: Built-in caching with refresh capability
  • 🛠 Flexible API: Multiple getter methods for different data types

Installation

npm install @framjet/spa-config
yarn add @framjet/spa-config
pnpm add @framjet/spa-config

Quick Start

1. Define Your Configuration Interface

// src/config.ts
declare module "@framjet/spa-config" {
  interface Configuration {
    API_URL: string;
    API_KEY: string;
    DEBUG_MODE: string;
    MAX_RETRIES: string;
    FEATURE_FLAGS: string;
  }
}

2. Use the Config Loader

import { configLoader } from '@framjet/spa-config';

// Basic usage
const apiUrl = configLoader.get('API_URL');
const apiKey = configLoader.getRequired('API_KEY'); // Throws if not found

// Type-safe getters
const debugMode = configLoader.getBoolean('DEBUG_MODE');
const maxRetries = configLoader.getNumber('MAX_RETRIES');
const featureFlags = configLoader.getJSON('FEATURE_FLAGS');

// With defaults
const timeout = configLoader.getWithDefault('TIMEOUT', 5000);

Environment Variable Sources

The loader automatically tries multiple sources based on your runtime:

Node.js / Bun

# .env file or process.env
API_URL=https://api.example.com
DEBUG_MODE=true

Vite

# .env file with VITE_ prefix
VITE_API_URL=https://api.example.com
VITE_DEBUG_MODE=true

React (Create React App)

# .env file with REACT_APP_ prefix
REACT_APP_API_URL=https://api.example.com
REACT_APP_DEBUG_MODE=true

Next.js

# .env file with NEXT_PUBLIC_ prefix
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_DEBUG_MODE=true

Deno

// Use Deno.env
Deno.env.set('API_URL', 'https://api.example.com');

Browser (Runtime Injection)

// Inject via window object
window.__ENV__ = {
  API_URL: 'https://api.example.com',
  DEBUG_MODE: 'true'
};

API Reference

Basic Methods

get(key, options?)

Get a configuration value with automatic fallbacks.

const value = configLoader.get('API_URL');
const prefixedValue = configLoader.get('API_URL', { prefix: 'CUSTOM_' });

getRequired(key, options?)

Get a required configuration value. Throws an error if not found.

const apiKey = configLoader.getRequired('API_KEY');

getWithDefault(key, defaultValue, options?)

Get a configuration value with a default fallback.

const timeout = configLoader.getWithDefault('TIMEOUT', 5000);
const retries = configLoader.getWithDefault('MAX_RETRIES', 3);

Type-Safe Getters

getBoolean(key, options?)

Parse string values as booleans. Accepts: 'true', '1', 'yes', 'on' (case-insensitive).

const isDebug = configLoader.getBoolean('DEBUG_MODE'); // true | false | undefined

getNumber(key, options?)

Parse string values as numbers. Throws if the value is not a valid number.

const port = configLoader.getNumber('PORT'); // number | undefined
const maxRetries = configLoader.getNumber('MAX_RETRIES');

getJSON(key, options?)

Parse JSON string values. Throws if the JSON is invalid.

interface FeatureFlags {
  newDashboard: boolean;
  betaFeatures: boolean;
}

const flags = configLoader.getJSON<FeatureFlags>('FEATURE_FLAGS');

getTransformed(key, transform, options?)

Apply a custom transformation function to the raw string value.

const urls = configLoader.getTransformed(
  'API_URLS',
  (value) => value.split(',').map(url => url.trim()),
  { prefix: 'CUSTOM_' }
);

Utility Methods

getAll(prefix?)

Get all configuration values, optionally filtered by prefix.

const allConfig = configLoader.getAll();
const viteConfig = configLoader.getAll('VITE_');

has(key)

Check if a configuration key exists.

if (configLoader.has('OPTIONAL_FEATURE')) {
  // Feature is configured
}

refresh()

Refresh the internal cache and reload environment variables.

configLoader.refresh();

set(key, value)

Set a value in the cache (useful for testing or runtime overrides).

configLoader.set('TEST_MODE', 'true');

getRuntime()

Get information about the detected runtime environment.

const runtime = configLoader.getRuntime();
console.log(runtime);
// {
//   isNode: false,
//   isBrowser: true,
//   isVite: true,
//   isWebpack: false,
//   isDeno: false,
//   isBun: false
// }

Framework Examples

React with Vite

// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  // Vite automatically loads .env files
});
# .env
VITE_API_URL=https://api.example.com
VITE_API_KEY=your-api-key
VITE_DEBUG_MODE=true
// src/api.ts
import { configLoader } from '@framjet/spa-config';

const API_URL = configLoader.getRequired('API_URL');
const API_KEY = configLoader.getRequired('API_KEY');
const DEBUG = configLoader.getBoolean('DEBUG_MODE') ?? false;

export const apiClient = new APIClient({
  baseURL: API_URL,
  apiKey: API_KEY,
  debug: DEBUG
});

Vue with Vite

// src/types/config.ts
declare module "@framjet/spa-config" {
  interface Configuration {
    API_URL: string;
    API_KEY: string;
    DEV_MODE: string;
    MAX_RETRIES: string;
  }
}
// src/config/index.ts
import { configLoader } from '@framjet/spa-config';

export const config = {
  apiUrl: configLoader.getRequired('API_URL'),
  apiKey: configLoader.getRequired('API_KEY'),
  isDevelopment: configLoader.getBoolean('DEV_MODE') ?? false,
  maxRetries: configLoader.getNumber('MAX_RETRIES') ?? 3
};

Node.js Backend

// types/config.ts
declare module "@framjet/spa-config" {
  interface Configuration {
    PORT: string;
    DATABASE_URL: string;
    JWT_SECRET: string;
  }
}
// server.ts
import { configLoader } from '@framjet/spa-config';

const PORT = configLoader.getNumber('PORT') ?? 3000;
const DATABASE_URL = configLoader.getRequired('DATABASE_URL');
const JWT_SECRET = configLoader.getRequired('JWT_SECRET');

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Next.js

// types/config.ts
declare module "@framjet/spa-config" {
  interface Configuration {
    API_URL: string;
    FEATURE_FLAGS: string;
  }
}
// lib/config.ts
import { configLoader } from '@framjet/spa-config';

export const clientConfig = {
  apiUrl: configLoader.getRequired('API_URL'), // NEXT_PUBLIC_API_URL
  features: configLoader.getJSON('FEATURE_FLAGS') ?? {}
};

Advanced Usage

Custom Prefixes

// Look for CUSTOM_API_URL instead of API_URL
const apiUrl = configLoader.get('API_URL', { prefix: 'CUSTOM_' });

Runtime Environment Detection

import { configLoader } from '@framjet/spa-config';

const runtime = configLoader.getRuntime();

if (runtime.isNode) {
  // Node.js specific logic
} else if (runtime.isBrowser) {
  // Browser specific logic
  if (runtime.isVite) {
    // Vite specific optimizations
  }
}

Testing

// test/config.test.ts
import { configLoader } from '@framjet/spa-config';

beforeEach(() => {
  configLoader.set('TEST_API_URL', 'http://localhost:3001');
  configLoader.set('TEST_DEBUG', 'true');
});

afterEach(() => {
  configLoader.refresh(); // Reset to actual environment
});

TypeScript Support

Extend the Configuration interface using module augmentation to add type safety for your specific configuration keys:

// src/types/config.ts or src/config.ts
declare module "@framjet/spa-config" {
  interface Configuration {
    API_URL: string;
    API_KEY: string;
    DEBUG_MODE: string;
    MAX_RETRIES: string;
    TIMEOUT: string;
    FEATURE_FLAGS: string;
    DATABASE_URL: string;
    JWT_SECRET: string;
  }
}

This approach allows multiple modules or packages to extend the same configuration interface without conflicts. The loader will now provide autocompletion and type checking for these keys.

Multiple Module Augmentation

Different parts of your application can extend the configuration independently:

// src/api/config.ts
declare module "@framjet/spa-config" {
  interface Configuration {
    API_URL: string;
    API_KEY: string;
    API_TIMEOUT: string;
  }
}

// src/database/config.ts  
declare module "@framjet/spa-config" {
  interface Configuration {
    DATABASE_URL: string;
    DATABASE_POOL_SIZE: string;
  }
}

// src/auth/config.ts
declare module "@framjet/spa-config" {
  interface Configuration {
    JWT_SECRET: string;
    JWT_EXPIRY: string;
    OAUTH_CLIENT_ID: string;
  }
}

Contributing

Contributions to @framjet/spa-config are welcome! If you encounter any issues or have suggestions for improvements, please feel free to submit a pull request or open an issue on the project's repository.

License

This project is licensed under the MIT License. See the LICENSE file for details.