A zero-dependency alternative to cosmiconfig for loading configuration files
npx @tessl/cli install tessl/npm-lilconfig@3.1.0Lilconfig is a zero-dependency alternative to cosmiconfig that provides configuration file discovery and loading functionality. It offers both synchronous and asynchronous APIs for searching and loading configuration files from common locations, supporting JavaScript, JSON, and custom file formats through configurable loaders.
npm install lilconfigimport { lilconfig, lilconfigSync, defaultLoaders, defaultLoadersSync } from 'lilconfig';For CommonJS:
const { lilconfig, lilconfigSync, defaultLoaders, defaultLoadersSync } = require('lilconfig');import { lilconfig, lilconfigSync } from 'lilconfig';
// Async usage
const explorer = lilconfig('myapp');
const result = await explorer.search();
if (result) {
console.log('Config found at:', result.filepath);
console.log('Config contents:', result.config);
}
// Sync usage
const explorerSync = lilconfigSync('myapp');
const resultSync = explorerSync.search();
if (resultSync) {
console.log('Config found at:', resultSync.filepath);
console.log('Config contents:', resultSync.config);
}Lilconfig is built around several key components:
Creates a configuration explorer that can search for configuration files in standard locations.
/**
* Creates an async configuration explorer
* @param name - The configuration name to search for (e.g., 'myapp')
* @param options - Optional configuration options
* @returns AsyncSearcher instance with search and load methods
*/
function lilconfig(name: string, options?: Partial<Options>): AsyncSearcher;
/**
* Creates a sync configuration explorer
* @param name - The configuration name to search for (e.g., 'myapp')
* @param options - Optional configuration options
* @returns SyncSearcher instance with search and load methods
*/
function lilconfigSync(name: string, options?: OptionsSync): SyncSearcher;Default Search Places:
For a configuration named myapp, lilconfig searches for files in this order:
// Async version (lilconfig) searches for:
[
'package.json', // Looks for myapp property
'.myapprc.json',
'.myapprc.js',
'.myapprc.cjs',
'.myapprc.mjs', // Only in async mode
'.config/myapprc',
'.config/myapprc.json',
'.config/myapprc.js',
'.config/myapprc.cjs',
'.config/myapprc.mjs', // Only in async mode
'myapp.config.js',
'myapp.config.cjs',
'myapp.config.mjs' // Only in async mode
]
// Sync version (lilconfigSync) excludes .mjs files due to module loading limitationsUsage Examples:
import { lilconfig, lilconfigSync } from 'lilconfig';
// Search for 'myapp' configuration files
const explorer = lilconfig('myapp');
const result = await explorer.search('/some/start/path');
// Search with custom options
const explorerWithOptions = lilconfig('myapp', {
searchPlaces: ['package.json', '.myapprc.js', 'myapp.config.js'],
stopDir: '/home/user',
ignoreEmptySearchPlaces: false
});
const customResult = await explorerWithOptions.search();The object returned by lilconfig() providing async search and load capabilities.
interface AsyncSearcher {
/**
* Search for configuration files starting from specified directory
* @param searchFrom - Directory to start searching from (defaults to process.cwd())
* @returns Promise resolving to LilconfigResult or null if not found
*/
search(searchFrom?: string): Promise<LilconfigResult>;
/**
* Load configuration from a specific file path
* @param filepath - Path to configuration file to load
* @returns Promise resolving to LilconfigResult
*/
load(filepath: string): Promise<LilconfigResult>;
/** Clear the load cache */
clearLoadCache(): void;
/** Clear the search cache */
clearSearchCache(): void;
/** Clear both load and search caches */
clearCaches(): void;
}The object returned by lilconfigSync() providing synchronous search and load capabilities.
interface SyncSearcher {
/**
* Search for configuration files starting from specified directory
* @param searchFrom - Directory to start searching from (defaults to process.cwd())
* @returns LilconfigResult or null if not found
*/
search(searchFrom?: string): LilconfigResult;
/**
* Load configuration from a specific file path
* @param filepath - Path to configuration file to load
* @returns LilconfigResult
*/
load(filepath: string): LilconfigResult;
/** Clear the load cache */
clearLoadCache(): void;
/** Clear the search cache */
clearSearchCache(): void;
/** Clear both load and search caches */
clearCaches(): void;
}Pre-configured loaders for common file types.
/** Default async loaders supporting .js, .mjs, .cjs, .json files and files with no extension */
const defaultLoaders: Loaders;
/** Default sync loaders supporting .js, .json, .cjs files and files with no extension (no .mjs support) */
const defaultLoadersSync: LoadersSync;Loader Resolution:
.js → JavaScript loader, .json → JSON parser)noExt loader keynoExt loader treats files as JSONnoExt behaviorUsage Example:
import { lilconfig, defaultLoaders } from 'lilconfig';
// Extend default loaders with custom loader
const explorer = lilconfig('myapp', {
loaders: {
...defaultLoaders,
'.yaml': (filepath, content) => require('yaml').parse(content)
}
});Configuration options for customizing search behavior and file processing.
interface Options {
/** Custom loaders for different file extensions */
loaders?: Loaders;
/** Transform function to modify loaded configuration */
transform?: Transform;
/** Enable/disable caching (default: true) */
cache?: boolean;
/** Directory to stop searching at (default: os.homedir()) */
stopDir?: string;
/** Custom list of places to search for config files */
searchPlaces?: string[];
/** Whether to ignore empty config files (default: true) */
ignoreEmptySearchPlaces?: boolean;
/** Property name(s) to extract from package.json (default: [name]) */
packageProp?: string | string[];
}
interface OptionsSync {
/** Custom sync loaders for different file extensions */
loaders?: LoadersSync;
/** Sync transform function to modify loaded configuration */
transform?: TransformSync;
/** Enable/disable caching (default: true) */
cache?: boolean;
/** Directory to stop searching at (default: os.homedir()) */
stopDir?: string;
/** Custom list of places to search for config files */
searchPlaces?: string[];
/** Whether to ignore empty config files (default: true) */
ignoreEmptySearchPlaces?: boolean;
/** Property name(s) to extract from package.json (default: [name]) */
packageProp?: string | string[];
}Option Details:
package.json, extracts configuration from the specified property. Can be a string ('myapp') or nested path (['config', 'myapp']). Defaults to the configuration name.null when no config found). Useful for adding defaults or metadata to loaded configurations.Usage Example:
import { lilconfig } from 'lilconfig';
import os from 'os';
const explorer = lilconfig('myapp', {
stopDir: os.homedir(),
searchPlaces: [
'package.json',
'.myapprc.json',
'.myapprc.js',
'myapp.config.js'
],
ignoreEmptySearchPlaces: false,
packageProp: ['myapp', 'config'],
cache: true,
transform: (result) => {
if (result && result.config) {
// Add metadata to config
return {
...result,
config: {
...result.config,
_loadedFrom: result.filepath
}
};
}
return result;
}
});/** Result object returned by search and load operations */
type LilconfigResult = null | {
/** Path to the configuration file that was found/loaded */
filepath: string;
/** The loaded configuration data */
config: any;
/** Whether the configuration file was empty */
isEmpty?: boolean;
};
/** Sync loader function for processing file content */
type LoaderSync = (filepath: string, content: string) => any;
/** Async loader function for processing file content */
type Loader = LoaderSync | ((filepath: string, content: string) => Promise<any>);
/** Map of file extensions to sync loaders */
type LoadersSync = Record<string, LoaderSync>;
/** Map of file extensions to async loaders */
type Loaders = Record<string, Loader>;
/** Sync transform function for modifying loaded configuration */
type TransformSync = (result: LilconfigResult) => LilconfigResult;
/** Transform function for modifying loaded configuration */
type Transform = TransformSync | ((result: LilconfigResult) => Promise<LilconfigResult>);import { lilconfig } from 'lilconfig';
import yaml from 'yaml';
// YAML loader example
function yamlLoader(filepath, content) {
return yaml.parse(content);
}
const explorer = lilconfig('myapp', {
loaders: {
'.yaml': yamlLoader,
'.yml': yamlLoader,
// Override default behavior for files with no extension
noExt: yamlLoader
}
});import { lilconfig } from 'lilconfig';
const explorer = lilconfig('myapp', {
transform: (result) => {
if (!result) return result;
// Add default values
return {
...result,
config: {
timeout: 5000,
retries: 3,
...result.config
}
};
}
});import { lilconfig } from 'lilconfig';
// Extract nested configuration from package.json
const explorer = lilconfig('myapp', {
packageProp: ['config', 'myapp'] // Looks for package.json.config.myapp
});
// Extract from array of possible properties
const explorerMulti = lilconfig('myapp', {
packageProp: ['myapp', 'myappConfig'] // Tries myapp first, then myappConfig
});lilconfig throws specific errors in the following cases:
Error('Missing loader for extension "<extension>"') when no loader is configured for a file extensionError('Loader for extension "<extension>" is not a function: Received <type>.') when a loader is not a functionError('load must pass a non-empty string') when load() is called with empty filepathError('No loader specified for extension "<ext>"') when trying to load a file without a configured loaderimport { lilconfig } from 'lilconfig';
try {
const explorer = lilconfig('myapp');
const result = await explorer.load('');
} catch (error) {
console.error('Error:', error.message); // "load must pass a non-empty string"
}