Skip to content
This repository was archived by the owner on Oct 27, 2020. It is now read-only.

Commit 20d9c2d

Browse files
committed
feat: Allow to change how cache is stored
1 parent 88dfc00 commit 20d9c2d

File tree

2 files changed

+146
-44
lines changed

2 files changed

+146
-44
lines changed

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ module.exports = {
4949
|:--:|:--:|:-----:|:----------|
5050
|**`cacheDirectory`**|`{String}`|`path.resolve('.cache-loader')`|Provide a cache directory where cache items should be stored|
5151
|**`cacheIdentifier`**|`{String}`|`cache-loader:{version} {process.env.NODE_ENV}`|Provide an invalidation identifier which is used to generate the hashes. You can use it for extra dependencies of loaders.|
52+
|**`write`**|`{Function(cacheKey, data, callback) -> {void}}`|`undefined`|Allows you to override default write cache data to file (e.g. Redis, memcached).|
53+
|**`read`**|`{Function(cacheKey, callback) -> {void}}`|`undefined`|Allows you to override default read cache data from file.|
54+
|**`makeKey`**|`{Function(options, request) -> {string}}`|`undefined`|Allows you to override default cache key generator.|
5255

5356
<h2 align="center">Examples</h2>
5457

@@ -95,6 +98,82 @@ module.exports = {
9598
}
9699
```
97100

101+
### `Example of Database Integration`
102+
103+
**webpack.config.js**
104+
```js
105+
const redis = require('redis'); // Or different database client - memcached, mongodb, ...
106+
const crypto = require('crypto');
107+
108+
// ...
109+
// ... connect to client
110+
// ...
111+
112+
const BUILD_CACHE_TIMEOUT = 24 * 3600; // 1 day
113+
114+
function digest(str) {
115+
return crypto.createHash('md5').update(str).digest('hex');
116+
}
117+
118+
/**
119+
* Generate own cache key
120+
*/
121+
function makeKey(options, request) {
122+
return `build:cache:${digest(request)}`;
123+
}
124+
125+
/**
126+
* Read data from database and parse them
127+
*/
128+
function read(cacheKey, callback) {
129+
client.get(cacheKey, (err, result) => {
130+
if (err) {
131+
return callback(err);
132+
}
133+
134+
if (!result) {
135+
return callback(new Error(`Key ${cacheKey} not found`));
136+
}
137+
138+
try {
139+
let data = JSON.parse(result);
140+
callback(null, data);
141+
} catch (e) {
142+
callback(e);
143+
}
144+
});
145+
}
146+
147+
/**
148+
* Write data to database under cacheKey
149+
*/
150+
function write(cacheKey, data, callback) {
151+
client.set(cacheKey, JSON.stringify(data), 'EX', BUILD_CACHE_TIMEOUT, callback);
152+
}
153+
154+
module.exports = {
155+
module: {
156+
rules: [
157+
{
158+
test: /\.js$/,
159+
use: [
160+
{
161+
loader: 'cache-loader',
162+
options: {
163+
read,
164+
write,
165+
makeKey,
166+
}
167+
},
168+
'babel-loader'
169+
],
170+
include: path.resolve('src')
171+
}
172+
]
173+
}
174+
}
175+
```
176+
98177
<h2 align="center">Maintainers</h2>
99178

100179
<table>

src/index.js

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,18 @@ const pkgVersion = require('../package.json').version;
99
const defaultCacheDirectory = path.resolve('.cache-loader');
1010
const ENV = process.env.NODE_ENV || 'development';
1111

12+
const defaultOptions = {
13+
cacheDirectory: defaultCacheDirectory,
14+
cacheIdentifier: `cache-loader:${pkgVersion} ${ENV}`,
15+
read,
16+
write,
17+
makeKey,
18+
};
19+
1220
function loader(...args) {
21+
const loaderOptions = loaderUtils.getOptions(this) || {};
22+
const options = Object.assign({}, defaultOptions, loaderOptions);
23+
const { write: writeFn } = options;
1324
const callback = this.async();
1425
const { data } = this;
1526
const dependencies = this.getDependencies().concat(this.loaders.map(l => l.path));
@@ -35,62 +46,33 @@ function loader(...args) {
3546
return;
3647
}
3748
const [deps, contextDeps] = taskResults;
38-
const writeCacheFile = () => {
39-
fs.writeFile(data.cacheFile, JSON.stringify({
40-
remainingRequest: data.remainingRequest,
41-
cacheIdentifier: data.cacheIdentifier,
42-
dependencies: deps,
43-
contextDependencies: contextDeps,
44-
result: args,
45-
}), 'utf-8', () => {
46-
// ignore errors here
47-
callback(null, ...args);
48-
});
49-
};
50-
if (data.fileExists) {
51-
// for performance skip creating directory
52-
writeCacheFile();
53-
} else {
54-
mkdirp(path.dirname(data.cacheFile), (mkdirErr) => {
55-
if (mkdirErr) {
56-
callback(null, ...args);
57-
return;
58-
}
59-
writeCacheFile();
60-
});
61-
}
49+
writeFn(data.cacheKey, {
50+
remainingRequest: data.remainingRequest,
51+
dependencies: deps,
52+
contextDependencies: contextDeps,
53+
result: args,
54+
}, () => {
55+
// ignore errors here
56+
callback(null, ...args);
57+
});
6258
});
6359
}
6460

6561
function pitch(remainingRequest, prevRequest, dataInput) {
6662
const loaderOptions = loaderUtils.getOptions(this) || {};
67-
const defaultOptions = {
68-
cacheDirectory: defaultCacheDirectory,
69-
cacheIdentifier: `cache-loader:${pkgVersion} ${ENV}`,
70-
};
7163
const options = Object.assign({}, defaultOptions, loaderOptions);
72-
const { cacheIdentifier, cacheDirectory } = options;
64+
const { read: readFn, makeKey: makeKeyFn } = options;
7365
const data = dataInput;
7466
const callback = this.async();
75-
const hash = digest(`${cacheIdentifier}\n${remainingRequest}`);
76-
const cacheFile = path.join(cacheDirectory, `${hash}.json`);
67+
const cacheKey = makeKeyFn(options, remainingRequest);
7768
data.remainingRequest = remainingRequest;
78-
data.cacheIdentifier = cacheIdentifier;
79-
data.cacheFile = cacheFile;
80-
fs.readFile(cacheFile, 'utf-8', (readFileErr, content) => {
81-
if (readFileErr) {
69+
data.cacheKey = cacheKey;
70+
readFn(cacheKey, (readErr, cacheData) => {
71+
if (readErr) {
8272
callback();
8373
return;
8474
}
85-
data.fileExists = true;
86-
let cacheData;
87-
try {
88-
cacheData = JSON.parse(content);
89-
} catch (e) {
90-
callback();
91-
return;
92-
}
93-
if (cacheData.remainingRequest !== remainingRequest || cacheData.cacheIdentifier !== cacheIdentifier) {
75+
if (cacheData.remainingRequest !== remainingRequest) {
9476
// in case of a hash conflict
9577
callback();
9678
return;
@@ -123,4 +105,45 @@ function digest(str) {
123105
return crypto.createHash('md5').update(str).digest('hex');
124106
}
125107

108+
const directoriesExists = {};
109+
function write(cacheKey, data, callback) {
110+
const dirname = path.dirname(cacheKey);
111+
const content = JSON.stringify(data);
112+
if (directoriesExists[dirname]) {
113+
// for performance skip creating directory
114+
fs.writeFile(cacheKey, content, 'utf-8', callback);
115+
} else {
116+
mkdirp(dirname, (mkdirErr) => {
117+
if (mkdirErr) {
118+
callback(mkdirErr);
119+
return;
120+
}
121+
directoriesExists[dirname] = true;
122+
fs.writeFile(cacheKey, content, 'utf-8', callback);
123+
});
124+
}
125+
}
126+
127+
function read(cacheKey, callback) {
128+
fs.readFile(cacheKey, 'utf-8', (err, content) => {
129+
if (err) {
130+
callback(err);
131+
return;
132+
}
133+
134+
try {
135+
const data = JSON.parse(content);
136+
callback(null, data);
137+
} catch (e) {
138+
callback(e);
139+
}
140+
});
141+
}
142+
143+
function makeKey(options, request) {
144+
const { cacheIdentifier, cacheDirectory } = options;
145+
const hash = digest(`${cacheIdentifier}\n${request}`);
146+
return path.join(cacheDirectory, `${hash}.json`);
147+
}
148+
126149
export { loader as default, pitch };

0 commit comments

Comments
 (0)