UNPKG

2.72 kBJavaScriptView Raw
1const fs = require('./utils/fs');
2const path = require('path');
3const md5 = require('./utils/md5');
4const objectHash = require('./utils/objectHash');
5const pkg = require('../package.json');
6const logger = require('./Logger');
7
8// These keys can affect the output, so if they differ, the cache should not match
9const OPTION_KEYS = ['publicURL', 'minify', 'hmr', 'target'];
10
11class FSCache {
12 constructor(options) {
13 this.dir = path.resolve(options.cacheDir || '.cache');
14 this.dirExists = false;
15 this.invalidated = new Set();
16 this.optionsHash = objectHash(
17 OPTION_KEYS.reduce((p, k) => ((p[k] = options[k]), p), {
18 version: pkg.version
19 })
20 );
21 }
22
23 async ensureDirExists() {
24 await fs.mkdirp(this.dir);
25 this.dirExists = true;
26 }
27
28 getCacheFile(filename) {
29 let hash = md5(this.optionsHash + filename);
30 return path.join(this.dir, hash + '.json');
31 }
32
33 async writeDepMtimes(data) {
34 // Write mtimes for each dependent file that is already compiled into this asset
35 for (let dep of data.dependencies) {
36 if (dep.includedInParent) {
37 let stats = await fs.stat(dep.name);
38 dep.mtime = stats.mtime.getTime();
39 }
40 }
41 }
42
43 async write(filename, data) {
44 try {
45 await this.ensureDirExists();
46 await this.writeDepMtimes(data);
47 await fs.writeFile(this.getCacheFile(filename), JSON.stringify(data));
48 this.invalidated.delete(filename);
49 } catch (err) {
50 logger.error('Error writing to cache', err);
51 }
52 }
53
54 async checkDepMtimes(data) {
55 // Check mtimes for files that are already compiled into this asset
56 // If any of them changed, invalidate.
57 for (let dep of data.dependencies) {
58 if (dep.includedInParent) {
59 let stats = await fs.stat(dep.name);
60 if (stats.mtime > dep.mtime) {
61 return false;
62 }
63 }
64 }
65
66 return true;
67 }
68
69 async read(filename) {
70 if (this.invalidated.has(filename)) {
71 return null;
72 }
73
74 let cacheFile = this.getCacheFile(filename);
75
76 try {
77 let stats = await fs.stat(filename);
78 let cacheStats = await fs.stat(cacheFile);
79
80 if (stats.mtime > cacheStats.mtime) {
81 return null;
82 }
83
84 let json = await fs.readFile(cacheFile);
85 let data = JSON.parse(json);
86 if (!await this.checkDepMtimes(data)) {
87 return null;
88 }
89
90 return data;
91 } catch (err) {
92 return null;
93 }
94 }
95
96 invalidate(filename) {
97 this.invalidated.add(filename);
98 }
99
100 async delete(filename) {
101 try {
102 await fs.unlink(this.getCacheFile(filename));
103 this.invalidated.delete(filename);
104 } catch (err) {
105 // Fail silently
106 }
107 }
108}
109
110module.exports = FSCache;