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

Commit

Permalink
Precision option for rounding time passed on stats and dep in the com…
Browse files Browse the repository at this point in the history
…pare function (#80)

* chore(na): change default cache directory

* chore(na): remove error changes

* feat: add "precision" option for rounding mtime

Some tools truncate mtime. (tar rounds to the second by default)

Adding this option would allow support for environments that use such tools.

# Conflicts:
#	src/index.js
#	src/options.json

* chore(na): remove param reassign to original vars

* test(na): some more tests for compare function

* test(na): add tests for precision option
  • Loading branch information
mistic committed May 31, 2019
1 parent 47ba95b commit 972a6c4
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 11 deletions.
5 changes: 3 additions & 2 deletions README.md
Expand Up @@ -52,10 +52,11 @@ module.exports = {
| **`cacheKey`** | `{Function(options, request) -> {String}}` | `undefined` | Allows you to override default cache key generator |
| **`cacheDirectory`** | `{String}` | `findCacheDir({ name: 'cache-loader' }) or os.tmpdir()` | Provide a cache directory where cache items should be stored (used for default read/write implementation) |
| **`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 (used for default read/write implementation) |
| **`write`** | `{Function(cacheKey, data, callback) -> {void}}` | `undefined` | Allows you to override default write cache data to file (e.g. Redis, memcached) |
| **`compare`** | `{Function(stats, dep) -> {Boolean}}` | `undefined` | Allows you to override default comparison function between the cached dependency and the one is being read. Return `true` to use the cached resource |
| **`precision`** | `{Number}` | `0` | Round `mtime` by this number of milliseconds both for `stats` and `dep` before passing those params to the comparing function |
| **`read`** | `{Function(cacheKey, callback) -> {void}}` | `undefined` | Allows you to override default read cache data from file |
| **`compare`** | `{Function(stats, dep) -> {Boolean}}` | `undefined` | Allows you to override default comparison function between the cached dependency and the one is being read. Return `true` to use the cached resource. |
| **`readOnly`** | `{Boolean}` | `false` | Allows you to override default value and make the cache read only (useful for some environments where you don't want the cache to be updated, only read from it) |
| **`write`** | `{Function(cacheKey, data, callback) -> {void}}` | `undefined` | Allows you to override default write cache data to file (e.g. Redis, memcached) |

## Examples

Expand Down
28 changes: 24 additions & 4 deletions src/index.js
Expand Up @@ -24,10 +24,11 @@ const defaults = {
cacheDirectory: findCacheDir({ name: 'cache-loader' }) || os.tmpdir(),
cacheIdentifier: `cache-loader:${pkg.version} ${env}`,
cacheKey,
compare,
precision: 0,
read,
readOnly: false,
write,
compare,
};

function pathWithCacheContext(cacheContext, originalPath) {
Expand All @@ -48,6 +49,10 @@ function pathWithCacheContext(cacheContext, originalPath) {
.join('!');
}

function roundMs(mtime, precision) {
return Math.floor(mtime / precision) * precision;
}

function loader(...args) {
const options = Object.assign({}, defaults, getOptions(this));
validateOptions(schema, options, 'Cache Loader');
Expand Down Expand Up @@ -135,11 +140,12 @@ function pitch(remainingRequest, prevRequest, dataInput) {
validateOptions(schema, options, 'Cache Loader (Pitch)');

const {
read: readFn,
readOnly,
cacheContext,
cacheKey: cacheKeyFn,
compare: compareFn,
read: readFn,
readOnly,
precision,
} = options;

const callback = this.async();
Expand Down Expand Up @@ -175,9 +181,23 @@ function pitch(remainingRequest, prevRequest, dataInput) {
return;
}

const compStats = stats;
const compDep = dep;
if (precision > 1) {
['atime', 'mtime', 'ctime', 'birthtime'].forEach((key) => {
const msKey = `${key}Ms`;
const ms = roundMs(stats[msKey], precision);

compStats[msKey] = ms;
compStats[key] = new Date(ms);
});

compDep.mtime = roundMs(dep.mtime, precision);
}

// If the compare function returns false
// we not read from cache
if (compareFn(stats, dep) !== true) {
if (compareFn(compStats, compDep) !== true) {
eachCallback(true);
return;
}
Expand Down
9 changes: 6 additions & 3 deletions src/options.json
Expand Up @@ -13,6 +13,12 @@
"cacheDirectory": {
"type": "string"
},
"compare": {
"instanceof": "Function"
},
"precision": {
"type": "number"
},
"read": {
"instanceof": "Function"
},
Expand All @@ -21,9 +27,6 @@
},
"write": {
"instanceof": "Function"
},
"compare": {
"instanceof": "Function"
}
},
"additionalProperties": false
Expand Down
35 changes: 33 additions & 2 deletions test/compare-option.test.js
@@ -1,22 +1,53 @@
const fs = require('fs');

const { webpack } = require('./helpers');

const mockCacheLoaderCompareFn = jest.fn();
const mockWebpackConfig = {
loader: {
options: {
compare: () => {
mockCacheLoaderCompareFn();
compare: (stats, dep) => {
mockCacheLoaderCompareFn(stats, dep);
return true;
},
},
},
};

describe('compare option', () => {
beforeEach(() => {
mockCacheLoaderCompareFn.mockClear();
});

it('should call compare function', async () => {
const testId = './basic/index.js';
await webpack(testId, mockWebpackConfig);
await webpack(testId, mockWebpackConfig);
expect(mockCacheLoaderCompareFn).toHaveBeenCalled();
});

it('should call compare function with 2 args', async () => {
const testId = './basic/index.js';
await webpack(testId, mockWebpackConfig);
await webpack(testId, mockWebpackConfig);
expect(mockCacheLoaderCompareFn).toHaveBeenCalled();
expect(mockCacheLoaderCompareFn.mock.calls[0].length).toBe(2);
});

it('should call compare function with correct args', async () => {
const testId = './basic/index.js';
await webpack(testId, mockWebpackConfig);
await webpack(testId, mockWebpackConfig);
expect(mockCacheLoaderCompareFn).toHaveBeenCalled();

// eslint-disable-next-line
const stats = mockCacheLoaderCompareFn.mock.calls[0][0];
// eslint-disable-next-line
const dep = mockCacheLoaderCompareFn.mock.calls[0][1];
expect(stats).toBeDefined();
expect(stats instanceof fs.Stats);
expect(dep).toBeDefined();
expect(dep.mtime).toBeDefined();
expect(dep.path).toBeDefined();
});
});
72 changes: 72 additions & 0 deletions test/precision-option.test.js
@@ -0,0 +1,72 @@
const { webpack } = require('./helpers');

const mockCacheLoaderCompareFn = jest.fn();
const mockCacheLoaderCompareWithPrecisionFn = jest.fn();
const mockWebpackConfig = {
loader: {
options: {
compare: (stats, dep) => {
mockCacheLoaderCompareFn(stats, dep);
return true;
},
},
},
};
const mockWebpackWithPrecisionConfig = {
loader: {
options: {
compare: (stats, dep) => {
mockCacheLoaderCompareWithPrecisionFn(stats, dep);
return true;
},
precision: 1000,
},
},
};

describe('precision option', () => {
beforeEach(() => {
mockCacheLoaderCompareFn.mockClear();
mockCacheLoaderCompareWithPrecisionFn.mockClear();
});

it('should not apply precision', async () => {
const testId = './basic/index.js';
await webpack(testId, mockWebpackConfig);
mockCacheLoaderCompareFn.mockClear();

await webpack(testId, mockWebpackConfig);

const pastPrecisionTime = mockCacheLoaderCompareFn.mock.calls[0][1].mtime;
mockCacheLoaderCompareFn.mockClear();

await webpack(testId, mockWebpackConfig);
expect(pastPrecisionTime).toBe(
mockCacheLoaderCompareFn.mock.calls[0][1].mtime
);
});

it('should call compare with values after applying precision', async () => {
const testId = './basic/index.js';
await webpack(testId, mockWebpackConfig);
mockCacheLoaderCompareFn.mockClear();
await webpack(testId, mockWebpackConfig);
await webpack(testId, mockWebpackWithPrecisionConfig);
expect(mockCacheLoaderCompareFn.mock.calls[0][1].mtime).not.toBe(
mockCacheLoaderCompareWithPrecisionFn.mock.calls[0][1].mtime
);
});

it('should apply precision dividing by the value', async () => {
const testId = './basic/index.js';
await webpack(testId, mockWebpackConfig);
mockCacheLoaderCompareFn.mockClear();
await webpack(testId, mockWebpackConfig);
await webpack(testId, mockWebpackWithPrecisionConfig);

const newMtime =
mockCacheLoaderCompareWithPrecisionFn.mock.calls[0][1].mtime;
const oldMtime = mockCacheLoaderCompareFn.mock.calls[0][1].mtime;
expect(newMtime).toBe(Math.floor(oldMtime / 1000) * 1000);
});
});

0 comments on commit 972a6c4

Please sign in to comment.