Loader keeps plugin and configuration discovery out of the main application setup flow.
Configuration and plugin loading utilities for the Ingest framework, providing file resolution and dynamic import capabilities with support for multiple file formats.
import { ConfigLoader, PluginLoader } from '@stackpress/ingest';
const configLoader = new ConfigLoader({
key: 'plugins',
extnames: ['.js', '.json', '.ts']
});
const pluginLoader = new PluginLoader({
cwd: process.cwd(),
plugins: ['./src/plugin.js', '@my/plugin']
});- ConfigLoader
- PluginLoader
- Plugin Resolution
- Bootstrap Process
- Error Handling
- Integration with Server
- Best Practices
- Examples
File loader specialized for configuration files with support for multiple file extensions and key extraction.
The following properties are available when instantiating a ConfigLoader.
| Property | Type | Description |
|---|---|---|
cwd |
string |
Current working directory (inherited) |
fs |
FileSystem |
Filesystem interface being used (inherited) |
The following methods are available when instantiating a ConfigLoader.
The following example shows how to load configuration files with fallback defaults.
const config = await loader.load('./config.json', {
default: 'value'
});
// Load with automatic key extraction
const plugins = await loader.load('./package.json');
// Extracts the 'plugins' key from package.jsonParameters
| Parameter | Type | Description |
|---|---|---|
filepath |
string |
Path to the configuration file |
defaults |
T |
Default value if file cannot be loaded (optional) |
Returns
A promise that resolves to the loaded configuration data or defaults.
The following example shows how to resolve configuration files with multiple extension support.
const resolved = await loader.resolveFile('./config');
// Tries: ./config/plugins.js, ./config/plugins.json,
// ./config/package.json, ./config/plugins.ts,
// ./config.js, ./config.json, ./config.tsParameters
| Parameter | Type | Description |
|---|---|---|
filepath |
string |
Path to resolve (default: current working directory) |
Returns
A promise that resolves to the resolved file path or null if not found.
ConfigLoader accepts the following options during instantiation for customized file loading behavior.
const loader = new ConfigLoader({
cwd: '/custom/working/directory',
fs: customFileSystem,
key: 'myPlugins',
extnames: ['/custom.js', '.custom.json']
});| Option | Type | Default | Description |
|---|---|---|---|
cwd |
string |
process.cwd() |
Working directory for file resolution |
fs |
FileSystem |
NodeFS |
Filesystem implementation |
key |
string |
'plugins' |
Key to extract from loaded objects |
extnames |
string[] |
See below | File extensions to try |
Default Extensions
[
'/plugins.js', // Directory-specific plugins file
'/plugins.json', // Directory-specific plugins config
'/package.json', // Package configuration
'/plugins.ts', // TypeScript plugins file
'.js', // JavaScript file
'.json', // JSON file
'.ts' // TypeScript file
]Extended configuration loader specialized for plugin management and bootstrapping with automatic dependency resolution.
The following properties are available when instantiating a PluginLoader.
| Property | Type | Description |
|---|---|---|
cwd |
string |
Current working directory (inherited) |
fs |
FileSystem |
Filesystem interface being used (inherited) |
The following methods are available when instantiating a PluginLoader.
The following example shows how to bootstrap all configured plugins with custom loading logic.
await pluginLoader.bootstrap(async (name, plugin) => {
console.log(`Loading plugin: ${name}`);
if (typeof plugin === 'function') {
// Plugin is a function, call it with context
await plugin(server);
} else {
// Plugin is a configuration object
server.configure(plugin);
}
});Parameters
| Parameter | Type | Description |
|---|---|---|
loader |
(name: string, plugin: unknown) => Promise<void> |
Function to handle each loaded plugin |
Returns
The PluginLoader instance to allow method chaining.
The following example shows how to get the list of configured plugins for inspection.
const plugins = await pluginLoader.plugins();
// Returns: ['./src/plugin.js', '@my/plugin', 'local-plugin']Returns
A promise that resolves to an array of plugin paths.
PluginLoader accepts the following options during instantiation for flexible plugin management.
const loader = new PluginLoader({
cwd: process.cwd(),
plugins: ['./plugin1.js', '@scope/plugin2'],
modules: '/path/to/node_modules'
});| Option | Type | Description |
|---|---|---|
cwd |
string |
Working directory for file resolution |
fs |
FileSystem |
Filesystem implementation |
plugins |
string[] |
Array of plugin paths (optional) |
modules |
string |
Path to node_modules directory (optional) |
key |
string |
Key to extract from configuration files |
extnames |
string[] |
File extensions to try |
PluginLoader supports various plugin path formats for maximum flexibility in plugin organization.
Load plugins from local files and directories within your project.
const plugins = [
'./src/plugins/auth.js', // Relative path
'/absolute/path/plugin.js', // Absolute path
'./plugins' // Directory with plugins config
];Load plugins from installed NPM packages with automatic resolution.
const plugins = [
'@my-org/auth-plugin', // Scoped package
'express-session', // Regular package
'local-plugin/dist/index.js' // Package with specific entry
];Support for nested plugin configurations allows for modular plugin organization.
// plugins.json
{
"plugins": [
"./auth-plugin",
{
"plugins": ["./nested-plugin1", "./nested-plugin2"]
}
]
}The bootstrap process follows a systematic approach to ensure reliable plugin loading and initialization.
The bootstrap process executes the following steps in order:
- Load Plugin List: Resolves the plugins array from configuration
- Process Each Plugin: Iterates through each plugin path
- Handle Nested Configs: Recursively processes nested plugin arrays
- Resolve Plugin Path: Converts relative paths to absolute paths
- Extract Plugin Name: Generates a clean name for the plugin
- Call Loader Function: Invokes the provided loader with name and plugin
Plugins are loaded in the order they appear in the configuration, allowing for dependency management.
const plugins = [
'./plugins/database', // Load database connection first
'./plugins/auth', // Then authentication (depends on database)
'./plugins/api' // Finally API routes (depends on auth)
];PluginLoader provides comprehensive error handling for robust plugin management.
Common error scenarios include missing files, invalid configurations, and plugin initialization failures.
try {
await pluginLoader.bootstrap(loader);
} catch (error) {
// Handles missing files, invalid configurations, etc.
console.error('Plugin loading failed:', error.message);
}Implement error handling that allows the application to continue running even if some plugins fail.
await pluginLoader.bootstrap(async (name, plugin) => {
try {
await loadPlugin(name, plugin);
console.log(`✓ Loaded plugin: ${name}`);
} catch (error) {
console.error(`✗ Failed to load plugin ${name}:`, error.message);
// Continue loading other plugins
}
});PluginLoader integrates seamlessly with the Server class for automatic plugin loading and configuration.
Use the server's built-in bootstrap method for standard plugin loading.
import { server } from '@stackpress/ingest/http';
const app = server();
// Bootstrap plugins from package.json
await app.bootstrap();Use a custom PluginLoader for advanced plugin management scenarios.
// Or use custom plugin loader
const pluginLoader = new PluginLoader({
plugins: ['./custom-plugin.js']
});
await pluginLoader.bootstrap(async (name, plugin) => {
if (typeof plugin === 'function') {
plugin(app);
}
});The following best practices ensure reliable and maintainable plugin management.
Organize plugins by functionality for better maintainability and understanding.
// Organize plugins by functionality
const plugins = [
'./plugins/auth', // Authentication
'./plugins/database', // Database connection
'./plugins/logging', // Logging setup
'@company/shared' // Shared company plugins
];await pluginLoader.bootstrap(async (name, plugin) => {
try {
await loadPlugin(name, plugin);
console.log(`✓ Loaded plugin: ${name}`);
} catch (error) {
console.error(`✗ Failed to load plugin ${name}:`, error.message);
// Continue loading other plugins
}
});const isDev = process.env.NODE_ENV === 'development';
const plugins = [
'./plugins/core',
...(isDev ? ['./plugins/dev-tools'] : []),
...(process.env.ENABLE_ANALYTICS ? ['./plugins/analytics'] : [])
];Ensure plugins are loaded in the correct order to handle dependencies properly.
const plugins = [
'./plugins/config', // Load configuration first
'./plugins/database', // Database depends on config
'./plugins/auth', // Auth depends on database
'./plugins/routes' // Routes depend on auth
];The following examples demonstrate common Loader usage patterns for real-world applications.
import { ConfigLoader } from '@stackpress/ingest';
const configLoader = new ConfigLoader({
key: 'database',
extnames: ['.json', '.js', '.env.js']
});
// Load database configuration
const dbConfig = await configLoader.load('./config/database', {
host: 'localhost',
port: 5432,
database: 'myapp'
});
console.log('Database config:', dbConfig);import { PluginLoader } from '@stackpress/ingest';
import { server } from '@stackpress/ingest/http';
const app = server();
const pluginLoader = new PluginLoader({
cwd: process.cwd(),
plugins: [
'./plugins/cors',
'./plugins/auth',
'./plugins/api',
'@company/monitoring'
]
});
await pluginLoader.bootstrap(async (name, plugin) => {
console.log(`Loading plugin: ${name}`);
if (typeof plugin === 'function') {
// Function plugin - call with server instance
await plugin(app);
} else if (plugin && typeof plugin === 'object') {
// Configuration plugin - apply settings
if (plugin.routes) {
app.use(plugin.routes);
}
if (plugin.middleware) {
plugin.middleware.forEach((mw: any) => app.use(mw));
}
}
console.log(`✓ Plugin ${name} loaded successfully`);
});
console.log('All plugins loaded, starting server...');
app.listen(3000);import { PluginLoader } from '@stackpress/ingest';
import { readdir } from 'fs/promises';
import { join } from 'path';
async function discoverPlugins(pluginsDir: string) {
const entries = await readdir(pluginsDir, { withFileTypes: true });
const plugins = [];
for (const entry of entries) {
if (entry.isDirectory()) {
const pluginPath = join(pluginsDir, entry.name);
plugins.push(pluginPath);
}
}
return plugins;
}
// Discover and load plugins dynamically
const discoveredPlugins = await discoverPlugins('./src/plugins');
const pluginLoader = new PluginLoader({
plugins: [
'./plugins/core', // Always load core plugins first
...discoveredPlugins
]
});
await pluginLoader.bootstrap(async (name, plugin) => {
try {
if (typeof plugin === 'function') {
await plugin(app);
}
console.log(`✓ Loaded plugin: ${name}`);
} catch (error) {
console.error(`✗ Failed to load plugin ${name}:`, error);
}
});import { PluginLoader } from '@stackpress/ingest';
const environment = process.env.NODE_ENV || 'development';
const features = process.env.FEATURES?.split(',') || [];
// Build plugin list based on environment and features
const plugins = [
'./plugins/core',
'./plugins/database'
];
// Environment-specific plugins
if (environment === 'development') {
plugins.push('./plugins/dev-tools', './plugins/hot-reload');
} else if (environment === 'production') {
plugins.push('./plugins/monitoring', './plugins/performance');
}
// Feature-specific plugins
if (features.includes('auth')) {
plugins.push('./plugins/auth');
}
if (features.includes('analytics')) {
plugins.push('./plugins/analytics');
}
const pluginLoader = new PluginLoader({ plugins });
await pluginLoader.bootstrap(async (name, plugin) => {
console.log(`Loading ${name} for ${environment} environment`);
if (typeof plugin === 'function') {
await plugin(app, { environment, features });
}
});