UNPKG

4.35 kBJavaScriptView Raw
1var fs = require('fs');
2var Utils = require('./HappyUtils');
3var assert = require('assert');
4
5/**
6 * @param {Object} config
7 * @param {String} config.id
8 * HappyPack Plugin ID, for logging purposes.
9 *
10 * @param {Boolean} config.verbose
11 * @param {Function?} config.generateSignature
12 * @param {String} config.path
13 * Absolute path to where the JSON representation of the cache should
14 * be stored. Path must be writable.
15 *
16 */
17module.exports = function HappyFSCache(config) {
18 var exports = {};
19 var id = config.id;
20 var cachePath = config.path;
21 var cache = { context: {}, mtimes: {} };
22 var generateSignature = config.generateSignature || getMTime;
23
24 assert(typeof cachePath === 'string',
25 "HappyFSCache requires a @path parameter that points to where it will be stored.");
26
27 /**
28 * @param {Object} params.context
29 * An object that should fully represent the context in which the plugin
30 * was run. This object will be used to determine whether previous cache
31 * entries are still valid or not.
32 *
33 * This MUST be JSON-serializable!
34 *
35 * @return {Boolean}
36 * Whether the cache was loaded from disk.
37 */
38 exports.load = function(currentContext) {
39 var oldCache, staleEntryCount;
40
41 cache.context = currentContext;
42
43 assert(typeof currentContext === 'object' && !!currentContext,
44 "HappyFSCache requires a @context parameter to work.");
45
46 if (!Utils.isReadable(cachePath)) {
47 if (config.verbose) {
48 console.log('Happy[%s]: No cache was found, starting fresh.', id);
49 }
50
51 return false;
52 }
53
54 try {
55 oldCache = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
56 } catch(e) {
57 oldCache = null;
58 }
59
60 if (!oldCache || toJSON(oldCache.context) !== toJSON(currentContext)) {
61 if (config.verbose) {
62 console.log('Happy[%s]: Cache is no longer valid, starting fresh.', id);
63 }
64
65 return false;
66 }
67
68 cache.mtimes = oldCache.mtimes;
69 cache.context = currentContext;
70
71 staleEntryCount = removeStaleEntries(cache.mtimes, generateSignature);
72
73 if (config.verbose) {
74 console.log('Happy[%s]: Loaded %d entries from cache. (%d were stale)',
75 id,
76 Object.keys(cache.mtimes).length,
77 staleEntryCount
78 );
79 }
80
81 return true;
82 };
83
84 exports.save = function() {
85 fs.writeFileSync(cachePath, JSON.stringify(cache));
86 };
87
88 exports.getCompiledSourceCodePath = function(filePath) {
89 return cache.mtimes[filePath] && cache.mtimes[filePath].compiledPath;
90 };
91
92 exports.getCompiledSourceMapPath = function(filePath) {
93 return cache.mtimes[filePath] && cache.mtimes[filePath].compiledPath + '.map';
94 };
95
96 exports.hasChanged = function(filePath) {
97 var nowMTime = generateSignature(filePath);
98 var lastMTime = getSignatureAtCompilationTime(filePath);
99
100 return nowMTime !== lastMTime;
101 };
102
103 exports.hasErrored = function(filePath) {
104 return cache.mtimes[filePath] && cache.mtimes[filePath].error;
105 };
106
107 exports.invalidateEntryFor = function(filePath) {
108 delete cache.mtimes[filePath];
109 };
110
111 exports.updateMTimeFor = function(filePath, compiledPath, error) {
112 cache.mtimes[filePath] = {
113 mtime: generateSignature(filePath),
114 compiledPath: compiledPath,
115 error: error
116 };
117 };
118
119 exports.dump = function() {
120 return cache;
121 };
122
123 function getSignatureAtCompilationTime(filePath) {
124 if (cache.mtimes[filePath]) {
125 return cache.mtimes[filePath].mtime;
126 }
127 }
128
129 return exports;
130}
131
132function toJSON(object) {
133 return JSON.stringify(object);
134}
135
136function getMTime(filePath) {
137 try {
138 return fs.statSync(filePath).mtime.getTime();
139 }
140 catch (e) {
141 return -1;
142 }
143}
144
145function removeStaleEntries(mtimes, generateSignature) {
146 return Object.keys(mtimes).reduce(function(acc, filePath) {
147 var entry = mtimes[filePath];
148
149 if (isStale(entry, filePath)) {
150 delete mtimes[filePath];
151 return acc + 1;
152 }
153
154 return acc;
155 }, 0);
156
157 function isStale(entry, filePath) {
158 return (
159 // was the source removed?
160 !Utils.isReadable(filePath)
161 // was the compiled version removed or never created?
162 || !Utils.isReadable(entry.compiledPath)
163 // has source been modified since we last compiled it?
164 || entry.mtime !== generateSignature(filePath)
165 );
166 }
167}