@speedy/require-cache
Version:
Speed up Node load time by caching resolved module paths to avoid module resolution and refetching each time the application is loaded.
145 lines (144 loc) • 4.91 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const nodeModule = require("module");
const path_1 = require("path");
const fs_extra_1 = require("fs-extra");
const node_core_1 = require("@speedy/node-core");
const MODULES_PATH = `node_modules${path_1.sep}`;
/**
* Speed up Node load time by caching resolved module paths to avoid Node refetching
* and resolving the modules each time the application is loaded.
*
* The first time the application loads, a cache of resolved file paths is saved in the file system.
*
* @example
* import { RequireCache } from "@speedy/require-cache";
* new RequireCache().start();
*
* import * as stylelint from "stylelint";
* import * as _ from "lodash";
*
* @class RequireCache
*/
class RequireCache {
/**
* Creates an instance of RequireCache.
* @param {Partial<CacheOptions>} [options]
*/
constructor(options) {
this.isCacheModified = false;
this.logger = new node_core_1.Logger("Require Cache");
this.resolveFileNameOriginal = nodeModule._resolveFilename;
this.cwd = process.cwd();
this.filesLookUp = {};
this._options = {
readOnlyMode: false,
cacheKiller: 0,
cacheFilePath: path_1.resolve("./.cache/speedy-require-cache.json")
};
this._isEnabled = false;
this._stats = {
cacheHit: 0,
cacheMiss: 0,
notCached: 0
};
if (!options || !options.cacheKiller) {
this._options.cacheKiller = node_core_1.packageMeta.getVersion();
}
this._options = Object.assign({}, this._options, options);
}
/**
* Start caching of module locations.
*
* @returns {RequireCache}
*/
start() {
this._isEnabled = true;
nodeModule._resolveFilename = this.resolveFilenameOptimized.bind(this);
process.once("exit", () => this.save());
let cacheFile;
try {
cacheFile = fs_extra_1.readJsonSync(this._options.cacheFilePath);
}
catch (error) {
return this;
}
const isKillerTimestamp = cacheFile.cacheKiller.toString().indexOf(".") === -1;
if (!cacheFile ||
(isKillerTimestamp && cacheFile.cacheKiller < new Date().getTime() / 1000) ||
(!isKillerTimestamp && cacheFile.cacheKiller !== this._options.cacheKiller)) {
return this;
}
this.filesLookUp = cacheFile.paths;
return this;
}
/** Stop caching of the modules locations. */
stop() {
this._isEnabled = false;
nodeModule._resolveFilename = this.resolveFileNameOriginal;
this.save();
}
/** Deletes the cache file. */
reset() {
fs_extra_1.removeSync(this._options.cacheFilePath);
}
/** Saves cached paths to file. */
save() {
if (!this.isCacheModified) {
this.logger.debug(this.save.name, "Save exited. Cache has not been modified");
return;
}
if (this._options.readOnlyMode) {
this.logger.debug(this.save.name, "Save exited. Cache is in 'ReadOnly' mode");
return;
}
const cacheFile = {
cacheKiller: this._options.cacheKiller,
paths: this.filesLookUp
};
const { cacheHit, cacheMiss } = this._stats;
this.logger.debug(this.save.name, `Trying to saving cache, Path: ${this._options.cacheFilePath}, cacheHit: ${cacheHit}, cacheMiss: ${cacheMiss}`);
fs_extra_1.ensureFileSync(this._options.cacheFilePath);
fs_extra_1.writeJsonSync(this._options.cacheFilePath, cacheFile);
this.logger.debug(this.save.name, `Saved cached successfully.`);
}
/**
* Whether or not the cache is currently enabled.
*
* @readonly
* @type {boolean}
*/
get isEnabled() {
return this._isEnabled;
}
/**
* Caching effectiveness statistics.
*
* @readonly
* @type {CacheStats}
*/
get stats() {
return this._stats;
}
resolveFilenameOptimized(path, parentModule) {
const key = this.getCacheKey(parentModule.id, path);
const cachedPath = this.filesLookUp[key];
if (cachedPath) {
this._stats.cacheHit++;
return cachedPath;
}
const filename = this.resolveFileNameOriginal.apply(nodeModule, arguments);
if (filename.indexOf("node_modules") > -1) {
this.filesLookUp[key] = filename;
this._stats.cacheMiss++;
this.isCacheModified = true;
}
else {
this._stats.notCached++;
}
return filename;
}
getCacheKey(path, filename) {
return `${path_1.relative(this.cwd, path).replace(MODULES_PATH, "").replace(/\\/g, "/")}:${filename}`;
}
}
exports.RequireCache = RequireCache;