Runs webpack loaders programmatically with full context support and dependency tracking
npx @tessl/cli install tessl/npm-loader-runner@4.3.0Loader Runner is a JavaScript library that provides a runtime execution engine for webpack loaders. It enables programmatic execution of webpack's transformation pipeline outside of webpack itself, offering a complete API for running sequences of loaders against resources with full context support, dependency tracking, and error handling.
npm install loader-runnerimport { runLoaders, getContext } from "loader-runner";For CommonJS:
const { runLoaders, getContext } = require("loader-runner");import { runLoaders } from "loader-runner";
import fs from "fs";
// Execute a simple loader chain
runLoaders({
resource: "/path/to/source.js",
loaders: [
"/path/to/babel-loader.js",
"/path/to/eslint-loader.js"
],
context: { minimize: true },
readResource: fs.readFile.bind(fs)
}, (err, result) => {
if (err) {
console.error("Loader execution failed:", err);
return;
}
console.log("Transformed code:", result.result.toString());
console.log("Dependencies:", result.fileDependencies);
});Loader Runner implements webpack's loader execution model with these key components:
Main function to execute sequences of loaders against resources with comprehensive result metadata.
/**
* Execute a sequence of loaders against a resource
* @param {LoaderOptions} options - Configuration options for loader execution
* @param {function} callback - Completion callback with signature (err, result)
*/
function runLoaders(options, callback);
interface LoaderOptions {
/** Absolute path to the resource file (optionally including query string) */
resource?: string;
/** Array of loader paths or loader configuration objects */
loaders?: (string | LoaderConfig)[];
/** Additional loader context used as base context */
context?: object;
/** Custom function to process the resource */
processResource?: (loaderContext: LoaderContext, resourcePath: string, callback: Function) => void;
/** Custom function to read the resource (defaults to fs.readFile) */
readResource?: (path: string, callback: Function) => void;
}
interface LoaderConfig {
/** Absolute path to the loader */
loader: string;
/** Loader options object */
options?: object;
/** Loader identifier for option serialization */
ident?: string;
/** Fragment identifier */
fragment?: string;
/** Loader type (module or commonjs) */
type?: string;
}
interface LoaderResult {
/** The transformation result (array of results from loader chain) */
result?: any[];
/** The raw resource as Buffer (useful for SourceMaps) */
resourceBuffer?: Buffer;
/** Whether the result is cacheable */
cacheable: boolean;
/** Array of file paths the result depends on */
fileDependencies: string[];
/** Array of directory paths the result depends on */
contextDependencies: string[];
/** Array of missing file paths the result depends on */
missingDependencies: string[];
}Usage Examples:
import { runLoaders } from "loader-runner";
import fs from "fs";
// Simple loader execution
runLoaders({
resource: "/project/src/index.js",
loaders: ["/node_modules/babel-loader/lib/index.js"]
}, (err, result) => {
if (!err) {
console.log("Result:", result.result.toString());
}
});
// Complex loader chain with options
runLoaders({
resource: "/project/src/styles.scss?theme=dark",
loaders: [
{
loader: "/node_modules/sass-loader/dist/cjs.js",
options: { sourceMap: true }
},
{
loader: "/node_modules/css-loader/dist/cjs.js",
options: { modules: true }
}
],
context: {
mode: "production",
minimize: true
},
readResource: fs.readFile.bind(fs)
}, (err, result) => {
if (!err) {
console.log("CSS:", result.result.toString());
console.log("Dependencies:", result.fileDependencies);
console.log("Cacheable:", result.cacheable);
}
});
// Custom resource processing
runLoaders({
resource: "/project/data.json",
loaders: ["/custom/json-transformer.js"],
processResource: (loaderContext, resourcePath, callback) => {
// Custom resource reading logic
fs.readFile(resourcePath, 'utf8', (err, data) => {
if (err) return callback(err);
// Add dependency tracking
loaderContext.addDependency(resourcePath);
// Custom processing
const processed = JSON.parse(data);
callback(null, JSON.stringify(processed, null, 2));
});
}
}, (err, result) => {
// Handle result
});Utility function to extract directory context from resource paths.
/**
* Extract the directory context from a resource path
* @param {string} resource - Resource path with optional query and fragment
* @returns {string} Directory path of the resource
*/
function getContext(resource);Usage Examples:
import { getContext } from "loader-runner";
// Extract directory from resource path
const context = getContext("/project/src/components/Button.jsx?inline");
console.log(context); // "/project/src/components"
// Handle paths with complex queries
const context2 = getContext("/assets/image.png?width=200&height=100#section");
console.log(context2); // "/assets"
// Root directory handling
const context3 = getContext("/index.js");
console.log(context3); // "/"The loader context object provided to loaders during execution contains comprehensive information and utilities:
interface LoaderContext {
/** Directory of the resource being processed */
context: string;
/** Full resource path with query and fragment */
resource: string;
/** Path to the resource file */
resourcePath: string;
/** Query string portion of the resource */
resourceQuery: string;
/** Fragment portion of the resource */
resourceFragment: string;
/** Full loader chain request string */
request: string;
/** Remaining loaders in the chain */
remainingRequest: string;
/** Current loader and remaining chain */
currentRequest: string;
/** Previously executed loaders */
previousRequest: string;
/** Current loader index in the chain */
loaderIndex: number;
/** Current loader options/query */
query: object | string;
/** Shared data object for pitch/normal phases */
data: object;
/** Array of all loaders in the chain */
loaders: LoaderObject[];
/** Async callback function (null until async() is called) */
async: Function | null;
/** Callback function (null until async() is called) */
callback: Function | null;
}interface LoaderContextMethods {
/**
* Make loader execution asynchronous
* @returns {function} Async callback function
*/
async(): Function;
/**
* Async callback function for returning results
* @param {Error} err - Error if execution failed
* @param {*} result - Transformation result
* @param {object} sourceMap - Optional source map
* @param {object} meta - Optional metadata
*/
callback(err?: Error, result?: any, sourceMap?: object, meta?: object): void;
/**
* Mark the result as cacheable or non-cacheable
* @param {boolean} flag - Cacheable flag (defaults to true)
*/
cacheable(flag?: boolean): void;
/**
* Add a file dependency for caching
* @param {string} file - File path to add as dependency
*/
addDependency(file: string): void;
/**
* Alias for addDependency (legacy compatibility)
* @param {string} file - File path to add as dependency
*/
dependency(file: string): void;
/**
* Add a directory dependency for caching
* @param {string} context - Directory path to watch
*/
addContextDependency(context: string): void;
/**
* Add a missing file dependency for caching
* @param {string} missing - Missing file path
*/
addMissingDependency(missing: string): void;
/**
* Get current file dependencies
* @returns {string[]} Array of file dependencies
*/
getDependencies(): string[];
/**
* Get current context dependencies
* @returns {string[]} Array of context dependencies
*/
getContextDependencies(): string[];
/**
* Get current missing dependencies
* @returns {string[]} Array of missing dependencies
*/
getMissingDependencies(): string[];
/**
* Clear all dependencies and reset cacheable flag
*/
clearDependencies(): void;
}Internal structure representing individual loaders in the execution chain:
interface LoaderObject {
/** Absolute path to the loader file */
path: string;
/** Query string from the loader request */
query: string;
/** Fragment from the loader request */
fragment: string;
/** Parsed options object */
options: object;
/** Loader identifier for options */
ident: string;
/** Normal phase loader function */
normal: Function;
/** Pitch phase loader function */
pitch: Function;
/** Whether loader expects raw Buffer input */
raw: boolean;
/** Shared data object for pitch/normal communication */
data: object;
/** Whether pitch phase has been executed */
pitchExecuted: boolean;
/** Whether normal phase has been executed */
normalExecuted: boolean;
/** Full loader request string */
request: string;
/** Loader type (module or commonjs) */
type?: string;
}Loader Runner provides comprehensive error handling with proper error propagation. Errors can occur during loader loading, resource processing, or loader execution:
Common Error Scenarios:
runLoaders({
resource: "/path/to/file.js",
loaders: ["/invalid/loader.js"]
}, (err, result) => {
if (err) {
console.log("Execution error:", err.message);
// Errors may include loader loading failures,
// resource processing errors, or loader execution errors
}
});runLoaders({
resource: "/project/template.html",
loaders: ["/custom/template-loader.js"],
processResource: (loaderContext, resourcePath, callback) => {
// Custom resource processing with caching control
loaderContext.addDependency(resourcePath);
// Custom read logic (e.g., from memory, network, etc.)
customReadFunction(resourcePath, (err, content) => {
if (err) return callback(err);
callback(null, content);
});
}
}, callback);// Conditional loader application
const loaders = [];
if (process.env.NODE_ENV === 'development') {
loaders.push('/dev/source-map-loader.js');
}
loaders.push('/transpiler/babel-loader.js');
runLoaders({
resource: "/src/app.js",
loaders: loaders,
context: { mode: process.env.NODE_ENV }
}, callback);runLoaders(options, (err, result) => {
if (!err && result) {
// Use dependency information for build optimization
console.log("Watch these files:", result.fileDependencies);
console.log("Watch these directories:", result.contextDependencies);
console.log("Cache valid:", result.cacheable);
// Implement custom caching based on dependencies
if (result.cacheable) {
cache.set(cacheKey, result.result, result.fileDependencies);
}
}
});