/
cache.js
112 lines (99 loc) 路 3.1 KB
/
cache.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// @flow
import fs from 'sb-fs'
import path from 'path'
import lowdb from 'lowdb'
import mkdirp from 'mkdirp'
import debounce from 'lodash/debounce'
import FileAsync from 'lowdb/adapters/FileAsync'
import { getStringHash, getFileKey, type Context, type ImportResolved, type ImportTransformed } from '@pundle/api'
import { version } from '../package.json'
function statsToCacheKey(stats) {
return stats ? `${parseInt(stats.mtime / 1000, 10)}::${stats.size}` : `null`
}
export default class Cache {
adapter: ?Object
context: Context
constructor(context: Context) {
this.adapter = null
this.context = context
}
_report = (issue: any) => {
this.context.invokeIssueReporters(issue)
}
async load() {
const { cache: cacheConfig, target, configFilePath, rootDirectory } = this.context.config
if (!cacheConfig.enabled) return
const filePath = path.join(
cacheConfig.rootDirectory,
`${getStringHash(`${rootDirectory}-${target}-${cacheConfig.cacheKey}`)}.json`,
)
await new Promise((resolve, reject) => {
mkdirp(path.dirname(filePath), err => {
if (err) {
reject(err)
} else resolve()
})
})
if (cacheConfig.reset && (await fs.exists(filePath))) {
await fs.unlink(filePath)
}
const cacheKey = statsToCacheKey(configFilePath ? await fs.stat(configFilePath) : null)
const fileAdapter = new FileAsync(filePath, {
defaultValue: {
cacheKey,
version,
files: {},
},
})
const adapter = await lowdb(fileAdapter)
if (adapter.get('cacheKey').value() !== cacheKey || adapter.get('version').value() !== version) {
adapter
.set('files', {})
.set('cacheKey', cacheKey)
.set('version', version)
.value()
this.write()
}
this.adapter = adapter
}
async getFile(fileImport: ImportResolved): Promise<?ImportTransformed> {
const { adapter } = this
if (!adapter) return null
let stats
try {
stats = await fs.stat(fileImport.filePath)
} catch (_) {
return null
}
const fileKey = getFileKey(fileImport)
const cachedVal = adapter.get(`files.${fileKey}`).value()
if (!cachedVal) return null
const cacheKey = statsToCacheKey(stats)
if (cacheKey !== cachedVal.cacheKey) return null
const fileTransformed = cachedVal.value
if (typeof fileTransformed.contents === 'object' && fileTransformed.contents) {
fileTransformed.contents = Buffer.from(fileTransformed.contents)
}
return fileTransformed
}
setFile(fileImport: ImportResolved, file: ImportTransformed): void {
const { adapter } = this
if (!adapter) return
fs.stat(fileImport.filePath).then(stats => {
const fileKey = getFileKey(fileImport)
const cacheKey = statsToCacheKey(stats)
adapter.set(`files.${fileKey}`, { cacheKey, value: file }).value()
this.write()
})
}
write = debounce(() => this.writeNow().catch(this._report), 1000, {
trailing: true,
})
async writeNow() {
this.write.cancel()
const { adapter } = this
if (adapter) {
await adapter.write()
}
}
}