Webpack loader that caches expensive loader results to disk or database storage for improved build performance
npx @tessl/cli install tessl/npm-cache-loader@4.1.0Cache Loader is a webpack loader that caches the result of expensive loaders to disk or database storage. It improves build performance by avoiding redundant processing and provides configurable caching strategies with support for custom cache directories, cache keys, comparison functions, and database integration.
npm install --save-dev cache-loaderES Module:
import cacheLoader, { pitch } from "cache-loader";CommonJS:
const cacheLoader = require("cache-loader");
// Or access the pitch function separately
const { pitch } = require("cache-loader");// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: ['cache-loader', 'babel-loader'],
include: path.resolve('src'),
},
],
},
};Advanced configuration with options:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'cache-loader',
options: {
cacheDirectory: path.resolve('.cache'),
cacheIdentifier: 'my-project-v1',
readOnly: process.env.NODE_ENV === 'production',
},
},
'babel-loader',
],
include: path.resolve('src'),
},
],
},
};Cache Loader implements a two-phase caching strategy:
The default export loader function processes and caches loader results after expensive operations complete.
/**
* Main webpack loader function that caches results of subsequent loaders
* @param {...any} args - Webpack loader arguments (source, map, meta)
* @returns {void} - Uses webpack callback mechanism
*/
function loader(...args);Usage:
// Automatically called by webpack when loader is configured
// No direct invocation neededThe pitch function executes before other loaders and attempts to return cached results to skip expensive processing.
/**
* Webpack loader pitch function that attempts to return cached results
* @param {string} remainingRequest - Remaining loaders in the chain
* @param {string} prevRequest - Previous loaders that have been processed
* @param {object} data - Data object shared between pitch and normal phases
* @returns {void} - Uses webpack callback mechanism
*/
function pitch(remainingRequest, prevRequest, data);The loader operates in raw mode to handle binary data correctly.
/**
* Indicates that the loader works with raw/binary data
*/
const raw = true;All configuration options are optional and have sensible defaults:
/**
* Override the default cache context to generate relative cache paths
* @type {string}
* @default "" (uses absolute paths)
*/
cacheContext: string;Usage:
{
loader: 'cache-loader',
options: {
cacheContext: path.resolve(__dirname, 'src'),
},
}/**
* Directory where cache files are stored
* @type {string}
* @default findCacheDir({name: 'cache-loader'}) || os.tmpdir()
*/
cacheDirectory: string;Usage:
{
loader: 'cache-loader',
options: {
cacheDirectory: path.resolve(__dirname, '.webpack-cache'),
},
}/**
* Cache invalidation identifier used to generate hashes
* @type {string}
* @default "cache-loader:{version} {process.env.NODE_ENV}"
*/
cacheIdentifier: string;Usage:
{
loader: 'cache-loader',
options: {
cacheIdentifier: `my-app-${process.env.NODE_ENV}-v1.2.0`,
},
}/**
* Custom cache key generator function
* @type {function}
* @param {object} options - Loader options
* @param {string} request - Current request string
* @returns {string} Cache key path
* @default Built-in function using MD5 hash
*/
cacheKey: (options, request) => string;Usage:
function customCacheKey(options, request) {
const crypto = require('crypto');
const hash = crypto.createHash('sha256')
.update(`${options.cacheIdentifier}\n${request}`)
.digest('hex');
return path.join(options.cacheDirectory, `${hash}.cache`);
}
{
loader: 'cache-loader',
options: {
cacheKey: customCacheKey,
},
}/**
* Custom comparison function between cached dependency and current file
* @type {function}
* @param {object} stats - File stats from fs.stat()
* @param {object} dep - Cached dependency information
* @returns {boolean} True to use cached resource
* @default Compares mtime timestamps
*/
compare: (stats, dep) => boolean;Usage:
function customCompare(stats, dep) {
// Custom logic for cache validity
return stats.mtime.getTime() === dep.mtime && stats.size === dep.size;
}
{
loader: 'cache-loader',
options: {
compare: customCompare,
},
}/**
* Round mtime by this number of milliseconds for comparison
* @type {number}
* @default 0
*/
precision: number;Usage:
{
loader: 'cache-loader',
options: {
precision: 1000, // Round to nearest second
},
}/**
* Make cache read-only (no writing/updating)
* @type {boolean}
* @default false
*/
readOnly: boolean;Usage:
{
loader: 'cache-loader',
options: {
readOnly: process.env.NODE_ENV === 'production',
},
}/**
* Custom cache read function (e.g., for Redis/database)
* @type {function}
* @param {string} cacheKey - Cache key to read
* @param {function} callback - Callback function(err, data)
* @returns {void}
* @default Built-in filesystem read
*/
read: (cacheKey, callback) => void;Usage:
function redisRead(key, callback) {
client.get(key, (err, result) => {
if (err) return callback(err);
if (!result) return callback(new Error(`Key ${key} not found`));
try {
const data = JSON.parse(result);
callback(null, data);
} catch (e) {
callback(e);
}
});
}
{
loader: 'cache-loader',
options: {
read: redisRead,
},
}/**
* Custom cache write function (e.g., for Redis/database)
* @type {function}
* @param {string} cacheKey - Cache key to write
* @param {any} data - Data to cache
* @param {function} callback - Callback function(err)
* @returns {void}
* @default Built-in filesystem write
*/
write: (cacheKey, data, callback) => void;Usage:
const BUILD_CACHE_TIMEOUT = 24 * 3600; // 1 day
function redisWrite(key, data, callback) {
client.set(key, JSON.stringify(data), 'EX', BUILD_CACHE_TIMEOUT, callback);
}
{
loader: 'cache-loader',
options: {
write: redisWrite,
},
}Complete Redis integration example:
// webpack.config.js
const redis = require('redis');
const crypto = require('crypto');
const client = redis.createClient();
const BUILD_CACHE_TIMEOUT = 24 * 3600; // 1 day
function digest(str) {
return crypto.createHash('md5').update(str).digest('hex');
}
function cacheKey(options, request) {
return `build:cache:${digest(request)}`;
}
function read(key, callback) {
client.get(key, (err, result) => {
if (err) return callback(err);
if (!result) return callback(new Error(`Key ${key} not found`));
try {
const data = JSON.parse(result);
callback(null, data);
} catch (e) {
callback(e);
}
});
}
function write(key, data, callback) {
client.set(key, JSON.stringify(data), 'EX', BUILD_CACHE_TIMEOUT, callback);
}
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'cache-loader',
options: { cacheKey, read, write },
},
'babel-loader',
],
include: path.resolve('src'),
},
],
},
};/**
* Cached data structure stored by the loader
*/
interface CacheData {
/** Remaining request string with cache context applied */
remainingRequest: string;
/** File dependencies with paths and modification times */
dependencies: Array<{
path: string;
mtime: number;
}>;
/** Context dependencies with paths and modification times */
contextDependencies: Array<{
path: string;
mtime: number;
}>;
/** Cached loader result arguments */
result: any[];
}/**
* Configuration options for cache-loader
*/
interface LoaderOptions {
cacheContext?: string;
cacheKey?: (options: LoaderOptions, request: string) => string;
cacheDirectory?: string;
cacheIdentifier?: string;
compare?: (stats: fs.Stats, dep: { path: string; mtime: number }) => boolean;
precision?: number;
read?: (cacheKey: string, callback: (err?: Error, data?: CacheData) => void) => void;
readOnly?: boolean;
write?: (cacheKey: string, data: CacheData, callback: (err?: Error) => void) => void;
}