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

Commit 9d93c31

Browse files
authored
Always use absolute paths after reading from cache (#83)
* chore(na): change default cache directory * chore(na): remove error changes * fix(na): remove contextual relative info from watchable paths * fix(na): always use absolute paths after reading from cache * test(na): add test for require absolute pahts * test(na): added correct cache context * test(na): fix all the tests * test(na): fix all the tests * chore(na): add development note about pathWithContext behaviour
1 parent badc3e9 commit 9d93c31

File tree

7 files changed

+110
-11
lines changed

7 files changed

+110
-11
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"npm-run-all": "^4.1.5",
7373
"prettier": "^1.17.1",
7474
"standard-version": "^6.0.1",
75+
"uuid": "^3.3.2",
7576
"webpack": "^4.33.0",
7677
"webpack-cli": "^3.3.2"
7778
},

src/index.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ function roundMs(mtime, precision) {
5353
return Math.floor(mtime / precision) * precision;
5454
}
5555

56+
// NOTE: We should only apply `pathWithCacheContext` transformations
57+
// right before writing. Every other internal steps with the paths
58+
// should be accomplish over absolute paths. Otherwise we have the risk
59+
// to break watchpack -> chokidar watch logic over webpack@4 --watch
5660
function loader(...args) {
5761
const options = Object.assign({}, defaults, getOptions(this));
5862
validateOptions(schema, options, 'Cache Loader');
@@ -120,7 +124,10 @@ function loader(...args) {
120124
writeFn(
121125
data.cacheKey,
122126
{
123-
remainingRequest: data.remainingRequest,
127+
remainingRequest: pathWithCacheContext(
128+
options.cacheContext,
129+
data.remainingRequest
130+
),
124131
dependencies: deps,
125132
contextDependencies: contextDeps,
126133
result: args,
@@ -134,6 +141,10 @@ function loader(...args) {
134141
);
135142
}
136143

144+
// NOTE: We should apply `pathWithCacheContext` transformations
145+
// right after reading. Every other internal steps with the paths
146+
// should be accomplish over absolute paths. Otherwise we have the risk
147+
// to break watchpack -> chokidar watch logic over webpack@4 --watch
137148
function pitch(remainingRequest, prevRequest, dataInput) {
138149
const options = Object.assign({}, defaults, getOptions(this));
139150

@@ -151,14 +162,20 @@ function pitch(remainingRequest, prevRequest, dataInput) {
151162
const callback = this.async();
152163
const data = dataInput;
153164

154-
data.remainingRequest = pathWithCacheContext(cacheContext, remainingRequest);
165+
data.remainingRequest = remainingRequest;
155166
data.cacheKey = cacheKeyFn(options, data.remainingRequest);
156167
readFn(data.cacheKey, (readErr, cacheData) => {
157168
if (readErr) {
158169
callback();
159170
return;
160171
}
161-
if (cacheData.remainingRequest !== data.remainingRequest) {
172+
173+
// We need to patch every path within data on cache with the cacheContext,
174+
// or it would cause problems when watching
175+
if (
176+
pathWithCacheContext(options.cacheContext, cacheData.remainingRequest) !==
177+
data.remainingRequest
178+
) {
162179
// in case of a hash conflict
163180
callback();
164181
return;
@@ -167,7 +184,14 @@ function pitch(remainingRequest, prevRequest, dataInput) {
167184
async.each(
168185
cacheData.dependencies.concat(cacheData.contextDependencies),
169186
(dep, eachCallback) => {
170-
FS.stat(dep.path, (statErr, stats) => {
187+
// Applying reverse path transformation, in case they are relatives, when
188+
// reading from cache
189+
const contextDep = {
190+
...dep,
191+
path: pathWithCacheContext(options.cacheContext, dep.path),
192+
};
193+
194+
FS.stat(contextDep.path, (statErr, stats) => {
171195
if (statErr) {
172196
eachCallback(statErr);
173197
return;
@@ -182,7 +206,7 @@ function pitch(remainingRequest, prevRequest, dataInput) {
182206
}
183207

184208
const compStats = stats;
185-
const compDep = dep;
209+
const compDep = contextDep;
186210
if (precision > 1) {
187211
['atime', 'mtime', 'ctime', 'birthtime'].forEach((key) => {
188212
const msKey = `${key}Ms`;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`compare option should call compare with contextualized dep: errors 1`] = `Array []`;
4+
5+
exports[`compare option should call compare with contextualized dep: warnings 1`] = `Array []`;

test/cacheContext-option.test.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,14 @@ const buildCacheLoaderCallsData = (calls, normalizePaths = true) =>
7373
).sort(sortData);
7474

7575
describe('cacheContext option', () => {
76+
beforeEach(() => {
77+
mockCacheLoaderWriteFn.mockClear();
78+
});
79+
7680
it('should generate relative paths to the project root', async () => {
7781
const testId = './basic/index.js';
82+
await webpack(testId, mockBaseWebpackConfig);
83+
mockCacheLoaderWriteFn.mockClear();
7884
const stats = await webpack(testId, mockRelativeWebpackConfig);
7985

8086
const cacheLoaderCallsData = buildCacheLoaderCallsData(
@@ -83,7 +89,8 @@ describe('cacheContext option', () => {
8389

8490
expect(
8591
cacheLoaderCallsData.every(
86-
(call) => !call.remainingRequest.includes(path.resolve('.'))
92+
(call) =>
93+
!call.remainingRequest.includes(normalizePath(path.resolve('.')))
8794
)
8895
).toBeTruthy();
8996
expect(BJSON.stringify(cacheLoaderCallsData, 2)).toMatchSnapshot(
@@ -95,6 +102,7 @@ describe('cacheContext option', () => {
95102

96103
it('should generate non normalized relative paths to the project root on windows', async () => {
97104
const testId = './basic/index.js';
105+
await webpack(testId, mockBaseWebpackConfig);
98106
await webpack(testId, mockRelativeWebpackConfig);
99107

100108
const cacheLoaderCallsData = buildCacheLoaderCallsData(
@@ -123,6 +131,8 @@ describe('cacheContext option', () => {
123131

124132
it('should generate absolute paths to the project root', async () => {
125133
const testId = './basic/index.js';
134+
await webpack(testId, mockRelativeWebpackConfig);
135+
mockCacheLoaderWriteFn.mockClear();
126136
const stats = await webpack(testId, mockBaseWebpackConfig);
127137

128138
const cacheLoaderCallsData = buildCacheLoaderCallsData(
@@ -131,15 +141,16 @@ describe('cacheContext option', () => {
131141

132142
expect(
133143
cacheLoaderCallsData.every((call) =>
134-
call.remainingRequest.includes(path.resolve('.'))
144+
call.remainingRequest.includes(normalizePath(path.resolve('.')))
135145
)
136-
).toBeFalsy();
146+
).toBeTruthy();
137147
expect(stats.compilation.warnings).toMatchSnapshot('warnings');
138148
expect(stats.compilation.errors).toMatchSnapshot('errors');
139149
});
140150

141151
it('should load as a raw loader to support images', async () => {
142152
const testId = './img/index.js';
153+
await webpack(testId, mockRelativeWebpackConfig);
143154
const stats = await webpack(testId, mockBaseWebpackConfig);
144155

145156
const cacheLoaderCallsData = buildCacheLoaderCallsData(

test/compare-option.test.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
const fs = require('fs');
2+
const path = require('path');
23

3-
const { webpack } = require('./helpers');
4+
const del = require('del');
5+
6+
const { getRandomTmpDir, webpack } = require('./helpers');
7+
8+
const mockRandomTmpDir = getRandomTmpDir();
49

510
const mockCacheLoaderCompareFn = jest.fn();
611
const mockWebpackConfig = {
712
loader: {
813
options: {
14+
cacheDirectory: mockRandomTmpDir,
915
compare: (stats, dep) => {
1016
mockCacheLoaderCompareFn(stats, dep);
1117
return true;
@@ -14,9 +20,27 @@ const mockWebpackConfig = {
1420
},
1521
};
1622

23+
const mockCacheLoaderCompareOnRelativeFn = jest.fn();
24+
const mockRelativeWebpackConfig = {
25+
loader: {
26+
options: {
27+
cacheContext: path.resolve('./'),
28+
cacheDirectory: mockRandomTmpDir,
29+
compare: (stats, dep) => {
30+
mockCacheLoaderCompareOnRelativeFn(stats, dep);
31+
return true;
32+
},
33+
},
34+
},
35+
};
1736
describe('compare option', () => {
1837
beforeEach(() => {
1938
mockCacheLoaderCompareFn.mockClear();
39+
mockCacheLoaderCompareOnRelativeFn.mockClear();
40+
});
41+
42+
afterAll(() => {
43+
del.sync(mockRandomTmpDir);
2044
});
2145

2246
it('should call compare function', async () => {
@@ -50,4 +74,21 @@ describe('compare option', () => {
5074
expect(dep.mtime).toBeDefined();
5175
expect(dep.path).toBeDefined();
5276
});
77+
78+
it('should call compare with contextualized dep', async () => {
79+
const testId = './basic/index.js';
80+
await webpack(testId, mockWebpackConfig);
81+
await webpack(testId, mockRelativeWebpackConfig);
82+
mockCacheLoaderCompareFn.mockClear();
83+
84+
const stats = await webpack(testId, mockRelativeWebpackConfig);
85+
86+
expect(stats.compilation.warnings).toMatchSnapshot('warnings');
87+
expect(stats.compilation.errors).toMatchSnapshot('errors');
88+
expect(mockCacheLoaderCompareOnRelativeFn).toHaveBeenCalled();
89+
90+
// eslint-disable-next-line
91+
const dep = mockCacheLoaderCompareOnRelativeFn.mock.calls[0][1];
92+
expect(path.isAbsolute(dep.path)).toBeTruthy();
93+
});
5394
});

test/helpers.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
const os = require('os');
12
const path = require('path');
23

34
const del = require('del');
4-
const webpack = require('webpack');
55
const MemoryFS = require('memory-fs');
6+
const uuidV4 = require('uuid/v4');
7+
const webpack = require('webpack');
68

79
const moduleConfig = (config) => {
810
return {
@@ -91,6 +93,11 @@ function compile(fixture, config = {}, options = {}) {
9193
);
9294
}
9395

96+
function getRandomTmpDir() {
97+
return path.resolve(os.tmpdir(), `test_${uuidV4()}`);
98+
}
99+
94100
module.exports = {
101+
getRandomTmpDir,
95102
webpack: compile,
96103
};

test/precision-option.test.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
const { webpack } = require('./helpers');
1+
const del = require('del');
2+
3+
const { getRandomTmpDir, webpack } = require('./helpers');
4+
5+
const mockRandomTmpDir = getRandomTmpDir();
26

37
const mockCacheLoaderCompareFn = jest.fn();
48
const mockCacheLoaderCompareWithPrecisionFn = jest.fn();
59
const mockWebpackConfig = {
610
loader: {
711
options: {
12+
cacheDirectory: mockRandomTmpDir,
813
compare: (stats, dep) => {
914
mockCacheLoaderCompareFn(stats, dep);
1015
return true;
@@ -15,6 +20,7 @@ const mockWebpackConfig = {
1520
const mockWebpackWithPrecisionConfig = {
1621
loader: {
1722
options: {
23+
cacheDirectory: mockRandomTmpDir,
1824
compare: (stats, dep) => {
1925
mockCacheLoaderCompareWithPrecisionFn(stats, dep);
2026
return true;
@@ -30,6 +36,10 @@ describe('precision option', () => {
3036
mockCacheLoaderCompareWithPrecisionFn.mockClear();
3137
});
3238

39+
afterAll(() => {
40+
del.sync(mockRandomTmpDir);
41+
});
42+
3343
it('should not apply precision', async () => {
3444
const testId = './basic/index.js';
3545
await webpack(testId, mockWebpackConfig);

0 commit comments

Comments
 (0)